Commit Graph

218 Commits

Author SHA1 Message Date
Anso 23fc702296 fix(network-topology): harden with edge-case fixes, logging, and test coverage (#583)
* fix(network-topology): harden with edge-case fixes, logging, and test coverage

Fixes stale data on node switch (topology now refreshes when active node changes),
adds manual refresh button to toolbar, and guards against containers with empty
Names arrays by falling back to a 12-char short ID. Adds operational and diagnostic
logging around topology fetches, and introduces 25 unit tests covering happy paths,
system network filtering, stack resolution, container deduplication, edge cases,
and error handling.

* docs: refresh screenshots
2026-04-14 10:59:11 -04:00
Anso 718c1eb1ea fix(host-console): harden with security fixes, validation, and test coverage (#580)
Security:
- Enforce RBAC (system:console permission) on WebSocket upgrade
- Validate token_version for user sessions on WS connections
- Expand env var sanitization to cover additional secret patterns
  (PRIVATE, AUTH, PASSPHRASE, ENCRYPT, SIGNING) and connection
  strings (REDIS_URL, MONGO_URI, AMQP_URL, DSN)

Reliability:
- Add session tracking with max 5 concurrent console sessions
- Add WebSocket heartbeat (30s ping, 60s pong timeout) to detect
  and clean up dead connections and orphaned PTY processes
- Differentiate PTY spawn error messages (shell not found,
  permission denied, generic failure)
- Guard against duplicate cleanup when both WS close and PTY exit
  fire

Observability:
- Add structured logging with [HostConsole] prefix for session
  lifecycle (open, close, duration, user, pid)
- Add diagnostic logging behind developer_mode for terminal resize
  events and message parse errors

Frontend:
- Replace hardcoded hex colors with oklch CSS custom properties
  (--terminal-bg, --terminal-fg, --terminal-cursor, etc.)
- Apply design system material tokens (shadow-card-bevel, recessed
  well shadow, card border hierarchy, strokeWidth 1.5)

Tests:
- Add 20 tests covering env sanitization patterns (10 keyword
  categories + safe vars + case insensitivity), session tracking,
  and console-token RBAC (admin, viewer, deployer, API tokens)

Docs:
- Document admin role requirement and session limits
- Add troubleshooting section (session limits, shell not found,
  proxy timeouts, missing console tab)
- Update security section with expanded env var coverage
2026-04-14 10:06:59 -04:00
Anso c4ff58347e fix(container-exec): harden with security fixes, validation, and test coverage (#577)
* fix(container-exec): harden with security fixes, validation, and test coverage

- Enforce admin role at WebSocket upgrade for container exec sessions
- Validate container is running before creating exec
- Fix bash-to-sh fallback (move .start() inside try/catch)
- Register container-exec as a capability for fleet visibility
- Add standard and diagnostic logging for exec lifecycle
- Fix false Admiral license claim in API docs
- Fix design system violations in BashExecModal (hardcoded colors)
- Remove duplicate legacy xterm dependencies
- Add 18-test suite covering auth, validation, fallback, and cleanup

* fix(container-exec): use correct Duplex type for exec stream

The stream variable was typed as NodeJS.ReadWriteStream, which lacks
.destroy(). Dockerode's Exec.start() returns stream.Duplex per its
type definitions. This caused tsc to fail while Vitest (which skips
full type checking) passed.
2026-04-14 08:23:05 -04:00
Anso 0a94df318a fix(alerts): harden with security fixes, design compliance, and test coverage (#570)
* fix(alerts): harden with security fixes, design compliance, and test coverage

Add authMiddleware to all alert endpoints, validate notification test
dispatch inputs, fix restart_count metric via Docker inspect, correct
network metric units, replace any types with DockerContainerStats
interface, add webhook timeouts and dispatch error tracking.

Frontend: migrate Select to Combobox, add ScrollArea and delete
confirmation AlertDialog, fix icon strokeWidth to 1.5.

Add update availability notifications for both Sencho version updates
(6-hour check in MonitorService) and stack image updates (state
transition detection in ImageUpdateService). Extract shared version
fetch logic into utils/version-check.ts.

Add diagnostic logging gated behind developer_mode for MonitorService
breach state machine and NotificationService dispatch routing.

Tests: 24 new alert API integration tests, restart_count and version
check unit tests (688 total passing). Docs updated with HTTPS
requirement, update notifications section, and troubleshooting guide.

* fix(alerts): remove unused TEST_USERNAME import in alerts-api tests
2026-04-13 21:40:53 -04:00
Anso e0d1ca9dc0 fix(api-tokens): harden with security fixes, design compliance, and test coverage (#567)
Security: add JWT-level expiry ceiling (400d), per-user token count limit (25),
and token name uniqueness enforcement. Fix async clipboard copy.

Design: migrate Select to Combobox, apply card bevel styling, fix icon
strokeWidth, add tabular-nums to timestamps, fix destructive button pattern.

Tests: expand from ~20 to 47 test cases covering creation validation, token
limits, name uniqueness, last_used_at tracking, ownership constraints, delete
edge cases, and registry blocked endpoints.

Docs: update API Tokens docs with token limits, name uniqueness, registry
restrictions, and JWT expiry ceiling. Update OpenAPI spec with 409 response.
2026-04-13 18:32:07 -04:00
Anso 1d89e8ce59 fix(sso): harden SSO with role sync, security fixes, design compliance, and test coverage (#564)
Rate-limit OIDC callback route and clear state cookie on all paths.
Validate GitHub API responses and add server-side config validation.
Sync SSO user roles on every login respecting seat limits.
Add LDAP connection timeout, bind DN warning, and multiple entry logging.
Replace Select with Combobox and apply card design tokens in SSOSection.
Expose OIDC scopes field in settings UI.
Fix hardcoded colors to use design system tokens.
Add standard and diagnostic logging throughout SSO flow.
Add tests for role sync, seat limits, LDAP escaping, and config validation.
Remove unused login-form.tsx template.
Update docs and screenshots for SSO feature.
2026-04-13 17:46:38 -04:00
Anso 6daa7a135d fix(audit): harden audit log with summary fixes, design compliance, and test coverage (#561)
* fix(audit): harden audit log with summary fixes, design compliance, and test coverage

Replace stale /compose/* route summaries with current /stacks/* patterns,
add ~30 missing route summaries for container ops, labels, fleet updates,
SSO, templates, and auto-update. Fix X-Forwarded-For extracting full
proxy chain instead of first client IP. Pre-sort pattern table at module
load for O(1) lookup instead of sorting per request.

Add Calendar and DatePicker UI components (react-day-picker v9 + date-fns)
replacing browser-native date inputs. Align AuditLogView with design system:
Combobox instead of Select, design token colors, strokeWidth 1.5, card bevel,
tabular-nums on all numeric cells.

Add diagnostic logging gated behind developer mode for audit middleware,
query, and export endpoints. Extract getAuditSummary to testable utility.
Add 32 tests covering summary resolution, DB round-trips, API permissions,
export formats, and middleware integration.

* fix(audit): use correct Chevron prop types for react-day-picker compatibility
2026-04-13 15:38:33 -04:00
Anso c261fbc327 fix(rbac): harden user management with token versioning, session invalidation, and test coverage (#558)
Security fixes:
- Map deploy-only API tokens to deployer role (not admin)
- Add token versioning to invalidate sessions on password/role changes
- Reject deleted users immediately in auth middleware (no 24h JWT grace)
- Use DB role instead of JWT role so changes take effect instantly
- Block password setting on SSO-provisioned users
- Check proxy variant in scoped permission resolver

Cleanup and logging:
- Remove orphaned role assignments when stacks/nodes are deleted
- Add standard logging for login, user CRUD, role assignments, password changes
- Add diagnostic logging gated behind developer_mode setting
- Extract issueSessionCookie helper (DRY across 5 JWT signing sites)

Frontend:
- Replace Select with Combobox in UsersSection (design system compliance)
- Add strokeWidth={1.5} to Lucide icons
- Hide password fields for SSO-provisioned users

Testing:
- Add 42-test RBAC suite covering user CRUD, token versioning, scoped
  assignments, permissions endpoint, password management, seat limits,
  last-admin protection, and orphan cleanup
2026-04-13 14:31:34 -04:00
Anso 809bf76c20 fix(fleet): harden fleet snapshots with DRY capture, audit fixes, and design compliance (#555)
Extract duplicated snapshot capture functions from index.ts and
SchedulerService.ts into a shared module (snapshot-capture.ts). Fix
audit log route patterns that used singular 'snapshot' instead of
plural 'snapshots'. Apply design system to FleetSnapshots component
(card styling, strokeWidth, font-mono, tabular-nums, ScrollArea).
Add safePage pagination, loading toast for creation, and fix the
restore dialog race condition with a controlled AlertDialog. Add
diagnostic logging gated behind Developer Mode. Add tests for
restore endpoint, admin role enforcement, and edge cases. Update
docs with troubleshooting section and refresh screenshots.
2026-04-13 13:26:55 -04:00
Anso a695251f38 fix(labels): harden stack labels with nodeId filtering, concurrency guard, and test coverage (#552)
* fix(labels): add nodeId filter, existence check, stale cleanup, concurrency guard, and body validation

- Fix getStacksForLabel to filter by node_id (prevents cross-node data leak)
- Add getLabel(id, nodeId) for single-label existence check
- Add getLabelCount(nodeId) for enforcing per-node label limit (50)
- Add cleanupStaleAssignments to remove orphaned assignments for deleted stacks
- Add label/assignment cleanup to deleteNode transaction
- Add label existence check on bulk action endpoint (returns 404 for missing labels)
- Add concurrency guard on bulk actions (returns 429 if already in-flight)
- Add requireBody guard on all mutation endpoints
- Extract isSqliteUniqueViolation helper to deduplicate constraint checks
- Add MAX_LABELS_PER_NODE constant (50) with limit enforcement on create
- Add diagnostic logging on all label endpoints (gated behind developer_mode)
- Add operational log line for bulk action results

* fix(labels): use ScrollArea, show failure details, add loading feedback, deduplicate constants

- Replace overflow-y-auto div with ScrollArea in LabelAssignPopover (design system)
- Show failed stack names in bulk action error toast
- Add loading toast for context menu label toggle
- Disable bulk action menu items while a bulk action is running
- Disable "New Label" button at 50-label limit with "Limit reached" text
- Export LABEL_COLORS and MAX_LABELS_PER_NODE from LabelPill (single source of truth)
- Import shared constants in LabelAssignPopover and LabelsSection (remove duplicates)
- Add BulkActionResult interface to replace inline type assertion

* test(labels): add comprehensive coverage for label CRUD, assignments, and bulk edge cases

42 tests covering:
- getLabels: empty, ordered, node isolation
- getLabel: found, wrong node, nonexistent
- createLabel: returns with ID, duplicate name constraint
- getLabelCount: correct count, zero for empty node
- updateLabel: name, color, both, not found, wrong node
- deleteLabel: removes label, cascades assignments, wrong node no-op
- setStackLabels: assign, replace, clear, invalid ID throws
- getLabelsForStacks: correct mapping, empty result
- getStacksForLabel: correct results, node filter, empty for nonexistent
- cleanupStaleAssignments: removes stale, preserves valid, handles empty
- deleteNode: cascades labels and assignments
- Edge cases: atomicity, cascade across stacks, multi-label assignment

* docs(labels): document 50-label limit and bulk action failure details

* fix(labels): add missing LabelColor type imports and explicit parameter types

* refactor(labels): extract label types and constants to label-types.ts

Moves LabelColor, Label, LABEL_COLORS, and MAX_LABELS_PER_NODE out of
LabelPill.tsx into a dedicated non-component file. This fixes the
react-refresh/only-export-components lint error caused by mixing
constant exports with component exports.
2026-04-13 12:49:03 -04:00
Anso 44e8fdfba9 fix(scheduler): harden scheduled operations with stale cleanup, cron validation, and design fixes (#549)
* fix(scheduler): clean up stale runs on startup and auto-disable invalid cron tasks

- Add markStaleRunsAsFailed() bulk DB method with status index
- Clean up orphaned 'running' records on scheduler startup
- Auto-disable tasks when cron expression becomes invalid at execution time
- Promote CRUD debug logs to standard logs for scheduled task admin actions
- Add diagnostic logging for task pre-checks, action timing, and prune fallback

* refactor(scheduling): extract shared types and fix design system violations

- Extract ScheduledTask, TaskRun, NodeOption to shared types file
- Extract getCronDescription and formatTimestamp to shared utilities
- Fix formatTimestamp falsy-zero null check
- Tighten last_status type to 'success' | 'failure' | null
- Add strokeWidth={1.5} to all action icons per design system
- Add sr-only DialogDescription for accessibility
- Wrap Sheet run history in ScrollArea
- Fix delete button styling to match design system pattern
- Change manual trigger toast from "executed" to "triggered"

* test(scheduler): add tests for snapshot, remote update, stale cleanup, and cron invalidation

- Add stale run cleanup tests (bulk markStaleRunsAsFailed, logging)
- Add cron invalidation test (auto-disable, error message)
- Add executeSnapshot tests (fleet capture, empty stacks)
- Add executeUpdateRemote tests (proxy success, remote error)
- Document stale run cleanup and cron auto-disable in troubleshooting docs
2026-04-13 11:32:09 -04:00
Anso a17b16b258 fix(scheduler): harden auto-update policies with cascade deletes, error reporting, and UI fixes (#545)
* fix(scheduler): harden auto-update policies with cascade deletes, error reporting, and UI fixes

- Fix orphaned task runs on policy/node deletion with transaction-wrapped cascade deletes
- Make manual trigger non-blocking (202 Accepted) to prevent proxy timeouts
- Distinguish registry check failures from clean "no update" results via structured ImageCheckResult
- Trim whitespace-only policy names in both frontend and backend validation
- Add strokeWidth={1.5} to action icons per design system
- Add sr-only DialogDescription for Radix accessibility
- Replace Select with Combobox for frequency picker
- Wrap run history sheet content in ScrollArea
- Support concurrent Run Now indicators via Set-based state
- Abort stale stack fetches on node switch with AbortController
- Add standard and diagnostic logging to SchedulerService and ImageUpdateService
- Add tests for cascade deletes, image checking, and scheduler edge cases
- Add troubleshooting section to auto-update docs

* fix(tests): resolve lint errors in image-update-service tests

Remove unused mock variables (mockGetImage, mockGetDocker) and unused
ImageCheckResult type import. Replace CommonJS require('yaml') with
ESM import to satisfy no-require-imports rule.

* chore(deps): bump Docker CLI to 29.4.0 and Compose to v5.1.2

Resolves Trivy CVE-2026-32282 (Go stdlib symlink follow in Root.Chmod)
by upgrading to releases that ship Go 1.25.9. Compose v5.1.2 also bumps
grpc to 1.80.0, resolving CVE-2026-33186.

* chore(security): accept CVE-2026-32282 in .trivyignore, update stale refs

Go stdlib symlink-following in Root.Chmod (CVE-2026-32282) affects both
Docker CLI 29.4.0 (Go 1.26.1) and Compose v5.1.2 (Go 1.25.8). Fix
requires Go 1.25.9 or 1.26.2; no upstream static binary ships a patched
runtime yet. The vulnerable code path requires a chroot context with
attacker-controlled filesystem, which does not apply to our usage.

Also updates version references from v5.1.1/v29.3.1 to v5.1.2/v29.4.0
for existing CVE entries, and notes that Compose v5.1.2 resolved
CVE-2026-33186 (grpc bumped to 1.80.0) for the compose binary.
2026-04-13 09:49:11 -04:00
Anso d23c6779af fix(fleet): harden remote node updates with admin enforcement, expiry fix, and diagnostics (#542)
- Fix completed-entry auto-expiry using resolvedAt instead of startedAt
- Add admin role enforcement to update trigger and update-all endpoints
- Add missing error field in rejected-promise fallback for update-status
- Fix rejected promises in update-all losing node names
- Harden frontend recheck button with try/catch/finally error handling
- Align frontend isValidVersion with stricter regex validation
- Add diagnostic logging gated behind developer_mode
- Extract resolveTracker helper to centralize terminal state transitions
- Add 15 new fleet test cases covering auth, tier gating, input validation, and admin roles
- Document admin requirement and troubleshooting in fleet-view docs
2026-04-12 22:07:41 -04:00
Anso a74a516850 fix(logs): harden global logs with shared parsing, SSE fixes, and level filter (#539)
- Extract log parsing utilities (normalizeContainerName, parseLogTimestamp,
  detectLogLevel, stripControlChars, demuxDockerLog) to shared module,
  eliminating duplication between polling and SSE endpoints
- Fix timestamp regex to accept timezone offsets (+HH:MM/-HH:MM), not just Z
- Replace any[] with typed GlobalLogEntry interface
- Add SSE heartbeat (30s) to prevent reverse proxy timeouts
- Add X-Accel-Buffering: no header for nginx SSE compatibility
- Replace swallowed catch blocks with console.warn diagnostics
- Fix SSE not reconnecting on node switch (missing dep in effect array)
- Add settings change event so devMode/pollRate updates apply without remount
- Add log level filter (ALL/ERROR/WARN/INFO) to toolbar
- Replace overflow-auto div with ScrollArea for design system compliance
- Add strokeWidth={1.5} to all toolbar icons
- Include stack name in download format
- Surface fetch errors with inline banner
- Add 39 unit tests for all log parsing utilities
- Update docs with level filter documentation and refreshed screenshot
2026-04-12 21:19:08 -04:00
Anso 1702dabb7a fix(fleet): add auth middleware, input validation, and design system compliance (#536)
Add authMiddleware to all 13 fleet endpoints that were previously
accessible without authentication. Add NaN validation for parseInt
params, stackName validation on snapshot restore, and description
length cap on snapshot creation. Clean up updateTracker entries on
node deletion to prevent memory leaks.

Replace hardcoded colors with design system tokens, swap Select for
Combobox, replace overflow-y-auto with ScrollArea, fix card styling
(shadow-card-bevel, border tokens). Fix stale container data by
always refetching on stack expand with a loading guard against
concurrent requests.

Add operational logging for state-changing fleet operations and
diagnostic logging gated behind Developer Mode. Add 20 fleet tests
covering auth enforcement, input validation, tier gating, and
snapshot CRUD lifecycle.
2026-04-12 20:28:18 -04:00
Anso cd3d7b23be feat(app-store): add port conflict indicator to deploy sheet (#533)
Show a pulsating warning dot next to host ports that are already in
use by a running container. Hovering over the dot reveals which
Sencho-managed stack or external app occupies the port.

Adds GET /api/ports/in-use endpoint that returns a map of bound host
ports with ownership info, and a getPortsInUse method on
DockerController that reuses the existing container-to-stack
resolution logic.
2026-04-12 19:57:38 -04:00
Anso 4909c35e50 fix(resources): harden Resource Explorer with auth, validation, design, and UX fixes (#527)
- Sanitize error messages in all delete/prune/create/inspect endpoints
  to prevent Docker internals from leaking to the frontend
- Add CIDR, IPv4, and Docker resource ID input validation
- Add requirePaid gate to network topology endpoint
- Add invalidateNodeCaches after image/volume/network mutations
- Fix design system violations: card borders, destructive button variant,
  visible DialogDescription, overflow-auto replaced with ScrollArea,
  hardcoded Tailwind colors replaced with tokens
- Gate purge button behind isAdmin to prevent silent 403s
- Fix shared inspect loading state to be per-network-row
- Parse error response bodies for meaningful toast messages
- Add clipboard API fallback for non-HTTPS contexts
- Render Options section in network inspect sheet
- Add operational and diagnostic logging for resource operations
- Extend validation and DockerController test suites
- Update docs with Options field in network inspect
2026-04-12 15:31:35 -04:00
Anso d4882d32d9 fix(app-store): harden App Store with auth, validation, bug fixes, and design compliance (#523)
* fix(app-store): harden App Store with auth, validation, bug fixes, and design compliance

Add authMiddleware to GET /api/templates and POST /api/templates/deploy
endpoints. Add isValidStackName and isPathWithinBase checks to the deploy
endpoint. Replace fs.existsSync with async fsPromises.access. Extract
FileSystemService to local variable to avoid repeated getInstance calls.

Fix template mutation bug where PUID/PGID/TZ duplicated on re-open by
working on a copy instead of mutating state. Make env_file conditional
in generated compose YAML (only when env vars exist). Add port validation
(range 1-65535) with visual feedback and deploy blocking. Add env key
collision warning toast for custom variables.

Replace any types with proper LSIO API interfaces. Change catch types
from any to unknown with getErrorMessage. Add structured logging with
[Templates] prefix and diagnostic logging gated behind Developer Mode.

Align with design system: remove hardcoded bg-white and text-red-500,
use ScrollArea, fix destructive button variant, add tabular-nums and
strokeWidth 1.5, use cn() for conditional classes. Add empty-registry
state distinct from no-search-results.

Add 16 unit tests for TemplateService covering compose generation,
conditional env_file, env string generation, and cache clearing.
Update App Store docs with port validation and env collision details.

* refactor(app-store): remove unused interface exports

Remove export keyword from interfaces that are only used within their
own file: TemplateEnv, TemplateVolume, and TemplatesResponse in
TemplateService.ts; TemplateEnv and Template in AppStoreView.tsx.
No external consumers import these types.

* fix(app-store): remove unused fs default import

The fs.existsSync call was replaced with fsPromises.access in the
deploy endpoint, leaving the fs default import unused. Remove it
to fix the ESLint no-unused-vars error in CI.
2026-04-12 14:31:00 -04:00
Anso 2465f7607e fix(stacks): harden stack management with security, validation, and logging (#520)
* fix(stacks): harden stack management with security fixes, validation alignment, and logging

Validate WebSocket stack names with isValidStackName() to close a
path-traversal gap on the /api/stacks/:stackName/logs WS endpoint.
Align POST /api/stacks to use the canonical validator (allows underscores).
Replace error: any catch blocks with error: unknown + type narrowing.
Add cache invalidation to PUT /api/stacks/:stackName/env.
Rename DELETE param from :name to :stackName for consistency.

Add standard [Stacks] lifecycle logs and diagnostic [Stacks:debug] logs
gated behind the Developer Mode toggle (with 5s TTL cache).
Extract shared isDebugEnabled() and getErrorMessage() utilities.

Frontend: roll back optimistic status on API failure, guard unsaved
changes when switching stacks, pre-check duplicate names in App Store.

* docs(settings): update Developer Mode description to mention debug diagnostics
2026-04-12 05:43:15 -04:00
Anso 9db97107aa fix(dashboard): harden real-time dashboard with bug fixes and design compliance (#517)
* fix(dashboard): harden real-time dashboard with bug fixes and design compliance

- Fix host alert spam: add 5-minute cooldown for CPU/RAM/disk threshold
  alerts, preventing duplicate notifications every 30s during sustained
  breaches. Extract shared dispatchWithCooldown helper (also used by
  Docker janitor alerts).
- Fix memory metric inflation: subtract filesystem cache from stored
  memory_mb values, matching the existing calculateMemoryPercent logic.
- Fix crash detection reliability: replace fragile 'seconds ago' string
  matching with a tracked Set of alerted container IDs. Containers are
  only alerted once per crash event, with automatic cleanup when they
  start running again or after a 1-hour TTL.
- Fix health status bar: exited containers now trigger 'degraded' state
  independently of unread error notifications.
- Fix CPU chart Y-axis: auto-scale when aggregate container CPU exceeds
  100% instead of silently clipping at the hardcoded domain ceiling.
- Fix grammar: 'actives' to 'active' in container count label.
- Add shadow-card-bevel to all dashboard cards per design system.
- Update dashboard docs to reflect revised health status thresholds.

* test(dashboard): update monitor service tests for new alert signatures

- Add container Id fields to crash detection test fixtures
- Update host alert assertions to match dispatchWithCooldown 3-arg call
- Fix unhealthy container test to use State: 'unhealthy' instead of
  State: 'running' (running containers are now skipped in crash detect)
2026-04-12 04:25:41 -04:00
Anso 4950cd0bd0 fix(updates): scan all filesystem stacks for image updates (#514)
ImageUpdateService previously discovered stacks by iterating Docker
containers, which meant stacks without containers (e.g. after
docker compose down) were silently excluded from update checks.

Switch to a hybrid discovery approach: enumerate stacks from the
filesystem via FileSystemService.getStacks(), parse compose files
for image refs with .env variable resolution, then augment with
container-based image discovery for running stacks.

Also cleans up stale stack_update_status entries when stacks are
deleted or no longer exist on disk, and replaces the plain
update-available tooltip with an animated cursor follow pattern.
2026-04-12 03:17:37 -04:00
Anso 622c1f9262 feat: home dashboard and Settings Hub polish (#506)
* feat(dashboard): drop CPU column and relative timestamp from Stack Health and status bar

The Stack Health table's CPU column duplicated data already surfaced in
the top ResourceGauges and the CPU Usage historical chart. The health
status bar's 'just now' timestamp was cosmetic: no consumer relied on
lastUpdated state for polling, staleness detection, or conditional
rendering. Removing both tightens the dashboard and eliminates a dead
prop chain through useDashboardData.

* refactor: remove dead admin_email field from setup flow

The Setup form captured an admin email under 'Used for license recovery.
Never shared with third parties.' but the value was written to
global_settings and read nowhere: no license recovery, SMTP, or support
contact flow consumed it. Rather than building UI on top of the dead
field, delete the input, the payload key, and the backend persistence.
Any orphaned row from prior setups is harmless and the frontend ignores
unknown settings keys.

* feat(settings): use Radix ScrollArea with per-section scroll memory

Settings Hub used a native-scroll div that snapped to the top every time
the user switched subsections and exposed the default browser scrollbar.
Wrap the nav and content panes with the shadcn ScrollArea (Radix under
the hood, type='hover') and expose a viewportRef so the modal can stash
each section's scrollTop in a ref and restore it via useLayoutEffect on
switch. Style the thumb with translucent foreground tokens so it reads
as glass against popovers and dialogs. Replaces a hand-rolled scroll
hook and ad-hoc CSS utility.
2026-04-12 01:04:15 -04:00
dependabot[bot] 6fc9c73463 chore(deps): bump the all-npm-frontend group in /frontend with 5 updates (#467)
Bumps the all-npm-frontend group in /frontend with 5 updates:

| Package | From | To |
| --- | --- | --- |
| [lucide-react](https://github.com/lucide-icons/lucide/tree/HEAD/packages/lucide-react) | `1.7.0` | `1.8.0` |
| [react](https://github.com/facebook/react/tree/HEAD/packages/react) | `19.2.4` | `19.2.5` |
| [react-dom](https://github.com/facebook/react/tree/HEAD/packages/react-dom) | `19.2.4` | `19.2.5` |
| [react-is](https://github.com/facebook/react/tree/HEAD/packages/react-is) | `19.2.4` | `19.2.5` |
| [vite](https://github.com/vitejs/vite/tree/HEAD/packages/vite) | `8.0.7` | `8.0.8` |


Updates `lucide-react` from 1.7.0 to 1.8.0
- [Release notes](https://github.com/lucide-icons/lucide/releases)
- [Commits](https://github.com/lucide-icons/lucide/commits/1.8.0/packages/lucide-react)

Updates `react` from 19.2.4 to 19.2.5
- [Release notes](https://github.com/facebook/react/releases)
- [Changelog](https://github.com/facebook/react/blob/main/CHANGELOG.md)
- [Commits](https://github.com/facebook/react/commits/v19.2.5/packages/react)

Updates `react-dom` from 19.2.4 to 19.2.5
- [Release notes](https://github.com/facebook/react/releases)
- [Changelog](https://github.com/facebook/react/blob/main/CHANGELOG.md)
- [Commits](https://github.com/facebook/react/commits/v19.2.5/packages/react-dom)

Updates `react-is` from 19.2.4 to 19.2.5
- [Release notes](https://github.com/facebook/react/releases)
- [Changelog](https://github.com/facebook/react/blob/main/CHANGELOG.md)
- [Commits](https://github.com/facebook/react/commits/v19.2.5/packages/react-is)

Updates `vite` from 8.0.7 to 8.0.8
- [Release notes](https://github.com/vitejs/vite/releases)
- [Changelog](https://github.com/vitejs/vite/blob/main/packages/vite/CHANGELOG.md)
- [Commits](https://github.com/vitejs/vite/commits/v8.0.8/packages/vite)

---
updated-dependencies:
- dependency-name: lucide-react
  dependency-version: 1.8.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: all-npm-frontend
- dependency-name: react
  dependency-version: 19.2.5
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: all-npm-frontend
- dependency-name: react-dom
  dependency-version: 19.2.5
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: all-npm-frontend
- dependency-name: react-is
  dependency-version: 19.2.5
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: all-npm-frontend
- dependency-name: vite
  dependency-version: 8.0.8
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: all-npm-frontend
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-04-10 14:02:21 -04:00
Anso 3d69746eee fix(fleet): make local self-update flow reliable end-to-end (#472)
The "Updating Sencho..." overlay used to dismiss prematurely while the
image pull was still running, after which the local node card would get
stuck in "updating" and eventually surface a generic "Timed Out" error
while the container remained on the old version.

Three root causes are addressed:

1. The image pull was synchronous (`execFileSync`), which blocked the
   Node event loop. The overlay's health probe saw the server come back
   the moment the pull finished and reloaded the page, even though the
   container had not restarted yet. The pull is now async via
   `promisify(execFile)`, so /api/health and /api/fleet/update-status
   keep serving throughout.

2. The overlay reloaded on the first 200 from /api/health regardless of
   whether the underlying process had actually restarted. /api/health
   now exposes the gateway boot timestamp, and the overlay captures it
   pre-update and only reloads when it observes a different value. A
   wasOffline-then-online fallback handles the case where the pre-update
   fetch failed.

3. Helper container spawn errors from `docker run` were silently
   discarded, so a failed compose recreate never surfaced anywhere.
   Errors are now captured into `lastUpdateError` via the execFile
   callback and surfaced through the existing /api/fleet/update-status
   error path.

A 3-minute early-fail heuristic on the local node block surfaces a clear
failure message when the helper fails silently, instead of waiting the
full 5-minute timeout for an unknown failure.
2026-04-10 12:00:44 -04:00
dependabot[bot] 39c63461d5 chore(deps-dev): bump eslint from 10.1.0 to 10.2.0 in /frontend (#438)
Bumps [eslint](https://github.com/eslint/eslint) from 10.1.0 to 10.2.0.
- [Release notes](https://github.com/eslint/eslint/releases)
- [Commits](https://github.com/eslint/eslint/compare/v10.1.0...v10.2.0)

---
updated-dependencies:
- dependency-name: eslint
  dependency-version: 10.2.0
  dependency-type: direct:development
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: SaelixCode <dev@saelix.com>
2026-04-09 20:55:18 -04:00
dependabot[bot] c5e972d244 chore(deps-dev): bump typescript-eslint in /frontend (#436)
Bumps [typescript-eslint](https://github.com/typescript-eslint/typescript-eslint/tree/HEAD/packages/typescript-eslint) from 8.58.0 to 8.58.1.
- [Release notes](https://github.com/typescript-eslint/typescript-eslint/releases)
- [Changelog](https://github.com/typescript-eslint/typescript-eslint/blob/main/packages/typescript-eslint/CHANGELOG.md)
- [Commits](https://github.com/typescript-eslint/typescript-eslint/commits/v8.58.1/packages/typescript-eslint)

---
updated-dependencies:
- dependency-name: typescript-eslint
  dependency-version: 8.58.1
  dependency-type: direct:development
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: SaelixCode <dev@saelix.com>
2026-04-09 20:54:56 -04:00
dependabot[bot] 9a45dc0cd8 chore(deps-dev): bump vite from 8.0.5 to 8.0.7 in /frontend (#434)
Bumps [vite](https://github.com/vitejs/vite/tree/HEAD/packages/vite) from 8.0.5 to 8.0.7.
- [Release notes](https://github.com/vitejs/vite/releases)
- [Changelog](https://github.com/vitejs/vite/blob/main/packages/vite/CHANGELOG.md)
- [Commits](https://github.com/vitejs/vite/commits/v8.0.7/packages/vite)

---
updated-dependencies:
- dependency-name: vite
  dependency-version: 8.0.7
  dependency-type: direct:development
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-04-09 20:51:21 -04:00
dependabot[bot] d515611640 chore(deps-dev): bump @types/node from 25.5.0 to 25.5.2 in /frontend (#431)
Bumps [@types/node](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/node) from 25.5.0 to 25.5.2.
- [Release notes](https://github.com/DefinitelyTyped/DefinitelyTyped/releases)
- [Commits](https://github.com/DefinitelyTyped/DefinitelyTyped/commits/HEAD/types/node)

---
updated-dependencies:
- dependency-name: "@types/node"
  dependency-version: 25.5.2
  dependency-type: direct:development
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-04-09 20:50:52 -04:00
Anso 8de82ed81f fix(fleet): prevent modal flash when clicking Recheck button (#457)
Use a separate recheckingUpdates state for the Recheck button so the
modal content stays visible while refreshing. The full-screen loading
spinner (checkingUpdates) is now only used for the initial "Check Updates"
open from the header.
2026-04-09 13:10:50 -04:00
Anso 368bef20d3 fix(fleet): detect updates via GitHub Releases instead of gateway self-comparison (#454)
* fix(fleet): detect updates via GitHub Releases instead of gateway self-comparison

The fleet update check compared each node's version against the gateway's
own version, so the local node could never appear outdated. Now fetches
the actual latest release from GitHub Releases API with a 30-minute
in-memory cache and thundering-herd protection. The Recheck button
invalidates this cache via ?recheck=true to force a fresh lookup.

* docs(fleet): update docs to reflect GitHub Releases version detection

Replace "Gateway version" references with "Latest version" to match the
new label. Document that version comparison uses the latest GitHub release
rather than the gateway's own version, and that Recheck refreshes the
cached latest version.
2026-04-09 12:21:37 -04:00
Anso 089d43b855 fix(ui): standardize toast background to match floating overlay glass style (#451)
Replace bg-card/80 with bg-popover/95 and match the backdrop-blur/saturate
values used by dialogs, dropdowns, and popovers. Remove per-type colored
gradient overlays so color differentiation comes from icons only. Unify the
determinate progress bar gradient with the loading bar's brand gradient.
2026-04-09 09:57:13 -04:00
Anso 3ee4fe6e44 feat(topology): overhaul network topology with dagre layout, enriched nodes, and click-to-logs (#447)
Replace N+1 Docker API calls (inspectNetwork per network) with a
container-centric approach that fetches all networks and containers
in 2 parallel calls, then maps relationships in memory.

Add dagre auto-layout algorithm for hierarchical DAG visualization,
replacing the static two-row layout that caused edge spaghetti at scale.

Add "Show system networks" toggle, enrich container nodes with running
state indicators, stack badges, and base image names. Clicking a
running container opens its log viewer directly from the topology graph.
2026-04-08 20:05:22 -04:00
Anso 8e0857e7f6 feat(contact): add official contact emails throughout app and docs (#428)
Surface six dedicated email channels (support, contact, licensing,
security, privacy, conduct) across contextually appropriate locations:

- security.txt: create .well-known/security.txt for app (RFC 9116)
- SECURITY.md: add security@sencho.io as alternative to GitHub reporting
- PaidGate/AdmiralGate: add licensing@sencho.io to upgrade prompts
- docs: new Contact & Support reference page listing all channels
- CHANGELOG: document additions under [Unreleased]

Website changes (separate repo): privacy@sencho.io in Privacy/Terms
pages, licensing@sencho.io in Refund page and pricing footer,
contact@sencho.io in website footer.
2026-04-08 09:52:42 -04:00
Anso be7eda85f1 fix(billing): hide billing portal for lifetime licenses (#427)
Lifetime licenses have no recurring subscription, so the Lemon Squeezy
customer portal cannot generate a URL. The Manage Subscription button in
Settings already had the isLifetime guard, but the Billing button in the
profile dropdown did not, causing a confusing "No billing portal
available" error.

- Add !license.isLifetime guard to UserProfileDropdown (matches
  LicenseSection pattern)
- Move lifetime detection into getBillingPortalUrl() so the service owns
  all billing eligibility logic
- Change return type to { url } | { error } discriminated union for
  clear error propagation
2026-04-08 09:51:27 -04:00
Anso f6d2199978 feat(resources): add loading toast for prune, delete, and purge operations (#426)
Show a loading notification with spinner and indeterminate progress bar
while Resource Hub operations are in progress, replacing the dead moment
between confirmation and result.
2026-04-08 09:46:27 -04:00
Anso 662bc1a210 fix(resources): unify container/resource classification with multi-fallback resolution (#425)
* fix(sidebar): add service name and config_files fallbacks for container-to-stack matching

Containers that predate Sencho's reorganization of compose files into
subdirectories carry stale Docker labels where the project name is set
to the COMPOSE_DIR basename (e.g. "compose") rather than the stack
directory name. The existing project name map and working_dir fallbacks
from PR #416 did not cover this case.

Added two new fallback strategies to getBulkStackStatuses:
- Match by com.docker.compose.service label against known stack names
- Extract stack name from com.docker.compose.project.config_files path

Also reused the existing isPathWithinBase utility for path containment
checks and hoisted path.resolve(COMPOSE_DIR) out of the per-container
loop.

* fix(resources): unify container/resource classification with multi-fallback resolution

Extract shared helpers (resolveContainerStack, resolveProjectLabel,
buildAbsDirMap) and apply them consistently across getClassifiedResources,
pruneManagedOnly, getDiskUsageClassified, and getBulkStackStatuses.

Fixes incorrect "External" tagging in Resources Hub for images, volumes,
and networks belonging to stacks that predate Sencho's compose file
reorganization. Also fixes the "active" plural on the dashboard
Containers card.
2026-04-08 09:46:21 -04:00
Anso a6849aedab feat(dashboard): add node badge to Recent Alerts for remote node alerts (#423)
Alerts from remote nodes now display a node name badge in the Recent
Alerts card, matching the existing badge style in the notification
panel. This helps users identify which node generated each alert.
2026-04-08 09:46:08 -04:00
Anso 455bfa8734 fix(schedules): filter auto-update policies from Scheduled Operations view (#420)
* fix(schedules): filter auto-update policies from Scheduled Operations view

Auto-update policies (action=update) were appearing in both the
Auto-Update tab and the Scheduled Operations tab. Added server-side
action/exclude_action query params to GET /api/scheduled-tasks.
ScheduledOperationsView now requests ?exclude_action=update and
AutoUpdatePoliciesView requests ?action=update, so each view only
shows its relevant tasks.

* fix(schedules): use typeof guard for query param type safety

Express can parse ?action[]=x as an array. Use typeof === 'string'
guard instead of unsafe type assertion.
2026-04-07 01:49:22 -04:00
Anso 8920d8c55a feat(nav): add pulsing animation to local node status dot (#418)
* feat(nav): add pulsing animation to local node status dot

Apply the same animate-ping pattern used by the notification bell badge
to the local node context pill in the top header bar. The green dot
now pulses to give a stronger "live connection" signal.

* fix(nav): use animate-pulse instead of animate-ping for node status dot

Switch the local node pill dot from the ping pattern (expanding ring)
to animate-pulse for consistency with the remote node pill. Only the
notification bell badge should use animate-ping.
2026-04-07 01:44:33 -04:00
Anso 5b06992e05 fix(nav): remove toggle behavior on navigation tabs (#417)
Clicking an already-active navigation tab previously toggled back to
the dashboard/editor view. This was a holdover from the old UI where
the tab buttons were different. Under the current design it causes
confusion, so clicking the active tab now does nothing.
2026-04-07 01:44:28 -04:00
Anso cc2da99d6f fix(fleet): resolve stuck update states and improve detection (#405)
* fix(fleet): resolve stuck update states and improve update UX

The fleet node update flow had several bugs: the in-memory update tracker
never cleared terminal states (timeout, failed, completed), leaving nodes
permanently stuck with no way to retry or dismiss. The Recheck button
only re-fetched stale state without clearing it, and the POST trigger
rejected retries with 409 even after timeout.

Backend fixes:
- Add DELETE endpoints (single node + batch) to clear tracker entries
- Fix 409 race: detect expired timeouts and clear terminal states before
  re-triggering
- Populate error messages in the tracker for timeouts and failures
- Include error field in the update-status API response
- Auto-expire completed entries after 60 seconds

Frontend fixes:
- Add retry (RotateCcw) and dismiss (X) buttons on failed/timed-out badges
- Show error details via animated cursor hover (CursorFollow pattern)
- Recheck button now batch-clears all terminal states before fetching
- Recheck shows loading spinner and disables while checking
- Extract NodeCardProps interface for readability

* fix(fleet): detect update completion via process start time

Remote nodes that cannot report their version (e.g. older builds)
caused updates to always time out because completion detection
relied solely on version comparison. The gateway now tracks the
remote node's process start time from /api/meta and detects
container restarts by comparing it across polls.

Also extracts a createTracker() factory to eliminate repeated
object construction across 5 call sites.

* docs: add troubleshooting for first-update timeout on old nodes

Adds a new troubleshooting entry explaining why the first remote
update on nodes running pre-v0.40.0 always times out (neither
version nor process start time can be detected). Documents the
fix: dismiss, recheck, and confirm the node updated.

Also adds a screenshot of the timed-out state with retry/dismiss
buttons to the remote updates feature page.

* fix(fleet): detect update completion via offline detection and error reporting

The update completion detection relied on version change and process
start time, both of which fail on nodes running older Sencho versions
that report "unknown" and lack the startedAt field. This caused every
update to time out after 5 minutes.

Add three-signal detection: version change, process restart (startedAt),
and offline/online detection (node went unreachable during update and
came back). Also add a 90-second early failure heuristic for when the
remote image pull fails silently, and surface pull errors from
SelfUpdateService via /api/meta so the gateway can report them
immediately.

* fix(deps): bump vite to 8.0.5 to resolve high severity vulnerabilities

Fixes GHSA-4w7w-66w2-5vf9, GHSA-v2wj-q39q-566r, GHSA-p9ff-h696-f583.

* fix(deps): bump vite in backend lockfile to resolve audit failures

Vitest pulls in vite as a transitive dependency. Bumps to 8.0.5.
2026-04-06 20:09:55 -04:00
Anso a55d1245f8 fix(fleet): resolve version detection pipeline for Docker builds (#402)
* fix(fleet): resolve version detection pipeline for Docker builds

The Dockerfile backend-builder stage was missing a COPY of the root
package.json, causing generate-version.js to fall back to "0.0.0-dev"
at build time. At runtime, the filesystem walk also failed (root
package.json not in the final image), producing the string "unknown"
which the frontend rendered as "vunknown".

Changes:
- Dockerfile: copy root package.json into backend-builder stage
- CapabilityRegistry: return null (not "unknown") for unresolvable
  versions; add isValidVersion() type guard; normalize remote meta
  responses to strip "unknown"/"0.0.0-dev" sentinel values
- Fleet endpoints: hoist gateway version validation outside per-node
  loops; treat unresolvable remote versions as "potentially outdated"
  instead of silently marking them up to date
- FleetView: guard all version display points (card badge, update
  button, gateway label, modal columns) via shared formatVersion()
- EditorLayout, CapabilityGate: use shared isValidVersion utility
- New frontend/src/lib/version.ts shared utility
- Docs: add troubleshooting section for version display edge cases
- Screenshots: updated Fleet Overview and Node Updates modal

* docs: update fleet node updates screenshot with live remote node
2026-04-06 03:03:56 -04:00
Anso 2089e75ef1 fix(fleet): filter invalid version strings from UI display (#399)
Treat 'unknown' and '0.0.0-dev' as absent versions in the node
switcher, fleet overview, and capability gate. Remote nodes running
older Docker images that return these fallback values now show a
clean label instead of "vunknown".
2026-04-06 01:43:52 -04:00
Anso dee7c6685b fix(fleet): resolve remote node capability detection failures (#388)
* fix(licensing): backward-compatible tier/variant enforcement and self-healing variant detection

Accept legacy tier ('pro') and variant ('personal', 'team') names from older
remote nodes, normalizing them to current values ('paid', 'skipper', 'admiral')
in authMiddleware. This fixes distributed license enforcement failing between
v0.38.3 and v0.38.0 nodes due to the tier rename in v0.38.1.

Also fixes:
- Self-healing getVariant() that cross-checks stored variant_type against
  product/variant name metadata on every call, correcting stale cached values
  from previous buggy resolution logic
- Unguarded API responses in ResourcesView causing potential t.map crashes
- Fleet update status now polls on a 120s interval (was only fetched on mount)
- fetchRemoteMeta failures now logged for diagnosability

* fix(fleet): resolve remote node capability detection failures

Exempt /api/meta and /api/health from the global rate limiter so
capability fetches are never blocked by proxied traffic. Add
backend-side caching (3-min TTL) with stale-while-revalidate to
absorb transient failures. Shorten frontend failure cache to 30s
for faster recovery. Evict meta cache on node deletion.
2026-04-05 22:46:47 -04:00
Anso 9e0c9d3f2d fix(licensing): backward-compatible tier/variant enforcement and self-healing variant detection (#385)
Accept legacy tier ('pro') and variant ('personal', 'team') names from older
remote nodes, normalizing them to current values ('paid', 'skipper', 'admiral')
in authMiddleware. This fixes distributed license enforcement failing between
v0.38.3 and v0.38.0 nodes due to the tier rename in v0.38.1.

Also fixes:
- Self-healing getVariant() that cross-checks stored variant_type against
  product/variant name metadata on every call, correcting stale cached values
  from previous buggy resolution logic
- Unguarded API responses in ResourcesView causing potential t.map crashes
- Fleet update status now polls on a 120s interval (was only fetched on mount)
- fetchRemoteMeta failures now logged for diagnosability
2026-04-05 21:50:34 -04:00
Anso 797623e56f fix(licensing): rename variant values to skipper/admiral and store resolved type (#379)
Rename internal variant values from 'personal'/'team' to 'skipper'/'admiral',
aligning code with user-facing tier names. Variant type is now resolved once
at activation/validation and stored in DB via license_variant_type, instead
of string-matching the Lemon Squeezy variant_name on every read. Also captures
variant_id for future lookups. Pre-existing installs auto-migrate on first
getVariant() call.
2026-04-05 18:45:57 -04:00
Anso f841c402b2 fix(licensing): resolve Admiral variant detection and lifetime license handling (#376)
* fix(licensing): resolve Admiral variant detection and lifetime license handling

The Lemon Squeezy variant name for Admiral licenses contains "Admiral"
(not "Team"), but getVariant() only checked for "team" and "personal".
This caused Admiral licenses to be misidentified as Skipper, locking all
Admiral-exclusive features.

- Map "admiral" variant names to internal "team" value, "skipper" to "personal"
- Add isLifetime field to LicenseInfo API response
- Hide "Manage Subscription" button for lifetime licenses (no billing portal)
- Show "Duration: Lifetime" instead of empty renewal date
- Hide upgrade cards for active Admiral users
- Add 23 unit tests covering variant resolution, tier computation, and lifetime detection
- Add troubleshooting entries for wrong tier label, locked features, and billing portal errors

* fix(licensing): address code review findings

- Fix nested ternary in LicenseSection JSX; restore conditional rendering
  to avoid showing an empty "N/A" row for non-subscription states
- Clean up test file: use shared svc variable, remove redundant comments,
  add trialDaysRemaining assertions, rename describe block
2026-04-05 07:00:21 -04:00
Anso f516275834 refactor(licensing): replace Pro branding with Community/Skipper/Admiral tiers (#375)
Eliminate all references to "Pro" across backend, frontend, and docs.
Internal tier value renamed from 'pro' to 'paid'; user-facing text now
uses the thematic tier names (Community, Skipper, Admiral).

- Rename LicenseTier 'pro' to 'paid' in backend and frontend types
- Rename requirePro guard to requirePaid, error code PRO_REQUIRED to PAID_REQUIRED
- Rename ProGate.tsx to PaidGate.tsx with updated copy
- Fix: trial users can now see upgrade/purchase cards in Settings
- Update all docs and openapi.yaml to use correct tier names
2026-04-05 05:59:36 -04:00
Anso a1804c8fbe docs: comprehensive review and refresh of all documentation (#374)
* docs: comprehensive review and refresh of all documentation pages

Reviewed every doc page against the current app state after the v0.38
dashboard redesign. Updated content, fixed inaccuracies, and refreshed
all screenshots at 1920x1080.

Pages updated:
- introduction: expanded feature list to 25 items across 6 subsections
- quickstart: fixed docker run command (Docker Hub, auto JWT, COMPOSE_DIR)
- configuration: replaced personal paths with generic /home/user/docker
- sso-quickstart: fixed Settings navigation reference
- sso: added SSO_LDAP_DISPLAY_NAME env var
- overview: added 8 missing feature sections (labels, API tokens, schedules, etc.)
- dashboard: complete rewrite for new health bar, gauges, stack health table
- stack-management: updated for UP/DN indicators, rollback button, split actions
- editor: rewritten for two-column layout, inline stats, embedded terminal
- resources: updated Quick Clean docs, added network topology and inspect
- app-store: updated categories, deploy sheet details, permission gate, settings
- openapi.yaml: fixed YAML parsing error on line 1831

Screenshots refreshed: 14 images across 6 feature areas.

* docs: review and update observability, console, multi-node, and compatibility pages

- Global Observability: fix log format fields, add download button docs,
  split display limits into memory buffer vs rendered rows, correct
  settings labels
- Host Console: remove internal implementation details per security
  docs policy, add stack directory behavior, expand header bar docs,
  remove unverified scrollback claim
- Multi-Node: add Compose Directory field, document connection test
  details panel, fix edit/delete node behavior, simplify token security
  section, remove internal details
- Node Compatibility: add missing self-update capability, remove
  internal endpoint paths and cache TTL, move from Features to
  Reference group in navigation
- Refresh all screenshots for the redesigned UI (7 images)

* docs: review and refresh fleet, remote updates, labels, alerts, routing, and webhooks pages

- Fleet View: added node updates modal, container detail, version/update/critical badges, Tags filter
- Remote Updates: removed internal details, added capability cross-link, fast polling
- Stack Labels: three creation methods, two assignment methods, 10 colors, bulk actions screenshot
- Alerts & Notifications: fixed metric labels, added notification popover detail, status banner
- Notification Routing: HTTPS requirement, rule card layout, channel terminology fix
- Webhooks: corrected license tier to Admiral, matched action labels to UI, removed internal security details, added local-only note
- Troubleshooting: centralized entries from remote-updates, stack-labels, notification-routing
- Refreshed all screenshots at 1920x1080, removed 11 orphaned images

* docs: review and refresh RBAC, user management, and atomic deployments pages

- RBAC: added missing Auditor role (5th role), updated permission matrix, fixed license tier references, documented username/password validation rules, self-deletion protection
- Atomic Deployments: added "Which operations are protected" section covering webhooks/schedules/app store, removed internal backup path, fixed license tier to Skipper/Admiral
- Screenshots: cropped to dialog element per updated strategic cropping guideline, removed 2 orphaned images

* docs: review and refresh fleet-wide backups and audit log pages

Update fleet-backups page to reflect current inline create form, add
scheduled snapshots section, document the detail view and restore dialog,
expand RBAC table to all five roles. Update audit log page to document
expanded row detail fields, pagination, refresh button, and data retention
screenshot. Replace all screenshots with fresh captures at 1920x720.

* docs: review and refresh API tokens and private registries pages

- API Tokens: clarify Full Admin scope, add Managing tokens section
  with card details, document revocation confirmation dialog, add
  usage tracking to security model, refresh screenshot
- Private Registries: add Managing registries section with card details
  and action buttons, document edit behavior, fix URL auto-fill
  description, remove encryption algorithm name per security policy,
  fix grammar, refresh both screenshots

* docs: review and refresh auto-update policies, scheduled operations, and SSO pages

- Auto-Update Policies: document all 8 table columns, expand action buttons,
  add "All Stacks" wildcard option, fix field labels, add CSV export and
  pagination details, refresh screenshots
- Scheduled Operations: fix System Prune target description, add Task List
  table columns, restructure create dialog fields with action-specific
  annotations, rewrite execution history with column table, refresh screenshots
- SSO: remove encryption algorithm name per security policy, add LDAP and
  OIDC configuration field tables, document provider card controls (Save,
  Test Connection, Remove, Active badge), refresh screenshots
- Move SSO troubleshooting entries to centralized troubleshooting page

* docs: review and refresh licensing & billing page

Update upgrade card feature lists to match actual tier gating (Skipper:
fleet view, webhooks, labels, atomic deployments, backups, auto-update
policies; Admiral: scoped RBAC, SSO, audit log, host console, API
tokens, private registries, scheduled operations). Add flex layout to
align upgrade card buttons at the bottom. Replace stale screenshot with
fresh community and active license captures. Add feature breakdown
subsection and profile menu billing shortcut to docs.

* docs: review and refresh settings reference and security advisories pages

Settings Reference: add 5 missing sections (SSO, API Tokens, Registries,
Labels, Routing), expand Users from 2 to 5 roles, fix System Limits and
Developer field labels to match UI, restructure Developer into Streaming
and Data Retention sub-tables, update App Store and Support sections,
refresh overview screenshot.

Security Advisories: restructure into versioned sections (v0.25.x
hardening and v0.19-v0.24 CVE remediation), expand from 3 bullet points
to 10 specific improvements, fix GitHub URL from SaelixCode to AnsoCode,
redact internal details per security docs policy.

Remove "Sencho Pro" product name from all three pages, replaced with
tier names (Community, Skipper, Admiral).

* docs: review and refresh troubleshooting page, remove architecture and development guides

- Rewrote forgotten password section to remove exposed SQL and table names
- Updated all Settings navigation paths to Profile > Settings > X
- Fixed network topology from "tab" to "view mode", added Pro license note
- Updated Prune Networks to current "Prune Dead Networks" label
- Corrected update check cooldown from vague to 2 minutes
- Consolidated two network creation error sections into one
- Removed hardcoded version reference (v0.34.0)
- Replaced em dashes throughout
- Deleted architecture.mdx (exposes internal implementation details)
- Deleted development.mdx (contributor guide belongs in repo, not public docs)
- Removed both pages from docs.json navigation

* docs: review and refresh operations pages (backup, upgrade, self-hosting, troubleshooting)

Backup & Restore:
- Added missing encryption.key to all backup/restore procedures
- Added Warning about restoring db without matching encryption key
- Added cross-reference to Fleet-Wide Backups for paid tiers
- Removed false claim about no built-in backup scheduler
- Updated cron example to include encryption key copy

Upgrading Sencho:
- Removed internal migration details (table names, column specs, encryption algorithm)
- Replaced with high-level migration summary per security docs policy
- Added encryption.key to pre-upgrade backup command
- Updated version pinning example from 0.25.3 to 0.38.0
- Added Remote Updates cross-reference for Skipper/Admiral users

Self-Hosting Best Practices:
- Removed JWT_SECRET from env var table (auto-generated, not an env var)
- Removed PORT from env var table (hardcoded to 3000, not configurable)
- Added API_RATE_LIMIT to env var table (actually exists in code)
- Fixed listen port description from "configurable" to "fixed"
- Updated resource recommendations based on measured footprint audit
- Removed su-exec reference (internal implementation detail)
- Upgraded data directory Note to Warning with file names

Troubleshooting:
- Fixed "Pro features" heading to "Paid features" with correct tier names
2026-04-04 21:32:51 -04:00
Anso 2ee959ec3b feat(dashboard): redesign as DevOps command center (#371)
* feat(dashboard): redesign as DevOps command center

Transform the dashboard from a basic stats viewer into a high-signal
operational command center with 5 composable sections:

- Health status bar with system health derivation (Healthy/Degraded/Critical)
- Resource gauges with visual progress bars and threshold coloring
- Paginated stack health table with per-stack UP/DN, CPU, memory, and
  click-to-navigate (8 per page)
- Enhanced historical CPU/RAM charts with skeleton empty states
- Recent alerts feed with severity-coded notifications

Extract monolithic HomeDashboard.tsx (447 lines) into composable
sub-components under dashboard/ directory. Remove Docker Run to Compose
converter from the landing surface. Add defensive .ok check on container
status fallback in EditorLayout.

* feat(dashboard): add Clear All Notifications button to Recent Alerts

Add a destructive ghost button below the alerts feed that calls
DELETE /api/notifications to clear all notifications, then refreshes
the list. Button only appears when there are alerts to clear.

* feat(dashboard): add pagination to Recent Alerts section

Same pattern as Stack Health table: 8 items per page with prev/next
chevron controls and page indicator in the card header. Pagination
auto-hides when there are 8 or fewer alerts. Page resets on clear all.

* fix(dashboard): resolve container count oscillation and add cursor hover detail

Fix container stats flickering between 0 and correct values by moving
state resets to the top of each useEffect body (runs once per node
switch, not on every poll tick). Add animate-ui cursor primitive and
wire it to the active containers number in ResourceGauges to show
managed/external breakdown on hover. Silence noisy Docker socket
errors when engine is unreachable.

* feat(dashboard): add cursor hover to health status with reason breakdown

Wrap the health badge (pulsing dot + label) in a CursorFollow tooltip
that explains why the node is Critical, Degraded, or Healthy. Shows
specific metrics (e.g. "RAM at 97.9%", "Disk at 96.4%") when hovered.
Displays "All systems nominal" for healthy nodes.

* fix(dashboard): resolve OOM from unbounded Docker stats polling

Three root causes addressed:

1. updateGlobalDockerNetwork had no overlap guard. When Docker was slow,
   3-second interval ticks stacked up, creating dozens of concurrent
   container.stats() calls that exhausted the heap. Added isUpdatingNetwork
   flag and increased interval from 3s to 5s.

2. Historical metrics query returned ~20K rows (1-minute buckets x 14
   containers x 24h). Downsampled to 5-minute buckets, reducing response
   size by ~5x.

3. Dashboard polling continued when the browser tab was hidden, creating
   phantom load. Replaced setInterval with visibilityInterval helper that
   pauses polling on tab hide and resumes with an immediate fetch on focus.

* fix(dashboard): use loadFile for stack navigation from Stack Health table

The onNavigateToStack callback was only calling setSelectedFile and
setActiveView, skipping the full load flow (YAML content, env files,
containers, backup info). This caused the editor to show stale state
with a "Start" button for running stacks and empty YAML. Now calls
loadFile() which is the same path the sidebar uses.

* refactor(dashboard): simplify Containers card layout

Replace 2-column grid with vertical layout matching other gauge cards.
Active count uses text-2xl hero number, exited count sits in subtitle
position. Removed redundant total count row.

* fix(dashboard): unify notification types and fix multi-node clear

- Replace duplicate Notification interface in EditorLayout with shared
  NotificationItem from dashboard/types.ts
- Tighten is_read type from number | boolean to number (matches SQLite)
- Pass notifications from EditorLayout (which aggregates all nodes) to
  HomeDashboard as props, removing duplicate local-only polling from
  useDashboardData
- Fix Clear All to use clearAllNotifications (deletes from all nodes)
  instead of fetchNotifications (which was just a re-fetch, causing
  remote notifications to reappear immediately after clearing)
- Delegate DELETE responsibility from RecentAlerts to parent handler

* fix(dashboard): handle optional nodeId in notification operations

Guard against undefined nodeId when calling fetchForNode for mark-read,
delete, and clear-all notification operations. The shared NotificationItem
type has nodeId as optional since the API response doesn't include it;
EditorLayout enriches it but TypeScript correctly flags the possibility.
2026-04-04 02:51:42 -04:00