mirror of
https://github.com/Studio-Saelix/sencho.git
synced 2026-08-09 10:21:03 +00:00
0ba09ebdee30d50e19e5f6a6438866a55a2b7f2e
11 Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
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. |
||
|
|
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. |
||
|
|
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 |
||
|
|
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. |
||
|
|
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. |
||
|
|
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.
|
||
|
|
03a5826f7e |
fix(dashboard): debounce state-invalidate refetches (#1209)
* fix(dashboard): debounce state-invalidate refetches and drop redundant listener useDashboardData fired three immediate HTTP requests (/stats, /system/stats, /stacks/statuses) for every Docker container event. A burst restart of a 50-container stack produced ~150 instant requests against the local instance with no throttle. Add a 250 ms trailing-edge debounce so an event storm collapses into a single coalesced refresh, mirroring the precedent in useNextAutoUpdateRun. The cleanup function now also clears any pending debounce timer so a late event cannot fire after the dashboard unmounts. useConfigurationStatus subscribed to the same event but its data is built from settings and policy tables (agents, alert rules, auto-heal, scheduled tasks, scan policies, backup config), none of which change on container state. Drop the listener entirely; the 60 s poll catches rare settings edits with acceptable latency. * fix(dashboard): scope settings-event listener back into useConfigurationStatus Address two follow-up findings from independent review of the earlier commit on this branch. 1. Restore a filtered sencho:state-invalidate listener in useConfigurationStatus. The earlier commit dropped the listener wholesale to keep container-event bursts from refetching settings data, but that also silenced the only settings-affecting event in the current taxonomy: action='auto-update-settings-changed' (emitted from backend/src/routes/stacks.ts when a user toggles a stack's auto-update setting). With the listener gone, the Configuration Status row for Auto-update stacks could sit stale until the 60 s poll. The new listener mirrors the precedent in useNextAutoUpdateRun: filter on the single configuration-relevant action, trailing-edge debounce 250 ms. 2. Add an `active` flag to the useDashboardData state-invalidate effect. Cleanup already clears the pending debounce timer, but a refresh() already in flight could still call setters after unmount because the awaited Promise.all has no abort hook. The flag is checked both before the await and after, matching the cleanup shape used by useNextAutoUpdateRun. Tests cover both: the configuration listener now ignores scope='stack' and scope='image-updates' bursts and refetches once on a settings-changed burst. |
||
|
|
e183153a64 |
chore(dashboard): drop misleading backup.requiredTier from configuration payload (#1212)
Cloud Backup has a per-provider tier: Custom S3 is open to every tier (PR #1143) while Sencho Cloud Backup requires Admiral. A single backup.requiredTier='admiral' on the configuration response misrepresented that split, and no consumer ever read the field. Remove it from the response interface and the response builder; update the frontend mirror type accordingly. Annotate the backup block so the per-provider intent is clear at the call site. |
||
|
|
ca144f07d9 |
chore(dashboard): drop unused AgentStatus exports on both sides (#1222)
The Phase 5 dead-code sweep across the dashboard call graph found two identically shaped findings: the AgentStatus interface is declared and exported in both backend/src/routes/dashboard.ts and frontend/src/components/dashboard/useConfigurationStatus.ts, but no other file imports it. (The frontend StackAlertSheet component has a separate, differently shaped private AgentStatus that does not refer to either of these.) Drop the export keyword on both. The interfaces stay alive as file-internal types, the public surface shrinks by two names, and no behaviour changes. The broader payload-cleanup opportunities surfaced during the sweep (unused requiredTier fields on individual row objects, unused top-level tier and variant fields on ConfigurationStatus) are out of scope for this PR and have been filed as Linear roadmap items. |
||
|
|
66b84932e0 |
feat(notifications): move Notification Routing to Skipper tier (#1145)
* feat(notifications): move Notification Routing to Skipper tier Notification routing is automation (route alerts to channels by rules), not enterprise compliance. Aligning the gate with Skipper makes the tier boundary read consistently with the rest of the automation surface (webhooks, auto-update, auto-heal, scheduled tasks). Backend: requireAdmiral -> requirePaid on the five /api/notification-routes endpoints. Dashboard configuration-status now exposes the routing-rules row to any paid tier. Frontend: settings registry tier flipped to skipper; the Admiral wrapper around NotificationRoutingSection is removed (the inner CapabilityGate stays, preserving forward-compat with older remote nodes). Tests: added a tier-enforcement describe block covering Skipper (200) and Community (403 PAID_REQUIRED on all five endpoints). Docs: refreshed alerts-notifications, licensing, overview, dashboard, troubleshooting, and reference/settings; cleaned one fence-spec line per Directive 31. * fix(notifications): address audit findings on tier-move PR Docs: rewrite three lines that survived the initial sweep. The dashboard "you do not see a locked placeholder" clause and the settings.mdx "hidden on Community and Skipper" phrase were Directive 31 fence-spec. The alerts-notifications troubleshooting note still said "an Admiral routing rule" and contradicted the tier move. Tests: the Community-negative cases on POST/PUT/DELETE/POST :id/test could not distinguish requirePaid from a stray requireAdmiral, because Community fails on the tier check before variant is read. Adding Skipper-positive coverage per endpoint locks the gate identity in. Replace the leaky mockReturnValueOnce with a per-test mockReturnValue plus an afterEach restore so spies cannot bleed across tests. |
||
|
|
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 |