Drift compared raw compose YAML to runtime, so ${VAR:-default} image
expressions false-positive as image-mismatch. Source the declared side
from docker compose config instead, matching deploy-time resolution.
Fixes#1572
* feat: move core Blueprint orchestration to Community tier
Blueprints CRUD, reconciliation, and drift modes are now available on
Community. Pin remains Admiral-only via Federation placement controls.
* test: update NodeCard cordon tests for Admiral-only tier gate
Cordon now requires both isPaid and node:manage permission, matching the
backend requirePaid + requirePermission guard. Three tests still used
isPaid:false but expected the menu to be visible.
Only one local node is allowed. Creating a second local returns 409,
and the last local node cannot be deleted or converted to a remote type.
Existing duplicate local nodes from older versions are preserved and can
be cleaned up individually. Zero-local recovery auto-assigns the default
flag. Frontend delete surfaces and the Add Node form respect the new
invariant.
Enforced in DatabaseService (addNode/updateNode/deleteNode guards) and
routes (error translations). Legacy test fixtures use raw SQL helpers.
Clamp the stack list width with ScrollArea block and min-w-0 on the row
flex chain so long names ellipsize instead of pushing update dots past the
sidebar edge. Add E2E layout coverage for trailing-indicator edge cases.
Detect services with build: in the update preview and run compose build --pull
plus pull --ignore-buildable when Update is triggered on those stacks, while
keeping the existing pull-only path for image-only stacks.
* feat: acknowledge Compose Doctor preflight findings per stack
Add node-scoped preflight acknowledgements with read-time filtering.
Supports four expiry modes and activeStatus for banner, tab dot, and readiness.
* fix: align preflight acknowledge UI with design system
Use Combobox, modal chrome, mono fields, and non-destructive clear confirm.
* fix: update test mocks to match new preflight field names
The preflight-acknowledgements feature renamed status-\>activeStatus and
highestSeverity-\>activeHighestSeverity in the preflight report shape. The
corresponding test mocks in three files still used the old field names,
causing 6 test failures across backend and frontend.
- backend: update-guard-service mock now passes activeStatus
- frontend PreflightPanel: Report interface and report() helper now include
activeStatus, activeHighestSeverity, activeCount, acknowledgedCount
- frontend StackAnatomyPanel doctor: mock API response now includes
activeHighestSeverity and activeStatus
LSIO encodes read-only mounts in volume path (e.g. /var/log:ro). Map
host_path and optional correctly so fail2ban and similar templates
generate valid compose specs.
Fixes#1554
* 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
* feat: add sidebar update indicator toggle and Stack Health badge
- Add image_update_sidebar_indicators setting (default off, node-scoped)
- Gate the Updates filter chip and sidebar status indicators on the setting
- Add "Update available" badge to Stack Health table (always visible)
- Extend ImageUpdateStatus with sidebarIndicators boolean
- Poll /api/image-updates/status alongside /detail in useImageUpdates
- React to SENCHO_SETTINGS_CHANGED for instant toggle propagation
- Reset sidebar state on node switch; generation-guard stale responses
- Disable toggle when status is null (loading) or field is absent (old node)
- Wire stackUpdates through ViewRouter → HomeDashboard → StackHealthTable
- Update settings registry, operator docs, and sidebar/dashboard docs
* fix: guard against stale node renders, memo drift, and cross-node error toasts
- Track owning node ID in useImageUpdates state so React never renders
node B with node A's data before the passive effect resets (P2)
- Replace incorrect stackUpdates dependency with sidebarStackUpdates in
chipFilteredFiles useMemo (P3)
- Guard the error toast in handleSidebarIndicatorsChange so a stale PATCH
failure from node A does not surface while viewing node B (P3)
* fix: default sidebar update indicators to on (opt-out)
The sidebar indicators are a safe convenience that most users want.
Switching the default from off to on matches the opt-out convention
used by prune_on_update, reclaim_hero, and health_gate_enabled.
* fix: fall back to enroll token when pilot tunnel JWT is rejected
On HTTP 401/404 upgrade rejection, delete stale pilot.jwt and retry with SENCHO_ENROLL_TOKEN.
* test: import pilot agent module after DATA_DIR is set in fallback test
The auth-fallback test statically imported pilot/agent, which freezes its
pilot.jwt path from DATA_DIR at module load, before setupTestDb redirects
DATA_DIR to a writable temp dir. On the Linux CI runner the path resolved
to a non-writable /app/data, so persistToken silently failed and the
round-trip assertion read null. Import the module dynamically in beforeAll
after setupTestDb, matching the sibling unit test.
* fix: remove unexpected-response listener that blocked pilot reconnect
The ws library skips abortHandshake when an unexpected-response listener
exists, so error and close never fire and the agent hangs in CONNECTING.
Detect auth rejection via the abortHandshake error message instead; the
close handler already performs enroll-token fallback and reconnect.
* feat: move core Blueprint orchestration to Community tier
Blueprints CRUD, reconciliation, and drift modes are now available on
Community. Pin remains Admiral-only via Federation placement controls.
* feat: move Federation placement controls to Community tier
Remove requirePaid from cordon, uncordon, and blueprint pin routes. Ungate the Federation tab and gate cordon UI on node:manage only. Update licensing and fleet docs for the new tier split.
* feat(scheduler): schedule container restart, stop, and start
Add container as a scheduled-task target type so operators can automate lifecycle actions against standalone containers by node and name, with matching UI pickers, validation, execution on local and remote nodes, and tests.
* fix(scheduler): stack service matching and container picker hygiene
Backfill Service on smartFallback containers so per-service stack restarts work when container_name is set. Match services by compose label and container name in stack routes and scheduled restarts. Exclude Sencho from GET /api/containers lists. Hide the Restart Stack service picker when a stack has only one service.
* test(scheduler): scope service checkbox assertion to Services block
The create dialog also has a Delete after run checkbox. Count checkboxes only inside the Services section so CI does not include unrelated form controls.
* fix(scheduler): narrow closest() result to HTMLElement in schedule test
The service-checkbox assertion passed an Element from closest() into
within(), which requires an HTMLElement, failing tsc -b in the frontend
build and Docker build stages. Use the closest<HTMLElement>() type
argument so the value type-checks without an unsafe cast.
* fix(scheduler): hide Sencho container on remote node picker lists
Remote container lists are proxied from peer Sencho instances, so id-only self filtering missed peers on older builds. Await SelfIdentity init, match ImageID, and drop official saelix/sencho images. Apply the same heuristic in the scheduled-operations UI and when the hub fetches remote containers for scheduled runs.
* test(monitor): add missing DatabaseService mocks for scan history cleanup
* test(scheduler): add missing markStaleScansAsFailed mock
SchedulerService.tick() calls db.markStaleScansAsFailed() to sweep stale
vulnerability scans. The scheduler-service test was missing this method in
its DatabaseService mock, causing TypeError failures during test initialization.
Added mockMarkStaleScansAsFailed to hoisted mocks and DatabaseService mock
object, returning safe default of 0 scans marked as failed.
* test(compose): add missing FileSystemService mocks for getStackContent/getEnvContent
* test(containers-route): mock SelfIdentityService to prevent initialize() crash
The excludeSelfContainers() helper calls SelfIdentityService.initialize(), which tries to access DockerController. Without a proper SelfIdentityService mock, the initialize() call fails silently, causing a 500 error on GET /api/containers.
Added SelfIdentityService mock with initialize(), isOwnContainer(), and isOwnImage() methods to prevent the crash.
* 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
* fix: wrap auto-heal policy history in stack monitor sheet
Restructure PolicyRow history entries to stack vertically with break-words
so long container names and reasons stay inside the sheet width.
Fixes#1532
* fix: cap auto-heal policy history to a scrollable region
Recent activity could grow the policy row unbounded, pushing the
add-new-policy form far down the sheet when a policy accumulated many
history entries. Wrap the list in a fixed-height ScrollArea so it
scrolls internally instead.
Bump the frontend dependency overrides to force patched versions of two
packages flagged by security advisories:
- dompurify: monaco-editor pins 3.2.7, which the existing override raised
only to 3.4.1. Raise the override to ^3.4.11 so the resolved version is
3.4.11, clearing eight dompurify advisories (IN_PLACE and template
sanitization bypasses, hook/config pollution, Trusted Types poisoning).
- @babel/core: force the transitive build-time dependency from 7.29.0 to
^7.29.6 (resolves to 7.29.7), clearing the sourceMappingURL arbitrary
file read advisory. Its consumers accept ^7.0.0 / ^7.24.4.
dompurify is not imported in application code; it reaches the bundle only
through monaco-editor, so the override is the correct control point.
Lock churn is limited to the @babel/* subtree, the browserslist toolchain
data packages, and dompurify. Typecheck, production build, and the full
frontend test suite (1613 tests) pass.
In inline deploy feedback style, the auto-close countdown label
("closes in Xs") was visible but permanently frozen. canAutoClose
was already false in inline style (the banner owns the lifecycle),
but showCountdown was driven by !gateHoldsOpen, showing the label
even when no countdown timer could ever start.
Change showCountdown to canAutoClose so the label only appears
when the modal is actually eligible to auto-close (modal style,
succeeded, no observing gate). No gate contracts or modal-style
behavior change.
Adds regression tests: inline succeeded hides the label, modal
succeeded shows it.
The New/Edit Scheduled Task modal always showed "Enabled" next to the
task toggle regardless of its on/off state. The label now tracks
formEnabled, matching the existing Enabled/Disabled label convention
used in the Stacks settings section.
The GitHub Sponsors PR (#1535) was merged as feat, which is not
accurate: it only touches FUNDING.yml and README/SUPPORT links, with
no codebase changes. This forces the next release back to a patch
bump instead of the minor bump release-please computed from that
commit.
Release-As: 0.93.3
Adds the github key to FUNDING.yml and links GitHub Sponsors from README and SUPPORT so both channels are visible; the org still needs to complete GitHub's Sponsors onboarding for the button to render.
PR #1521 added max-height and overflow-y-auto but the card stayed content-
sized because flex items default to min-height:auto and justify-center
clipped overflow while html/body stay overflow:hidden. Add min-h-0 and
my-auto, use dvh, scroll the auth shell, and pin Enter Sencho outside the
preflight scroll region.
Remove redundant nav and label dots, move desktop masthead status to animated left rails, and let AuthCanvas scroll long preflight content on small viewports.
Stack success events were hidden from the panel but still counted unread on the bell and dashboard.
Share one visibility helper across badge, panel, and Recent Alerts.
Harden mark-all-read against partial API failures.
Fixes#1513
GitHub Releases appear before docker-publish.yml finishes pushing images.
Probe Docker Hub and GHCR manifests before advertising a version as available.
Sanitize registry probe debug logs for CodeQL log-injection.
Rename "Janitor threshold" to "Reclaimable Docker data threshold" in Settings
and update the unused-Docker-data alert to point users to the Resources view
and the Prune Node Resources scheduled action, which are the real UI surfaces
for reclaiming disk space. Remove "janitor" from settings search keywords.
The internal docker_janitor_gb database key and JANITOR_* constants are
unchanged.
* fix: enforce 1:1 compose path mapping for Pilot agent mounts
Pilot enrollment now generates validated 1:1 bind mounts so every
agent path maps to a unique compose directory. Persisted agent paths
reconcile during startup to catch drift. Unsafe relative-bind redeploys
are blocked before container removal to prevent path escapes.
- Add composePathMapping utility with strict path validation
- Generate COMPOSE_DIR and validated mounts during Pilot enrollment
- Reconcile persisted agent paths during startup bootstrap
- Block redeploy when a relative-bind mount would escape the compose root
- Default Pilot UI path to /opt/docker/sencho
- Update multi-node and pilot-agent documentation
- Add regression tests for enrollment, bootstrap, compose-service,
and environment-check paths
* fix: update E2E enrollment regexes for YAML-quoted token values
* docs(introduction): refresh for the redesigned UI and replace screenshots
Bring the Getting Started Introduction page in line with the current
product:
- Add the Security top-level view to the navigation list and a
dedicated Security section with a new screenshot.
- Correct the Fleet tab names (Snapshots, Status, Map, Deployments,
Routing, Federation, Actions, Secrets).
- Split Settings out from security and list the current nine setting
groups (Security graduated to its own view).
- Refine the navigation paragraph so role, tier, and local-vs-remote
context read accurately.
Replace all four existing screenshots (Home, stack workspace, Fleet,
Resources) with fresh captures of the redesigned UI and add a Security
overview screenshot.
* docs(configuration): document advanced env vars and clarify deployment vs runtime config
Add an Advanced environment variables section (TRIVY_BIN, SENCHO_MESH_SUBNET,
GITSOURCE_MAX_CLONE_BYTES, SENCHO_PUBLIC_URL, SENCHO_COMPOSE_STALL_TIMEOUT_MS) and
reframe the intro to separate deployment-time configuration from the runtime
settings that live in the in-app Settings Hub. Cross-link the pilot-agent
variables to the Pilot Agent page instead of duplicating them.
* docs(sso): refresh SSO Setup Guide and SSO & LDAP reference for the redesigned UI
Refresh both SSO documentation pages against the current product and the
redesigned settings UI.
- Correct the navigation path to Settings -> Access -> SSO on both pages.
- Fix the "Require 2FA on SSO sign-in" toggle location to
Settings -> Personal -> Account.
- Describe the login-page experience (the Local / LDAP toggle and the
branded OIDC buttons under the "Or continue with" divider) and the SSO
panel masthead (SCOPE, PROVIDERS, ENABLED).
- Replace all six SSO screenshots with fresh captures of the redesigned UI.
* docs(features): refresh the Features Overview page for the redesigned UI
Rewrite docs/features/overview.mdx to mirror the current Features
navigation grouping (Stacks, Deployment, Resources, Observability,
Fleet, Automation, Security & Identity) and add the recently shipped
capabilities surfaced in the redesign: Stack Dossier, Drift Detection,
Compose Doctor, Compose Networking, Environment & secrets guardrails,
Storage portability, Health-Gated Updates, Fleet Dossier, and the
dedicated Security page.
Correct stale claims (the file explorer now gates writes on stack edit
permission, not an admin role; downloads are a read action; bulk label
assign now spans nodes) and standardize the tier callouts so partly
paid features read as "Admiral adds X". Replace the three pre-redesign
screenshots and add a Security overview banner, all captured from a
populated fleet.
* docs(features): refresh the Appearance page for the redesigned UI
Add fresh screenshots and a troubleshooting section to the Appearance
page, verified against the live product.
- Add four screenshots: the Theme card (live preview, mode, accent, and
fine-tune sliders), the top-bar quick switcher, the Typography card,
and the Display card.
- Refresh the Density screenshot used by the Settings reference page.
- State that the quick switcher also covers text size, and that the
contrast, border, and glow sliders stay in Settings.
- Add a Troubleshooting accordion covering per-browser persistence,
resets to defaults, cross-operator scope, and the quick-switcher
versus full-Settings split.
* docs(introduction): refresh screenshots and correct stale content
* docs(reference): refresh the Settings Reference page for the redesigned UI
Replace all seven stale screenshots with fresh 1920x1080 captures.
Add five new screenshots for the sections that previously had none.
Content changes:
- Sidebar table: rename Infrastructure "Fleet Mesh" entry to "Fleet";
add "Image update checks" to the Automation group list
- Fleet section: rename heading to match registry label; add the
Documentation snapshots subsection (snapshot_documentation toggle)
- Container Alerts: add screenshot
- Image update checks: add the full section (Registry checks table,
scheduling mode, interval presets, cron expression support)
- Stacks / Deploy Guardrails: add screenshot
- Recovery: add the full section (System health snapshot, Environment
preflight checks, Safe actions, Command-line recovery table)
* docs(sso): refresh screenshots for SSO quickstart and feature pages
* docs: refresh Features Overview screenshots and content
Replace all 4 hero screenshots with fresh 1920x1080 production captures.
Correct security posture state names (Action needed / Monitoring / Secure),
add the Policies tab to the Security section tab list, mention the Simple
mode in Scheduled operations, and update all alt text to match the new
screenshots.
* docs: refresh Appearance page screenshots and correct quick-switcher scope
Replace all four Appearance screenshots with fresh production captures.
Fix the quick-switcher control list: remove fonts (not present in the
popover), add visual style and readability which are. Add Log chip color
to the Display section. Update all screenshot alt text to match new
captures.
* docs: refresh stack management page with current UI and anatomy tabs
* docs: fix convert-tab-error screenshot with fully visible error toast
* docs: convert troubleshooting section to AccordionGroup format
* docs(quickstart): refresh screenshots and align dashboard description
Replace all three first-boot and dashboard screenshots with current UI.
Add Security to the top navigation list, update gauge and Stack health
descriptions to reflect sparklines and column detail, and align
Configuration Status wording with the Introduction page.
* docs(editor): rewrite anatomy panel, replace all screenshots
- Correct the anatomy panel tab inventory: the panel has eight tabs
(Anatomy, Activity, Dossier, Drift always; Environment, Networking,
Doctor, Storage when the node advertises the matching capability), not
three as previously documented
- Add table describing all eight tabs with capability gates and links to
dedicated feature pages
- Add anatomy-tabs.png screenshot showing the scrollable tab row
- Note the Doctor severity dot (red for blocker, amber for high-risk)
- Remove the stale Markdown-export subsection; Dossier and Activity are
now covered in the tab table
- Replace all six stale screenshots with fresh 1920x1080 captures
- Replace the compose diff preview screenshot
* docs(files): refresh Files & Volumes screenshots and fix context-menu alt text
Replace all 9 stale screenshots on the Files & Volumes page with fresh
captures from the production node. Fix three alt-text strings that did
not match the live UI: removed hardcoded octal value 644, and added the
Duplicate, Copy to, and Move to entries missing from the context-menu
alt text.
* docs: rewrite Stack Activity page with full event categories and fresh screenshots
Expands the event category table from 5 to 10 entries to cover drift
detected, drift resolved, update started, health gate passed, and health
gate failed. Adds a live-disconnected-state section, a background-actor
attribution table, and a corrected troubleshooting accordion covering the
WebSocket reconnect case. Replaces both stale screenshots with fresh
1920x1080 captures from the production node.
* docs(drift): rewrite drift detection page with screenshots and full coverage
Full rewrite of the Drift Detection feature page. Adds two previously
undocumented finding types (network-undeclared, network-missing), expands
the temporal section to distinguish the raw-file hash from the parsed-model
hash, documents the two-layer spatial-engine and ledger architecture,
explains when the ledger is reconciled (post-deploy vs manual re-check vs
tab open), adds Activity timeline integration note, introduces a Limitations
section (no background scanner, port-range caveat, history cap, advisory-only
enforcement), expands Troubleshooting from five entries to seven using the
AccordionGroup convention, and adds four production screenshots.
* docs(drift): use CardGroup for Related section
* docs(dossier): rewrite Stack Dossier page with full feature coverage
* docs(networking): rewrite Compose Networking page with full feature coverage
* docs(doctor): rewrite Compose Doctor with full 30-rule reference, screenshots, and cross-links
* docs(networking): add production screenshots and correct alt text
Adds 7 production screenshots for all sections of the Compose Networking
page and updates the four placeholder alt texts written before screenshots
were taken to match what the actual images show (arr-net external badge,
swag service with 443/tcp and 80/tcp, single-service exposure intent row).
Also adds the full-panel overview image at the top of the page.
* docs(environment-guardrails): rewrite with project env file, env file status, and screenshots
* docs(storage): rewrite Storage Portability page with screenshots and full coverage
Rewrites compose-storage.mdx from a 61-line sketch into a complete
reference page. Key additions: Where to find it section with screenshot,
full storage inventory section documenting all mount type/access/status
chips and the Linux owner display, expanded portability verdict section
with per-reason detail and edge-case caveats (read-only binds, symlink
escapes, anonymous volume risks), snapshot coverage section with admin
scope and remote-node behavior, Findings in Doctor cross-reference, and
six troubleshooting accordions covering tab visibility, bind status,
external named volumes, render errors, and snapshot coverage states.
Adds two production screenshots: storage-tab.png and storage-node-bound.png.
* docs(stack-labels): rewrite with accurate permissions, capability gate, dry run, live preview, and color conflict docs
* docs: rewrite Stack Sidebar page with accurate feature coverage
Rewrites the Stack Sidebar documentation page to match the current UI.
Key changes:
- Fix branding header description (shows logo + version, not just version)
- Fix bulk mode icon description (stacked-rows, not square)
- Add cross-node search section (fan-out behavior, Other nodes section,
unreachable-node warnings, click-to-switch navigation)
- Update Labels submenu description (inline New label creation, Manage labels link)
- Note that Delete only appears when the user has delete permission
- Remove the auto-update implication from Schedule task description
- Rewrite the Activity ticker section with the full 6-state priority cascade
table; remove the non-existent IDLE state; correct pulsing-dot behavior
- Replace all 7 stale screenshots with fresh production screenshots
- Add new sidebar-cross-node-search.png screenshot
* docs(atomic-deployments): refresh screenshot and document project env files, rollback readiness, and recovery actions
* docs(atomic-deployments): fix rollback permission visibility and banner string accuracy
The Rollback menu entry is hidden by the frontend when the user lacks
stack:deploy; it never appears and does not 403. Fixed the step-4
narrative and troubleshooting accordion to match.
The rollback-failure banner emitted by ComposeService is
'=== Rollback failed. Manual intervention may be required ==='
(period, capital M). Fixed both occurrences in the page.
Updated the Settings navigation path from the nonexistent
'Roles & Access' to the real 'Access'.
* docs(deploy-progress): rewrite with health gate, inline style, and 9 fresh screenshots
Add health gate section covering all four states (observing, passed, failed,
unknown) with exact UI banner text and the configurable observation window.
Expand the inline style section with full band content, 4s auto-dismiss, and
pill handoff. Add Scanning as a supported entry point. Replace all 6 existing
screenshots and add 3 new ones (modal-health-gate, inline-banner, setting-style).
Add two health gate troubleshooting accordions. Add Related CardGroup linking to
health-gated-updates, stack-activity, deploy-enforcement, and atomic-deployments.
* docs(health-gated-updates): refresh screenshots and correct signal row order and label
* docs(deploy-enforcement): rewrite with fleet replication, honor suppressions location, scan-failed dialog state, and fresh screenshots
Adds the Fleet policy replication section covering control/replica behavior,
Managed by control node banner, and Demote to control. Documents the exact
location of the Honor suppressions toggle (bottom of Policies tab). Expands
the block dialog section with the scan-failed row state. Updates all three
screenshots to the current visual design. Restores the Admiral license note
and corrects the policy-card scope description.
* docs(app-store): rewrite with mobile layout, fresh screenshots, and registry admin note
- Replace all 5 stale screenshots with 1920x1080 production captures
- Add app-store-mobile.png showing the status masthead layout
- Document mobile single-column layout in a new Mobile subsection
- Note that the featured hero has its own Deploy button
- Mark the category rail as desktop only with a cross-link to Mobile
- Add admin-account requirement to the custom registry section
- Add Related CardGroup linking vulnerability scanning, deploy progress, deploy enforcement, and resources
On phone widths the volume browser kept its fixed 260px tree column beside the
preview, leaving the preview roughly 54px wide so file content wrapped to one or
two characters per line. Below the md breakpoint the two panels now stack into a
single column (tree above, preview below) with the tree capped at 40% of the
height, so the preview spans the full width. Desktop and tablet layouts are
unchanged.
When a remote node's bulk /stacks/statuses is unavailable or returns the
legacy plain-string format, the sidebar derived a stack's status from its
containers by treating "any container running" as healthy. A stack with one
running and one crashed container was shown green UP and excluded from the
Down filter.
Re-derive status from the per-stack container list in both compatibility
cases, mirroring the backend classifier: a stack with a running container and
a genuinely crashed one (dead, or exited/restarting with a non-zero code) is
now partial, while a cleanly finished one-shot container (exit 0) stays
running. Legacy plain-string bulk responses are routed through this path too,
since they have already collapsed the degraded case.
The cross-node capability gate cached its verdict, so a remote replaced by older
code at the same URL stayed trusted until the cache expired, reopening the
non-admin HTTP escalation and the over-broad stop. The probe now hits the
remote's live /api/meta on every gated action (concurrent calls deduped, never
cached across requests, fail-closed), so a downgraded remote is detected
immediately.
Two stop-result gaps are also closed:
- A remote stop result must now cover exactly the confirmed stacks (one per
stack, no extras, no omissions), not merely exclude extras, so a dropped
confirmed stack is no longer accepted as clean. runLocalLabelStop reports one
result per confirmed stack even when the label has vanished, so a current
remote always satisfies the check.
- The local stop exception path now reports the full confirmed set, so a
confirmed stack that lost its label is not dropped when the local stop throws.
An older remote node ignores the forwarded actor-role header (running proxied
requests as admin) and ignores the stop-by-label stack allowlist (stopping
every label-matched stack). The control could neither detect nor prevent this
on a mixed-version fleet.
Instances now advertise a cross-node-rbac capability, and the control refuses
to act when a remote lacks it:
- HTTP proxy: a non-admin user's request is not forwarded to a remote that does
not advertise the capability (fails closed when it cannot be determined).
Admins are unaffected.
- Stop-by-label: a real stop bound to a confirmed stack set is not sent to a
remote lacking the capability; the node is reported as needing an upgrade. As
defense in depth, a node whose results name stacks outside the confirmed set
is failed rather than rendered as a clean stop.
Separately, the stop's lock-contention path now reports every confirmed stack
as a contention failure (including one that lost its label), so a confirmed
stack is never silently dropped and the result is never empty.
A remote WebSocket upgrade was forwarded to the target node before any role
check, and the forwarder authenticates the connection to the remote as an
admin-gated console_session. A non-admin could therefore open a remote
container-exec or host-console socket that the local handlers reject.
The hub now applies the same gate before forwarding: logs and notifications
stay open to any authenticated user, every other path (container exec, host
console) is admin-only, and machine node_proxy tokens are rejected on the
interactive paths. This covers proxy-mode and pilot-agent remotes, which share
the forward path.