README: add walkthrough GIF, architecture diagram and gallery; add demo seed and capture scripts
Lint / JS (eslint) (push) Successful in 18s
Lint / Notify on failure (push) Skipped
Security / JS Security (npm audit) (push) Failing after 9s
Test / JS Tests (jest) (push) Successful in 9s
Lint / Deploy (push) Successful in 6s

This commit is contained in:
2026-10-03 01:19:43 -04:00
parent d9f3986198
commit 3572d5ba35
19 changed files with 209 additions and 0 deletions
+64
View File
@@ -36,6 +36,70 @@ Reference documentation:
- [`web_template/aesthetic_diff.md`](https://code.lotusguild.org/LotusGuild/web_template/src/branch/main/aesthetic_diff.md) — cross-app divergence analysis and convergence guide
- [`web_template/node/middleware.js`](https://code.lotusguild.org/LotusGuild/web_template/src/branch/main/node/middleware.js) — Express auth, CSRF, CSP nonce middleware
![PULSE walkthrough: dashboard, workers, workflows, execution logs with an approval prompt, a multi-worker quick command and the command palette](docs/img/demo.gif)
## Why this exists
I run a six-node Proxmox/Ceph cluster plus a pile of LXC containers. Day-to-day work is the same handful of questions asked of many machines: *what is the disk usage everywhere, restart that service on one node, is it safe to proceed?* SSH-ing into each box does not scale, and a full config-management system is more than I need. PULSE is a small orchestration layer: a server with a web UI, and a lightweight **worker** on each machine that executes what the server dispatches over a WebSocket. It adds what shell loops lack: multi-step **workflows** with conditions and human approval gates, scheduled commands, a full execution history, and an API that other tools call (GANDALF uses it to run network diagnostics).
```mermaid
flowchart LR
user["Browser<br/>(Authelia SSO)"] -->|HTTPS| srv["PULSE server<br/>Express + EJS + WebSocket"]
gd["GANDALF"] -->|internal API| srv
srv --> db[("MariaDB<br/>workflows, executions,<br/>schedules")]
srv <-->|"WebSocket<br/>commands / results / heartbeat"| w1["worker: node-01"]
srv <-->|WebSocket| w2["worker: node-02"]
srv <-->|WebSocket| w3["worker: node-03"]
```
Workflows are plain JSON with five step types: `execute`, `prompt` (pause for a human decision), `wait`, `parse` (pull `KEY=VALUE` lines from output into state) and `route` (branch on parsed state).
## Gallery
**Dashboard**: recent executions at a glance and live worker status.
![Dashboard](docs/img/dashboard.png)
**Workers** report system info, memory, load, uptime and task slots on every heartbeat:
![Workers](docs/img/workers.png)
**Workflows** are reusable multi-step jobs. The editor takes the JSON definition directly:
| Workflow list | Workflow editor |
|---|---|
| ![Workflows](docs/img/workflows.png) | ![Workflow editor](docs/img/workflow-editor.png) |
**Executions**: full history with step-by-step logs, per-command output and durations.
![Executions](docs/img/executions.png)
| Completed run with step logs | A failed command, with its error |
|---|---|
| ![Execution detail](docs/img/execution-detail.png) | ![Failed execution](docs/img/execution-failed.png) |
**Approval gates:** a `prompt` step pauses the workflow until someone chooses an option (here, a rolling restart waits for a human to approve):
![Awaiting approval](docs/img/execution-awaiting-approval.png)
**Quick Command** runs a one-off command on one or many workers at once:
| Compose | Result |
|---|---|
| ![Quick command](docs/img/quick-command.png) | ![Quick command result](docs/img/quick-command-result.png) |
**Scheduler** runs commands on an interval or hourly schedule:
![Scheduler](docs/img/scheduler.png)
**Command palette** (`Ctrl+K`) and **light theme**:
| Command palette | Light theme |
|---|---|
| ![Command palette](docs/img/command-palette.png) | ![Dashboard, light theme](docs/img/dashboard-light.png) |
<sub>Screenshots are generated with Playwright against a throwaway local instance with three local workers running harmless commands (`uptime`, `df`, `echo`). See `scripts/demo/`.</sub>
## Web UI
The UI is server-rendered with EJS. Every route renders `views/pages/<page>.ejs` into the shared