Commit Graph

10 Commits

Author SHA1 Message Date
Anso 0ba09ebdee feat: add ntfy notification channel (#1761)
* chore: bump brace-expansion and fast-uri via npm audit fix

Resolves GHSA-rgw5-rvv9-x895 (brace-expansion DoS via unbounded
intermediate arrays). Both transitive dev dependencies updated:
- brace-expansion 5.0.8 -> 5.0.9
- fast-uri 3.1.4 -> 3.1.5

* chore: also bump frontend deps via npm audit fix

Fixes brace-expansion and postcss in the frontend lockfile so
npm audit --audit-level=high passes on both packages.

* chore: bump ip-address transitive dep via npm audit fix

Resolves three new ip-address advisories (GHSA-mwp4-54f8-5fhr,
GHSA-4xrf-jv44-h6hh, GHSA-22jq-vg5j-6vgg) published between prior
push and CI run.

* feat: add ntfy notification channel

Add ntfy (https://ntfy.sh) as the fifth notification channel alongside
Discord, Slack, Webhook, and Apprise. ntfy speaks its native protocol:
plain-text POST body with Content-Type, Title, Priority, and Tags
headers. Priority maps info/warning/error to ntfy's default/high/urgent.

URL validation allows both HTTP and HTTPS (common for LAN self-hosting)
but rejects embedded credentials, consistent with Apprise. Token auth
via ntfy's documented ?auth= query parameter is supported.

* fix: correct ntfy channel test cases for Linux URL parsing and required type field

- notification-channels.test.ts: replace http:///topic host check with a
  cross-platform invalid-URL case (WHATWG parser treats triple-slash
  authority differently on Linux vs Windows)
- ConfigurationStatus.test.tsx: add ntfy agent slot to makePayload and
  inline agents fixtures (required by the expanded ConfigurationAgents
  type)

* fix: remove unused import and update 0/4 masthead assertions to 0/5

* ci: exclude NotificationService.ts from js/request-forgery CodeQL rule

Notification channel dispatch methods (Discord, Slack, Webhook, Apprise,
ntfy) all call fetch() with admin-configured URLs and notification bodies
that may embed stack or path data. This matches the trust model already
documented for registry-api.ts: single-tenant self-hosted, admin owns
the server, outbound posting is the intended behavior. The write path is
gated by requireAdmin or requirePermission(node:manage), and every
dispatch runs with a 10s AbortSignal.timeout.

* ci: also exclude NotificationService.ts from js/file-access-to-http

Notification messages may embed stack names, paths, or compose-derived
content. Same trust model as js/request-forgery: admin owns the server
and the configured endpoints, write path is gated.

* fix: correct ntfy channel tab copy and validation error message

The ntfy settings tab was reusing the generic webhook label, helper, and
placeholder (Webhook URL / JSON payloads / https://...). Give ntfy its own
copy: label names the server-and-topic URL, helper states plain-text delivery
and the mandatory topic path, placeholder matches the routing section.

Also fix the routing-rule validation toast: the guard correctly exempts ntfy
from the HTTPS check but the error message was not updated alongside it, so
ntfy URLs received a misleading HTTPS-required message.

* fix: strip trailing slash from ntfy topic URL before dispatch

A topic URL like https://ntfy.sh/mytopic/ validates fine (the check strips
the trailing slash internally) but was stored and dispatched with the slash
intact, causing the real ntfy server to 404. Normalize before fetch so the
request reaches the correct topic path.

Also add ntfy to the Channels card description in the settings registry.
2026-08-03 19:29:41 -04:00
Anso 83b3d932e5 feat: add Apprise as a fourth notification channel (#1644)
* feat: add Apprise as a fourth notification channel

Support keyed and stateless Apprise endpoints with secret-safe public DTOs, fail-closed malformed config, and mode-specific Settings UI. Docs and screenshots updated for four-channel Channels and routing.

* fix: harden Apprise secrets at rest and preserve-on-write saves

Encrypt Apprise endpoint and config with CryptoService so a downgrade cannot leak via SELECT *. Align channel and routing saves so blank destination fields omit config on same-mode URL edits, enforce keyed notify IDs, and keep secrets_redacted truthful.

* fix: harden Apprise route type changes and mixed-version config UI

Require a raw channel_url when switching notification-route types so ciphertext cannot strand under Discord/Slack/webhook. Default missing remote apprise status, replace Channels state on node switch, and exercise the production config-column migrator.

* fix: tolerate stub fleet configuration payloads without agents

Normalize remote Apprise agent status only when notifications.agents is present so successful Pilot/stub fetches stay online instead of throwing into the offline catch path.

* fix: correct TypeScript in configuration normalize tests

* fix: ignore stale Channels agent bodies after node switch

Compare the active node after response JSON parsing so a slow body
cannot overwrite the newly selected node's channel state.

* fix: isolate corrupt Apprise crypto and keep keyed Tags visible

Decrypt failures on one Apprise row no longer 500 agent/route lists or
suppress sibling channel dispatch. Treat public /notify/<redacted> as keyed
so Tags remain editable after reload.
2026-07-18 16:32:58 -04:00
Anso 381ed2a91f feat: add Admiral Hardened Build channel and business assurance surfaces (#1629)
* feat: add Admiral Hardened Build channel and business assurance surfaces

Introduce Studio Saelix entitlement-backed Hardened Build switching, a
single-flight image operation coordinator, Recovery Vault naming, Admiral
Account settings, and typed Fleet update failures while preserving Community
custom-repo and targetless pull-current updates.

* fix: harden image-op paths and clear CI CodeQL/pilot flake

Validate operation IDs before filesystem use, use hostname checks in Fleet
fetch mocks, sanitize registry probe logs, and swallow expected TCP teardown
errors in the pilot reverse-route post-handshake test.

* fix: sanitize image-op docker config write and probe logs

Allowlist-copy registry host keys and base64 auth before writing the
temp DOCKER_CONFIG, and log registry probe failures with a fixed message
so CodeQL no longer flags network-to-file and log-injection mediums.

* fix: address Admiral Hardened Build audit blockers

Expose imageChannel so hardened Fleet peers still POST for typed rejection, claim community updates before 202, terminalize helper failures, gate Hardened on paid, and align support/docs/e2e wording.

* fix: terminalize image ops on helper survival and aborted claims

* fix: prevent recreating persist from overwriting helper-exit failure

* test: assert helper-exit failure lands before recreating persist

* fix: keep current pointer when acknowledging a stale image operation
2026-07-14 10:47:54 -04:00
Anso bbcc4b59e4 fix: UI polish - tooltips, masthead, tabs, settings, and layout consistency (#1563)
* fix: increase masthead rail shimmer visibility

Bumped shimmer highlight opacity from 25% to 50% and reduced
animation duration from 11s to 5s, matching the login page
AuthCanvas shimmer precedent (via-white/60 at 4.5s).

* fix: reduce masthead rail opacity to match login page pattern

Changed all MastheadRail background classes from 100% to 70% opacity
(bg-brand → bg-brand/70, etc.) so the shimmer highlight reads as a surface
reflection rather than a detached floating bar. Matches the AuthCanvas
login card precedent.

* fix: improve masthead rail glow visibility

Replaced the invisible inset box-shadow glow (10px blur inside a 3px
element) with a white overlay that pulses opacity 0.15-0.55 over 4s.
Sped up glow animation from 5.5s to 4s for a more noticeable breath.

* fix: replace cursor-follow tooltip with standard Radix tooltip in sidebar rows

Replaced the Cursor/CursorFollow animate-ui primitives in StackRow
with the standard Radix Tooltip/TooltipTrigger/TooltipContent already
used throughout the app. The custom cursor dot that followed the mouse
is gone; tooltips now appear as static popovers on hover/touch.

* fix: wrap tooltip icon triggers in span for Radix compatibility

TooltipTrigger asChild requires a native element to forward props.
Lucide icon components did not reliably receive data-state and event
handlers, so tooltips on the check-failed and git-pending indicators
did not open. Wrapped each icon in a span.

* fix: rename Notification Routing to Routing in settings sidebar

* fix: replace Timeline/All tasks buttons with SegmentedControl

The two loose Button elements had indistinguishable selected/unselected
states (secondary vs ghost). Replaced with the SegmentedControl component
already used elsewhere on the same page, which has a clear active
highlight indicator.

* fix: standardize Blueprint empty state headings to font-heading

Replaced manual font-serif [font-style:var(--heading-style)] with the
font-heading class on the main heading and three step titles so the
Deployments empty state follows the same heading convention as the
rest of the app and respects the Calm/Signature theme toggle.

* fix: remove custom tooltip styling, use standard TooltipContent defaults

The RowTooltip wrapped its label in a font-mono text-stat-value span
instead of using the TooltipContent base styling (text-xs
text-popover-foreground) like every other tooltip in the app.

* Revert "fix: remove custom tooltip styling, use standard TooltipContent defaults"

This reverts commit 209fbd2742.

* fix: standardize all tooltips to match sidebar RowTooltip design

Wrapped every TooltipContent child in a font-mono text-xs
tabular-nums text-stat-value span so all tooltips across the app
share a consistent look. Previously some used plain text, others
used <p> elements with different fonts and sizes.

* Revert "fix: standardize all tooltips to match sidebar RowTooltip design"

This reverts commit 9e1d97d39e.

* fix: bake standard tooltip styling into TooltipContent component

Added font-mono tabular-nums text-stat-value directly to the
TooltipContent base styles so every tooltip in the app inherits
the same look without per-instance wrappers. Removed the now-
redundant span from StackRow's RowTooltip.

* fix: replace custom tooltip with shadcn/ui Radix tooltip

Replaced the glass/blur-styled tooltip with the standard shadcn/ui
pattern: bg-foreground/text-background, arrow pointer, proper
slide/fade animations, and w-fit max-w-xs sizing. This gives all
tooltips a consistent dark-solid look across the entire app.

* fix: apply glass/blur design to shadcn tooltip component

Replaced the solid bg-foreground/text-background style with the app's
glass design: bg-popover, border-glass-border, backdrop-blur, and
text-popover-foreground. Kept the shadcn structure (Portal, Arrow,
slide/fade animations).

* fix: replace native title tooltips with Radix tooltips on Fleet toolbar

Converted Refresh, Export Dossier, Search, Sort direction, Node Update,
and Manage Nodes buttons from native HTML title attributes to the shared
Radix Tooltip component so they match the app-wide glass tooltip design.

* fix: replace native title tooltip on Fleet Snapshots upload button

* fix: replace native title tooltips with Radix tooltips on Resources page

Converted all actionable button tooltips (Open stack, Inspect image,
Scan, Browse volume, and delete buttons) plus the SenchoBadge
informational tooltip. Wrapped disabled protected-resource delete
buttons in span triggers so tooltips fire on disabled elements.

* fix: replace native title tooltips with Radix tooltips on Security page

Converted scanner info icon, exploitability n/a badge, and scan
image button from native HTML title attributes to the shared Radix
Tooltip component.

* fix: replace native title tooltips with Radix tooltips on Schedules and Settings pages

Converted Run now, Execution history, Edit, Delete buttons on Schedules;
Download/Delete on Cloud Backup; Send test/Edit/Delete on Notification
Routing; Reset 2FA on Users; and Edit/Remove suppression on
Suppressions panel. Skipped SettingsSection/Modal title props which
are React component props, not native HTML attributes.

* feat: add copy button to execution history details column

Replaced the plain truncated Details cell with a flex layout containing
a copy button. Users can now copy the full error/output text instead
of relying on the native title tooltip to read long messages.

* fix: change tab highlight from neutral to brand color

Replaced bg-glass-highlight with bg-brand/20 on all TabsHighlight
instances and updated TabsTrigger active state to text-brand,
matching the SegmentedControl selected style. Works on both
dark and light themes.

* fix: restructure Resources page tabs to match Fleet/Security pattern

Moved tab band out of the outer card into a standalone full-width
element. Replaced custom FilterToggle with Fleet-style Button
toggles. Added search inputs per tab. Wrapped each resource table
in a card container matching the Security Images table design.

* fix: match Resources tab font size to Fleet and Security

Removed explicit text-xs from Resources TabsTrigger elements so they
use the default text-sm, matching Fleet and Security tab typography.

* fix: restore missing closing bracket on TabsTrigger tag

* fix: add aria-labels to schedule action buttons, update test selectors

Replaced native title attributes with aria-labels on Run now,
Execution history, Edit, and Delete buttons so the Radix tooltip
conversion does not break test selectors that relied on title.
Updated tests from findByTitle to findByRole.

* fix: add fingerprint-based dismiss to post-deploy scan banner

Created useScanBannerDismiss hook following the usePreflightDismiss
pattern. The banner now persists dismissal in localStorage keyed to a
fingerprint of scan status + attemptedAt. Dismissal survives page
reloads for the same scan outcome, and the banner automatically
reappears when a new scan runs or the status changes.

* fix: update Configuration Status card labels and add new rows

Notifications section: renamed Notification agents to Channels,
Notification routing to Routing, added Mute Rules row linking to
notification-suppression settings.

Security section: added Trivy installed Yes/No row, renamed
Vulnerability scanning to Scan policies.

Backend: added trivyInstalled and suppressionRules fields to
the /dashboard/configuration payload.

* fix: style Trivy row as badge and link to Security scanner setup

* fix: remove Mute button from stack anatomy panel header

* Revert "fix: remove Mute button from stack anatomy panel header"

This reverts commit 16843a81b5.

* fix: remove Mute button from stack anatomy panel header

* fix: remove unused stackMuteActions imports and props from StackAnatomyPanel

The removal of the ActivityMuteKebab rendering left orphaned imports
and props that cause TS6133 errors under strict mode. Clean up the
imports, prop type, destructuring, and caller prop passes.

* fix: refine anatomy tab sizing and add pulsing scroll chevron

Changed anatomy tabs from 12px fixed height to 11px with py-1
padding, matching the Fleet tab approach. Bumped Files/Edit buttons
from 10px to xs for better visibility. Added subtle animate-pulse
to the right scroll chevron to hint at overflow tabs, gated behind
the Reduced effects appearance setting.

* fix: link reclaim hero number to heading style

* fix: replace native title tooltips with Radix tooltips on stack detail page

Converted Expand/Collapse logs, Stats unavailable, Copy service URL,
Dismiss findings, Likely secret badge, Move up/down, and Remove
buttons from native HTML title attributes to the shared Radix
glass tooltip component.

* fix: replace native title tooltip on image source link button

* fix: add tooltips to container logs and bash session buttons

* fix: add tooltip to download logs button

* fix: add tooltip to copy digest button

* fix: replace cursor-follow tooltip with Radix tooltip on severity badge

* fix: add tooltips to density toggle buttons

* feat: add expand button to multi-container health panel

Added an expand/collapse toggle next to the density controls that
expands the containers panel to fill the column and hides the logs
section. The expand and logs-expand are mutually exclusive. Only
appears for stacks with more than one container.

* fix: reduce Structured/Raw terminal toggle font to 10px

* fix: set compact density as default for multi-container stacks

* fix: change Feedback to Open New Issue with GitHub issues link

* fix: replace ExternalLink with GitHub icon for Open New Issue menu item

* fix: restore ExternalLink import needed by other menu items

* fix: default host threshold alerts to off for new installations

* fix: resolve CI typecheck and test failures

Broaden MenuRowProps.icon type to accept custom SVG components alongside
lucide icons, fixing TS2741 on GitHubIcon import.

Update host_alerts_enabled seed test assertion to match the new opt-in
default of off (0).

* fix: update ContainersHealth tests for compact default density

Adjust three tests to reflect the new default of compact mode:
- Rename 'detailed mode is the default' to 'compact mode is the default'
- Update sparkline visibility checks to switch to detailed first
- Rename remount test and verify reset to compact (new default)
2026-07-06 04:25:01 -04:00
Anso 04e69021e0 feat: make all security features available on every tier (#1502)
Scan policies, deploy enforcement, the suppression-aware deploy-block
toggle, SARIF export, and OpenVEX export now work on Community, matching
the rest of the vulnerability-scanning surface that was already free.

Backend: drop the tier gate from the seven security routes and from the
dashboard configuration-status scan-policies row, so the Dashboard and
Fleet config cards stop hiding the Vulnerability scanning row. Reading
policies stays auth-only; mutations and exports stay admin-only.

Frontend: always show the Policies tab and panel, the SARIF and VEX
export actions, and the honor-suppressions toggle for admins.

Docs: move scan policies, SARIF, and OpenVEX to every tier across the
feature and API-reference pages; clarify that Fleet Sync's cross-node
replication remains the paid part.
2026-06-28 08:14:21 -04:00
Anso e9c262ae6a feat: split Host Alerts into Host Alerts, Container Alerts, and Stacks guardrails (#1461)
* feat: split Host Alerts into Host Alerts, Container Alerts, and Stacks guardrails

Move global_crash from Host Alerts to new Monitoring > Container Alerts section.
Move health gate and env deploy guardrails from Host Alerts to
Infrastructure > Stacks > Deploy Guardrails subsection.

Host Alerts now contains only host threshold settings (CPU, RAM, disk,
alert suppression, and the master host_alerts_enabled toggle).
Stacks gains a Deploy Guardrails subsection (node-scoped, admin-gated)
alongside the existing Workflow controls (browser-local).

Dashboard Crash detection row now routes to Container Alerts.

* docs: update crash detection toggle description to match new Container Alerts section
2026-06-25 21:04:55 -04:00
Anso b7dd9dc1b0 feat: add ON/OFF toggle for host threshold alerts (#1456)
* feat: add ON/OFF toggle for host threshold alerts

Add host_alerts_enabled setting (default ON) as a master switch for CPU,
RAM, and disk host threshold evaluation. When OFF, the four threshold
controls in Settings > Host Alerts are disabled and MonitorService skips
the systeminformation calls and alert dispatch entirely, while clearing
stale suppression state so re-enabling starts fresh.

The dashboard Configuration Status card shows "Off" when host threshold
alerts are disabled. Crash capture, health gate, deploy guardrails,
stack alert rules, and the Docker janitor are all unaffected.

* fix: exit NumberChip edit mode when externally disabled

When the host threshold alerts master toggle is turned OFF while a
NumberChip is in edit mode, force-exit edit mode so the chip renders
the greyed-out button state consistently with the other chips.
2026-06-25 16:05:16 -04:00
Anso 865d792874 feat(pricing): collapse to two tiers (#1309)
* feat(pricing): collapse to two tiers (Community + Admiral)

Collapse Sencho's pricing from three tiers (Community / Skipper / Admiral)
to two: a generous free Community tier and a single paid Admiral tier. The
Skipper tier is removed.

Now free in Community: auto-heal, auto-update, scheduled operations,
webhooks, notification routing, Fleet Actions and bulk operations, SSO
preset providers (Google / GitHub / Okta), unlimited users with admin and
viewer roles, and deploy safety (atomic deploys, auto-rollback, and
one-click rollback).

Admiral (paid) is focused on running and governing a fleet: blueprints,
Fleet Secrets, deploy enforcement, vulnerability report export, audit log,
host console, private registries, mesh networking, node cordon, managed
cloud backup, LDAP / Active Directory SSO, and the advanced RBAC roles
(deployer, node-admin, auditor) with per-resource scoped assignments.

Internally the license variant distinction is removed so tier is binary
(community / paid). License validation still verifies the Lemon Squeezy
store and product before granting paid status.

Docs and the contributor guide are updated to the two-tier model.

* docs(pricing): correct licensing page to two-tier pricing and tidy stale tier wording

The licensing docs page kept the old Admiral pricing plus a Founder
Lifetime column and an Enterprise paragraph after the two-tier collapse.
Update it to $12/month or $99/year, drop the lifetime and Enterprise
content, and link to the pricing page for current pricing.

Also fix stale "Skipper" wording in CLA.md, SUPPORT.md, one test title,
and three test comments. Historical CHANGELOG entries and the
retired-Skipper license-guard test are intentionally left as-is.

* docs: align licensing and SSO pages with the two-tier model

Correct the SSO overview so the Google, GitHub, and Okta presets read as
available on every tier, matching the provider table; only LDAP and Active
Directory require Sencho Admiral. Remove the lifetime-plan references from the
licensing, settings, and troubleshooting pages so they reflect subscription-only
Admiral pricing.

* fix(rbac): omit scoped permissions from /me on the Community tier

Scoped role assignments only take effect on the paid tier, but GET /api/permissions/me returned them unconditionally, so a downgraded instance with leftover assignments rendered per-resource affordances the API then rejected with 403. The endpoint now mirrors the permission middleware and includes scoped permissions only on the paid tier. Adds a regression test covering the downgrade case.

* docs: use custom-pricing wording on the contact page

The two-tier model has no Enterprise tier; reword the contact page's enterprise pricing/deals to custom pricing/deals so it does not imply a tier that no longer exists.
2026-06-04 17:45:53 -04:00
Anso adcd04b01a refactor(auto-update): retire per-stack gate, drive auto-update from schedules only (#1233)
* refactor(auto-update): retire per-stack gate, drive auto-update from schedules only

The per-stack Auto-update toggle in the stack sidebar context menu wrote a
gate row to `stack_auto_update_settings`, but actual updates only ran when a
`scheduled_tasks` row with `action='update'` fired. On a fresh install the
toggle was inert: detection ran every 6h, nothing was applied.

The same context menu already exposes `Schedule task`, which opens
ScheduledOperationsView pre-filled for the stack where the user can pick
`Auto-update Stack` and any cron. Keeping the toggle alongside that flow
duplicated the same action and turned the gate table into a parallel store
of "is a covering schedule active" derivable from `scheduled_tasks` itself.

Drop the gate model entirely:
- Backend: remove the `stack_auto_update_settings` table and its four
  accessors, the three routes under /api/stacks/*/auto-update, the per-stack
  skip in /api/auto-update/execute and SchedulerService.executeUpdate's
  fleet branch, and the clearStackAutoUpdateSetting call on stack delete.
  Dashboard `autoUpdate` count derives from scheduled_tasks (action='update'
  rows pinned to the node, total/enabled split).
- Frontend: drop the Auto-update entry from the sidebar context menu and its
  optimistic toggle plumbing. Drop autoUpdateSettings state, the
  /stacks/auto-update-settings fetch, and the auto-update-settings-changed
  WebSocket branch. Slim useSidebarActivitySummary (just nextRunAt; no
  enabled/total counts). AutoUpdateReadinessView's per-card autoUpdateEnabled
  now means "a covering enabled action='update' schedule exists" (per-stack
  row or fleet row on this node, earliest next_run_at wins, per-stack row
  wins on ties), with the gate-fetch removed.
- New: scheduledTasksRouter broadcasts scope: 'scheduled-tasks' on POST,
  PUT, PATCH /toggle, and DELETE so useConfigurationStatus and
  useNextAutoUpdateRun refetch under the 250ms debounce instead of waiting
  for the 60s poll. The broadcast is wrapped so a broken subscriber socket
  cannot turn a successful mutation into a 500.
- Docs: rewrite the "Per-stack control" section of auto-update-policies.mdx
  to describe the schedule-based model; update the matching troubleshooting
  entry. The misleading fleet-update help text in ScheduledOperationsView
  is corrected to reflect that every stack on the node is covered.

Tier parity: the surviving auto-update path (Schedule task -> Auto-update
Stack / All Stacks) is gated `requirePaid + requireAdmin` backend and
`isPaid + isAdmin` frontend, matching the gate the deleted routes carried.
The pre-commit grep returns no tier-related diff outside this PR's scope.

No data migration is provided: greenfield rules apply, and the leftover
table on already-shipped instances is harmless because no code reads or
writes it after this PR.

* docs: sweep remaining references to the per-stack auto-update toggle

The previous commit retired the per-stack Auto-update gate in favor of
configuring auto-update purely through scheduled tasks. This commit
removes the now-stale mentions of that toggle across the operator docs:

- docs/features/sidebar.mdx: drop the Auto-update entry from the Inspect
  group description, the matching screenshot alt-text, and the Skipper
  Note that listed it. Schedule task now carries the cross-link to
  Auto-Update Policies.
- docs/features/stack-management.mdx: drop the Auto-update list item;
  refresh the Schedule task entry to mention the Auto-update Stack action.
- docs/features/dashboard.mdx: rename the Configuration Status row from
  "Auto-update stacks" to "Auto-update schedules" with the new value
  shape, and rewrite the troubleshooting accordion to describe the
  scheduled-tasks invalidation path.
- docs/features/scheduled-operations.mdx: rewrite the Auto-update All
  Stacks row and helper text to reflect that every stack on the node is
  covered (no per-stack opt-out from this surface anymore).
- docs/features/multi-node.mdx: rewrite the Updates column definition to
  derive the Auto/Off flag from enabled Auto-update Stack / Auto-update
  All Stacks schedules instead of the removed per-stack policy.

The auto-update-policies.mdx rewrite in the previous commit already
covered the main reference page. The sidebar-context-menu.png screenshot
will be refreshed on release once the new menu is live in production;
the alt text is updated in this commit so it accurately describes the
shipping state.

No website edits needed: the Auto-Update Policies feature card description
("Schedule automatic image pulls and redeployments per stack on your own
cadence") and the feature matrix labels ("Auto-update stack schedule",
"Auto-update all stacks schedule") remain accurate under the new model.

* fix(stacks): drop orphaned requireAdmin import after auto-update route removal

CI's backend lint step flagged this PR's earlier deletion of the three
/api/stacks/*/auto-update routes: those handlers were the only callers of
`requireAdmin` inside routes/stacks.ts, leaving the named import on line 15
unreferenced. `requirePaid` and `effectiveTier` from the same line are still
in use elsewhere in the file and stay.

tsc --noEmit does not flag unused named imports; ESLint's no-unused-vars
does. Local backend lint reproduces and now reports 0 errors against the
existing 334-warning baseline.
2026-05-26 11:08:33 -04:00
Anso 05c3975d6d test(dashboard): cover dashboard routes, ConfigurationStatus tier parity, and useMeshDataPlane (#1221)
* test(dashboard): cover dashboard routes, ConfigurationStatus tier parity, and useMeshDataPlane

The dashboard router had no dedicated Vitest coverage; tier parity in the
ConfigurationStatus component was only proved by manual inspection; and the
Admiral short-circuit in useMeshDataPlane had no automated regression net.

Add three spec files:

- backend/src/__tests__/dashboard-routes.test.ts: 11 cases against the live
  Express app. Both routes reject unauthenticated requests; the
  configuration response matches its documented shape; the tier x variant
  `locked` matrix is asserted end-to-end for Community, Skipper, and
  Admiral via LicenseService spies; a seeded Discord agent URL is shown
  never to appear in the serialized response; /stack-restarts clamps days
  values of 0, 999, and NaN without bailing.

- frontend/src/components/dashboard/__tests__/ConfigurationStatus.test.tsx:
  five render cases prove the parity contract. Community hides the entire
  Automation section plus the four gated rows (Notification routing,
  Webhooks, Scheduled tasks, Vulnerability scanning); Skipper shows
  everything except Scheduled tasks (Admiral-only); Admiral shows every
  gated row plus the SSO provider name mapping (oidc_google -> "Google").
  Skeleton and load-error paths are also covered.

- frontend/src/components/dashboard/__tests__/useMeshDataPlane.test.tsx:
  four hook cases prove the Admiral short-circuit. Non-Admiral sessions
  never fire /mesh/status; Admiral sessions fetch once and populate the
  localDataPlane payload; a 403 response leaves status null without
  raising; a response that omits localDataPlane also leaves status null.

Backend route suite + dashboard-only frontend suite green in isolation.
The full backend suite shows one pre-existing Windows-only EBUSY flake in
filesystem-backup.test.ts (SQLite file lock on unlink) that reproduces on
the unmodified branch tip and is unrelated to these changes.

* test(dashboard): drop backup.requiredTier from ConfigurationStatus fixture

The fixture's `backup.requiredTier: 'admiral'` field was authored to match
the type on this branch's original base. Main has since removed that field
from the ConfigurationStatus payload, so the fixture now over-specifies a
property the type forbids and fails tsc.

Drop the field to realign with the current type.
2026-05-25 12:14:33 -04:00