mirror of
https://github.com/Studio-Saelix/sencho.git
synced 2026-08-21 15:46:43 +00:00
feat: make image-update check cadence configurable and visible (#1377)
* feat: make image-update check cadence configurable and visible The background image-update scanner polled registries on a hardcoded 6-hour interval, with no way to see when it last ran or when the next run was due. Operators testing updates read this as auto-update being unreliable: a manual update checks the registry immediately and applies, so the slow background scan rarely raised the "update available" notification before the stack was already current. Backend: - ImageUpdateService reads image_update_check_interval_minutes (15-1440, default 120) and drives a single generation-guarded self-rescheduling timer with 10% per-run jitter so fleet nodes do not poll in lockstep. restartPolling() applies a new interval live, with no restart, and cannot leave a duplicate timer when a save lands mid-scan. - GET /api/image-updates/status now returns checking, intervalMinutes, lastCheckedAt, nextCheckAt, and the manual-cooldown fields. New admin-only PUT /api/image-updates/interval persists the setting and reschedules. Frontend: - New Settings > Automation > Image update checks section to choose the interval (read-only for non-admins; admin enforced on the backend). - The Auto-Update readiness view shows last-checked, next-check, and a ticking manual-recheck cooldown, and the copy distinguishes registry detection from scheduled auto-update execution. Adds backend unit and route tests and frontend component tests, and updates the auto-update documentation. * fix: drop stale image-update status response in the readiness strip loadCadence() ran on mount and again after a Recheck with no request token, so a slow initial /image-updates/status response could resolve after the recheck-triggered one and overwrite the fresh cooldown with stale data, or set state after the view unmounted. Guard setCadence with a monotonic token mirroring loadReadiness, and bump it on unmount. Adds a regression test for the out-of-order resolution.
This commit is contained in:
@@ -1,4 +1,5 @@
|
||||
import { Router, type Request, type Response } from 'express';
|
||||
import { z } from 'zod';
|
||||
import DockerController from '../services/DockerController';
|
||||
import { DatabaseService } from '../services/DatabaseService';
|
||||
import { NodeRegistry } from '../services/NodeRegistry';
|
||||
@@ -50,7 +51,31 @@ imageUpdatesRouter.post('/refresh', authMiddleware, (req: Request, res: Response
|
||||
});
|
||||
|
||||
imageUpdatesRouter.get('/status', authMiddleware, (_req: Request, res: Response): void => {
|
||||
res.json({ checking: ImageUpdateService.getInstance().isChecking() });
|
||||
res.json(ImageUpdateService.getInstance().getStatus());
|
||||
});
|
||||
|
||||
// Min/max mirror ImageUpdateService's clamp; the service is the authority and
|
||||
// re-clamps on read, so this is the user-facing validation boundary.
|
||||
const IntervalPatchSchema = z.object({
|
||||
minutes: z.coerce.number().int().min(15).max(1440),
|
||||
});
|
||||
|
||||
imageUpdatesRouter.put('/interval', authMiddleware, (req: Request, res: Response): void => {
|
||||
if (!requireAdmin(req, res)) return;
|
||||
const parsed = IntervalPatchSchema.safeParse(req.body);
|
||||
if (!parsed.success) {
|
||||
res.status(400).json({ error: 'minutes must be an integer between 15 and 1440' });
|
||||
return;
|
||||
}
|
||||
try {
|
||||
DatabaseService.getInstance().updateGlobalSetting('image_update_check_interval_minutes', String(parsed.data.minutes));
|
||||
// Reschedule the live timer so the new cadence takes effect without a restart.
|
||||
ImageUpdateService.getInstance().restartPolling();
|
||||
res.json(ImageUpdateService.getInstance().getStatus());
|
||||
} catch (error) {
|
||||
console.error('Failed to update image-update interval:', error);
|
||||
res.status(500).json({ error: 'Failed to update interval' });
|
||||
}
|
||||
});
|
||||
|
||||
imageUpdatesRouter.get('/fleet', authMiddleware, async (req: Request, res: Response): Promise<void> => {
|
||||
|
||||
Reference in New Issue
Block a user