Skip to content

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/