From e0e8e8827650c01d05a080a1a85afb6b87e74ec8 Mon Sep 17 00:00:00 2001 From: kisfenyo Date: Sat, 14 Feb 2026 17:55:14 +0100 Subject: [PATCH] GUI updates, live log visibility --- CLAUDE.md | 3 + CONTEXT.md | 26 ++---- controller/README.md | 6 +- controller/internal/web/server.go | 11 ++- controller/internal/web/templates.go | 120 ++++++++++++++++++++++++--- 5 files changed, 134 insertions(+), 32 deletions(-) diff --git a/CLAUDE.md b/CLAUDE.md index 680b14a..297c367 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -99,6 +99,9 @@ ssh kisfenyo@192.168.0.162 "cd /opt/docker/felhom-controller && docker pull gite - Protected stacks (traefik, cloudflared, felhom-controller) can't be stopped from UI - `app.yaml` persists deploy config; `deployed: true` flag controls UI state - Password fields require explicit user input or generation (no silent auto-fill) +- App cards on dashboard and stacks pages are clickable via `data-href` attribute (skip protected stacks) +- Logs page uses AJAX polling (`?raw=1` query param returns plain text) with auto-scroll and pause/resume +- Memory bar on deploy page uses two-segment stacked bar (committed = solid green, new = translucent green) ## Git sync module (internal/sync) diff --git a/CONTEXT.md b/CONTEXT.md index 804b28b..b75fb82 100644 --- a/CONTEXT.md +++ b/CONTEXT.md @@ -31,24 +31,14 @@ Last updated: 2026-02-14 (session 3) ### What was just completed (2026-02-14 session 3) - **Enhanced debug logging** across all stack operations in `internal/stacks/`: - **Operation timing**: All stack ops (start, stop, restart, update, deploy) now log elapsed time - - Success: `[INFO] Stack immich started successfully (took 45.2s)` - - Failure: `[ERROR] Stack immich start failed after 3.1s: exit code 1` - - **`composeExecCustomEnv` improvements**: - - Logs env var **keys** at debug level (never values — secrets stay safe) - - Logs exit code, truncated stdout/stderr (max 500 chars) on failure - - Logs command completion time on success - - **Post-start container state check** (StartStack, RestartStack, UpdateStack, DeployStack): - - Async goroutine: sleeps 3s, runs `docker compose ps -a`, logs each container's state - - Critical for detecting crash-loops that `docker compose up -d` wouldn't surface - - Non-blocking — never fails the operation, just logs a warning if check fails - - **Image pull detection** (DeployStack, UpdateStack at debug level): - - Parses `docker-compose.yml` for `image:` lines - - Runs `docker image inspect` per image to check local availability - - Skips images with `${VAR}` interpolation (can't resolve at check time) - - **GetLogs improvement**: Logs byte count of returned logs (distinguishes empty vs failure) - - **ScanStacks improvement**: `[INFO] Scanned stacks: 10 found (3 deployed, 7 available)` - - **New helpers added to manager.go**: `isDebug()`, `truncateStr()`, `logPostStartStatus()`, `checkLocalImages()` - - All verbose checks gated on `cfg.Logging.Level == "debug"`; timing and container states always logged at INFO + - **Post-start container state check**: Async goroutine after start/restart/update/deploy + - **Image pull detection**: Checks local images before deploy/update (debug level) + - **GetLogs/ScanStacks improvements**: Byte count logging, deployed/available counts + - All verbose checks gated on `cfg.Logging.Level == "debug"`; timing always at INFO +- **UI improvements** in `internal/web/templates.go` and `server.go`: + - **Memory bar fix on deploy page**: Bar segments now always visible (min-width: 3px), new app segment uses translucent green with distinct border for clear visual separation from committed memory + - **Clickable app cards**: Cards on Vezérlőpult and Alkalmazások pages are now clickable (navigates to deploy/detail page). Uses `data-href` attribute + delegated click handler. Protected stacks excluded + - **Live-scrolling logs**: Logs page now auto-refreshes every 3s via AJAX polling (`?raw=1` returns plain text). Fixed-height container (70vh) with auto-scroll to bottom. Pulsing green "Élő" indicator. Pause/resume toggle ("Szüneteltetés"/"Folytatás"). User scroll position preserved when scrolled up to read history ### Previously completed (2026-02-15 session 2) - **Phase 4: Git Sync + App Catalog Audit** — major milestone diff --git a/controller/README.md b/controller/README.md index f020ac9..46a3186 100644 --- a/controller/README.md +++ b/controller/README.md @@ -30,7 +30,7 @@ Current version: **v0.2.1** - Dashboard with live container state (green/orange/yellow/red) - Deploy form with password validation, auto-generation, and field locking - Stack operations: start, stop, restart, update (pull + recreate) -- Log viewer for each stack +- Live-scrolling log viewer with auto-refresh (3s polling), pause/resume, and scroll position tracking - Deploy page doubles as config viewer (read-only mode for deployed apps) - Periodic stack rescanning (every 2 minutes) - Manual rescan endpoint (`POST /api/stacks/rescan`) @@ -42,6 +42,8 @@ Current version: **v0.2.1** - Memory summary bar shown on deploy page before deployment - Felhom.eu logo SVG in sidebar and login page - Verbose debug logging with operation timing, post-start container state checks, and image pull detection +- Clickable app cards on dashboard and applications pages (navigate to detail/deploy page) +- Memory bar with two-segment visualization on deploy page (committed vs new app allocation) ### Known issues / next priorities - Cloudflare Tunnel + Traefik TLS: paperless.demo-felhom.eu works locally but shows "Not secure" (certificate chain not fully validated through tunnel) @@ -361,7 +363,7 @@ docker compose up -d | POST | `/api/stacks/{name}/stop` | Yes | Stop stack (not protected) | | POST | `/api/stacks/{name}/restart` | Yes | Restart stack | | POST | `/api/stacks/{name}/update` | Yes | Pull images + recreate | -| GET | `/api/stacks/{name}/logs` | Yes | Container logs | +| GET | `/api/stacks/{name}/logs` | Yes | Container logs (add `?raw=1` for plain text) | | POST | `/api/stacks/rescan` | Yes | Trigger manual stack discovery | | GET | `/api/system/info` | Yes | System resource usage (RAM, disk, HDD) | diff --git a/controller/internal/web/server.go b/controller/internal/web/server.go index 64c5d0f..8c78114 100644 --- a/controller/internal/web/server.go +++ b/controller/internal/web/server.go @@ -373,10 +373,10 @@ func (s *Server) stacksHandler(w http.ResponseWriter, _ *http.Request) { s.render(w, "stacks", data) } -func (s *Server) logsHandler(w http.ResponseWriter, _ *http.Request, name string) { +func (s *Server) logsHandler(w http.ResponseWriter, r *http.Request, name string) { stack, ok := s.stackMgr.GetStack(name) if !ok { - http.NotFound(w, nil) + http.NotFound(w, r) return } @@ -385,6 +385,13 @@ func (s *Server) logsHandler(w http.ResponseWriter, _ *http.Request, name string logs = fmt.Sprintf("Hiba a naplók lekérésekor: %v", err) } + // Raw mode: return plain text for AJAX polling + if r.URL.Query().Get("raw") == "1" { + w.Header().Set("Content-Type", "text/plain; charset=utf-8") + fmt.Fprint(w, logs) + return + } + data := s.baseData("logs", stack.Meta.DisplayName+" — Naplók") data["Stack"] = stack data["Logs"] = logs diff --git a/controller/internal/web/templates.go b/controller/internal/web/templates.go index 41fa554..97e1550 100644 --- a/controller/internal/web/templates.go +++ b/controller/internal/web/templates.go @@ -37,6 +37,11 @@ const layoutTmpl = ` {{define "layout_end"}} {{template "layout_end" .}} {{end}} ` @@ -1221,19 +1284,21 @@ select.form-control option { background: var(--bg-secondary); color: var(--text- } .memory-bar-segment { height: 100%; - min-width: 0; position: relative; z-index: 1; transition: width 0.3s ease; } +.memory-bar-segment:not([style*="width:0%"]) { + min-width: 3px; +} .memory-bar-committed { background: var(--green); box-shadow: 0 0 6px rgba(35, 134, 54, 0.4); border-radius: 5px 0 0 5px; } .memory-bar-new { - background: #4edf72; - box-shadow: 0 0 6px rgba(78, 223, 114, 0.3); + background: rgba(35, 134, 54, 0.45); + border-right: 2px solid #4edf72; border-radius: 0 5px 5px 0; } .memory-bar-legend { @@ -1259,7 +1324,8 @@ select.form-control option { background: var(--bg-secondary); color: var(--text- background: var(--green); } .memory-legend-new { - background: #4edf72; + background: rgba(35, 134, 54, 0.45); + border: 1px solid #4edf72; } /* Logs */ @@ -1269,6 +1335,8 @@ select.form-control option { background: var(--bg-secondary); color: var(--text- border-radius: var(--radius); padding: 1rem; overflow-x: auto; + overflow-y: auto; + max-height: 70vh; margin-bottom: 1rem; } .logs-output { @@ -1278,8 +1346,37 @@ select.form-control option { background: var(--bg-secondary); color: var(--text- line-height: 1.5; white-space: pre-wrap; word-break: break-all; + margin: 0; +} +.logs-actions { + display: flex; + gap: .5rem; + align-items: center; +} +.logs-live-indicator { + display: inline-flex; + align-items: center; + gap: .35rem; + font-size: .8rem; + font-weight: 600; + color: var(--green); + margin-right: .5rem; +} +.logs-live-indicator.logs-live-paused { + color: var(--text-muted); +} +.logs-live-dot { + display: inline-block; + width: 8px; + height: 8px; + border-radius: 50%; + background: var(--green); + animation: logs-pulse 1.5s ease-in-out infinite; +} +@keyframes logs-pulse { + 0%, 100% { opacity: 1; } + 50% { opacity: 0.3; } } -.logs-actions { display: flex; gap: .5rem; } /* Sync toast */ .sync-toast { @@ -1301,6 +1398,9 @@ select.form-control option { background: var(--bg-secondary); color: var(--text- border-color: rgba(218, 54, 51, 0.3); } +/* Clickable cards */ +[data-href] { cursor: pointer; } + .empty-state { text-align: center; padding: 3rem; color: var(--text-muted); } /* Login page */