Scripts UI — Script editor and runner¶
Role in the stack¶
Scripts UI is a web interface embedded in PerfShop that lets students edit, manage, and run their test scripts directly from the browser — with no command line, no SSH access, no local IDE.
perfshop-scripts-ui ← Node.js :3008
├── CodeMirror editor (.robot and .py) — 100% local dependencies
├── File tree with drag & drop upload and move
├── Launching robot / pytest directly from the UI
├── Real-time results (1.5s polling)
├── Runs tab — persistent execution history
├── Publish to Forgejo
└── Pull from Forgejo (git pull → /scripts)
Access¶
| Environment | URL | Login |
|---|---|---|
| NAS prod | https://perfshop-scripts.perfshop.io |
admin@perfshop.fr / perfshop |
| Docker Desktop / local build | http://localhost:3008 |
admin@perfshop.fr / perfshop |
Features¶
| Feature | Description |
|---|---|
| File tree | Navigation in the script folders with expand/collapse |
| Drag & drop in the tree | Move a file to a folder via drag-and-drop |
| CodeMirror editor | Robot Framework and Python syntax highlighting — local dependencies |
| New file | Creation with a .robot or .py template |
| New folder | Subfolder creation (automatic .gitkeep) |
| Upload | Drag & drop or click — choice of target folder |
| Download | Export a script locally |
| Move / Rename | Move via modal or drag & drop in the tree |
| Delete | File or folder deletion (recursive) |
| Run | Execute .robot via robot or .py via pytest — button grayed out on other extensions |
| Live results | Polling every 1.5s during execution |
| Runs tab | Execution history persisted on disk — click → detail in the Result tab |
| Publish | Push the current script to Forgejo (with SHA) |
| ⬇️ Pull Forgejo | git pull from Forgejo to /scripts |
| History | Display of the latest Forgejo commits |
Technical architecture¶
perfshop-scripts-ui:
image: node:20-alpine ← NAS: no build, code mounted as a volume
command: npm install && node src/server.js
volumes:
- ./scripts-ui:/app ← source code
- /var/run/docker.sock:/var/run/docker.sock ← docker exec test-runner
- forgejo-token-data:/token:ro ← Forgejo API token (read-only)
- scripts-ui-modules:/app/node_modules
- ./test-runner/logs:/rf-logs ← run persistence
environment:
- FORGEJO_INTERNAL_URL=http://perfshop-forgejo:3000
- FORGEJO_CI_USER=perfshop-ci
- FORGEJO_REPO=perfshop-tests
- FORGEJO_TOKEN_FILE=/token/forgejo_token
- TEST_RUNNER_CONTAINER=perfshop-test-runner
- SESSION_SECRET=${SESSION_SECRET:-perfshop-dev-secret}
- RUNS_DIR=/rf-logs/runs ← persistence folder (optional, default value)
Script execution flow¶
Student clicks "▶ Run"
│
▼
scripts-ui checks the extension (.robot / .py only)
│
▼
scripts-ui checks that perfshop-test-runner is UP (docker inspect)
│
▼
docker exec perfshop-test-runner robot /scripts/my-script.robot
(or pytest /scripts/my-script.py --junit-xml=...)
│
▼
Polling every 1.5s → real-time stdout display
│
▼
PASS / FAIL result in the bottom panel
│
▼
Persistence: /rf-logs/runs/<runId>/meta.json + output.txt + result.xml
Concurrency limit
A maximum of 3 scripts can run simultaneously (MAX_CONCURRENT_RUNS = 3).
Beyond that, the UI returns an HTTP 429 error.
Runs tab — execution persistence¶
Each finished run (PASS, FAIL, or error) is persisted on disk in /rf-logs/runs/:
/rf-logs/runs/
run-1-1773912837160/
├── meta.json ← script, startedAt, endedAt, status, rc
├── output.txt ← complete run output
└── result.xml ← copy of the JUnit XML (if present)
The 🏃 Runs tab lists the last 50 runs sorted by descending date. Clicking a row switches to the 📋 Result tab with the full detail.
Required volume
The ./test-runner/logs:/rf-logs volume must be mounted on perfshop-scripts-ui
without :ro. A docker compose stop + up is required (not a simple restart)
for Docker to pick up this new mount.
Publication flow to Forgejo¶
Student clicks "Publish"
│
▼
scripts-ui reads the token from /token/forgejo_token
│
▼
GET /api/v1/repos/perfshop-ci/perfshop-tests/contents/{file}
→ retrieves the SHA if the file already exists
│
▼
PUT (update) or POST (create) with the base64 content
│
▼
Forgejo commits on main: "feat: update {file}"
Server API routes¶
| Method | Route | Description |
|---|---|---|
GET |
/api/tree |
Forgejo file tree (.robot / .py) |
GET |
/api/file?path= |
File contents |
GET |
/api/download?path= |
Raw download |
PUT |
/api/file |
Publish (create / update) |
DELETE |
/api/file |
Delete a file |
POST |
/api/upload |
Upload from the browser |
POST |
/api/mkdir |
Create a folder (.gitkeep) |
POST |
/api/move |
Move / rename |
GET |
/api/commits |
Last 15 commits |
POST |
/api/sync |
git pull Forgejo → /scripts |
POST |
/api/run |
Launch a test |
GET |
/api/run/:id |
Status of a live run (polling) |
GET |
/api/runs |
List of the last 50 persisted runs |
GET |
/api/runs/:id |
Detail of a persisted run (meta + output) |
GET |
/api/runner-status |
test-runner container state |
Security¶
| Measure | Detail |
|---|---|
| Authentication | Express session cookie (PerfShop login) |
| Forgejo token | Injected via volume, never in the URL |
| XSS | File tree and table built 100% with DOM API — zero innerHTML with data |
| Event delegation | Admin account emails read from a JS Map — never from an HTML attribute |
| File validation | [\w\-.] only + mandatory .robot / .py extension |
| Run id validation | Regex [\w\-]+ — path traversal protection |
| Tree depth | Max 4 folder levels |
| Memory cleanup | In-memory runs purged after 1h (every 15 min) |
| Concurrency | Max 3 simultaneous runs — HTTP 429 beyond |
Inter-service dependencies¶
perfshop-scripts-ui
├── depends_on: perfshop-app (auth session)
├── depends_on: perfshop-forgejo (healthcheck) ← Git sync + Publish
└── depends_on: perfshop-test-runner (Run scripts)
Useful commands¶
# Real-time logs
docker compose logs -f perfshop-scripts-ui
# Restart after a code change
# (if a new volume is added → down/up is mandatory)
docker compose stop perfshop-scripts-ui
docker compose up -d perfshop-scripts-ui
# Check that the Forgejo token is present
docker exec perfshop-scripts-ui cat /token/forgejo_token
# Check run persistence
ls -la /volume4/docker-speed/perfshop/test-runner/logs/runs/