docs(architecture): reflect personal/global topology scope feature

This commit is contained in:
Brückner
2026-06-10 16:20:42 +02:00
parent 84bad8c0e6
commit d78ade4629

View File

@ -231,8 +231,11 @@ CREATE TABLE IF NOT EXISTS labs (
deviceIds TEXT NOT NULL, -- JSON string: string[]
topology TEXT NOT NULL, -- JSON string: TopologyLink[]
semaphoreSetupTemplateId TEXT NOT NULL DEFAULT '',
semaphoreTeardownTemplateId TEXT NOT NULL DEFAULT ''
semaphoreTeardownTemplateId TEXT NOT NULL DEFAULT '',
scope TEXT NOT NULL DEFAULT 'global', -- 'global' | 'personal'
ownerId TEXT NOT NULL DEFAULT '' -- userId of creator; '' = legacy (pre-migration)
);
-- scope/ownerId were added after initial release via idempotent ALTER TABLE in server-db.ts
CREATE TABLE IF NOT EXISTS bookings (
id TEXT PRIMARY KEY,
@ -295,7 +298,7 @@ CREATE TABLE IF NOT EXISTS caddy (
| JSON columns | `labs.deviceIds` and `labs.topology` are JSON strings, parsed in the API layer |
| Booleans | Booking flags (`notified`, `emailSent`, `ansible*Triggered`) are `INTEGER` 0/1, mapped to JS booleans on read |
| Cascade | No FK cascades; referential cleanup is done in code (e.g. deleting a device scrubs it from every lab's `deviceIds`/`topology`) |
| Schema changes | Fresh-install model — edit the `CREATE TABLE` block in `server-db.ts` directly; there is no migration helper |
| Schema changes | Fresh-install model — edit the `CREATE TABLE` block in `server-db.ts` directly. To add columns to an existing DB, place idempotent `ALTER TABLE … ADD COLUMN` calls (wrapped in try/catch) in `server-db.ts` after the `db.exec()` block — they run on every startup and are no-ops when the column already exists |
### 4.3 Settings (key/value config)
@ -346,9 +349,9 @@ All `/api/*` routes return JSON. Every route except the public auth/config endpo
|
+-- /labs
| +-- GET / # List labs (parses deviceIds/topology JSON) [auth]
| +-- POST / # Create lab [auth]
| +-- PUT /{id} # Update lab [auth]
| +-- DELETE /{id} # Delete + cancel upcoming bookings [auth]
| +-- POST / # Create lab; sets ownerId=req.user [auth]
| +-- PUT /{id} # Update lab; 403 if not owner/admin/legacy [auth]
| +-- DELETE /{id} # Delete + cancel upcoming bookings; same 403 guard [auth]
|
+-- /bookings
| +-- GET / # List bookings (int flags > booleans) [auth]
@ -390,8 +393,10 @@ Auth model
+-- Storage: browser localStorage (ghostgrid_token, ghostgrid_user)
+-- Middleware
| +-- requireAuth — verifies JWT, sets req.user; applied to all data routes
| +-- requireAdmin — checks users.role === 'admin' ⚠ DEFINED BUT NOT WIRED
+-- Roles: role column defaults to 'User'; no route currently enforces admin
| +-- requireAdmin — checks users.role === 'admin' ⚠ DEFINED BUT NOT WIRED to routes
+-- Roles: role column defaults to 'User'
+-- Lab ownership: PUT/DELETE /api/labs/:id enforce inline ownership check
| (owner || admin || legacy-lab with ownerId=''); 403 otherwise
```
**Local flow:** `register` (bcrypt hash, role `User`) / `login` (bcrypt compare) > issue JWT > client stores token + user.
@ -588,6 +593,9 @@ Device Inventory
Lab Templates + Topology
+-- Lab CRUD; Semaphore setup/teardown template selection
+-- Scope toggle (Global / Personal) per lab; Personal labs visible only to owner + admins
+-- List sectioned: "My Topologies" / "Global Topologies" / "Others' Personal" (admin only)
+-- Edit/Delete buttons hidden for labs the current user cannot modify
+-- Topology link editor (fromDevice > toDevice, link type)
+-- TopologyPanel: SVG layout by node count (1 / 2 / 3 / circular)
@ -612,7 +620,7 @@ The single contract between frontend and backend — imported by **both** `serve
| `DeviceType` | `'Switch' \| 'Access-Point' \| 'Firewall' \| 'Controller' \| (string & {})` — presets + free-form |
| `Device` | `status: 'online' \| 'offline' \| 'unknown'`; `emergencySheet` markdown; optional `cmkHostname`, `lastCheckedAt` |
| `TopologyLink` | `{ fromDevice, toDevice, type }` (e.g. `LACP-Trunk`, `Uplink`, `OOB-Management`) |
| `LabTemplate` | `deviceIds: string[]`, `topology: TopologyLink[]`, optional Semaphore template IDs |
| `LabTemplate` | `deviceIds: string[]`, `topology: TopologyLink[]`, optional Semaphore template IDs; `scope: 'global' \| 'personal'`, `ownerId: string` (userId of creator, `''` for legacy rows) |
| `Booking` | `status: 'active' \| 'upcoming' \| 'completed' \| 'cancelled'`; ansible trigger flags + job IDs |
| `LogEntry` | `type: 'maintenance' \| 'booking' \| 'status' \| 'system'` |
| `User` | `{ id, name, role, email }` (never password on the client) |
@ -693,6 +701,8 @@ Backup : ghostgrid.db + ghostgrid.db-wal + ghostgrid.db-shm
| +-- role column ('User'/'admin') exists |
| +-- ⚠ requireAdmin defined but NOT applied — any |
| authenticated user can read/write settings + users |
| +-- Lab ownership enforced on PUT/DELETE /api/labs/:id |
| (owner || admin || legacy ownerId=''); 403 otherwise |
+-------------------------------------------------------------+
| Secret Handling |
| +-- Integration secrets stored in settings table |
@ -774,7 +784,7 @@ GhostGrid/
| CheckMK/Semaphore outage | Integration loops catch errors, log them, and retry next cycle; non-fatal |
| Caddy admin API unreachable | `pushCaddyConfig()` failures are logged as warnings; routes apply when Caddy starts |
| Data loss | Back up `ghostgrid.db` + `-wal`/`-shm`; each instance has its own DB |
| Schema evolution | Edit the `CREATE TABLE` block in `server-db.ts` (fresh-install model, no migrations); new settings need seed + allow-list (+ `SECRET_KEYS` if secret) |
| Schema evolution | Edit the `CREATE TABLE` block in `server-db.ts` (fresh-install model). For live DBs, add new columns via idempotent `ALTER TABLE … ADD COLUMN` (try/catch) after the `db.exec()` block. New settings need seed + allow-list (+ `SECRET_KEYS` if secret) |
---
@ -842,7 +852,7 @@ Express (server.ts) ──► better-sqlite3 (ghostgrid.db, WAL)
- `labs.deviceIds` / `labs.topology` are JSON strings in SQLite, parsed in the API.
- Booking boolean flags are 0/1 integers in SQLite, mapped on read.
- A new settings key must be: **seeded** in `server-db.ts`, **allow-listed** in `PUT /api/settings`, and (if secret) added to `SECRET_KEYS`.
- Schema changes go straight into the `CREATE TABLE` block in `server-db.ts` fresh-install model, no migration helper.
- Schema changes go into the `CREATE TABLE` block in `server-db.ts` (fresh-install). For adding columns to live DBs, use idempotent `ALTER TABLE … ADD COLUMN` (try/catch) placed after the `db.exec()` block.
- The SPA catch-all (`app.get('*')`) + static serving are registered **last** in `startServer()`, after every `/api` route — otherwise GET `/api/*` falls through to `index.html`. All `/api` responses carry `Cache-Control: no-store`.
- One Caddy per container; `POST /load` replaces the whole config. Only the `CADDY_MANAGER=true` instance may push/seed/edit routes — never let the non-manager push.
- All user-facing strings are in **English**.