Backend — How It Works¶
ScreenSage's backend is a Rust Actix-web server running on 0.0.0.0:8080. It serves the web UI, handles API calls from the browser, runs shell commands on the host system, and manages file I/O for JSON configs and media.
Request Flow¶
Browser → HTTP request → Actix route → handler function
↓
Shell command / file read/write
↓
JSON response back to browser
Every tab in the UI maps to a handler file. The browser makes fetch() calls; the server responds with JSON or HTML.
File Map¶
| File | Responsibility |
|---|---|
main.rs |
Entry point — wires routes, initialises shared state |
handlers.rs |
General commands, config read, WiFi management |
battle_handlers.rs |
Combat tracker state (in-memory) |
image_handlers.rs |
Directory listing, media serving |
upload_handlers.rs |
File upload → disk |
json_handlers.rs |
Read / write arbitrary JSON files |
sageslate_handlers.rs |
Pushes images to e-ink displays |
vtt_handler.rs |
Serves the VTT page |
display_handler.rs |
Serves the Display page |
commands.rs |
Predefined shell commands (start/stop screens etc.) |
template_loader.rs |
Loads HTML templates, does {{var}} substitution |
Shared State¶
Two pieces of data live for the lifetime of the process:
Predefined commands — loaded once at startup from commands.rs, shared read-only across all requests via web::Data<HashMap<String, PredefinedCommand>>.
Battle state — a Mutex<HashMap<String, BattleState>> keyed by session ID. Any handler can lock it, read or write the current combat state, then release. There is no database; state is lost on restart.
Key Handler Groups¶
Command Execution (handlers.rs)¶
POST /execute and GET /run/{command_id} both ultimately do the same thing: run a shell command on the host and return stdout/stderr/exit code as JSON. The predefined commands in commands.rs are things like:
stop-vtt— kills the Python display processstart-battlemap-screen— launches ScryingGlass on a given monitorlist-monitors— runsxrandrto find connected displays
This is how the Command Centre tab works — every button is a predefined command sent to /run/{id}.
JSON Config (json_handlers.rs)¶
GET /json/read?path=... and POST /json/save read and write JSON files anywhere on the filesystem. The VTT editor uses these to load a scene config, let you edit it in the browser, then write it back — which ScryingGlass picks up via its file watcher.
Media (image_handlers.rs)¶
GET /api/images/list?path=... returns directory contents filtered to supported media types (jpg, png, gif, webp, mp4, webm, etc.), sorted with folders first.
GET /api/images/serve?path=... reads the file and returns it with the correct MIME type. This is how the browser plays videos or shows images that live outside the static folder.
Battle Tracker (battle_handlers.rs)¶
The browser sends a full BattleState to POST /api/battle/save after every change (turn advance, HP change, etc.). GET /api/battle/load retrieves it. State lives in the Mutex<HashMap> — there's no persistence to disk.
A BattleState holds:
- combatants — list of name, initiative, current HP, max HP, AC, type
- current_turn, round, active_combatant_id
SageSlate (sageslate_handlers.rs)¶
Handles requests from the SageSlate tab to push an image to an e-ink device. It opens a TCP connection to the device's IP (port 8080), converts the image to 1-bit packed bytes matching the display's resolution, and streams the data. The ESP32 on the other end receives it and refreshes the screen.
Template System (template_loader.rs)¶
HTML pages are built from templates in src/templates/. The loader reads each template file at startup and holds them in a HashMap<String, String>. Rendering does simple {{variable}} string substitution — no external templating engine.
render_with_base() wraps a page template inside the shared base layout (nav, head, scripts) and injects the correct <script> tag for the page being rendered.
API Summary¶
| Method | Route | Purpose |
|---|---|---|
| GET | / |
Main dashboard |
| GET | /api/config?path= |
Read a JSON config file |
| POST | /execute |
Run an arbitrary shell command |
| GET | /run/{id} |
Run a predefined command |
| GET | /api/images/list?path= |
List a directory |
| GET | /api/images/serve?path= |
Serve a media file |
| POST | /api/upload/image |
Upload an image |
| GET | /json/read?path= |
Read a JSON file |
| POST | /json/save |
Write a JSON file |
| POST | /api/battle/save |
Save battle state |
| GET | /api/battle/load |
Load battle state |
| GET | /api/wifi/scan |
Scan WiFi networks |
| POST | /api/wifi/connect |
Connect to a network |