mirror of
https://github.com/Studio-Saelix/sencho.git
synced 2026-07-27 20:29:10 +00:00
e2a9db3329336046d6bb5cf3100ebebc8bbc4554
377 Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
e2a9db3329 |
docs(resources): refresh Resources Hub page for v1 redesign and feature additions
Rewrite the page to match the shipping UI and replace stale screenshots with fresh captures of the redesigned chrome. - Document the admin-only Reclaim hero and clarify the per-tile Sencho-only vs. All Docker (includes external) split in Quick Clean. - Add coverage of the Scan history toolbar button and the per-row severity badge plus shield-icon scan dropdown in the Images tab; cross-link to the vulnerability scanning page. - Correct the Volumes column list (no Size column; size lives on the Largest 5 landing card) and call out admin gates on browse and delete. - Spell out the List/Topology view-mode toggle and that Create Network is admin-only and List-mode only. - Rewrite the Unmanaged tab section around the project-grouped layout, the Select all + Purge Selected (N) admin-only multi-select, and the empty state copy. - Replace screenshots: resources-reclaim, networks-list, create-network, network-inspect, network-topology, network-topology-toggle. Add fresh resources-volumes-tab and resources-unmanaged-tab captures. |
||
|
|
4fca38ec3d |
docs(deploy-progress): rewrite around current modal + capture v1 screenshots
Aligns the page with the v0.72.0 implementation and standardizes the troubleshooting layout with the rest of the docs refresh. Setting and gating - Renames the Settings field to "Deploy progress modal" and quotes the current helper text verbatim. Documents that the toggle is off by default, lives under Settings > Appearance > Display, is stored in localStorage, and syncs across tabs in the same browser. Modal anatomy - Names every visible UI string: header verbs, status indicator (with the "closes in <n>s" countdown that was previously undocumented), empty-body strings, footer toggle that flips between "Raw output" and "Hide raw", and the destructive border on ERR rows vs the softer warn tint on WARN rows. - Replaces the vague "after a few seconds" with the actual 4-second auto-close timer; documents hover-to-pause and the leave-hover-restarts-the-countdown behavior. - Documents the truncated error message in the failed-state header and the manual close-only requirement. - Notes that the pill is portal-mounted and survives navigation. Stage badges - Keeps the 9-badge table but adds an honest note that most lines render as LOG because the badges are gated on Compose's "[+]" progress prefix, which Compose only emits in TTY mode and Sencho spawns it without one. Entry points - Splits the supported actions into the four that produce a populated structured-log body (Deploy, Update, Install, Git Apply) and the two that bypass compose and finish with 0 lines (Restart, Stop). Drops the "Down" claim from the user-facing list since no UI control currently triggers it; mentions the down route as an automation surface only. Troubleshooting - Wraps the existing accordions in an <AccordionGroup> matching the pattern used by the editor and stack-activity refreshes. Adds two new entries: one explaining the Restart/Stop "0 lines" outcome, one explaining the LOG-everywhere case for non-TTY compose output. Screenshots (six PNGs in docs/images/deploy-progress/, 1920x900, dark theme, captured against the upgraded production node) - setting-toggle.png: the Display section with the toggle enabled. - modal-streaming.png: a real update in flight at 19s, 554 lines. - modal-succeeded.png: succeeded state with the live closes-in countdown visible. - modal-raw-output.png: structured rows with the Raw output panel expanded beneath. - pill.png: minimized pill anchored bottom-center on a stack editor view. - modal-failed.png: failed state with the truncated error in the header and ERR rows highlighted. |
||
|
|
0a3dfc95a5 |
docs(stack-file-explorer): refresh page around v1 explorer and add screenshots
Full rewrite to match the live two-pane Files tab. Replaces the false 'Edit' toolbar flow with the read-only chip + always-on Save model, fixes the protected-files list (5 names) and dedicated-tab redirect list (3 names), drops the fabricated 100 MB download cap, and corrects the upload claim to single file at a time. Adds sections for New File, Rename, Permissions, the type-to-confirm protected-file delete flow, the non-empty folder delete confirmation, the 500-entry tree display cap, and the symlink rendering. Restructures troubleshooting around <AccordionGroup> + <Accordion> to match fleet-federation.mdx, and adds two new entries (display cap, 403 on Community admin write). Ships nine screenshots captured against v0.72.0 production at 1920x900 in dark theme: overview, two-pane layout, protected-tree-marker, viewer-edit-mode, new-file-dialog, context-menu-folder, context-menu-file, permissions-dialog, delete-protected-confirm. |
||
|
|
8ad5bb2e39 |
docs(features): wrap troubleshooting accordions in AccordionGroup
Wraps the loose <Accordion> blocks in editor.mdx and stack-activity.mdx in a single <AccordionGroup> to match the troubleshooting design used by fleet-federation.mdx. Structural only; no content changes. |
||
|
|
bd25458a26 |
docs(editor): refresh page around v1 cockpit and recapture screenshots
Rewrites the page around the dual-mode right panel (Anatomy by default, Monaco when the user clicks edit), the redesigned Command Center action bar, the new container row layout with status badges and live stats, the Structured / Raw terminal logs toggle, the Git Source toolbar button, and the opt-in diff preview. Drops the obsolete persistent embedded terminal section. Preserves the #diff-preview-before-save and #log-viewer anchors referenced from settings.mdx and global-observability.mdx. Replaces editor-overview.png and container-exec-modal.png with fresh captures against v0.72.0 production at 1920x900 dark theme; renames container -actions.png to containers-list.png; adds command-center.png and editor-edit-mode.png. PII scrubbed (host paths normalized) and the compose-diff-preview/diff-modal.png shared asset is left untouched after visual diff against the live UI showed no chrome change. |
||
|
|
156a9ffd23 |
docs(stack-activity): wrap troubleshooting entries in Accordion blocks
Match the foldable troubleshooting pattern established by docs/features/deploy-progress.mdx so the page stays compact and readers can scan to their issue. This is the canonical formatting for the /features section's troubleshooting blurbs going forward. |
||
|
|
99a1cb45be |
docs(stack-activity): refresh page around v1 cockpit and recapture screenshots
Realign the page with the current Anatomy panel tab strip and the StackActivityTimeline component: - Frame the Activity tab as a sibling of Anatomy under the right-hand panel, with files/edit actions belonging to the strip. - Expand the category guidance: list the five iconized categories and call out that other stack-scoped notifications (deploy failure, available image updates, auto-heal triggers, monitor alerts, scan findings) flow into the timeline with a generic icon. - Tighten the actor-attribution rule to match the component: omitted for events without an actor and for system-driven events. - Add the day-format example to the relative-time row. - Recapture both screenshots from a populated stack (Today + Yesterday + Earlier with three distinct icons) and an empty stack. - Convert troubleshooting blurbs to H3 for anchor links and consistency with the v1-refresh sibling pages. |
||
|
|
4ddf44c5e6 |
docs(stack-management): refresh page around v0.72.0 cockpit and add screenshots
Updates the Stack Management page to match the current UI: sidebar with filter chips and label groups, bulk mode, restructured kebab menu, two-tab anatomy panel, and three-source New stack dialog (Empty, From Git, From Docker Run). Adds sections for Filter chips, Pinned and label groups, and Bulk mode. Restructures the Stack context menu around the inspect, organize, lifecycle, and destructive groups with their keyboard shortcuts. Documents the From Git tab and cross-links Git Sources for the full sync flow. Replaces every existing screenshot with fresh captures from the current UI and adds eight new captures for the new sections. Cross-links Stack Activity, Stack Labels, Stack File Explorer, Compose Editor, Atomic Deployments, Scheduled Operations, Auto-Heal Policies, Auto-Update Policies, and Alerts and Notifications for features documented on their own pages. |
||
|
|
6b4c565794 |
docs(features-overview): regroup catalog and add 17 missing features
Restructure the Features Overview into the same six groups the docs sidebar uses (Stacks & Deployments, Observability, Fleet & Multi-Node, Security & Identity, Automation, Platform) plus a short Reference tail. Add catalog entries for 17 shipped features that the previous overview never mentioned: Stack Activity, Stack File Explorer, Deploy Progress, Deploy Enforcement, Blueprints, Git Sources, Global Search, Pilot Agent, Sencho Mesh, Fleet Federation, Fleet Actions, Fleet Sync, Fleet Secrets, Two-Factor Authentication, CVE Suppressions, Auto-Heal Policies, Stack Sidebar. Fix two factual inaccuracies: - Fleet View blurb wrongly gated search, sort, filter, and stack drill-down behind Skipper. The deep-dive is explicit that those are available on every tier; only the bulk Update All action inside the Node Updates modal is paid. - Auto-update entry was titled and described as a scheduling system. The deep-dive page is the Auto-Update Readiness board (risk tags, changelog previews, rollback targets); the scheduler lives under Scheduled Operations. Add three hero screenshots captured from production at 1920x900, illustrating the redesigned cockpit visual language: Home dashboard, stack anatomy, and fleet topology. |
||
|
|
4ba319adaa |
docs(sso): refresh setup guide and drop misleading "one-click" wording
Brings the SSO Setup Guide in line with how SSO actually works in the current build, and corrects misleading copy across both SSO docs pages. - Setup Guide: explains the env-var-seeds-once / DB-is-authoritative config model up front, replacing per-section "Restart Sencho" wording that implied a restart was always required. - Setup Guide: promotes the per-provider Test Connection button out of the LDAP-only paragraph into a generic intro callout, and adds a self-signed LDAPS tip. - Setup Guide: notes that all OIDC providers accept a *_DISPLAY_NAME override for the login button label, and adds a commented LDAP TLS toggle to the full compose example. - Setup Guide: adds two screenshots of the redesigned Settings > SSO panel (overview + LDAP card expanded with form). - Both pages: replaces "one-click presets" / "one-click configuration" with "preset providers". The Skipper-tier presets still require an OAuth app provisioned in the provider's console; what they actually buy is provider-aware defaults and a branded login button. The old wording overpromised. |
||
|
|
ee08f35930 |
docs(configuration): replace em-dash-substitute hyphens in prose
Three sentences used ` - ` (space-hyphen-space) as an em-dash substitute. Replaced with the punctuation that fits each case: - "How Sencho organizes your compose directory": semicolon between the two related clauses. - Data directory Warning: parentheses around the parenthetical insertion. - 1:1 path rule explanation: comma before the contrastive clause. Heading slugs and YAML code-block comments are unchanged. |
||
|
|
c757210cf7 |
docs(configuration): align env var reference with current backend
Bring docs/getting-started/configuration.mdx up to date with the v0.72.0 backend: - Remove PORT from the optional env vars table. The listen port is hardcoded to 1852 in backend/src/helpers/constants.ts and is never read from the environment. Replace it with a Listen port section that explains the fixed port and host-port remapping. - Document API_RATE_LIMIT (default 200) and API_POLLING_RATE_LIMIT (default 300), both applied in production only. - Note the /app/compose fallback default for COMPOSE_DIR while still pointing readers at the 1:1 path rule. - Point the SSO env var section at the new SSO Quickstart page and keep the SSO feature reference as the deeper dive. - Tighten First boot and cross-link to Quickstart so the screenshot is not duplicated. - Add a Where to next CardGroup matching the refreshed Introduction and Quickstart pages. Drop stale PORT=1852 and JWT_SECRET=your-secure-jwt-secret-here lines from .env.example so the example no longer contradicts the docs (PORT is hardcoded, JWT_SECRET is auto-generated and persisted to the database during initial setup). |
||
|
|
bd1b57bcd3 |
docs(quickstart): rewrite around v0.72.0 cockpit and add screenshots
Replaces the bare install snippet with a five-minute walkthrough that matches the redesigned UI. Leads with a docker-compose.yml block (the bare docker run command is collapsed in an Accordion), keeps the 1:1 path rule, and adds two new sections that show the user what happens on first boot. Adds two screenshots at 1920x900 dark theme: - setup-cold-start.png: the Cold start card with Username, Password, Confirm password fields, and the Initialize console button. - dashboard.png: the post-sign-in dashboard captured against v0.72.0 with the full top nav (Home, Fleet, Resources, App Store, Logs, Auto-Update, Console, Audit, Schedules) and the populated Stack health table sorted by load. Where-to-next now uses CardGroup cols=2 to match the introduction page's pattern. |
||
|
|
b79354922b |
docs(introduction): re-shoot screenshots against v0.72.0 production
Re-capture all four introduction screenshots from the upgraded production node so the fleet view shows the current full tab strip (Overview / Snapshots / Status / Deployments / Traffic / Federation / Fleet Actions / Secrets) instead of the older Overview / Snapshots / Status only. Same 1920x900 dark-theme capture and the same PII scrub applied (node names, usernames, IPs, home paths neutralized). |
||
|
|
ac38c5c404 |
docs(introduction): add Federation and Fleet Secrets
Federation has shipped (Admiral) with cordon and pin policy as operator overrides on the blueprint reconciler. Fleet Secrets is landing as a Skipper+ tab for versioned env-var bundles encrypted at rest, with diff preview and target push. Mirror those in the Run-one-machine-or-many bullet list and in the Tiers paragraph so the introduction matches the current product. |
||
|
|
eb5989ac47 |
docs(introduction): rewrite intro page and refresh screenshots
Rewrite the Getting Started introduction to reflect the current product: adds the Mesh, Blueprints, Pilot, Fleet, Resources concepts up front; restructures capability sections around Stacks, Fleet (now including Fleet Actions), Mesh, Blueprints, Monitoring, Resources, Security, Automation, and Pilot/Remote ops; cross-links every claim to the matching feature page. Replaces the dashboard hero shot with a fresh capture against the redesigned cockpit chrome and adds three inline shots (running stack with anatomy and logs, fleet command deck, resources hub with reclaim header). All screenshots taken at 1920x900, dark theme, with node names, usernames, IPs, and home paths neutralized. Drops outdated claims: stale "190+ templates" count, the "viewer accounts" RBAC summary, and the "atomic" deployment label that did not match the actual rollback mechanism. |
||
|
|
0f0b22c51a |
feat(fleet): Fleet Secrets tab with env-var bundles (v1 MVP) (#965)
* feat(fleet): add Fleet Secrets tab with versioned env-var bundles (Skipper+) Centralized, encrypted-at-rest secret bundles that can be pushed to labeled nodes' stacks. Each save bumps a monotonic version; each push records a per-node-per-version row in `secret_pushes` plus an entry in `audit_log`. Conflict detection shows added/changed/unchanged/removed (informational) diffs before write. Overlay merge preserves keys missing from the bundle. - Adds `secrets`, `secret_versions`, `secret_pushes` tables. - New `SecretsService` reuses CryptoService for AES-256-GCM, NodeLabelService for selectors, and direct fetch + Bearer for outbound calls to remote nodes. - New `secretsRouter` with 9 endpoints under `/api/secrets`, gated by `requirePaid`. Mounted after the auth gate. - Audit summary patterns added for the new routes. - New Fleet › Secrets tab with bundle list, editor sheet (key=value rows, versions tab), and push wizard (selector, target stack, env file picker, per-node diff preview, results pills). - Documentation: docs/features/fleet-secrets.mdx + docs.json nav entry. - 26 Vitest cases cover parser, encryption, versioning, push aggregation, tier gating. * fix(fleet): use const for rawValue in env parser ESLint prefer-const flagged the let declaration as a CI-blocking error; the variable is never reassigned. |
||
|
|
52b46753af |
feat(fleet): add Federation tab with cordon and pin policy (Admiral) (#964)
Ships the v1 MVP for the Federation tab as placement control, not
placement automation:
- Cordon a node: marks the node unschedulable so the BlueprintReconciler
skips it for new placements only. Existing deployments continue to
drift-check and redeploy on revision changes; cordon never triggers
withdraw or eviction. Toggle on the NodeCard kebab (Admiral, admin
role); Cordoned pill renders for all tiers.
- Pin a blueprint to a node: stores blueprints.pinned_node_id, replacing
the desired set with the pinned node regardless of selector. Pin
overrides cordon by design. Action lives only in the Federation tab;
BlueprintDetail and the deployment table show read-only Pinned
indicators.
Backend: idempotent migrations add nodes.cordoned/cordoned_at/cordoned_reason
and blueprints.pinned_node_id. New routes POST /api/nodes/:id/cordon,
POST /api/nodes/:id/uncordon, PUT /api/blueprints/:id/pin, all gated by
requireAdmiral plus requireAdmin. Audit summaries added so the existing
auditLog middleware records every operator action. deleteNode clears
dangling pins.
Reconciler: pin override evaluated before selector match; cordon filter
applied only to the new-placement branch (deploy/stateReview without an
existing deployment). 11 new Vitest cases cover cordon filter, pin
override, pin-overrides-cordon, missing pin target, pin shrinks
desired set (stateless withdraw + stateful evict_blocked), and pin
clearing on node delete.
Frontend: new FederationTab.tsx with cordoned-nodes summary and
pin-policy table. Federation moved out of the experimental flag into
{isAdmiral && (...)} + AdmiralGate, mirroring the Routing tab pattern.
Secrets stays under experimental.
Tests pass: backend tsc, full Vitest suite (1704 passed), frontend
tsc -b, ESLint (0 errors). Manual verification via the local dev
instance confirmed the tab is hidden at Community, the kebab and pill
render at Admiral, and cordon and pin endpoints round-trip end to end.
Refs cut-line-1.0.md Federation v1 MVP.
|
||
|
|
77d5ff58d3 |
feat(fleet): add Fleet Actions tab for cross-node bulk operations (#963)
* feat(fleet): add Fleet Actions tab for cross-node bulk operations Introduces a new "Actions" sub-tab in Fleet view with two Skipper+ cards that fill gaps in the existing surface: - Stop fleet by label: matches a label name across every node and stops every stack assigned to it, reporting per-node and per-stack results. - Bulk label assign: applies the same label set to many stacks on one node in a single round trip. Other bulk operations stay in their existing homes (sidebar bulk mode, Schedules, NodeUpdatesSheet) to avoid duplicate surfaces. Backend: - POST /api/fleet/labels/fleet-stop (gateway-orchestrated, multi-node) - POST /api/fleet-actions/labels/bulk-assign (per-node, capped at 1000) - Tightens /api/fleet proxy-exempt prefix to /api/fleet/ so /api/fleet-actions/* is routed through the proxy for per-node calls. - Exports activeBulkActions from labels.ts so fleet-stop and label-action share the per-node lock and cannot double-stop the same containers. - Extracts containerActionForStack helper from stacks.ts for reuse. * chore(fleet): rename Actions tab to Fleet Actions and reorder Fleet sub-tabs - Tab label "Actions" -> "Fleet Actions" so the surface is unambiguous alongside Schedules and the sidebar bulk bar. - Reorder Fleet sub-tabs as Overview / Snapshots / Status | Deployments / Traffic / Fleet Actions, with the separator after Status. - Rename "Traffic · Routing" -> "Traffic" and update Sencho Mesh docs to match the shorter label. - Update Fleet Actions docs to the new tab name and placement. |
||
|
|
7fe90d9f3a |
feat(blueprints): capture compose snapshot before stateful eviction (#957)
Wire the snapshot_then_evict withdraw mode to actually persist the blueprint's compose YAML to fleet_snapshots before running the eviction. The mode previously recorded intent only. Capture failure aborts the eviction with HTTP 500 rather than silently falling through to a destructive withdraw. Volume bytes remain out of scope: the snapshot holds the compose definition only. UI copy and the Blueprints docs (Withdraw note, Migrating stateful data section, two new Troubleshooting entries) clarify that operators must move volumes by hand if they need the data on another node. Adds 9 route-level tests covering the success path, snapshot DB write failure, orphan-row cleanup when insertSnapshotFiles fails, empty compose_content, evict_and_destroy unchanged, stateless unchanged, evict_blocked gate, omitted confirm field, and bad confirm value. |
||
|
|
aa00dc2b89 |
docs: split Features by capability and tighten nav (#956)
Subdivide the 40-page Features sidebar into six capability subgroups (Stacks & Deployments, Observability, Fleet & Multi-Node, Security & Identity, Automation, Platform) so users find features by purpose rather than scrolling a flat list. Move the licensing page into the Reference group where it belongs and reorder Reference for scan-ability. Reorder the Operations group by lifecycle: install, upgrade, backup, troubleshoot, then per-feature admin tools. Nav-only change. No .mdx files were moved or renamed; every page keeps its existing URL. Tier badges already inside each page are unchanged. |
||
|
|
ecf4dd5d52 |
feat: open security basics, manual fleet ops, and basic fleet management to Community (#930)
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.
|
||
|
|
a85af40cb5 |
fix(frontend): tighten bell notification panel toolbar (#929)
* fix(frontend): tighten bell notification panel toolbar Restructure the popover so the segmented filter (All / Unread / Alerts) and the action icons (filter toggle, mark all read, clear all) share a single row inside the 360px panel. The previous layout wrapped the segmented control onto a second row once the type filter was added. Move the unread badge to the right of the title, collapse the node and type dropdowns behind a filter-toggle icon (accent dot signals an active filter), equalize both Select widths, and extract the duplicated className strings into local constants. * docs: refresh bell notification popover screenshot Reflects the reworked toolbar (segmented filter + filter toggle + mark-all-read + clear-all on a single row). |
||
|
|
49d775c61f |
feat(volumes): add read-only volume browser (#926)
* feat(volumes): add read-only volume browser Adds a browser for the contents of any Docker named volume. Click the folder icon on a volume row (admin only) to open a sheet with a directory tree on the left and a file viewer on the right. Backend ------- New VolumeBrowserService spawns a one-shot Alpine 3.20 helper container with the target volume mounted read-only at /v. The container runs as nobody (65534:65534) with a read-only rootfs, no network, all caps dropped, no-new-privileges, and capped at 64 PIDs and 128 MiB. The helper image is pulled on first use per node. Listing and stat use a portable busybox-compatible shell loop (find -printf is not available on Alpine). Reads use head -c with an explicit -- separator; the helper's working directory is /v so user paths are passed as ./<path> argv elements and never as flags. The container lifecycle is managed manually (create, attach, start, wait, remove) to avoid the AutoRemove race where dockerode sees a 404 on its post-exit container lookup. Path safety: relative paths are sanitized server-side, rejecting parent-escape segments, absolute paths, null bytes, and oversized input. Symlinks are listed but never followed on read. Files larger than 5 MB are truncated; binary content is detected via null-byte scan and returned base64-encoded. Non-zero helper exits map to 404, 403, or 500 by classifying stderr. Routes mounted at /api/volumes: - GET /:name/list?path= - GET /:name/stat?path= - GET /:name/read?path= All three require admin. The read endpoint always inserts an audit log row (success or failure) with the actual response status code, volume name, and relative path. Frontend -------- FileTree generalized to take a loadDir callback and a sourceKey instead of a hard-coded stackName. The single existing consumer (StackFileExplorer) was updated and its tests rewritten. The loader is read through a ref so re-creating the arrow on every parent render does not re-trigger the root fetch effect. New VolumeBrowserSheet renders the tree against the volume API, shows file content (hex view for binaries), and surfaces truncation. Rapid sheet open and reopen on different volumes is generation- checked to avoid stomping the visible result with a stale read. A persistent footnote reminds the user that file reads are recorded in the audit log, and the docs page warns about the typical contents of database volumes. Tests ----- 15 new vitest cases cover the pure helpers (path traversal, volume name validation, binary detection). The Docker-facing exec path is exercised by manual end-to-end via curl against a seeded volume. * fix(volumes): truncate long volume names in browser sheet header Wide volume names overlapped the close X. Reserve right padding on the header, set min-w-0 on the flex title, mark the icon and refresh button shrink-0, and truncate the name span. * fix(volumes): satisfy lint on volume browser additions prefer-const on sanitizeRelPath's local; drop unused FileTree entry arg from the file-select callback (variance lets the arrow take fewer params than the contract). |
||
|
|
7e5dc2d9ea |
feat(resources): add image details sheet with layer history (#925)
Adds a read-only inspect panel for Docker images. Click the eye icon on any image row to open a sheet showing: - Overview: ID (with copy), size, created date, arch/OS, author, tags - Config: Cmd, Entrypoint, WorkingDir, User, exposed ports, env (collapsible), labels (collapsible) - Layers: ordered history list with size, age, and build command per layer. Empty layers (metadata-only) are dimmed. Backend adds DockerController.inspectImage(id) which combines image.inspect() and image.history() in parallel, exposed via GET /api/system/images/:id. The route accepts both bare hex IDs and sha256-prefixed IDs, since the list endpoint surfaces the prefixed form. Returns 400 for malformed IDs and 404 for missing images. Documents the new panel in docs/features/resources.mdx under Images. |
||
|
|
945259f048 |
refactor(frontend): migrate CreateStackDialog to Modal chrome system (D-4) (#908)
* refactor(frontend): migrate CreateStackDialog to Modal chrome system (D-4)
Swap raw shadcn Dialog/DialogContent/DialogHeader/DialogFooter for the
Modal/ModalHeader/ModalBody/ModalFooter primitives shipped in D-1 and
introduce an inline ModeRail to replace the TabsHighlight chip. The new
chrome carries the cyan rail, mono kicker (STACKS · NEW), italic serif
title, and contextual footer hints per mode (ALPHANUMERIC · HYPHENS,
HTTPS REPOS ONLY, CONVERT FIRST / YAML READY + line-count accent).
ModeRail implements the full WAI-ARIA tabs pattern: aria-selected on the
active tab, aria-controls/role=tabpanel/aria-labelledby linkage to each
mode panel, roving tabIndex (active=0, inactive=-1), and ArrowLeft /
ArrowRight / Home / End keyboard navigation matching the contract that
the prior shadcn Tabs primitive provided.
Each mode also gains an explicit Cancel button alongside the existing
primary action, the empty-mode primary button is now disabled until the
stack name is non-empty, and the ModeRail disables itself while a Git
or docker-run create is in flight.
The async handlers (handleCreateStack, handleCreateStackFromGit,
handleConvertDockerRun, handleCreateStackFromDockerRun) and form-reset
helpers are unchanged; this PR is structural plus the segmented-control
redesign called out in the migration tracker as the D-4 risk note.
* test(e2e): align git-sources spec with new CreateStackDialog title
D-4 renamed the dialog title from "Create New Stack" to "New stack".
The shared openCreateStackDialog helper in git-sources.spec.ts was
still asserting the old literal, so three tests in the "Create stack
from Git" group failed at the helper's first assertion.
Update the assertion to match the shipped title and refresh the two
stale references in docs/features/stack-management.mdx so the docs
stay in sync with the UI copy.
* test(e2e): use dialog accessible name in openCreateStackDialog helper
The previous helper used getByText('New stack') which matched two
elements: the dialog title h2 and the sr-only description (which
starts "Create a new stack: empty, ..." and contains the substring).
Playwright fails with a strict mode violation.
Switch to getByRole('dialog', { name: 'New stack' }), which asserts
the dialog by its accessible name (provided by DialogTitle via the
Radix aria-labelledby wiring). One match, more precise, and
independent of any future description copy.
|
||
|
|
577acc056b |
docs: refresh screenshots (#882)
Co-authored-by: AnsoCode <18150933+AnsoCode@users.noreply.github.com> |
||
|
|
9ba6b604e3 |
docs: refresh screenshots (#869)
Co-authored-by: AnsoCode <18150933+AnsoCode@users.noreply.github.com> |
||
|
|
5e29649f3e |
chore: workflow hardening (husky+commitlint, dependency-review, stale, GHCR, mesh-sidecar digest) (#863)
* chore(repo): enforce Conventional Commits via husky and commitlint release-please parses commit subjects on main to compute the next version and regenerate CHANGELOG.md. A non-conforming commit silently breaks both, so the format must be enforced at commit time, not by review. Adds husky 9 to wire a commit-msg hook, commitlint with the conventional config, and a small rule override (loosen subject length to 120 chars, disable subject-case so existing imperative subjects keep passing). The prepare script runs husky on npm install so contributors do not need to configure it manually. * ci: add dependency-review workflow GitHub-native action that diffs the PR's manifests (package.json, package-lock.json) against main and fails when a new transitive dep introduces a high or critical CVE. Existing vulnerabilities tracked in security/vex/sencho.openvex.json and the Trivy scan in ci.yml are not re-flagged here. Pinned to actions/dependency-review-action v4.9.0 by commit SHA. Comment summaries are posted to the PR only on failure to keep the conversation clean on green PRs. * ci: add stale workflow for issues and pull requests Marks issues and PRs as stale after 60 days of inactivity and closes 14 days later unless re-engaged. Issues labeled pinned, security, bug, or tracking are exempt and never auto-closed; PRs labeled pinned or security are exempt. Runs daily at 01:30 UTC and processes up to 30 items per run to stay within the action's rate budget. Pinned to actions/stale v10.2.0 by commit SHA. * chore(repo): route new issues to docs, discussions, and security policy Disables the blank-issue option and adds three contact links the issue chooser surfaces above the bug-report and feature-request templates: docs.sencho.io for setup questions, GitHub Discussions for open-ended chat, and the repository security policy for private vulnerability reports. This keeps the bug tracker focused on actionable bug reports and feature requests instead of support questions. * ci(docker): publish multi-arch image to GHCR alongside Docker Hub Mirrors every released sencho and sencho-mesh image to ghcr.io with the same tags, signing, and supply-chain attestations as the Docker Hub copy. Same content; pull from whichever registry your environment prefers. - Adds packages:write to both publish jobs so the auto-provisioned GITHUB_TOKEN can push to ghcr.io/studio-saelix/sencho and -mesh. - Adds a second docker/login-action step authenticating to ghcr.io. The Docker Hub login still resolves credentials from the production env. - docker/metadata-action now lists both image references; one buildx push attaches the manifest to both registries in a single round trip. - Cosign keyless signing already loops over $TAGS, so adding the GHCR reference is enough to sign both digests. - The cosign attest step now loops over both registry paths so SBOM (CDX + SPDX) and OpenVEX attestations are resolvable from either registry. Quickstart and image-verification docs note GHCR as an alternative pull source with identical content. * fix(mesh-sidecar): pin base image by digest The main Sencho Dockerfile pins every base image by sha256 digest so a republish of an upstream tag cannot silently shift content into a release. The mesh-sidecar Dockerfile pinned node:22-alpine by tag, which is exactly the gap that pinning closes. Resolves node:22-alpine to its current multi-arch index digest (covers linux/amd64 and linux/arm64) and threads it through both build stages via a single ARG so a future digest roll only edits one line. The inline comment documents the resolution command. The sidecar holds an outbound websocket and has no inbound HTTP listener, so adding a HEALTHCHECK is intentionally out of scope: a process-liveness probe would be tautological with Docker's restart policy. The Dockerfile now records that decision so it does not get reopened on every audit. * ci(docker): use repository_owner for GHCR login username github.actor varies by event source (the maintainer who merged the release PR on tag pushes, github-actions[bot] on bot-driven runs, the dispatcher on workflow_dispatch). The username field is metadata only; GITHUB_TOKEN is what authenticates against GHCR. github.repository_owner resolves to a fixed value (studio-saelix) on every event and matches GitHub's own example workflows for GHCR push. * chore(repo): tighten commit subject-case rule Disabling subject-case entirely allowed accidental ALL-CAPS or PascalCase subjects through. Restrict to "never upper-case or pascal-case" instead, which preserves the lowercase / kebab-case norm of this repo and lets sentence-case subjects (used sparingly on main) keep passing. |
||
|
|
7cde9917a5 |
docs: refresh screenshots (#866)
Co-authored-by: AnsoCode <18150933+AnsoCode@users.noreply.github.com> |
||
|
|
e5391e66cb |
feat(blueprints): add Fleet > Deployments tab UI, node labels, and docs (#861)
Implements the frontend layer for the Blueprint Model feature (backend landed in PR #860). Fleet > Deployments tab is now live for Skipper+ users; Community users see the existing locked badge. Key additions: - blueprintsApi.ts: typed apiFetch wrappers (localOnly: true on all calls) - BlueprintCatalog: featured hero, filter pills, classification-chipped tile grid - BlueprintEditor: Monaco YAML editor with debounced live classification, label/node selector, three-mode drift radio cards, create/edit modes - BlueprintDeploymentTable: per-node status rows with action buttons (Confirm deploy, Retry, Withdraw/Evict, DATA PINNED HERE for stateful) - EvictionDialog: dual-affordance (Snapshot then evict / Evict and destroy) - StateReviewDialog: fresh-deploy acceptance gate for stateful blueprints - BlueprintClassificationBanner: real-time stateless/stateful/unknown banner - DeploymentsTab: wires catalog, empty state, and create dialog - BlueprintDetail: Sheet with Apply/Edit/overflow, themed delete dialog - NodeLabelPicker + NodeLabelPill: label CRUD per node in NodeManager - FleetView: gates Deployments tab behind isPaid; mounts DeploymentsTab - NodeManager: Labels column with NodeLabelPicker (Skipper+ users) - docs/features/blueprint-model.mdx + docs.json entry + 6 screenshots |
||
|
|
7663f4cd8b |
feat(fleet): sencho mesh in traffic and routing tab (#858)
* feat(fleet): sencho mesh in traffic and routing tab Lights up Sencho Mesh: cross-node container forwarding rendered as if the container next to you were on localhost. Builds on the dormant TCP frame plumbing from the prior PR (pilot tunnel TCP frames + sencho-mesh sidecar package) and exposes the Admiral-only orchestrator surface. Backend - New mesh_stacks table (per-node opt-ins) + nodes.mesh_enabled column via DatabaseService.migrateMeshTables. - MeshService singleton: sidecar lifecycle via Dockerode, opt-in/out with cascading override regeneration, request-based resolver from sidecar control WS, cross-node TCP forwarding via PilotTunnelManager (same-node fast path included), in-memory 1000-event activity ring buffer with durable mirror to audit_log for state-change events, per-node and per-route diagnostics, and the Test upstream probe. - MeshComposeOverride: pure YAML generator that injects extra_hosts using host-gateway. The user's docker-compose.yml is never mutated; overrides live under DATA_DIR/mesh/overrides. - ComposeService deploy/update splice the override file when the stack is opted in; non-mesh stacks behave identically to today. - Pilot agent resolveMeshTarget consults the local mesh_stacks table (defense in depth) and resolves Compose containers via Dockerode. - /api/mesh router with 13 Admiral-gated endpoints covering status, enable/disable, stack opt-in/out, alias listing, per-route diagnostic, Test upstream probe, per-node diagnostic, sidecar restart, activity log paginated and SSE. - meshControl WS slot at /api/mesh/control validates the mesh_sidecar JWT minted by MeshService; dispatched as upgrade slot 2 (canonical order preserved). Frontend - New Traffic Routing tab in FleetView, gated by isAdmiral and wrapped in AdmiralGate. Tab uses the cyan brand glyph and italic-serif state typography from the audit. - RoutingTab masthead with mesh activity drawer, per-node card grid with TogglePill, alias rows with five-state pill taxonomy (healthy / degraded / unreachable / tunnel-down / not-authorized), inline Test buttons. - Four sheets: opt-in picker with port-collision inline error, per-route detail with diagnostic + filtered activity, per-node diagnostics with active streams + resolver cache + restart action, fleet-wide activity log with filters. - meshRouteState helper centralizes pill-state mapping; pure-function tests cover all five states. Docs - User docs at /docs/features/sencho-mesh.mdx covering opt-in, troubleshooting, security model (4 guarantees + 4 explicit non-guarantees), and V1 limitations. - Internal architecture and runbook pages. - websocket-dispatch internal doc updated with the new slot. * fix(mesh): validate stack name before path use; fix test DB lifecycle Two surgical fixes against the prior PR. Path-injection (CodeQL js/path-injection): MeshService.optInStack, optOutStack, ensureStackOverride, and removeStackOverride now validate stackName via isValidStackName from utils/validation, reject malicious names at the API boundary, and additionally check isPathWithinBase on the resolved override file path for defense in depth. The dataflow from req.params.stackName to fs.writeFile no longer reaches an unsanitized path expression. Test DB lifecycle: mesh-service.test.ts used per-test setupTestDb / cleanupTestDb, which deletes the temp dir while DatabaseService still holds an open SQLite handle. On Linux CI this raises SQLITE_READONLY_DBMOVED on the next prepare() because the inode has been unlinked. Switched to file-scoped beforeAll/afterAll matching agents-routes.test.ts, with a per-test beforeEach that truncates mesh_stacks plus non-default nodes and resets the MeshService singleton in-memory state. Adds a new test case asserting the path-traversal rejection. * fix(compose): use discovered compose filename instead of hardcoded docker-compose.yml composeArgs() hardcoded `-f docker-compose.yml` for every deploy. Sencho writes its canonical compose file as `compose.yaml`, so any stack created via the UI failed to deploy with `open ...docker-compose.yml: no such file or directory`. When no mesh override applies, drop the explicit `-f` so docker compose's built-in discovery resolves the actual filename. When an override exists, look up the real base filename via FileSystemService.getComposeFilename() and pass both files explicitly. Also hoist the MeshService import to module top now that the dependency is known to be acyclic, and revert the matching unit-test assertion. |
||
|
|
a25acbec7c |
feat(editor): opt-in diff preview before save (#855)
* feat(editor): add useComposeDiffPreviewEnabled hook * feat(editor): add ComposeDiffPreviewDialog component * fix(editor): replace HTML entity with Unicode arrow in ComposeDiffPreviewDialog * feat(editor): add diff preview toggle to Appearance settings Added a new 'Diff preview before save' toggle in the Display section of the Appearance settings panel. Users can now enable or disable the side-by-side diff view before compose and env file edits are saved to disk. * feat(editor): wire diff preview dialog into compose save flow * fix(editor): snapshot diff content at open time and fix event name - Fix useComposeDiffPreviewEnabled and useDeployFeedbackEnabled to use the canonical SENCHO_SETTINGS_CHANGED constant from @/lib/events instead of the hardcoded string literal (wrong value) - Snapshot language, original, modified, and fileName into diffPreview state at click time to prevent tab-switching from corrupting dialog content mid-review - Remove React.MouseEvent from diffPreview state; pass a no-op stub to deployStack in the confirm path (preventDefault/stopPropagation are no-ops on an already-settled event anyway) - Add diff-modal screenshot and document the feature in editor.mdx and settings.mdx * docs(editor): add settings-toggle screenshot for diff preview feature |
||
|
|
3e01daf76f |
feat(stack): per-stack activity timeline with actor attribution (#852)
* feat(stack): per-stack activity timeline with actor attribution Adds an Activity tab to the Stack Anatomy panel showing a timestamped event log for each stack: deploys, restarts, starts, stops, and image updates, attributed to the user who triggered them or 'system' for automated actions. Backend: - Extends notification_history with actor_username column (idempotent migration) and a partial composite index on (node_id, stack_name, timestamp DESC) for efficient per-stack lookups. - NotificationService.dispatchAlert() accepts an optional actor that is written to the new column. - Success-side dispatchAlert calls added after deploy, bulkContainerOp (start/stop/restart), and update handlers in routes/stacks.ts so user-initiated operations are recorded, not just failures. - New GET /api/stacks/:stackName/activity?limit&before endpoint with stack:read permission gate and cursor-based pagination. Frontend: - StackAnatomyPanel grows an Anatomy / Activity tab pair using the existing Tabs primitive. - StackActivityTimeline fetches the initial 50 events, paginates on demand, and prepends live events arriving over the existing WS notifications stream without duplicates. - NotificationPanel bell dropdown suppresses user-initiated success events (start/stop/restart/deploy/update triggered by a real user), keeping the tray focused on alerts and system events. * docs(stack): add stack activity timeline feature page and internal arch docs * fix(test): add actor_username to notification-routing history assertions dispatchAlert now passes actor_username to addNotificationHistory after the activity timeline PR added the column. Update the two exact-match assertions that were failing because the expected object shape was missing this field. |
||
|
|
b5d0e7e4db | chore: verify docs sync in new organization | ||
|
|
2c85bc7219 |
Merge branch 'main' of https://github.com/AnsoCode/Sencho
# Conflicts: # package.json |
||
|
|
3da0aa6036 |
chore: migrate repository URLs from AnsoCode/Sencho to studio-saelix/sencho
Updates all hardcoded GitHub repository references across 21 files: - package.json: repository URL, bugs URL, homepage, description, author - CONTRIBUTING.md: bug report template URL - SECURITY.md: advisory URL, cosign cert-identity regexp - .github/CODEOWNERS: @AnsoCode -> @studio-saelix/maintainers - .github/workflows/ci.yml: repositories scope (Sencho -> sencho), docs-sync git URL - .github/workflows/cla.yml: path-to-document URL - .github/workflows/docker-publish.yml: cosign verify comment - frontend/**/*.tsx: issues and changelog links (3 components) - frontend/public/.well-known/security.txt: Contact and Policy URLs - security/vex/sencho.openvex.json: @id field - docs/openapi.yaml: license URL - docs/docs.json: navbar and footer GitHub links (5 instances) - docs/security.mdx: advisory and SECURITY.md links - docs/reference/verifying-images.mdx: repo link + cosign regexp + legacy identity note - docs/reference/contact.mdx: issues, LICENSE, advisory, policy, CoC links - docs/reference/security-advisories.mdx: releases link - docs/operations/verifying-images.mdx: cosign regexps and VEX download URL (6 instances) - docs/operations/upgrade.mdx: releases links (2 instances) - backend/src/utils/version-check.ts: GitHub Releases API endpoint CHANGELOG.md intentionally excluded (release-please managed). Legacy cosign identity note added for pre-migration image verification. |
||
|
|
a2d06a2ef6 |
docs: refresh screenshots (#843)
Co-authored-by: AnsoCode <18150933+AnsoCode@users.noreply.github.com> |
||
|
|
f318ec5523 | docs: updates to the README and screenshots (#834) | ||
|
|
8d3fc1bc77 |
docs: refresh screenshots (#832)
Co-authored-by: AnsoCode <18150933+AnsoCode@users.noreply.github.com> |
||
|
|
5d376590f8 |
docs: refresh screenshots (#809)
Co-authored-by: AnsoCode <18150933+AnsoCode@users.noreply.github.com> |
||
|
|
3f4f7c6135 |
docs: refresh screenshots (#805)
Co-authored-by: AnsoCode <18150933+AnsoCode@users.noreply.github.com> |
||
|
|
fb88ea41ed |
docs: refresh screenshots (#800)
Co-authored-by: AnsoCode <18150933+AnsoCode@users.noreply.github.com> |
||
|
|
3668c71860 |
feat(security): add SBOM attestations, VEX document, and retire .trivyignore (#790)
* feat(security): add SBOM attestations, VEX document, and retire .trivyignore Add OpenVEX triage document (security/vex/sencho.openvex.json) for the 5 residual CVEs vendored inside docker/compose v5.1.2 that were carried over from the previous PR. All 5 are marked not_affected with justifications. Configure Trivy in both CI and release workflows to consume the VEX document via trivy.yaml so the same source of truth gates PR scans and release scans. Delete .trivyignore, which is fully superseded by the VEX file. Add two new release pipeline steps after image publication: - CycloneDX 1.6 SBOM via anchore/sbom-action (also installs syft) - SPDX 2.3 SBOM via syft directly (reuses OCI layer cache from prior step) Both are attached as cosign OCI referrer attestations (keyless, OIDC-signed) and uploaded as GitHub Release assets alongside the OpenVEX file. Bump docker-publish.yml permissions from contents:read to contents:write, required for softprops/action-gh-release to create Release assets. Add docs/operations/verifying-images.mdx with copy-paste verification commands for all supply-chain artifacts: signature, SLSA provenance, CycloneDX SBOM, SPDX SBOM, OpenVEX, and Rekor entry. Update docs.json navigation and expand the Supply chain security section in docs/security.mdx. Add a Verifying Release Artifacts section to SECURITY.md. * fix(vex): cover otel SDK CVE-2026-39883 in rebuilt Docker CLI binary The rebuilt Docker CLI v29.4.0 vendors otel/sdk v1.42.0, which still contains CVE-2026-39883 (BSD kenv PATH hijacking; fixed in v1.43.0). docker-compose v5.1.2 vendors otel/sdk v1.38.0 separately. The original VEX statement only covered the compose binary's location and version, so Trivy's scan of /usr/local/bin/docker was not suppressed. Add a second subcomponent entry for the CLI binary path with the correct vendored version. The not_affected justification (BSD-only code path; we ship linux/amd64 and linux/arm64 only) holds for both binaries. * fix(ci): use list form for vulnerability.vex in trivy.yaml Trivy's config schema requires vulnerability.vex to be a list (mapped to the multi-value --vex flag). The previous bare-string value was silently dropped, so the OpenVEX document was never loaded and HIGH findings already covered by VEX statements still failed the scan. * fix(ci): mirror VEX CVE in .trivyignore for local-image scan Trivy does not emit an OCI purl for locally-built images without a RepoDigests entry (aquasecurity/trivy#9399), so OpenVEX product matching against the CI build target sencho:pr-test resolves to no artifact and every statement is silently dropped. The VEX document remains the canonical triage record and is still attached as a cosign attestation on the published image; this file just mirrors the single CVE that surfaces on the local scan so CI does not block on a finding already triaged in VEX. Updates the Trivy step comment to document the relationship between the two files. |
||
|
|
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 |
||
|
|
03f91cd5bb |
feat(cloud-backup): mirror fleet snapshots to S3-compatible storage (#782)
* feat(cloud-backup): mirror fleet snapshots to S3-compatible storage
Add an Admiral-tier Cloud Backup feature that replicates every fleet
snapshot to off-site storage, with two provider modes that share the
same `@aws-sdk/client-s3` code path:
- Sencho Cloud Backup: zero-config, 500 MB allowance backed by
Cloudflare R2, provisioned via the sencho.io worker against the
user's Lemon Squeezy license.
- Custom S3 (BYOB): any S3-compatible bucket (AWS, MinIO, Backblaze
B2, Wasabi, R2 with own keys), with credentials encrypted via
`CryptoService` before storage.
API-triggered snapshots upload fire-and-forget so the UI returns
immediately; scheduled snapshots block on the upload so the task's
success/failure reflects cloud durability. Object keys include the
instance_id segment to prevent collisions when the same Admiral
license is activated on multiple Sencho instances.
* fix(cloud-backup): drop ES2022-only Error cause arg breaking ES2020 build
The backend tsconfig pins lib to ES2020. The two-argument
`Error(message, { cause })` form requires ES2022, so tsc rejected it
with TS2554. Revert to single-argument throw to match the
convention used elsewhere in the backend services.
|
||
|
|
801a098a5b |
feat(files): per-stack file explorer (#780)
* feat(files): backend foundation for stack file explorer
Install multer for multipart file upload handling. Add
isValidRelativeStackPath to validation.ts to guard client-supplied
relative paths against traversal, absolute paths, NUL bytes, backslash
injection, and double-slash segments. Add isBinaryBuffer to a new
binaryDetect.ts utility for heuristic text/binary detection via
NUL-byte fast exit and non-printable byte ratio sampling.
* fix(files): reject bare dot segments in isValidRelativeStackPath
* feat(files): add safe stack-scoped file I/O methods to FileSystemService
Adds FileEntry interface and seven new public methods to FileSystemService
for stack-scoped file operations: listStackDirectory, readStackFile,
streamStackFile, writeStackFile, deleteStackPath, mkdirStackPath, and
statStackEntry.
Each method routes through a private resolveSafeStackPath helper that
enforces two-phase path containment: a pre-realpath lexical check plus a
post-realpath symlink-escape check. ENOENT targets are handled by walking
up to the deepest existing ancestor, realpaths that ancestor, and
reattaching the remaining suffix.
Binary detection delegates to isBinaryBuffer; path safety delegates to
isPathWithinBase. Protected file names and the MIME map are module-level
constants to avoid repeated allocation.
* feat(files): frontend API wrappers and Monaco language helper
* fix(files): tighten stackFilesApi error handling and localOnly support
* fix(files): FileSystemService safety and correctness fixes
* feat(files): add file explorer API endpoints to stacks router
* feat(files): FileTree and FileTreeNode components
* fix(files): route security hardening and stream cleanup
* fix(files): FileTree accessibility, icon stroke, stale fetch guard
Add strokeWidth={1.5} to all Lucide icons in FileTreeNode to match the
design system. Add aria-expanded to directory rows for accessibility.
Guard handleDirClick .then() callbacks against stale stack name
references when the component re-renders with a new stack. Add
toast.info fallbacks when compose.yaml or .env is clicked without a
navigation callback registered.
* feat(files): FileViewer, FileUploadDropzone, NewFolderDialog, DeleteFileConfirm
* fix(files): resolve code quality findings in file explorer components
- Move editorOptions useMemo above conditional returns in FileViewer (Rules of Hooks fix)
- Fix blob download: append anchor to DOM before click, defer URL revoke 100ms
- Keep protected-file confirm input visible during NOT_EMPTY recursive retry in DeleteFileConfirm
- Remove non-functional cursor-pointer/onClick from Community upgrade pill in FileUploadDropzone
- Add success toast on folder creation in NewFolderDialog
- Switch all (e as Error).message casts to instanceof Error narrowing
* test(files): unit tests for binary detection, stack path safety, and file explorer routes
- binary-detection.test.ts: covers isBinaryBuffer edge cases (empty, NUL,
PNG header, threshold boundary, sampleBytes parameter)
- filesystem-stack-paths.test.ts: covers isValidRelativeStackPath (accepts/
rejects matrix) and FileSystemService stack methods against a real temp dir
(listStackDirectory sort and protection flags, readStackFile text/binary/
oversized paths, writeStackFile/Buffer, deleteStackPath, mkdirStackPath,
traversal guard); platform-specific empty-dir/NOT_EMPTY cases skip on Windows
- stack-files-routes.test.ts: route-level integration tests for all seven
file explorer endpoints; covers auth gating, Community-tier 403 gates,
input validation, 413 TOO_LARGE upload limit, and 204/200 happy paths
* feat(files): StackFileExplorer container with lazy tree, viewer, and action bar
* fix(files): add Download button to explorer toolbar, fix Community upgrade pill, reset state on stack change
* test(files): add missing test coverage for file explorer routes and service
* feat(files): add Files tab to EditorLayout with StackFileExplorer integration
* fix(files): add defensive activeTab guard to saveFile and discardChanges
* test(files): unit tests for FileTree expand/collapse and FileViewer render modes
Covers the three FileViewer content modes (text/Monaco, binary panel,
oversized panel) and the FileTree expand/collapse/cache cycle: first
expand fetches the subdirectory, second click collapses without a fetch,
third click re-expands from the in-memory cache without a second fetch.
* test(e2e): file explorer community and skipper+ flows
Covers the full file-explorer feature surface in two describe blocks:
Community (read-only): intercepts /api/license to simulate community
tier, confirms the upgrade pill is visible in the left pane, and
asserts that the Save button is absent after opening a text file.
Skipper+ (full CRUD): uploads a text file and confirms it appears in
the tree; edits config/app.conf and saves via Monaco; deletes an
uploaded file and asserts the tree entry is gone; issues a raw HTTP
request to the download endpoint and checks for status 200 and the
content-disposition: attachment header.
Also adds data-testid="file-action-delete" to the action bar Delete
button in StackFileExplorer for stable targeting, and exports
waitForStacksLoaded from e2e/helpers.ts to eliminate the three
identical local copies in stacks, deploy-log-panel, and stack-files
spec files.
* fix(e2e): improve test isolation and selector stability in stack-files spec
Move beforeEach seed to beforeAll/afterAll so fixtures are created once per
suite, not before every test. Extract shared seedSuite/teardownSuite helpers
to eliminate the duplicate beforeAll/afterAll blocks. Wrap teardown in
try/catch so failures log a warning rather than masking test results.
Replace waitForTimeout(500) with a deterministic expect on the file tree
sentinel. Add data-testid="anatomy-files-btn" and data-testid="delete-confirm-btn"
to replace the fragile button text/positional selectors. Assert Save button
starts disabled before editing.
* docs(files): add stack file explorer documentation
Add user-facing guide for the stack file explorer feature covering
tier access (Community read-only, Skipper+ read-write), viewing
limits, upload/download caps, protected file routing, and
troubleshooting. Update the editor page to reference the new guide
and register the page in the navigation.
* fix(docs): use canonical Skipper tier name in file explorer overview card
* fix(files): resolve lint errors blocking CI
Remove unnecessary backslash escape before double-quote in the
Content-Disposition regex (no-useless-escape). Replace five synchronous
setState resets at the top of the FileTree mount effect with a React key
prop on the FileTree element in StackFileExplorer so remounting resets
state automatically, eliminating the react-hooks/set-state-in-effect
violation.
* test(files): fix e2e seeding to work on community-tier CI
Replace the browser-side paid upload/mkdir API calls in seedTestStack with
direct Node fs writes. The upload and folder endpoints require Skipper+ so
they returned 403 on CI, which runs with no license set. Stack creation
via POST /api/stacks stays as an API call since it is community-allowed and
keeps the backend registry in sync.
Add a per-test tier check in the Skipper+ beforeEach that skips gracefully
when the instance is community, matching the pattern in auto-heal-policies.
|
||
|
|
dd9d33813b |
feat(deploy-logs): opt-in deploy progress modal with structured log rows (#779)
* feat(notifications): dispatch deploy_failure alert on stack action errors
* feat(terminal): add onReady and onMessage callback props
* feat(deploy-logs): add DeployLogContext with runWithLog API
* feat(deploy-logs): add DeployLogPanel bottom drawer with resize and minimize
* feat(deploy-logs): wire DeployLogContext to App and EditorLayout action runners
* test(deploy-logs): add E2E test for deploy log panel open, failure, and minimize
* docs(deploy-logs): add user-facing and internal architecture docs
* feat(deploy-logs): redesign as opt-in modal with structured log rows
Replace the full-width bottom drawer (DeployLogPanel) with a centered
modal that streams structured log output for deploy, stop, restart,
update, install, and Git apply operations. The modal is disabled by
default; users opt in from Settings -> Appearance.
Core changes:
- New DeployFeedbackContext with runWithLog() API: if opt-in is off,
silently bypasses the UI so all call sites degrade to the existing
toast behavior without code changes.
- composeLogParser.ts: pure parser that strips ANSI escapes and
classifies compose output into stage badges (PULL, BUILD, CREATE,
START, STOP, DOWN, WARN, ERR, LOG). 15 unit tests.
- StructuredLogRow.tsx: memoized row with timestamp, stage badge, and
message. Error rows get a rose left rail; warn rows get a tinted bg.
- DeployFeedbackModal: Dialog-based, max-w-640px/max-h-70vh, elapsed
timer, auto-close 4s on success (hover cancels), persistent on
failure. Raw xterm output collapsible in footer.
- DeployFeedbackPill: minimized state anchored top-right, survives
navigation, click restores modal.
- Wires App Store install (action: install), Git apply (action: deploy),
and Git pull (action: update) in addition to the existing EditorLayout
actions.
- Fixes Terminal.tsx WS URL in generic mode (was connecting to root path
not proxied by Vite; now uses /ws).
- Settings: adds "Show deploy progress modal" checkbox to Appearance.
- Docs: renames deploy-logs.mdx to deploy-progress.mdx; updates
internal architecture doc.
* fix(deploy-logs): connect Terminal in generic mode and move pill to bottom-center
Terminal was passed stackName which routes it to the stack logs WS
(container stdout). In that mode onReady is never called, so the
deployStarted gate never resolves and the compose command never runs.
Remove stackName so Terminal uses generic WS mode, which calls onReady
on open and streams compose output.
Also reposition the minimized pill from top-right to bottom-center
(fixed bottom-6 left-1/2 -translate-x-1/2) per UX feedback.
* docs(deploy-logs): update pill position to bottom center
* test(deploy-logs): rewrite E2E spec for deploy feedback modal
The old spec targeted the removed bottom-drawer DeployLogPanel and used
the wrong field name when calling POST /api/stacks (sent 'name' but the
endpoint reads 'stackName'), causing every test to fail with a 400 before
any UI assertions ran.
Fixes:
- POST /api/stacks body now uses 'stackName' matching the API contract
- All locators updated to target the new DeployFeedbackModal and
DeployFeedbackPill components (data-testid attributes added)
- Added enableDeployFeedback helper to opt-in via localStorage before
each test that expects the modal (feature is off by default)
- Added opt-in OFF test to confirm the modal is suppressed when disabled
- Minimize/expand test now asserts the pill appears and contains the
stack name before clicking to restore the modal
* test(deploy-logs): fix compose file write endpoint in E2E helper
createStackViaApi was calling PUT /api/stacks/:name/files/docker-compose.yml
which does not exist. The correct endpoint is PUT /api/stacks/:name with
{ content } in the body.
* test(deploy-logs): use addInitScript to persist opt-in across reloads
The opt-in flag was set via page.evaluate before setupDeployStack, which
calls page.reload() and loginAs (a second navigation). Although localStorage
should persist across same-origin reloads, the React tree was reading
'false' on remount in CI. Switching to addInitScript guarantees the
localStorage value is set before any page script on every navigation, so
useDeployFeedbackEnabled's useState initializer always sees the right
value when React mounts.
* test(deploy-logs): verify localStorage and re-dispatch event before deploy
Adds syncDeployFeedbackState() called right before each deploy click in
the ON tests. It both verifies localStorage is set (failing the test
loudly with a clear message if not) and re-dispatches the
SENCHO_SETTINGS_CHANGED event to defeat any stale React state after
navigation. If the modal still does not appear with the assertion green,
the issue is downstream of localStorage and we have a clear signal.
* test(deploy-logs): wait for React re-render after dispatching opt-in event
After syncDeployFeedbackState dispatches SENCHO_SETTINGS_CHANGED, React
schedules the state update but does not flush it synchronously. The
click that follows can fire against the stale closure where isEnabled is
still false, so runWithLog takes its early-return path and the modal
never opens. A 200ms wait is enough to let React commit the new state
before the next interaction.
* test(deploy-logs): wait for stack file fetch before clicking deploy
deployStack() in EditorLayout returns early at 'if (!selectedFile)'
without calling runWithLog. selectedFile is set inside loadFile() after
GET /api/stacks/:name resolves. The previous setup clicked the stack in
the sidebar and immediately asked the test to click Deploy, racing the
fetch. CI backend logs confirmed no deploy POST ever fired for the ON
tests, while the OFF test passed only because it asserts non-existence.
Now setup awaits both the stack click and the file response together,
then verifies the action bar's deploy button is visible before returning.
* test(deploy-logs): wait for network idle and capture browser logs
Adds a networkidle wait plus a 500ms settle after the stack click so
React commits selectedFile and any follow-up env/container/backup
fetches drain before the deploy click. Also mirrors browser console
errors and pageerrors into the Playwright output so the next failure
ships with the React stack trace instead of just a 'modal not visible'
message.
* test(deploy-logs): temporary debug logging in runWithLog
Adds a console.log at the entry of runWithLog so we can see in CI logs
whether it is being called and what isEnabled value the closure has.
Also widens the test's console capture to include these debug lines.
This is diagnostic only and will be removed once the root cause of the
modal-not-opening-in-CI failure is identified.
* test(deploy-logs): debug log at deployStack entry to trace click path
Adds console.log at the first line of deployStack handler so we can
confirm in CI whether the click is reaching it at all and what
selectedFile/isStackBusy resolve to. Combined with the existing
runWithLog debug logs, this isolates whether the modal failure is in
deployStack guarding out, runWithLog early-returning, or something
else entirely.
* test(deploy-logs): drop filter, log every browser console msg
The previous filter only emitted error/warning plus the deploy-feedback
substring. The deploy-feedback debug logs never appeared, so we don't
yet know whether the log itself is firing. Remove the filter so the
full console stream shows up in CI.
* test(deploy-logs): app-level console log to verify capture pipeline
If even an unconditional log at App component render time does not
appear in CI browser logs, then the console capture listener is broken
or the dispatched logs are being filtered upstream of Playwright. This
isolates whether the issue is in the production code or the test
harness.
* test(deploy-logs): use testid locator for stack action button
Replaces the regex-based getByRole locator (/Deploy|Start/i) with
getByTestId('stack-deploy-button'). The regex matched something other
than the actual deploy button: backend logs proved no deploy POST ever
fired, and instrumentation confirmed neither deployStack nor runWithLog
ran on click despite the test claiming success.
Adds data-testid='stack-deploy-button' to both the Restart and Start
button branches in EditorLayout's action bar so the same locator works
whether the stack is running or not.
Also drops the temporary debug console.log entries in deployStack,
runWithLog, and App, and restores the test's console listener filter
to only emit error and warning messages.
* test(deploy-logs): park cursor in corner so auto-close countdown fires
After clicking the deploy button, the cursor lands inside the centered
modal. The modal pauses its 4s auto-close countdown on hover, so the
HAPPY test was waiting for a close that never happened. page.mouse.move
to (0,0) parks the cursor outside the modal before the success banner
appears, letting the countdown complete.
* test(deploy-logs): drop redundant loginAs after page.reload
page.reload preserves auth cookies, so the page lands back on the
dashboard without needing a fresh login. The loginAs call after reload
was racing on isLoginPage(): a transient login-page state during page
load made loginAs commit to filling #username, then the dashboard
committed and #username never came back. Playwright's auto-wait then
hung the fill until the test's 120s timeout, which also dragged later
stacks.spec tests down with collateral timeouts.
waitForStacksLoaded is enough to confirm we're on the dashboard with
the sidebar populated before clicking the new stack.
* test(e2e): make loginAs race-safe when login page is a false positive
isLoginPage() reports the page as a login screen if the Login button
locator reports visible at the moment of the check. Under CI load (more
real container deploys from the deploy-log-panel suite), the auth
context can render the login form for one paint, then redirect to the
dashboard. The original code committed to filling #username and hung
until the test timeout when the field was no longer there.
Now the login branch waits up to 2s for #username to actually appear
before filling. If it never appears, we fall through to the dashboard
check instead of hanging.
|
||
|
|
6986b927e3 |
feat(stacks): per-service start/stop/restart lifecycle actions (#778)
* feat(stacks): add per-service start/stop/restart lifecycle routes
Adds POST /:stackName/services/:serviceName/{start,stop,restart} routes
that operate on containers belonging to a single Compose service, using
the same Engine API pattern as the existing stack-level lifecycle routes.
Includes isValidServiceName validator and audit-summary entries for the
three new paths.
* test(stacks): add per-service action route tests
* test(stacks): fix test quality issues in service action tests
* feat(stacks): add per-service lifecycle menu to container cards
* fix(stacks): handle paused container state in service action menu
* docs(stacks): add per-service lifecycle actions documentation
* docs(stacks): add validation screenshots for per-service lifecycle actions
|
||
|
|
abee078741 |
feat(scheduler): add auto_backup, auto_stop, auto_down, auto_start and delete_after_run one-shot mode (#777)
* feat(scheduler): add auto_backup, auto_stop, auto_down, auto_start actions and delete_after_run one-shot mode Extends the scheduler with four new stack-targeted actions: - auto_backup: backs up stack compose files and .env using the existing FileSystemService.backupStackFiles primitive - auto_stop: runs compose stop (containers preserved) - auto_down: runs compose down (containers removed) - auto_start: runs compose up -d via deployStack (universal start for both stopped and down stacks) Adds delete_after_run boolean column to scheduled_tasks. When enabled, the task self-deletes after its first successful execution; failures keep the task so the user can debug and retry. All four new actions gate at Admiral tier, consistent with restart/snapshot/prune. Migration is idempotent (maybeAddCol). * docs(scheduler): update scheduled-operations doc with new lifecycle actions and delete-after-run Adds the four new actions (Backup Stack Files, Stop Stack, Take Stack Down, Start Stack) to the action table. Documents the delete-after-run one-shot mode with its success-only deletion semantics. Adds the Stack Lifecycle Scheduling section explaining stop-vs-down semantics and the local-execution boundary. Adds three troubleshooting entries: auto-start on a missing compose folder, auto-backup single-slot overwrite by design, and one-shot task disappearing after successful run. Updates the timeline description from four to five lanes. Refreshes screenshots to show the new dialog layout with the Lifecycle lane visible. |