mirror of
https://github.com/Studio-Saelix/sencho.git
synced 2026-08-19 14:56:27 +00:00
3bf677af6ca5d0afcc9595e2bf1076cb282bb78b
23 Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
7c3ba3f24d |
feat(dashboard): surface metrics-stale indicator after sustained poll failure (#1213)
* feat(dashboard): surface metrics-paused indicator after sustained poll failure useDashboardData previously failed silently when /stats or /system/stats returned an error: stale data kept rendering and the last sync timestamp quietly drifted. The operator could not tell whether the dashboard was just slow or whether the Docker socket / metrics path had genuinely gone down. Track consecutive failures per live-metrics endpoint. After three in a row on either /stats or /system/stats (≈15 s at the 5 s poll cadence), expose a metricsStale boolean on the hook result. HealthStatusBar renders a small amber "metrics paused" chip beside the meta line when set. The indicator clears on the first successful response when both endpoints are within the threshold. A unit test for the threshold logic is intentionally deferred to the Phase 4 E2E dashboard spec, which exercises the same path end-to-end by stopping the Docker daemon and asserting the user-visible indicator. * fix(dashboard): rename stale-metrics chip and cover the threshold with tests Address two follow-up findings from independent review of the earlier commit on this branch. 1. Rename the masthead chip from "metrics paused" to "metrics stale". The hook keeps polling on every cycle; the chip describes the freshness of the displayed numbers, not the polling cadence. The new wording matches the underlying `metricsStale` state variable. 2. Add a Vitest spec for the threshold logic. Captures the visibilityInterval callback at registration time and drives each polling cycle on demand, covering: three consecutive /stats failures trip the indicator and the next successful poll clears it; three consecutive /system/stats failures trip the indicator on the other endpoint; clearing requires both endpoints under threshold (a single endpoint recovering while the other is still failing keeps the indicator set). The clarifying comment in useDashboardData notes that polling is unaffected and only the data freshness is in scope, so future readers do not interpret "stale" as "paused". |
||
|
|
775fab7d64 |
feat(dashboard): replace duplicate Recent Activity card with Fleet Heartbeat / Stack Restart Map (#932)
* feat: open security basics, manual fleet ops, and basic fleet management to Community
Realign tier guards to the user-stated philosophy: Community covers
deploy/monitor at scale plus security basics, Skipper adds automation
and advanced fleet management, Admiral keeps enterprise control.
Community now includes:
- Trivy install / uninstall / update from the Settings Hub (admin role)
- CVE suppressions CRUD (admin role; replicates fleet-wide)
- Manual image scan with vuln, secret, and misconfig results
- Stack-config scan, scan comparison
- Manual fleet snapshots: create, list, view, restore, delete
- Per-node Sencho self-update (Check Updates + per-node Update)
- Fleet Overview search, sort, filters, node-card expand, auto-refresh
Stays paid:
- Scan policies with block_on_deploy enforcement (Skipper+)
- SBOM (SPDX, CycloneDX), SARIF export (Skipper+)
- Bulk Update All across the fleet (Skipper+)
- Scheduled snapshot create (now Skipper, was Admiral)
- Trivy auto-update toggle, fleet-wide policy push (Admiral)
The Settings -> Security tab is unhidden by setting the registry tier to
null. The SecuritySection no longer early-returns a PaidGate; the policy
list, Add Policy button, and policy dialogs are wrapped in {isPaid && }.
The Fleet view drops isPaid gates on the Snapshots tab, Check Updates
button, per-node update handlers, OverviewToolbar grid controls, the
NodeCard expand affordance, and the auto-refresh notice. The
NodeUpdatesSheet receives a canBulkUpdate prop and gates the Update All
button on it. useFleetUpdateStatus and useFleetPolling drop their isPaid
guards so polling runs for Community; useFleetOverview drops the isPaid
wrap on the filter and sort path.
Backend route guards are flipped per the matrix above. The scheduler
tick and requireScheduledTaskTier add 'snapshot' to the Skipper+ branch.
Backend test assertions are inverted for the now-Community endpoints
and a positive Skipper-snapshot-task test is added.
Documentation across features/, api-reference/, and operations/ is
updated to reflect the new tier mapping.
* feat: add node last-contact tracking, fleet latency, and stack-restart summary
- DatabaseService: add last_successful_contact column to nodes table via
idempotent migration; expose updateNodeLastContact() and getStackRestartSummary()
methods; include the column in NODE_COLUMNS so getNodes/getNode return it
- fleet.ts: record latency_ms and last_successful_contact on each remote
node overview fetch; pilot-agent nodes surface pilot_last_seen instead;
pass db singleton into fetchRemoteNodeOverview to avoid redundant getInstance calls
- dashboard.ts: replace /recent-activity with /stack-restarts endpoint that
groups notification_history events by stack and category (crash/autoheal/manual)
over a configurable window (default 7 days, max 30)
* refactor(dashboard): remove redundant per-route authMiddleware
All routes under /api/ are covered by the global auth gate in app.ts.
The inline authMiddleware arguments on /configuration and /stack-restarts
were redundant with that gate and inconsistent with every other route in
the file. Remove them and drop the now-unused import.
* refactor(backend): consolidate Date.now(), move SQL aggregation, normalize node row mapping
- Capture a single completedAt timestamp in fetchRemoteNodeOverview to
eliminate two separate Date.now() calls and ensure latency_ms and
last_successful_contact are derived from the same instant
- Inline the redundant contactedAt variable; use completedAt directly
- Move stack-restart aggregation from JS into SQL (GROUP BY stack_name
with CASE/SUM counts), replacing the Map loop in the route handler
- Export StackRestartSummary interface from DatabaseService and remove
the duplicate local definition in dashboard.ts; handler now returns
the query result directly
- Add last_successful_contact normalization in decryptNodeRow, mirroring
the existing pilot_last_seen pattern
- Add authGate reliance comment above dashboardRouter route handlers
* feat(dashboard): replace Recent Activity card with context-aware Fleet Heartbeat / Stack Restart Map
- Multi-node installs (≥1 remote node): shows Fleet Heartbeat — real-time
reachability, latency, and container count per registered node
- Local-only installs: shows Stack Restart Map — 7-day restart frequency
per stack grouped by crash / auto-heal / manual category
- Conditional wrapper (DashboardActivityCard) switches states automatically
when the node list changes, with no page reload required
- Deletes RecentActivity card and hook (duplicated data already in Recent Alerts)
- Extracts formatRelativeTime to frontend/src/lib/utils.ts for reuse
* fix(dashboard): add pilot_last_seen to FleetNodeOverview and use it in getLastSeenLabel
* fix(fleet): expose mode and pilot_last_seen in overview, consolidate formatRelativeTime, drop em dash
- Add `mode` and `pilot_last_seen` (in seconds) to the FleetNodeOverview
interface and to both the pilot-agent and HTTP-proxy return paths in
fetchRemoteNodeOverview so the frontend getLastSeenLabel pilot branch
can fire correctly
- Remove the private formatRelativeTime from RecentAlerts.tsx and use
the shared implementation from lib/utils, converting the millisecond
timestamp at the call site
- Replace the em dash in getLatencyLabel with 'n/a' per project rules
|
||
|
|
eead195529 |
feat(settings): dress the page to match the audit (#849)
* feat(settings): dress the page to match the audit (cyan rail, italic serif, two-column rows)
Brings the full-page Settings route into the Sencho voice. The page now
opens with a full-width PageMasthead (cyan rail, mono crumb, italic
serif title, contextual stat strip) above a sidebar and main-content
panel, each as a rounded-xl card inset on the dark background.
Sidebar drops the duplicate "Settings" header and the candy tier badges.
Group headers carry mono labels with visible/total counts; gated rows
get a neutral uppercase lock chip and dim. Active rows keep the cyan
2px rail.
Five new primitives (SettingsSection, SettingsField, SettingsCallout,
SettingsActions / SettingsPrimaryButton, TierLockChip) replace the
stacked label-input-help shadcn defaults and the per-section ad-hoc
chrome. AccountSection, AppearanceSection, LicenseSection, SystemSection,
NotificationsSection, DeveloperSection, AppStoreSection, AboutSection,
and SupportSection are migrated to the new layout. The list-driven
sections (Webhooks, Routing, Users, Labels, Security, CloudBackup,
ApiTokens, Registries, NodeManager, SSO) keep their list cards but get
the new chrome and primary CTAs.
Each section can publish contextual stats to the masthead via a small
context channel: 2FA state on Account, plan/trial/renews on License,
edited count on System, channel counts on Notifications, etc.
* refactor(settings): drop react-router-dom and align with DESIGN.md
The Settings page was the only surface using react-router-dom for sub-section
navigation. Every other primary view (Home, Fleet, Resources, App Store,
Schedules, etc.) drives view switching through a single activeView useState in
EditorLayout. This change removes the dependency end-to-end:
- App.tsx drops BrowserRouter
- EditorLayout adds 'settings' to the activeView union; SettingsPage renders
inside the same flex-1 overflow-y-auto p-6 wrapper as siblings
- UserProfileDropdown receives an onOpenSettings callback instead of
useNavigate. SettingsPage owns currentSection via props lifted to
EditorLayout, so cross-component navigation (openLabelManager,
onManageNodes, ConfigurationStatus rows) can route to a sub-section
- SettingsSidebar items become buttons (no more NavLink); SectionGate's
redirect-on-invisible falls back through SettingsPage's safeSection memo
- e2e/nodes.spec.ts updates the Nodes selector from link to button role
- react-router-dom removed from package.json + package-lock.json
The visual treatment is brought into alignment with frontend/DESIGN.md,
which was rewritten this week to be the normative extract of the audit:
- PageMasthead: title text-3xl → text-[22px] Section rung italic; kicker
11px → 10px Label rung; stat label tracking 0.22em → 0.18em; stat value
font-medium for mono Stat-rung family discipline
- SettingsField helper: mono → sans Body rung 14/22; success tone now uses
--success green (was incorrectly mapped to brand cyan)
- SettingsCallout: title tracking 0.18em; subtitle Body rung 14px; success
tone now genuinely uses --success green; new brand tone for promotional
callouts (Trial CTA, Admiral upgrade) that should read cyan
- SettingsActions: SettingsPrimaryButton renders mono uppercase tracked,
size sm by default. DESIGN §9.10 requires "small mono uppercase, cyan-
filled" for every Settings primary CTA
- TierLockChip: 9px → 10px Label rung floor
- SettingsSidebar: group header tracking 0.18em; ⌘K kbd 9px → 10px;
aside gains text-card-foreground transition-colors per §10 canonical
card class
- SettingsPage main panel: text-card-foreground transition-colors added;
uses h-full overflow-auto p-6 to mirror FleetView's wrapper rhythm
- Field rows, section headers, action rows now consume var(--density-*)
tokens with literal fallbacks so Settings respects the comfortable/
compact toggle
* fix(e2e): update mfa openAccountSettings to match settings redesign
Settings now opens to the Account section by default when accessed from
the profile dropdown, and the Account section no longer renders an h2
heading element. Update the openAccountSettings helper to open the
correct section and assert on the Password h3 heading that SettingsSection
renders instead.
* test(e2e): fix MFA enrolment assertion after settings redesign
The 2FA enrolment badge was replaced with a kicker/field pattern.
Assert on the 'enrolled' text that the new design renders instead of
the removed Enabled badge.
* test(e2e): fix low-backup-codes warning assertions after settings redesign
Update two assertions in the 'low backup codes warning' test that
referenced UI text removed in the settings redesign:
- '1 backup code remaining' -> '1 remaining' (SettingsField body text)
- 'Regenerate now' button -> callout subtitle text, which uniquely
identifies the zero-codes error card without hitting strict-mode
from two identically-labelled Regenerate buttons on the page
* test(e2e): navigate to root before re-opening settings for mock refresh
The settings redesign uses a nested full-page route. Navigating to the
same URL a second time does not remount the component, so AccountSection
retains cached MFA state and the 0-codes branch never fetches. A
page.goto('/') ensures full unmount before the second openAccountSettings
call, so the refreshed mock is actually hit.
* test(e2e): scroll zero-codes callout into view before asserting visibility
The callout sits below the Disable 2FA section in the MFA settings page
and is scrolled out of the clipped content area on initial render.
scrollIntoViewIfNeeded() brings it into the visible viewport before the
toBeVisible assertion.
* test(e2e): scroll Radix ScrollArea viewport for zero-codes callout assertion
The settings page wraps content in a Radix ScrollArea whose Root has
overflow:hidden, so the browser's native scrollIntoView cannot scroll
the inner viewport. Wait for the callout to attach (confirms mock data
loaded), then programmatically set scrollTop on the Radix viewport
element before asserting visibility.
* test(e2e): use toBeAttached for zero-codes callout to avoid Radix clip issue
The callout renders below the Disable 2FA section, outside the visible
clip area of the Radix ScrollArea Root (overflow:hidden) on a standard
viewport. Playwright's visibility check uses the clip intersection, so
toBeVisible() fails even after programmatic scroll. toBeAttached()
confirms the component rendered the warning card for backupCodesRemaining:0
without depending on the element's scroll position.
|
||
|
|
9a1c043189 |
refactor(settings): replace modal with nested full-page route (#848)
* refactor(settings): replace modal with nested full-page route
Settings sections are now URL-addressable at /settings/:sectionId, rendered
nested inside EditorLayout alongside the stack sidebar. Browser back/forward
navigates between sections. Deep links (e.g. /settings/cloud-backup) load
the section directly on hard reload.
- Add react-router-dom v7; BrowserRouter wraps the full app tree
- New SettingsPage (scroll memory, Cmd+K palette), SettingsSidebar (NavLink
active styling, back-arrow), SectionGate (visibility + tier lock card)
- Rename SectionId 'appstore' to 'app-store' so slug === SectionId
- Decouple SystemSection, DeveloperSection, AppStoreSection from modal-
passed props; each fetches its own data on mount
- Replace onLabelsChanged prop chain with SENCHO_LABELS_CHANGED window event
- Drop onOpenSettings prop from UserProfileDropdown, HomeDashboard,
ConfigurationStatus; each calls useNavigate directly
- Delete SettingsModal.tsx
* fix(settings): validate sectionId against registry before property write
Prevents prototype pollution (CodeQL js/remote-property-injection #243).
URL param sectionId is checked against SETTINGS_ITEMS before being used
as a property key on scrollPositionsRef.
* fix(settings): eliminate remote property injection via Map and registry-sourced key
Two-part fix for CodeQL js/remote-property-injection:
1. currentSection is now derived from SETTINGS_ITEMS.find().id (trusted
registry data) instead of the raw sectionId URL param. The tainted
string never flows into any property access.
2. scrollPositionsRef uses Map<SectionId, number> with .get()/.set()
instead of a plain object. Map operations do not write to the prototype
chain, removing the prototype pollution vector entirely.
* test(e2e): align settings selectors with full-page route
The settings refactor (
|
||
|
|
d7d8f9bfe8 |
feat(dashboard): replace 24h charts with Configuration Status and Recent Activity (#785)
* feat(dashboard): replace 24h charts with Configuration Status and Recent Activity The 24-hour CPU/Memory area charts summed per-container metrics normalized to each container's CPU quota, producing numbers that bore no honest relationship to host load. The live ResourceGauges strip already shows accurate host-level stats, making the historical charts both inaccurate and redundant. This commit replaces that row with two side-by-side cards: - **Configuration Status**: aggregates every toggleable feature on the active node (notification agents, alert rules, routing rules, auto-heal, auto-update, webhooks, scheduled tasks, MFA, SSO, vulnerability scanning, cloud backup, and alert thresholds) into a single at-a-glance card. Tier-locked rows display an upgrade indicator instead of a value. Each row is clickable and navigates to the relevant settings section. Data refreshes every 60 s and immediately on state-invalidate events. - **Recent Activity**: lists the ten most recent notification-history events for the active node (deployments, image updates, auto-heal actions, scan findings, cloud backup events, system notices) with category icons and relative timestamps. Refreshes every 30 s. New backend endpoints: - GET /api/dashboard/configuration - per-node feature status with locked/ requiredTier markers so the frontend renders upgrade chips without extra calls. The endpoint sits after authGate and before the remote proxy so remote-node requests are transparently forwarded. - GET /api/dashboard/recent-activity?limit=N - thin wrapper over DatabaseService.getNotificationHistory. - GET /api/fleet/configuration - fleet-wide fan-out using the same Promise.allSettled dead-node-tolerant pattern as /fleet/overview. Exposed as the new "Status" tab on the Fleet page (after Snapshots). Shared utilities: - visibilityInterval and formatCount extracted to frontend/src/lib/utils.ts so the three polling hooks and two components share a single copy. * docs(dashboard): fix stale alt text referencing removed historical charts |
||
|
|
748ba46669 |
feat(dashboard): status masthead, unified gauges, stack health sparklines (#676)
* feat(dashboard): status masthead, unified gauges, stack health sparklines Rework the home dashboard around a single cyan-railed status masthead that carries the health state word, node meta, and reasons inline. The resource block collapses into one strip with a CPU hero sparkline, memory and disk gauge bars, and a network tile whose sparkline is built from per-container byte-counter deltas. Stack health becomes an 8-column mono table with row tinting, an uptime column sourced from the oldest running container's creation time, and a per-stack 10-minute CPU sparkline. Historical charts pick up a cyan gradient and an amber peak marker. A shared Sparkline primitive backs all of the above. * docs(dashboard): refresh screenshot for phase B layout * fix(dashboard): anchor sparkline bucketing to latest metric timestamp The cpuHistory, netHistory, and cpuPeakLabel memos called Date.now() inside useMemo, which violates react-hooks/purity: the rule fires because re-renders can produce different bucket boundaries from the same inputs. Derive a historyEndAt anchor from the newest metric sample in the polled series and thread it through to ResourceGauges. |
||
|
|
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. |
||
|
|
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. |
||
|
|
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. |
||
|
|
d393d06885 |
Replace Sonner with custom Sera UI-inspired toast system (#296)
* refactor(ui): replace Sonner with custom Sera UI-inspired toast system Replace the Sonner toast library with a custom implementation inspired by Sera UI's glassmorphism design. The new system uses an external store pattern with useSyncExternalStore for React integration, keeping the same toast.success()/error()/warning() API so all 172 call sites required only an import path change. Key changes: - New toast-store.ts: singleton store with identical API to Sonner - New toast.tsx: Sera UI-faithful Notification component with Framer Motion animations, frosted glass (backdrop-blur-xl), type gradient overlays, animated progress bar (green→blue→sky gradient), and hover:scale-105 - Removed sonner and next-themes dependencies - Rewired all 19 consumer files to import from the new store * fix(ui): resolve ESLint errors in toast system - Use const for listeners Set (prefer-const) - Initialize startRef with 0 instead of Date.now() to satisfy react-hooks/purity rule, set actual value inside useEffect |
||
|
|
7637091e84 |
feat(ui): glassmorphism redesign with settings decomposition (#274)
* feat(ui): add glassmorphism design tokens and utility classes Introduce glass design system foundation: translucent oklch color variables for both light and dark themes, glass/glass-border/glass-highlight tokens, semantic status colors (success/warning/info), .glass and .glass-strong utility classes with backdrop-filter, reduced shadow values, and standardized spring animation presets in lib/motion.ts. * feat(ui): apply glass treatment to core components Update card, dialog, input, button, popover, sheet, tooltip, dropdown-menu, context-menu, select, alert-dialog, and tabs components with glassmorphism styling: translucent backgrounds via new CSS variables, backdrop-blur layers, glass-border luminous edges, and glass-highlight hover states. * refactor(settings): decompose Settings Modal into section components Extract 10 inline sections from the 1,987-line SettingsModal into dedicated files under components/settings/. Introduce section registry pattern replacing 14 conditional blocks. Add shared types, sidebar navigation grouping with separators, glass treatment on sidebar and nav buttons, and responsive modal height. SettingsModal shell shrinks to ~380 lines. * refactor(ui): unify all tabs to animate-ui TabsHighlight with glass styling Migrate 4 tab instances (EditorLayout, FleetView, ResourcesView, NotificationsSection) from inconsistent patterns (manual layoutId, underline border-b-2, default fade) to the shared TabsHighlight primitive with glass-highlight indicator and springs.snappy transition. Standardize EditorLayout nav highlight spring config, apply glass-highlight to sidebar stack list hover/active states, and update mobile nav styling. * refactor(ui): migrate hardcoded colors to semantic CSS variables Replace hardcoded Tailwind color classes across ~19 component files with semantic CSS variable classes: emerald/green to success, orange/amber to warning, blue to info. Preserves brand/decorative colors (Crown amber, Admiral blue). Enables consistent theming of status indicators across the entire application. * refactor(ui): Linear dark precision aesthetic — solid surfaces, depth cues, text hierarchy Replace glassmorphism with Linear.app-inspired design: solid surface tokens (card #111111, sidebar #0d0d0d, root #0a0a0a), backdrop-blur restricted to floating overlays only (blur(10px) saturate(1.15)), desaturated teal accent, font-weight 500 everywhere, monochrome chart palette, and three depth cues: root ambient glow, luminous card top-edge, steep text brightness ramp. * refactor(ui): precision polish — fix muddy dark, snowblind light, add design anchors - Replace 34 hardcoded rgba values with theme-aware stat-* CSS tokens - Fix light theme: solid white cards, off-white background, readable text - Add card-border tokens with sharper directional lighting (top edge 2x) - Add chart-grid/chart-tick tokens for theme-aware axis rendering - Upgrade body glow: teal-tinted (dark), warm amber (light) - Terminal-inspired sidebar: monospaced UP/DN status codes, Geist Mono - Add tabular-nums to stat values to prevent layout jitter - Light mode cards get shadow-sm for depth against off-white background * refactor(ui): Linear materiality pass — ghosted nav, translucent sidebar, font unity - De-escalate Delete button from solid destructive to ghost with hover fill - Make sidebar translucent (80% opacity + backdrop-blur) so body glow bleeds through - Bump dark nav accent to 0.07 for ghosted backlit selection - Unify all terminal/editor fonts to Geist Mono (was JetBrains/Consolas mix) - Add Monaco editor fontFamily for YAML/env editing consistency - Add threshold-based color to Host RAM and Host Disk stat values (warn/crit) * refactor(ui): material simulation — inherent depth, layer separation, recessed terminal - Bump dark background 0.065→0.08, card surfaces 0.10→0.12 for 4% layer separation - Add card-bevel token (inset top shimmer) for permanent structural depth - Add button-inner-glow token for physical key feel on outline buttons - Recess terminal with inset shadow and dimmed label - Reduce action icon strokeWidth to 1.5 for refined industrial feel - Add teal LED backlight bar on active nav item via blur pseudo-element * fix(ui): parse usagePercent string to number for getValueColor usagePercent is typed as string in SystemStats but getValueColor expects number, causing TS2345 in CI builds. |
||
|
|
0d5dc574a4 | fix(dashboard): correct stale Stats reset with inactive field | ||
|
|
5191737d53 |
feat(resources): managed/unmanaged resource separation across Resources Hub
- Classify all Docker images, volumes, and networks as managed (Sencho stack), external (other Compose project), or unused/system via a new getClassifiedResources() method and GET /api/system/resources endpoint - Add pruneManagedOnly() + getDiskUsageClassified() to DockerController - Prune buttons now default to Sencho-managed scope; "All Docker" is hidden in a ⋮ dropdown with a distinct destructive confirm dialog - Replace Reclaimable Space donut with interactive Docker Disk Footprint widget (stacked bar with clickable segments that filter resource tabs) - Add managed/external filter toggles and classification badges per tab - GET /api/stats now returns managed + unmanaged container counts; Home Dashboard Active Containers card subtitle shows "N managed · N external" - Rename "Ghost Containers" tab/copy to "Unmanaged Containers" throughout |
||
|
|
c8a54a988b |
fix(lint): resolve all ESLint errors to pass CI lint step
- Replace catch (error: any) with catch (error) + (error as Error).message cast in EditorLayout, NodeManager, HomeDashboard, AppStoreView - Define TemplateVolume interface in AppStoreView; replace volumes any[] with typed array - Define MetricPoint interface in HomeDashboard; replace metrics any[] with MetricPoint[] - Define TerminalContainer type in BashExecModal; replace as any DOM property casts - Define NodeTestInfo interface in NodeManager; replace info: any with typed shape - Fix DockerNetworkStats cast in EditorLayout container stats WebSocket handler - Remove unused catch variable (e) in api.ts and other components - Cast streamFilter onValueChange val to union type in GlobalObservabilityView - Add eslint-disable-next-line react-refresh/only-export-components to badge.tsx, button.tsx, AuthContext, NodeContext, use-data-state, use-is-in-view - Add eslint-disable-next-line react-hooks/set-state-in-effect in LogViewer - Add /* eslint-disable */ to animate-ui third-party primitive files |
||
|
|
3b2634f9dd |
fix(dashboard): surface server error messages in create-stack flow
- Read JSON error body from non-ok responses before throwing, so status-specific messages (409 already exists, 400 invalid name) reach the user instead of generic hardcoded strings - Apply defensive toast pattern: error?.message || error?.error || fallback |
||
|
|
4e9777d47f | perf: fix dashboard out of memory crashing from massive historical metrics payloads | ||
|
|
ee9311ad03 |
fix: dashboard cards and stacks list do not update on remote node switch
- HomeDashboard: both polling useEffects now depend on activeNode?.id so intervals restart and stale local-node data is cleared immediately on node switch; added res.ok guard before calling res.json() to prevent error objects being written into stats/systemStats state - EditorLayout: refreshStacks now guards res.ok before parsing the JSON body and iterates a typed fileList instead of the raw response value, preventing SyntaxError/TypeError when proxy returns a non-JSON response for an unreachable remote node |
||
|
|
29b10150b4 | fix: remediate observability dashboard and global logs parsing | ||
|
|
1fd078616a | feat: add global Docker network stats tracking and update HomeDashboard title | ||
|
|
5fe416f941 |
feat: add frontend index.html and Vite logo; enhance system stats API with network metrics
- Created a new index.html file for the frontend with theme handling and linked assets. - Added a Vite logo SVG to the public directory. - Updated the system stats API to include network statistics (rxBytes, txBytes, rxSec, txSec). - Enhanced the EditorLayout component to display network I/O rates for containers. - Modified HomeDashboard to show host network statistics with appropriate formatting. |
||
|
|
ac6b8e2849 | feat: Initialize core frontend application, editor layout, and integrate UI components including alert dialogs and toasts. | ||
|
|
800d47845f |
refactor: migrate to directory-based stack structure
- Updated ComposeService to run docker commands from stack-specific directories, ensuring relative paths resolve correctly. - Refactored FileSystemService to manage stacks as directories, including methods for creating, updating, and deleting stacks. - Implemented automatic migration of existing flat-file stacks to the new directory structure on server startup. - Adjusted API routes to use stack names instead of filenames, simplifying stack management. - Modified frontend components to align with the new stack naming conventions and removed unnecessary file extensions. |
||
|
|
293f9cef26 | Initial commit: Sencho V1 complete with Auth and Dockerization |