mirror of
https://github.com/Studio-Saelix/sencho.git
synced 2026-08-11 11:16:55 +00:00
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.
This commit is contained in:
@@ -165,8 +165,8 @@ export function ConfigurationStatus({ onOpenSection }: ConfigurationStatusProps
|
||||
onClick={open('system')}
|
||||
/>
|
||||
<Row
|
||||
label="Auto-update stacks"
|
||||
value={automation.autoUpdate.total === 0 ? 'None' : `${automation.autoUpdate.enabled} / ${automation.autoUpdate.total}`}
|
||||
label="Auto-update schedules"
|
||||
value={automation.autoUpdate.total === 0 ? 'None' : `${automation.autoUpdate.enabled} / ${automation.autoUpdate.total} active`}
|
||||
onClick={open('system')}
|
||||
/>
|
||||
{!automation.webhooks.locked && (
|
||||
|
||||
@@ -79,7 +79,7 @@ describe('ConfigurationStatus tier parity', () => {
|
||||
expect(screen.queryByText('Notification routing')).toBeNull();
|
||||
expect(screen.queryByText('Automation')).toBeNull();
|
||||
expect(screen.queryByText('Auto-heal policies')).toBeNull();
|
||||
expect(screen.queryByText('Auto-update stacks')).toBeNull();
|
||||
expect(screen.queryByText('Auto-update schedules')).toBeNull();
|
||||
expect(screen.queryByText('Webhooks')).toBeNull();
|
||||
expect(screen.queryByText('Scheduled tasks')).toBeNull();
|
||||
expect(screen.queryByText('Vulnerability scanning')).toBeNull();
|
||||
@@ -121,7 +121,7 @@ describe('ConfigurationStatus tier parity', () => {
|
||||
|
||||
expect(screen.getByText('Automation')).toBeDefined();
|
||||
expect(screen.getByText('Auto-heal policies')).toBeDefined();
|
||||
expect(screen.getByText('Auto-update stacks')).toBeDefined();
|
||||
expect(screen.getByText('Auto-update schedules')).toBeDefined();
|
||||
expect(screen.getByText('Webhooks')).toBeDefined();
|
||||
expect(screen.getByText('Notification routing')).toBeDefined();
|
||||
expect(screen.getByText('Vulnerability scanning')).toBeDefined();
|
||||
|
||||
@@ -83,17 +83,17 @@ describe('useConfigurationStatus state-invalidate handling', () => {
|
||||
expect(apiFetchMock.mock.calls.length).toBe(baseline);
|
||||
});
|
||||
|
||||
it('refetches once on a settings-affecting auto-update-settings-changed event', async () => {
|
||||
it('refetches once on a scheduled-tasks invalidation', async () => {
|
||||
renderHook(() => useConfigurationStatus());
|
||||
await act(async () => { await Promise.resolve(); await Promise.resolve(); });
|
||||
const baseline = apiFetchMock.mock.calls.length;
|
||||
|
||||
act(() => {
|
||||
// Burst three settings-change events; the debounce should collapse
|
||||
// them into a single refetch.
|
||||
fireInvalidate({ action: 'auto-update-settings-changed' });
|
||||
fireInvalidate({ action: 'auto-update-settings-changed' });
|
||||
fireInvalidate({ action: 'auto-update-settings-changed' });
|
||||
// Burst three scheduled-tasks-change events; the debounce should
|
||||
// collapse them into a single refetch.
|
||||
fireInvalidate({ scope: 'scheduled-tasks', action: 'created' });
|
||||
fireInvalidate({ scope: 'scheduled-tasks', action: 'toggled' });
|
||||
fireInvalidate({ scope: 'scheduled-tasks', action: 'deleted' });
|
||||
});
|
||||
// Before debounce window elapses, no new fetch.
|
||||
expect(apiFetchMock.mock.calls.length).toBe(baseline);
|
||||
|
||||
@@ -80,19 +80,15 @@ export function useConfigurationStatus() {
|
||||
return visibilityInterval(guard, 60_000);
|
||||
}, [nodeId, fetchStatus]);
|
||||
|
||||
// Filter `sencho:state-invalidate` so only settings-affecting events
|
||||
// refetch the configuration; the high-frequency `scope: 'stack'` and
|
||||
// `scope: 'image-updates'` container/image bursts are ignored. Today the
|
||||
// only such settings event is `auto-update-settings-changed` (emitted by
|
||||
// the stack auto-update toggle). The filter is debounced and the
|
||||
// configuration response includes the toggled state, so a user editing
|
||||
// the setting sees the row update under a second instead of waiting up
|
||||
// to a minute.
|
||||
// Refetch the configuration card when a scheduled-tasks mutation fires
|
||||
// an invalidate. High-frequency `scope: 'stack'` and `scope: 'image-updates'`
|
||||
// events are ignored; they don't change any tile on this card. Debounced
|
||||
// so a burst of edits coalesces into a single fetch.
|
||||
useEffect(() => {
|
||||
let invalidateTimer: ReturnType<typeof setTimeout> | null = null;
|
||||
const onInvalidate = (e: Event) => {
|
||||
const detail = (e as CustomEvent<{ action?: string; scope?: string }>).detail;
|
||||
if (detail?.action !== 'auto-update-settings-changed') return;
|
||||
const detail = (e as CustomEvent<{ scope?: string }>).detail;
|
||||
if (detail?.scope !== 'scheduled-tasks') return;
|
||||
if (invalidateTimer) clearTimeout(invalidateTimer);
|
||||
invalidateTimer = setTimeout(() => {
|
||||
invalidateTimer = null;
|
||||
|
||||
Reference in New Issue
Block a user