62 Commits

Author SHA1 Message Date
cc96f5b6ce chore(release): merge dev into main 2026-06-10 16:39:27 +02:00
e0fd19f471 feat(topology): hide Ansible Automation section when Semaphore is disabled 2026-06-10 16:37:50 +02:00
5c7ad3140a feat(db): add lightweight migration system
Introduce server-migrations.ts with a named-migration runner that tracks
applied migrations in a _migrations table. runMigrations(db) is called at
startup before routes, so additive schema changes (ALTER TABLE, new settings)
are applied once and skipped on subsequent restarts.

Update ARCHITECTURE.md: five inline edits + new §4.4 documenting the convention.
2026-06-10 16:30:44 +02:00
c3931e7f36 style(ui): remove placeholder text from all input fields
Strips example/hint placeholder attributes across all components for a cleaner, less cluttered form UX.
2026-06-10 16:25:28 +02:00
d78ade4629 docs(architecture): reflect personal/global topology scope feature 2026-06-10 16:20:42 +02:00
84bad8c0e6 feat(auth): admin role management with logbook entries 2026-06-10 16:05:08 +02:00
08a4df5503 feat(topology): add personal/global scope to lab templates
Labs can now be marked as Personal or Global when creating or editing.
Personal topologies are visible only to the owner and admins; others
cannot see, book, or edit them. Global topologies are visible to all
but editable only by the creator, admins, or legacy (migrated) labs.

- DB: idempotent ALTER TABLE adds scope + ownerId columns to labs
- API: POST sets ownerId from JWT; PUT/DELETE enforce ownership (403 for
  unauthorized edits; legacy ownerId='' remains freely editable)
