feat(ui): light-mode sky palette for Caddy card, favicon, doc sync

- index.css: add :root.light overrides for the sky-* accent used only by the
  Caddy settings card (buttons, badges, hovers) + the missing red-950/30 hover
- favicon: add public/favicon.svg (GhostGrid logo) and link it in index.html
- ARCHITECTURE.md: GET /caddy/routes returns a plain array, document the Caddy
  startup import, https:// upstream, favicon/public dir, and the SPA-catch-all-last
  + Cache-Control: no-store invariant
This commit is contained in:
Brückner
2026-06-08 14:51:36 +02:00
parent f6263ad2f3
commit 1dba721a9a
4 changed files with 80 additions and 7 deletions

View File

@ -374,7 +374,7 @@ All `/api/*` routes return JSON. Every route except the public auth/config endpo
|
+-- /caddy
+-- GET /status # Caddy admin API reachable? [auth]
+-- GET /routes # { system, custom } routes [auth]
+-- GET /routes # Custom routes (plain array) [auth]
+-- POST /routes # Add custom route + push config [auth]
+-- PUT /routes/{id} # Update custom route + push config [auth]
+-- DELETE /routes/{id} # Remove custom route + push config [auth]
@ -481,15 +481,19 @@ pushCaddyConfig(): POST <caddy_admin_url>/load (Content-Type: text/caddyfile)
### 6.4 First-start Initialization
Runs in `startServer()` before any routes are registered, every startup — both operations
are idempotent and only fire once on a blank database.
Runs in `startServer()` on every startup — each step is idempotent.
```
Default admin user:
Default admin user (only on a blank database):
if users table is empty:
INSERT user (name='Admin', role='Admin', email='admin@ghostgrid.local', password=bcrypt('admin'))
INSERT user (name='admin', role='admin', email='admin@ghostgrid.local', password=bcrypt('admin'))
→ log "[Init] Default admin user created"
Caddy route import (re-deploy safety net):
if caddy_enabled === 'true' AND caddy table is empty:
importCaddyfileRoutes() → seed routes from /etc/caddy/Caddyfile
(also runs in PUT /api/settings on the disabled → enabled transition)
Default settings:
INSERT OR IGNORE all DEFAULT_SETTINGS keys from server-db.ts
→ existing values in the settings table are never overwritten
@ -577,7 +581,8 @@ Settings
+-- CheckMK (API URL/user/secret, sync interval, "Run sync now")
+-- Ansible Semaphore (API URL/token/project, "Test connection")
+-- Caddy (admin URL, custom route management;
auto-seeded from /etc/caddy/Caddyfile on first enable)
auto-seeded from /etc/caddy/Caddyfile on first enable;
https:// upstream → TLS proxy, certificate not verified)
+-- Secret inputs use the __SET__ sentinel (blank = keep existing)
```
@ -696,7 +701,9 @@ Backup : ghostgrid.db + ghostgrid.db-wal + ghostgrid.db-shm
GhostGrid/
+-- server.ts # Express app: all routes, auth, integrations, background jobs
+-- server-db.ts # SQLite connection, full schema, settings/Caddy helpers
+-- index.html # Vite HTML entry (#root > src/main.tsx)
+-- index.html # Vite HTML entry (#root > src/main.tsx); links /favicon.svg
+-- public/
| +-- favicon.svg # app favicon (GhostGrid logo; served at site root by Vite)
+-- vite.config.ts # Vite + React + Tailwind; '@' alias > repo root
+-- tsconfig.json # noEmit, react-jsx, bundler resolution
+-- package.json # scripts + deps (package name "react-example" is vestigial)
@ -820,6 +827,7 @@ Express (server.ts) ──► better-sqlite3 (ghostgrid.db, WAL)
- 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.
- 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`.
- All user-facing strings are in **English**.
---

View File

@ -3,6 +3,8 @@
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<link rel="icon" type="image/svg+xml" href="/favicon.svg" />
<meta name="theme-color" content="#0b0f19" />
<title>GhostGrid</title>
</head>
<body>

24
public/favicon.svg Normal file
View File

@ -0,0 +1,24 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100">
<rect width="100" height="100" rx="22" fill="#0b0f19"/>
<!-- ghost body -->
<path d="M 24,78 C 18,65 14,35 34,22 C 48,12 62,15 68,26" stroke="#06b6d4" stroke-width="4" stroke-linecap="round" fill="none"/>
<path d="M 24,78 C 26,83 31,81 35,74 C 38,68 41,74 45,77 C 48,79 50,70 52,65" stroke="#06b6d4" stroke-width="4" stroke-linecap="round" fill="none"/>
<!-- eyes -->
<rect x="38" y="32" width="6" height="13" rx="3" fill="#00f0ff"/>
<rect x="52" y="32" width="6" height="13" rx="3" fill="#00f0ff"/>
<!-- network grid -->
<line x1="56" y1="38" x2="88" y2="38" stroke="#06b6d4" stroke-width="2.5"/>
<line x1="46" y1="62" x2="84" y2="62" stroke="#06b6d4" stroke-width="2.5"/>
<line x1="56" y1="20" x2="56" y2="80" stroke="#06b6d4" stroke-width="2.5"/>
<line x1="68" y1="15" x2="68" y2="76" stroke="#0891b2" stroke-width="2"/>
<line x1="80" y1="26" x2="80" y2="62" stroke="#06b6d4" stroke-width="2"/>
<!-- nodes -->
<circle cx="56" cy="26" r="4" fill="#00f0ff"/>
<circle cx="68" cy="26" r="4" fill="#00f0ff"/>
<circle cx="80" cy="26" r="4" fill="#00f0ff"/>
<circle cx="56" cy="38" r="4" fill="#00f0ff"/>
<circle cx="80" cy="38" r="5" fill="#38bdf8"/>
<circle cx="88" cy="38" r="4" fill="#00f0ff"/>
<circle cx="68" cy="62" r="5" fill="#38bdf8"/>
<circle cx="80" cy="62" r="4" fill="#00f0ff"/>
</svg>

After

Width:  |  Height:  |  Size: 1.4 KiB

View File

@ -872,3 +872,42 @@
:root.light .group:hover .group-hover\:text-slate-300 {
color: var(--text-muted) !important;
}
/* ── Settings → Caddy section: sky accent ─────────────────────────── */
/* sky-* is used only by the Caddy card; map its dark tokens to light. */
:root.light .bg-sky-950\/60,
:root.light .bg-sky-950\/40,
:root.light .bg-sky-900\/40 {
background-color: #e0f2fe !important;
border-color: #7dd3fc !important;
color: #0369a1 !important;
}
:root.light .border-sky-900\/50,
:root.light .border-sky-900\/40 {
border-color: #7dd3fc !important;
}
:root.light .text-sky-400,
:root.light .text-sky-500 {
color: #0284c7 !important;
}
:root.light .bg-sky-950\/30,
:root.light .hover\:bg-sky-950\/30:hover {
background-color: #e0f2fe !important;
}
:root.light .hover\:bg-sky-900\/40:hover {
background-color: #bae6fd !important;
}
:root.light .hover\:text-sky-400:hover {
color: #0284c7 !important;
}
/* Delete-icon hover (red-950/30 is the only red opacity not yet mapped) */
:root.light .bg-red-950\/30,
:root.light .hover\:bg-red-950\/30:hover {
background-color: #fee2e2 !important;
}