mirror of
https://github.com/Studio-Saelix/sencho.git
synced 2026-08-08 18:05:10 +00:00
4e54a9272d783f8484a9fc864625afc10bc18762
21 Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
4e54a9272d |
feat: add inspect image action on stack container cards (#1780)
Open the existing image detail sheet from each container card, between View logs and Monitor, so operators can inspect a container's image without leaving stack details. |
||
|
|
c704cb54d2 | fix(rbac): enforce operational permission parity (#1736) | ||
|
|
9922d8e765 |
feat(rbac): make stack-scoped grants node-specific (#1727)
* feat(rbac): make stack-scoped grants node-specific Qualify stack role assignments as (nodeId, stackName), migrate legacy rows to the default node, and forward bound multi-action evidence on Proxy/Pilot hops so scoped users keep least-privilege remote access without shipping the full grant table. * fix: mirror scoped-stack-auth-evidence capability to frontend, sanitize node id in role assignment log Backend added the scoped-stack-auth-evidence capability without the matching frontend entry, failing the capability parity test. The role assignment log also interpolated the node id without sanitizeForLog, unlike the rest of the line. * fix(rbac): honor node-wide scopes and fix proxied DELETE cleanup Node-scoped grants now authorize that role's stack actions on the same node in the backend resolver, frontend can(), and remote evidence. Proxied DELETE cleanup uses the gate-stashed route because pathRewrite mutates req.path before proxyRes. Add proxy integration coverage and drop the stale scoped-permissions screenshot. * fix(rbac): preserve node-qualified grants during repair |
||
|
|
b0b423b234 |
feat(fleet): reapply Compose configuration without a version update (#1716)
* feat(fleet): reapply Compose configuration without a version update Add a distinct Fleet Reapply configuration path so Compose-managed nodes can recreate Sencho from the current on-disk project when already up to date, without pulling or rewriting the image reference. * fix(fleet): confirm remote reapply and close concurrent tracker race Require confirmation for remote compose reapply, and lock dispatch before the remote POST so a second request cannot overwrite a successful in-flight tracker. * fix(ui): icon-only Reapply control so Up to date badge can breathe Collapse the Node updates Reapply label into a tooltip so the status pill no longer wraps in the Status column. * feat(editor): Save & Reapply self-stack via fleet compose reapply (#1726) * feat(editor): Save & Reapply self-stack via fleet compose reapply Eligible admins can apply on-disk Compose edits to Sencho's own stack from the editor using the same confirm, dispatch, and reconnect path as Fleet Node Updates. * fix(editor): gate Save & Reapply label to self-stack only Ordinary stacks were labeled Save & Reapply whenever the node was reapply-eligible. Require the selected file to be the self-stack for the toolbar label and diff confirm CTA. * fix(ui): move compose diff action label helper out of dialog module Keep ComposeDiffPreviewDialog component-only so react-refresh Fast Refresh lint passes after the Save and reapply stacked merge. |
||
|
|
60092dd462 |
feat: live-refresh stack detail container and health state (#1712)
* feat: live-refresh stack detail container and health state Keep the open stack's container cards in sync with Docker via state-invalidate events and a visibility-aware poll, without reloading compose, env, or logs. * fix: remove unused _ms parameter from visibilityInterval mock Fixes the @typescript-eslint/no-unused-vars ESLint error in CI lint job. * fix: stop stack detail live-refresh when leaving the editor Gate poll and invalidate handling on editor visibility, refresh the current selection after a mid-flight stack switch, and skip starting visibilityInterval when the tab is already hidden. * fix: avoid return in finally for stack detail live-refresh Satisfy no-unsafe-finally by gating the trailing refresh with a positive condition instead of early returns inside the finally block. |
||
|
|
524cc56d2f |
feat: surface stack Monitor from header and service cards (#1693)
Make Alerts and Auto-heal reachable from the stack More menu and container cards, with optional Compose service prefill in add forms. |
||
|
|
a89498ae5b |
fix(ui): hide log service chips on single-service stacks (#1689)
Service chips only differentiate multi-service or multi-container log streams. Gate rendering with the same layout criterion already used in stack details, while keeping parsed prefixes and download attribution intact. |
||
|
|
b06dfd7175 |
fix: prevent false empty states during stack hydration (#1659)
* fix: prevent false empty states during stack hydration Only show confirmed-empty UI after successful stack, status, and container fetches. Distinguish loading and recoverable error states in the sidebar, dashboard, and container health panel. * fix: arbitrate overlapping stack status and container fetches Prevent older dashboard status and same-owner container responses from overwriting newer load state after concurrent poll, invalidation, retry, or lifecycle refresh. * fix: do not let soft status polls starve slow foreground loads Skip soft /stacks/statuses poll and invalidation while a statuses request is already in flight so a deferred foreground hydration can still commit after the ten-second cadence. * fix(stacks): surface recoverable errors for confirmed-empty soft failures Sidebar and dashboard soft (background) refresh failures after a confirmed-empty state silently kept showing the empty/adopt prompt instead of a recoverable error, since only the error message was set without flipping the load status. Also reject malformed non-array /stacks responses instead of coercing them into a confirmed-empty list, and drop malformed per-stack status entries before they reach the dashboard table, which previously crashed the entire app on a null entry. * fix(stacks): close two review-found gaps in the load-failure fix A non-empty stack-statuses map where every entry failed validation was still committed as a confirmed-empty success; it now surfaces as a recoverable error instead, and dropped entries are logged. The sidebar's background-failure helper also checked a stale closure snapshot of the file list, which could wipe a list that had just loaded non-empty in the same attempt if the follow-up statuses fetch then failed; it now tracks the freshest committed list for that decision. Also collapses two refs tracking dashboard status-map emptiness into one. |
||
|
|
63213c0960 |
feat: add service-scoped Compose update and restore (#1648)
* feat: add service-scoped Compose update and restore Allow updating or rebuilding one declared Compose service on multi-service stacks without recreating siblings, with recovery snapshots, health-gate observation, and prune holds for rollback images. Full-stack update paths and single-service UX stay unchanged. * fix: sanitize service-scoped update log messages for CodeQL * fix: address service-scoped update audit findings B-01 through B-07 * fix: complete service-scoped update audit metadata and surfaces * test: wrap Updates readiness tests for deploy-feedback context * fix: keep service recovery reachable without Deploy Progress Make failed service-gate recovery discoverable when Deploy Progress is disabled or dismissed, suppress stale image-scan notification side effects, normalize ComposeService line endings, and add focused regression coverage. * fix: resurface ContainersHealth density and expand on multi-service stacks Service grouping hid the summary strip and Compact/Detailed/Expand controls that still applied to multi-container stacks. |
||
|
|
e2001fed72 |
feat: open compose editor in edit mode with one click (#1592)
* feat: open compose editor in edit mode with one click Remove the desktop double edit gate so Anatomy "Edit compose" and empty-stack create land in an immediately editable workspace. Close discards both compose and env buffers; deferred loads keep startInComposeEdit through unsaved confirm. * docs: align compose editor close behavior and Edit compose labels Update stack-management and editor docs for one-click edit mode. Close discards unsaved compose/env edits. Anatomy shortcuts use Edit compose. * fix(e2e): update routing test to use anatomy-edit-compose-btn test id The compose editor env tab routing test was written against the old 'edit' button name. The single-compose-edit-gate branch renamed that button to 'Edit compose' with test id 'anatomy-edit-compose-btn'. Update the selector to match. |
||
|
|
d113004359 |
feat: add confirmed Take down stack action with optional volume removal (#1599)
* feat: add confirmed Take down stack action with optional volume removal Expose Take down in the stack header and sidebar with a confirmation dialog that runs compose down while keeping the stack definition on disk. Optional volume removal is gated by node capability and stack:deploy permission, with remote gateway preflight before proxying removeVolumes requests. Closes #1582 * fix: reset take-down volume checkbox when dialog closes * test: align getStackMenuVisibility assertions with showTakeDown key getStackMenuVisibility now returns a fifth lifecycle flag, showTakeDown, but three exhaustive toEqual assertions still listed only the prior four keys and failed. Add the expected showTakeDown value to each: true for the partial and exited running-stack cases, false for the self stack. * test: cover Take down visibility for running non-self stacks The getStackMenuVisibility assertions exercised the partial and exited branches and the self-stack guard, but not the raw === 'running' literal that drives showTakeDown for a normal running stack. Add a case so a regression dropping 'running' from that check is caught. * fix: drop Take down from header overflow and wire activity shortcut Remove duplicate Take down from More actions. Keep inline button when running, sidebar menu, and Cmd+ArrowDown. Record stack_taken_down in activity on successful POST /down. |
||
|
|
dbe230eef3 |
fix(editor): remove misleading image line above stack actions (#1584)
Closes #1580. The Command Center header showed the first container's image directly above stack-wide Start/Stop/Update controls, which implied those buttons targeted one image. Remove the header image/digest row; per-container ImageSourceMenu on each row remains. |
||
|
|
bbcc4b59e4 |
fix: UI polish - tooltips, masthead, tabs, settings, and layout consistency (#1563)
* fix: increase masthead rail shimmer visibility Bumped shimmer highlight opacity from 25% to 50% and reduced animation duration from 11s to 5s, matching the login page AuthCanvas shimmer precedent (via-white/60 at 4.5s). * fix: reduce masthead rail opacity to match login page pattern Changed all MastheadRail background classes from 100% to 70% opacity (bg-brand → bg-brand/70, etc.) so the shimmer highlight reads as a surface reflection rather than a detached floating bar. Matches the AuthCanvas login card precedent. * fix: improve masthead rail glow visibility Replaced the invisible inset box-shadow glow (10px blur inside a 3px element) with a white overlay that pulses opacity 0.15-0.55 over 4s. Sped up glow animation from 5.5s to 4s for a more noticeable breath. * fix: replace cursor-follow tooltip with standard Radix tooltip in sidebar rows Replaced the Cursor/CursorFollow animate-ui primitives in StackRow with the standard Radix Tooltip/TooltipTrigger/TooltipContent already used throughout the app. The custom cursor dot that followed the mouse is gone; tooltips now appear as static popovers on hover/touch. * fix: wrap tooltip icon triggers in span for Radix compatibility TooltipTrigger asChild requires a native element to forward props. Lucide icon components did not reliably receive data-state and event handlers, so tooltips on the check-failed and git-pending indicators did not open. Wrapped each icon in a span. * fix: rename Notification Routing to Routing in settings sidebar * fix: replace Timeline/All tasks buttons with SegmentedControl The two loose Button elements had indistinguishable selected/unselected states (secondary vs ghost). Replaced with the SegmentedControl component already used elsewhere on the same page, which has a clear active highlight indicator. * fix: standardize Blueprint empty state headings to font-heading Replaced manual font-serif [font-style:var(--heading-style)] with the font-heading class on the main heading and three step titles so the Deployments empty state follows the same heading convention as the rest of the app and respects the Calm/Signature theme toggle. * fix: remove custom tooltip styling, use standard TooltipContent defaults The RowTooltip wrapped its label in a font-mono text-stat-value span instead of using the TooltipContent base styling (text-xs text-popover-foreground) like every other tooltip in the app. * Revert "fix: remove custom tooltip styling, use standard TooltipContent defaults" This reverts commit |
||
|
|
0f9925e04f |
feat: block self-stack lifecycle ops with UI and preflight guardrails (#1569)
* feat: block self-stack lifecycle ops with UI and preflight guardrails Refuse update, deploy, down, stop, and delete when the stack matches Sencho's compose project. Return 409 self_stack_protected. Expose isSelf on /statuses and disable guarded UI actions. Add SelfStackProtectedDialog and self-managed-stack preflight warning. Closes #1564 * fix: add missing stackSelfFlags mock to useSidebarContextMenu test The production hook now reads stackListState.stackSelfFlags[file], but the test mock did not include it, causing 6 tests to fail with TypeError: Cannot read properties of undefined (reading 'web.yml'). * fix: harden self-stack protection during startup Add a global environment preflight warning when Sencho is managed inside COMPOSE_DIR. Align status decoration and route guards on Docker label fallback detection. Block rollback and service-level stop on the protected self stack. * fix: add self_stack_location to diagnostics-route expected check IDs |
||
|
|
fbe98676cb |
feat: bound container health pane so logs stay visible on large stacks (#1556)
* feat: bound container health pane so logs stay visible on large stacks On the stack overview page, multi-container stacks (8+ services) pushed the logs pane off-screen because the Command Center card had shrink-0 and no scroll mechanism. This reworks the desktop layout for stacks with more than one container: - The Command Center card is bounded at max-h-[42%] so it never consumes the full left column. The container list scrolls internally via Sencho's existing Radix ScrollArea. - A summary strip shows total / running / paused / unhealthy counts above the list, with a Compact / Detailed density toggle. Detailed (default) shows CPU / Mem / Net sparklines; Compact hides them for a denser list. - The logs pane has a guaranteed min-h-[180px] so it is always reachable. - Single-container stacks are untouched: the card keeps its original shrink-0 behaviour, and no summary strip or density toggle appears. Mobile is unaffected (it already uses segmented Health / Logs / Compose panes). * fix: key ContainersHealth by node+stack so density resets on navigation The density toggle state (compact/detailed) is local to ContainersHealth. Without a key, selecting Compact on one multi-container stack would persist when navigating to a single-container stack, hiding sparklines while also hiding the toggle itself. Keying by node+stack ensures a clean remount. Also added test coverage for zero containers and density reset on remount. * fix: remove unused rerender variable from ContainersHealth test |
||
|
|
b65daf6845 |
feat: add notification suppression rules (#1525)
* feat: add notification suppression rules * fix: restore label routing and routing test mocks for suppression * fix: allow bell mute shortcuts for history-only notification categories Suppression rule validation used the routable category whitelist, which rejected history-only categories such as update_started that appear in the bell during stack updates. * feat: expand Mute Rules UX with compose-first entry points and activity badges * fix: add missing NodeContext mocks for notification suppression tests |
||
|
|
a5109e7916 |
feat(editor): enable mobile compose and env editing (#1371)
* feat(editor): enable mobile compose and env editing The mobile stack-detail Compose segment was read-only and told users to edit on desktop. Operators need to make small emergency edits from a phone, so the Compose segment now opens a full-screen editor for small, safe compose and .env changes. The editor is a lightweight monospace textarea rather than Monaco, sized for small corrections at common phone widths. It reuses the existing desktop save path from useStackActions and the global overlays, so every protection behaves the same: ETag conflict handling, diff preview when enabled, save-only, save-and-deploy, and the unsaved-changes guard. A compose/.env toggle appears when the stack has an env file, and the env-file picker is locked while edits are unsaved so switching files cannot drop them. Editing is gated by the same stack:edit permission as desktop. A footer note reminds users that mobile editing is for small changes and points large rewrites to desktop. The desktop Monaco editor is unchanged. * fix(editor): keep the mobile editor save target in sync with the shown buffer Two edge cases in the mobile compose/.env editor could silently drop an edit: - When the desktop editor was on the Files tab (or an env tab with no env file) and the viewport crossed into the mobile breakpoint, the editor showed the compose buffer while the shared active tab stayed on files, so a save quietly no-opped. Normalize the active tab to compose on the mobile surface so the visible edit always saves to the visible file. - The textarea stayed writable while an env-file switch was loading, so edits typed during the fetch were overwritten when it resolved. Make the textarea read-only while a file load is in flight. Adds unit tests for both normalizations and the read-only-during-load guard. |
||
|
|
e20f1fe415 |
feat: add an inline deploy-progress style for the stack detail (#1355)
* feat: add an inline deploy-progress style for the stack detail Deploy progress gains a presentation choice under Settings > Appearance > Display: Modal (the default centered overlay) or Inline. In Inline style a compact status band on the stack detail shows the running operation, its elapsed time, the live phase, the latest output line, and the post-update health gate result. A "View output" button opens the full log modal on demand, a dismiss control clears the band, and the band auto-clears a few seconds after a clean completion. The live progress socket is lifted to an always-mounted owner so the band streams without the modal; the default Modal style is unchanged. Operations carry their node so a band never bleeds onto a same-named stack on another node. The stack detail's redundant "CONTAINERS" section heading is removed; the band reserves that vertical space. * fix: keep inline deploy progress reachable off the stack detail Review of the inline presentation found a gap: a failed operation, an App Store install, or navigating away leaves the inline session with no visible surface, since the band only renders on the operation's own stack detail. Restore the minimized pill as the inline fallback, shown only when the band is not covering the session, so there is always a click-through to the log without ever overlapping the band. Closing the modal for a failed op now ends the session (the band has stepped aside) instead of only hiding it. Also document the unsupported mid-operation style switch, and refresh the deploy-progress, settings, appearance, and app-store docs for the renamed "Deploy progress" setting and the Modal/Inline choice. |
||
|
|
d369b03a38 |
feat: detect stalled stack updates and add in-app recovery actions (#1347)
* feat: detect stalled stack updates and add in-app recovery actions Add a backend idle-output backstop that stops a deploy/update compose step that has gone silent (SENCHO_COMPOSE_STALL_TIMEOUT_MS, default 10m), so a hung image pull surfaces a fast failure instead of spinning indefinitely. Surface failed, timed-out, and stalled operations with recovery actions on the stack page: a desktop chip plus popover menu and an inline mobile card offering retry, restart, roll back (when a backup exists), refresh state, and copy diagnostics, all gated by deploy permission. The streaming deploy/update progress modal is now on by default and warns when output goes quiet. Container state is refreshed after a failed or stalled operation, and the UI never sits in an indefinite spinner. * fix: harden rollback against policy-blocked file mutation and refine recovery Address review findings on the stalled-update recovery work: - The rollback route restored backup files before running the policy gate, so a policy-blocked rollback could leave the on-disk config rolled back while the deployed containers were unchanged. Snapshot the current files first and revert them when the gate blocks; if that revert itself fails, escalate it on the persistent alert feed since the 409 is already sent. - Refresh container state after a successful manual rollback (rollback redeploys), without mis-recording a refetch failure as a rollback failure. - Suppress the stalled-output warning once live progress is unavailable. * test: mock snapshotStackFiles in the atomic-deploy rollback route tests The rollback route now snapshots stack files before restoring a backup, so its FileSystemService mock needs snapshotStackFiles. Without it the mocked call threw and the route returned 500, failing the success-path rollback assertions. |
||
|
|
928a3a8343 |
feat(mobile): bespoke phone layouts for dashboard, fleet, schedules, and settings (#1330)
* feat(mobile): masthead-led dashboard and 5-tab bottom nav on phones On phones (below the md breakpoint) the dashboard now renders a bespoke, masthead-led layout instead of the reflowed desktop workspace: - A status masthead leads with the overall system-health verdict, the node, and a live summary (stack counts, last sync, a "metrics stale" marker when polling stops). - A CPU hero card with a sparkline, then a memory / disk / network strip with threshold-colored bars, then a tappable stack-health list. - The bottom tab bar gains a Home tab (Home / Stacks / Fleet / Sched / Settings); the global top bar is dropped on this screen, with notifications and a "more" menu rehomed into the masthead. The health-verdict logic is extracted into a shared helper so the phone masthead and the desktop health bar read from one source, with unit tests. All changes are scoped below the md breakpoint or rendered only on the mobile shell; desktop layout is unchanged (verified against the desktop snapshot gate). * feat(mobile): bespoke fleet glance and node detail on phones On phones (below the md breakpoint) the Fleet view now renders a bespoke, masthead-led layout instead of the reflowed desktop workspace: - A fleet masthead leads with the overall fleet-health verdict and a running / cpu / mem summary band, then a list of node cards. The local node is marked with a cyan rail and a "you are here" tag; offline nodes are dimmed. - Tapping a node opens a full-screen node detail: state pill, resource bars (cpu / mem / disk), the stacks running on that node, and an Inspect action that switches to the node. Operators with the right permissions also get a Drain (cordon) action. - The screen polls the fleet overview every 30 seconds; the global top bar is dropped here, with notifications and a "more" menu in the masthead. All changes are scoped below the md breakpoint or rendered only on the mobile shell; desktop layout is unchanged. * feat(mobile): bespoke schedules and settings screens on phones On phones (below the md breakpoint) Schedules and Settings now render bespoke, masthead-led layouts instead of the reflowed desktop workspace: - Schedules: a "next up" glance leading with the next run time and countdown, then upcoming runs grouped by day with a per-action status dot and target. It is read-only on mobile; creating and editing schedules stays on desktop. - Settings: a grouped-card list of every reachable section; tapping one opens it full-screen with a back affordance and a section masthead. The section content itself is the same as on desktop. The settings section switch, lazy-loaded section chunks, and tier gating are moved into a shared component so the desktop and mobile screens render the same section content from one place. The global top bar is dropped on both screens, with notifications and a "more" menu in the masthead. All changes are scoped below the md breakpoint or rendered only on the mobile shell; desktop layout is unchanged. * fix(mobile): show notifications and more-menu on the stack detail header The full-screen stack detail on phones drops the global top bar, but its header was missing the notifications bell and the "more" navigation menu that the other mobile screens carry in their masthead, leaving no way to reach notifications or other destinations while viewing a stack. Render the same header-actions cluster in the detail header (and the loading placeholder), next to the back affordance. Desktop is unaffected. |
||
|
|
e8f271f5f6 |
feat(ui): make the core stack flow usable on mobile (#1327)
* feat(ui): make the core stack flow usable on mobile Below the md breakpoint the app collapses to a single full-width column: the stack list is full-screen, tapping a stack opens a full-screen detail with a Health / Logs / Compose segmented control (Logs first) and a back button, and a bottom tab bar switches Stacks, Fleet, Schedules, and Settings. Compose is read-only on a phone with a prompt to edit on desktop. Desktop (md and up) is unchanged: the mobile shell is gated behind a useIsMobile hook plus max-md/md variants, and the stack-detail blocks are shared with the desktop two-pane view so it renders identically. Also generalizes the unsaved-changes guard so leaving a dirty editor (back, tab bar, hamburger) prompts before discarding; adds 44px touch targets on list rows, filter chips, and actions; makes log and shell modals full-screen on mobile; and offsets toasts and the deploy pill above the bottom tab bar. * fix(ui): keep mobile nav in sync when opening views from outside the bottom bar On a phone the sidebar activity actions, the node switcher's Manage Nodes, the profile Settings entry, and the dashboard configuration links set the active view without flipping the mobile surface to content, so the user stayed on the stack list and never saw the destination. Route these through the mobile-aware navigation and settings helpers (a no-op on desktop). |