- Types: LabTemplate extended with scope and ownerId fields
- LabTemplates UI: sectioned list (My / Global / Others' Personal),
  Personal/Global toggle in form, Lock/Globe badges on cards,
  edit+delete buttons hidden for non-owners
- BookingCalendar: personal labs filtered from selects/quick booking,
  optgroup grouping for Global vs Personal in topology dropdown
- Light mode: add missing bg-slate-950/50 and border-slate-800/50
  overrides so the Global badge renders correctly
2026-06-10 15:51:53 +02:00
cb36caff2e fix(auth): log Entra login events to logbook 2026-06-10 15:15:23 +02:00
be007791dc refactor(db): rename redirect_path→redirect, add uid/addLog helpers, simplify Caddy CRUD
- Rename caddy.redirect_path to caddy.redirect across schema, server, frontend and docs
- Remove obsolete ALTER TABLE migration (fresh-install model has no migrations)
- Move uid() from server.ts to server-db.ts for shared use
- Add addLog() general helper (prepared statement, shared timestamp support) and
  replace ~24 inline INSERT INTO logs calls throughout server.ts
- Caddy CRUD now takes CaddyRouteInput object instead of positional arguments;
  add/update reuse getCaddyRouteById() to avoid duplicate SELECT
2026-06-10 15:08:35 +02:00
515052fbda refactor: replace CADDY_MANAGER with DEPLOY_ENV for instance-role awareness
DEPLOY_ENV=production now marks the primary instance globally - used for
Caddy ownership, the Dev/Prod header badge, and Caddy UI gating. Removes
build-time VITE_DEPLOY_ENV/import.meta.env.DEV from the header in favour
of the runtime API response (isProduction field in /api/auth/config).
2026-06-10 14:43:31 +02:00
49cd0ae4f6 feat(caddy): optional root redirect per route
Add a redirect_path column to the caddy table and an optional 'root redirect'
field in the route form. When set, buildCaddyfile emits 'redir / <path>' so the
bare host (e.g. checkmk.domain.local/) redirects to a sub-path (e.g.
/monitoring/check_mk/) while every other path still passes through to the
backend — the safe pattern for apps like CheckMK that bake their site path into
absolute URLs. Defensive ALTER TABLE keeps existing databases working.
2026-06-10 10:22:39 +02:00
a2d515992c fix(logbook): 'All' filter shows every log including system entries
Drop the 'non-system' default filter; 'All' now means all log types.
2026-06-09 13:09:04 +02:00
2a2902d5bc feat(ui): distinguish dev/prod via VITE_DEPLOY_ENV
Both instances run with NODE_ENV=production, so import.meta.env.PROD was
always true and the header always showed 'Production'. deploy.sh now passes
VITE_DEPLOY_ENV=<branch> into the build and Header reads it to label the
system indicator dev vs prod correctly.
2026-06-09 13:09:03 +02:00
ac1cf8fec7 docs(architecture): sync Caddy manager gate in first-start + ownership invariant 2026-06-09 13:09:01 +02:00
e0332b05ad feat(caddy): single owner via CADDY_MANAGER env flag
One Caddy serves the whole container and POST /load replaces the entire
config, so two instances pushing would clobber each other. Now only the
instance with CADDY_MANAGER=true (production) pushes, seeds routes from the
Caddyfile, and accepts route mutations (others get 403). /api/auth/config
exposes caddyManaged so the non-owner Settings UI shows the Caddy section
read-only. The installer sets the flag on the production .env only.
2026-06-09 12:47:20 +02:00
bc677ff805 feat(caddy): add standard forwarding headers to every reverse_proxy
Every generated reverse_proxy block now emits header_up for
X-Forwarded-Proto, X-Real-IP and Host. Caddy already sets the X-Forwarded-*
family and Host by default; this makes them explicit and adds X-Real-IP
(nginx convention) for backends that expect it. The https:// transport block
is preserved alongside the headers.
2026-06-09 11:39:45 +02:00
1dba721a9a 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
2026-06-08 14:51:36 +02:00
f6263ad2f3 feat(caddy): support HTTPS upstreams via https:// prefix
When a route's upstream starts with https://, buildCaddyfile emits a
transport http { tls_insecure_skip_verify } block so Caddy connects over TLS
and accepts the self-signed certificate typical of backends like Semaphore.
Added a UI hint explaining the https:// prefix.
2026-06-08 14:43:29 +02:00
6f621067b9 fix(server): register SPA catch-all last so /api GET routes are reachable
The static/SPA fallback (app.get('*')) was registered before the Caddy
routes, so every GET /api/caddy/* request was swallowed by the catch-all and
returned index.html instead of JSON. POST/PUT/DELETE still worked because
app.get('*') only matches GET — which is why adding routes worked but the
list was always empty. Move the static block to just before app.listen, and
add Cache-Control: no-store on /api so stale HTML can't be served via 304.
2026-06-08 14:37:33 +02:00
d429b2d252 refactor(caddy): flatten routes to a plain array like bookings
GET /api/caddy/routes now returns the route array directly instead of
{ system, custom }. Frontend state is CaddyRoute[] initialised to [],
rendered with a simple .map() and an empty-state message — mirroring how
bookings are loaded and displayed.
2026-06-08 14:08:57 +02:00
1526d25144 fix(caddy): decouple status check from routes fetch, use useEffect for load trigger
Routes now load immediately from DB without waiting for the Caddy Admin API
status check (which can take up to 2s timeout). A dedicated useEffect on
caddyEnabled replaces the unreliable fire-and-forget call inside loadSettings.
2026-06-08 13:59:03 +02:00
2857040803 docs(architecture): remove revision history, add PUT /caddy/routes/{id} to API reference 2026-06-08 13:40:36 +02:00
acadf8db7c fix(caddy): prevent duplicate routes and make status/routes fetches independent
POST /api/caddy/routes now returns 409 if the hostname already exists,
preventing duplicate DB entries that cause Caddy's "ambiguous site definition" error.

loadCaddyRoutes uses Promise.allSettled so a failure in the status check
can no longer silently prevent the routes list from loading.
2026-06-08 13:37:22 +02:00
250c347f58 feat(caddy): import Caddyfile routes on startup if table is empty
On every startup, if Caddy is already enabled but the caddy table has no
routes (e.g. after a re-deploy), importCaddyfileRoutes() is called so the
static GhostGrid entries from /etc/caddy/Caddyfile are seeded automatically.
Also ensures deploy.sh is executable via proxmox-ghostgrid.sh.
2026-06-08 13:17:48 +02:00
f66b1ca456 feat(caddy): route edit, system log entries, fix routes load timing
Add inline edit for custom routes (Pencil icon → inline form with all fields).
Log route add/update/delete/import to the logs table (type: system) so
operations appear in the Logbook. Fix loadCaddyRoutes() called without await
after settings save, causing a race between the success message and route list.
2026-06-08 13:04:01 +02:00
00cf5dd02d feat(caddy): auto-import Caddyfile on first enable; seed default admin user
When Caddy is enabled for the first time (caddy routes table empty),
importCaddyfileRoutes() reads /etc/caddy/Caddyfile and seeds all
hostname/upstream blocks as custom routes — no manual entry needed after deploy.

On first startup with an empty users table, a default admin user is created
(admin@ghostgrid.local / admin) so the system is immediately usable.
2026-06-08 10:09:26 +02:00
47e7b65613 chore: replace arrow glyphs with ASCII and tidy whitespace 2026-06-08 09:31:44 +02:00
e5e7c571a4 feat(settings): add database panel with info, backup and import
Add a Database section under Settings (split into Integrations/System
tabs) showing SQLite file size, last-modified date, a proportional
table-usage bar and per-table row counts. Supports downloading a
consistent backup and importing a .db file that overwrites the entire
database, with an explicit overwrite warning and confirmation.

Backend adds GET /api/database/info, GET /api/database/backup and
POST /api/database/import; DB_FILE is now exported from server-db.
2026-06-08 09:31:35 +02:00
f1200425af refactor(caddy): remove redundant GhostGrid domain fields, keep only custom routes
caddy_prod_domain and caddy_dev_domain are already handled by the Proxmox deploy
process. The Caddy integration is a generic TLS proxy for additional services
(Semaphore, Netbox, etc.) — the custom routes list is the sole mechanism.
2026-06-08 08:45:24 +02:00
7afb4829bc refactor(ui): comprehensive light mode fixes and dashboard cleanup
- Light mode: fix 40+ missing CSS overrides (solid emerald/cyan bg-950,
  300-level text colours, border opacity variants, hover states, violet
  accent, bg-slate-900/30 and /90, bg-rose-950/30, red-950/50)
- Light mode: fix broad bg-gradient-to-br override to only target dark
  banner cards (from-[#1E293B]), preserving coloured user avatar gradients
- Light mode: BookingDetailsModal JSON panel switched to GitHub-Light style
  (bg #f6f8fa) including <pre> override so the general 'pre' rule cannot
  darken it back
- Dashboard: simplify banner (flat card, no gradient/watermark/time-widget)
- Dashboard: reduce visual noise (shorter titles, remove LIVE animated badge,
  remove italic notes quote, neutral checklist items, no footer jargon)
- Dashboard: normalise section-icon colours to slate-400 except Active (emerald)
- Dashboard: replace non-standard Tailwind classes (slate-101/350/905/1000,
  indigo-405, emerald-990) with valid equivalents
- Dashboard: standardise button style to rounded-lg + text-xs across
  Active Reservations and Upcoming cards; add visible borders on Cancel/Purge
2026-06-05 11:08:34 +02:00
33c7b2ba65 fix(light-mode): JSON panel header dark, orange Ansible card overrides
- JSON panel header (bg-slate-900) no longer flips to light gray inside
  the dark terminal block — scoped override keeps it #161b22
- Title text and copy button styled consistently for dark context
- Orange Ansible status card gets proper light-mode colors (orange-50 bg,
  orange-200 border, orange-600 text)
- application/json badge tweaked to text-indigo-400 for consistency
2026-06-05 10:09:52 +02:00
aa5c2332e8 fix(ui): use semi-transparent indigo badge for application/json label 2026-06-05 10:03:46 +02:00
de4aef3d19 fix(logbook): remove 'All incl. System' filter, Ansible triggers as booking logs
Ansible trigger successes now logged as type 'booking' so they appear
in the default filter view. Removed the redundant 'All incl. System'
filter button.
2026-06-05 10:02:01 +02:00
7758bcaa02 refactor(ui): remove mock Ansible panel, settings in 3-column grid
BookingDetailsModal: remove static playbook template and fake simulator,
keep only the JSON REST response panel. Settings: drop max-w-2xl,
wrap integration cards in lg:grid-cols-3 so Azure, CheckMK and
Semaphore sit side by side on wide screens.
2026-06-05 09:54:54 +02:00
c428b12352 fix(semaphore): update modal status immediately after manual trigger
Local state tracks in-session triggers so the UI flips to 'Triggered'
without waiting for the parent to re-fetch bookings.
2026-06-05 09:44:33 +02:00
70399a00ec feat(semaphore): trigger Ansible tasks at booking start/end via Semaphore
- Background scheduler checks every 30s for bookings that need setup or teardown
- Per-lab Semaphore template IDs stored on the labs table
- Booking flags track which jobs have been triggered and their Semaphore job IDs
- Immediate teardown triggered when an active booking is cancelled
- Settings UI section for Semaphore API URL, token, and project ID
- Lab template form fields for setup/teardown template IDs
- BookingDetailsModal shows live Ansible job status with manual trigger buttons
2026-06-05 09:39:58 +02:00
11eb06c5ad fix(logbook): system filter as proper type button, default hides system entries 2026-06-05 09:20:52 +02:00
ea9e6c1d46 feat: CheckMK host link in inventory, system logs hidden by default in logbook 2026-06-05 09:16:08 +02:00
20308b53d6 fix(checkmk): correct columns query param format for /objects/host endpoint 2026-06-05 08:57:52 +02:00
744468c13d debug(checkmk): log full host response, add columns param to get state 2026-06-04 15:32:10 +02:00
985178ea84 fix(checkmk): per-host state lookup via /objects/host/{name}, remove batch collection call 2026-06-04 15:27:21 +02:00
15c4e3f6ac debug(checkmk): log raw monitoring response, remove query param, try multiple container keys 2026-06-04 15:22:00 +02:00
7731a1a9af fix(checkmk): add query filter to monitoring endpoint, precise permission guidance in log 2026-06-04 15:17:28 +02:00
9fba11ccd6 fix(checkmk): detect empty monitoring collection, log permission hint + host_config probe 2026-06-04 15:04:19 +02:00
789fe1f8e0 fix(checkmk): add diagnostic log to compare config vs monitoring host IDs 2026-06-04 14:57:19 +02:00
a58b321a50 fix: replace dynamic auth import with static import to silence Vite warning 2026-06-04 14:49:25 +02:00
626871213d fix(checkmk): use monitoring collection endpoint, batch state fetch, clearer Settings hints 2026-06-04 14:48:15 +02:00
59f11356ec fix(checkmk): readable error messages in Logbook, strip HTML from API errors 2026-06-04 14:30:25 +02:00
b223e6dfe9 fix: red error banner readable in light mode 2026-06-04 14:26:32 +02:00
e13e11ce6a feat: log login events in logbook; improve CheckMK error reporting
Successful logins now write a system-type logbook entry.
CheckMK sync reports configuration errors, host-fetch failures,
and per-device sync failures as logbook entries instead of silently
dropping them; inherits effective_attributes IP fallback.
2026-06-04 14:21:05 +02:00