From 28c7a8fd544f33ea0fbe90f19ed96154743eb527 Mon Sep 17 00:00:00 2001 From: Anso Date: Wed, 1 Apr 2026 00:22:47 -0400 Subject: [PATCH] feat(auto-update): add auto-update policies and fix image update detection (#297) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat(auto-update): add auto-update policies and fix image update detection Auto-Update Policies (Skipper+ tier): - New scheduled task action type 'update' for check-then-update flow - Dedicated AutoUpdatePoliciesView with CRUD, cron presets, and run history - Conditional tier gating: Skipper gets auto-update, Admiral gets full scheduled ops - Backend executeUpdate: checks digests, pulls only if newer, atomic redeploy Image Update Detection fixes (all tiers): - Fix stack name key mismatch: use working_dir label instead of project label - Add 5-minute periodic frontend polling for background check results - Replace fixed 3s timeout with polling-based manual refresh via /api/image-updates/status - Clear update status after successful stack update * fix(ui): remove Skipper tier badge from Auto-Update Policies header * fix(ui): remove auto-update action from Scheduled Operations view Admiral users have a dedicated Auto-Update view — showing update tasks in Scheduled Operations too was confusing duplication. Each view now owns a distinct, non-overlapping set of action types. * fix(auto-update): fix node-stack linking and add All Stacks option - Stack dropdown now re-fetches when node selection changes using fetchForNode, and resets the selected stack - Node selector moved above stack selector with stack disabled until a node is picked - Added "All Stacks" wildcard option that checks and updates every stack on the selected node - Backend executeUpdate refactored to iterate over all stacks when target_id is "*", with per-stack error isolation * refactor(ui): replace Select dropdowns with searchable Combobox component Add a reusable Combobox component with inline search and use it for Node/Stack selectors in both Auto-Update Policies and Scheduled Operations dialogs. Also fixes node-stack linking bug where changing node didn't update the stack list. * fix(ui): resolve CI TypeScript errors in Combobox and ScheduledOperationsView Add missing searchPlaceholder prop to ComboboxProps interface and remove dead 'update' action filter that conflicted with the narrowed type union. * fix(ui): use Geist Sans font in toast component The toast renders via React portal on document.body, bypassing the app's font inheritance. Add explicit font-family declaration using var(--font-sans) to match Sencho's design system. --- CHANGELOG.md | 11 + backend/src/index.ts | 56 +- backend/src/services/DatabaseService.ts | 6 +- backend/src/services/ImageUpdateService.ts | 16 +- backend/src/services/SchedulerService.ts | 98 ++- docs/docs.json | 1 + docs/features/auto-update-policies.mdx | 115 ++++ .../auto-update-policies/create-dialog.png | Bin 0 -> 52138 bytes docs/images/auto-update-policies/overview.png | Bin 0 -> 44066 bytes .../src/components/AutoUpdatePoliciesView.tsx | 570 ++++++++++++++++++ frontend/src/components/EditorLayout.tsx | 33 +- .../components/ScheduledOperationsView.tsx | 74 +-- frontend/src/components/ui/combobox.tsx | 148 +++++ frontend/src/components/ui/toast.tsx | 2 +- 14 files changed, 1073 insertions(+), 57 deletions(-) create mode 100644 docs/features/auto-update-policies.mdx create mode 100644 docs/images/auto-update-policies/create-dialog.png create mode 100644 docs/images/auto-update-policies/overview.png create mode 100644 frontend/src/components/AutoUpdatePoliciesView.tsx create mode 100644 frontend/src/components/ui/combobox.tsx diff --git a/CHANGELOG.md b/CHANGELOG.md index 2ee57a2e..3ffe49da 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,10 +8,21 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Added +* **auto-update:** Auto-Update Policies — schedule automatic image checks and container updates per stack (Skipper & Admiral) +* **auto-update:** Dedicated Auto-Update Policies view with create/edit dialog, cron presets, run history, and manual trigger +* **auto-update:** Extends the existing SchedulerService with a new `update` action type, reusing cron scheduling and run history infrastructure +* **docs:** Auto-Update Policies feature documentation page * **docs:** OpenAPI 3.1 spec covering ~55 public API endpoints across 8 categories (Stacks, Containers, API Tokens, Webhooks, Nodes, Fleet, Scheduled Tasks, Health) * **docs:** Interactive API Reference tab in Mintlify documentation powered by native OpenAPI rendering * **docs:** API overview page with authentication guide, token scopes, node routing, error format, and WebSocket examples +### Fixed + +* **image-updates:** Fix stack name key mismatch — use `com.docker.compose.project.working_dir` label instead of `com.docker.compose.project` so update indicators work when compose files set `name:` +* **image-updates:** Add 5-minute periodic polling so background check results are picked up without manual refresh +* **image-updates:** Replace fixed 3-second timeout with polling-based refresh that waits for the check to complete (up to 60s) +* **image-updates:** Clear update status (blue dot) after a stack is updated via the UI + ## [0.23.0](https://github.com/AnsoCode/Sencho/compare/v0.22.1...v0.23.0) (2026-03-31) diff --git a/backend/src/index.ts b/backend/src/index.ts index e3133144..1878502a 100644 --- a/backend/src/index.ts +++ b/backend/src/index.ts @@ -807,6 +807,12 @@ const requireAdmin = (req: Request, res: Response): boolean => { return true; }; +// Tier gate for scheduled tasks: 'update' action requires Pro, everything else requires Admiral. +const requireScheduledTaskTier = (action: string, _req: Request, res: Response): boolean => { + if (action === 'update') return requirePro(_req, res); + return requireAdmiral(_req, res); +}; + // --- Scoped RBAC Permission Engine (Admiral) --- type PermissionAction = @@ -2835,6 +2841,7 @@ app.post('/api/stacks/:stackName/update', async (req: Request, res: Response) => try { const atomic = LicenseService.getInstance().getTier() === 'pro'; await ComposeService.getInstance(req.nodeId).updateStack(stackName, terminalWs || undefined, atomic); + DatabaseService.getInstance().clearStackUpdateStatus(stackName); res.json({ status: 'Update completed' }); } catch (error) { const rolledBack = LicenseService.getInstance().getTier() === 'pro'; @@ -3693,9 +3700,14 @@ app.delete('/api/api-tokens/:id', authMiddleware, async (req: Request, res: Resp app.get('/api/scheduled-tasks', (req: Request, res: Response): void => { if (!requireAdmin(req, res)) return; - if (!requireAdmiral(req, res)) return; + if (!requirePro(req, res)) return; try { - const tasks = DatabaseService.getInstance().getScheduledTasks(); + let tasks = DatabaseService.getInstance().getScheduledTasks(); + // Skipper users only see 'update' tasks; Admiral sees all + const ls = LicenseService.getInstance(); + if (ls.getVariant() !== 'team') { + tasks = tasks.filter(t => t.action === 'update'); + } res.json(tasks); } catch (error) { console.error('[ScheduledTasks] List error:', error); @@ -3705,7 +3717,6 @@ app.get('/api/scheduled-tasks', (req: Request, res: Response): void => { app.post('/api/scheduled-tasks', (req: Request, res: Response): void => { if (!requireAdmin(req, res)) return; - if (!requireAdmiral(req, res)) return; try { const { name, target_type, target_id, node_id, action, cron_expression, enabled, prune_targets, target_services, prune_label_filter } = req.body; @@ -3715,13 +3726,18 @@ app.post('/api/scheduled-tasks', (req: Request, res: Response): void => { if (!['stack', 'fleet', 'system'].includes(target_type)) { res.status(400).json({ error: 'Invalid target_type. Must be stack, fleet, or system.' }); return; } - if (!['restart', 'snapshot', 'prune'].includes(action)) { - res.status(400).json({ error: 'Invalid action. Must be restart, snapshot, or prune.' }); return; + if (!['restart', 'snapshot', 'prune', 'update'].includes(action)) { + res.status(400).json({ error: 'Invalid action. Must be restart, snapshot, prune, or update.' }); return; } + // Tier gate based on action type + if (!requireScheduledTaskTier(action, req, res)) return; // Validate action-target combos if (action === 'restart' && target_type !== 'stack') { res.status(400).json({ error: 'Restart action requires target_type "stack".' }); return; } + if (action === 'update' && target_type !== 'stack') { + res.status(400).json({ error: 'Update action requires target_type "stack".' }); return; + } if (action === 'snapshot' && target_type !== 'fleet') { res.status(400).json({ error: 'Snapshot action requires target_type "fleet".' }); return; } @@ -3795,12 +3811,13 @@ app.post('/api/scheduled-tasks', (req: Request, res: Response): void => { app.get('/api/scheduled-tasks/:id', (req: Request, res: Response): void => { if (!requireAdmin(req, res)) return; - if (!requireAdmiral(req, res)) return; + if (!requirePro(req, res)) return; try { const id = parseInt(req.params.id as string, 10); if (isNaN(id)) { res.status(400).json({ error: 'Invalid task ID' }); return; } const task = DatabaseService.getInstance().getScheduledTask(id); if (!task) { res.status(404).json({ error: 'Scheduled task not found' }); return; } + if (!requireScheduledTaskTier(task.action, req, res)) return; res.json(task); } catch (error) { console.error('[ScheduledTasks] Get error:', error); @@ -3810,7 +3827,7 @@ app.get('/api/scheduled-tasks/:id', (req: Request, res: Response): void => { app.put('/api/scheduled-tasks/:id', (req: Request, res: Response): void => { if (!requireAdmin(req, res)) return; - if (!requireAdmiral(req, res)) return; + if (!requirePro(req, res)) return; try { const id = parseInt(req.params.id as string, 10); if (isNaN(id)) { res.status(400).json({ error: 'Invalid task ID' }); return; } @@ -3818,13 +3835,14 @@ app.put('/api/scheduled-tasks/:id', (req: Request, res: Response): void => { const db = DatabaseService.getInstance(); const existing = db.getScheduledTask(id); if (!existing) { res.status(404).json({ error: 'Scheduled task not found' }); return; } + if (!requireScheduledTaskTier(existing.action, req, res)) return; const { name, target_type, target_id, node_id, action, cron_expression, enabled, prune_targets, target_services, prune_label_filter } = req.body; if (target_type && !['stack', 'fleet', 'system'].includes(target_type)) { res.status(400).json({ error: 'Invalid target_type' }); return; } - if (action && !['restart', 'snapshot', 'prune'].includes(action)) { + if (action && !['restart', 'snapshot', 'prune', 'update'].includes(action)) { res.status(400).json({ error: 'Invalid action' }); return; } @@ -3833,6 +3851,9 @@ app.put('/api/scheduled-tasks/:id', (req: Request, res: Response): void => { if (finalAction === 'restart' && finalTargetType !== 'stack') { res.status(400).json({ error: 'Restart action requires target_type "stack".' }); return; } + if (finalAction === 'update' && finalTargetType !== 'stack') { + res.status(400).json({ error: 'Update action requires target_type "stack".' }); return; + } if (finalAction === 'snapshot' && finalTargetType !== 'fleet') { res.status(400).json({ error: 'Snapshot action requires target_type "fleet".' }); return; } @@ -3904,7 +3925,7 @@ app.put('/api/scheduled-tasks/:id', (req: Request, res: Response): void => { app.delete('/api/scheduled-tasks/:id', (req: Request, res: Response): void => { if (!requireAdmin(req, res)) return; - if (!requireAdmiral(req, res)) return; + if (!requirePro(req, res)) return; try { const id = parseInt(req.params.id as string, 10); if (isNaN(id)) { res.status(400).json({ error: 'Invalid task ID' }); return; } @@ -3912,6 +3933,7 @@ app.delete('/api/scheduled-tasks/:id', (req: Request, res: Response): void => { const db = DatabaseService.getInstance(); const existing = db.getScheduledTask(id); if (!existing) { res.status(404).json({ error: 'Scheduled task not found' }); return; } + if (!requireScheduledTaskTier(existing.action, req, res)) return; db.deleteScheduledTask(id); res.json({ success: true }); @@ -3923,7 +3945,7 @@ app.delete('/api/scheduled-tasks/:id', (req: Request, res: Response): void => { app.patch('/api/scheduled-tasks/:id/toggle', (req: Request, res: Response): void => { if (!requireAdmin(req, res)) return; - if (!requireAdmiral(req, res)) return; + if (!requirePro(req, res)) return; try { const id = parseInt(req.params.id as string, 10); if (isNaN(id)) { res.status(400).json({ error: 'Invalid task ID' }); return; } @@ -3931,6 +3953,7 @@ app.patch('/api/scheduled-tasks/:id/toggle', (req: Request, res: Response): void const db = DatabaseService.getInstance(); const existing = db.getScheduledTask(id); if (!existing) { res.status(404).json({ error: 'Scheduled task not found' }); return; } + if (!requireScheduledTaskTier(existing.action, req, res)) return; const newEnabled = existing.enabled ? 0 : 1; const nextRun = newEnabled ? SchedulerService.getInstance().calculateNextRun(existing.cron_expression) : null; @@ -3951,7 +3974,7 @@ app.patch('/api/scheduled-tasks/:id/toggle', (req: Request, res: Response): void app.post('/api/scheduled-tasks/:id/run', async (req: Request, res: Response): Promise => { if (!requireAdmin(req, res)) return; - if (!requireAdmiral(req, res)) return; + if (!requirePro(req, res)) return; try { const id = parseInt(req.params.id as string, 10); if (isNaN(id)) { res.status(400).json({ error: 'Invalid task ID' }); return; } @@ -3959,6 +3982,7 @@ app.post('/api/scheduled-tasks/:id/run', async (req: Request, res: Response): Pr const db = DatabaseService.getInstance(); const existing = db.getScheduledTask(id); if (!existing) { res.status(404).json({ error: 'Scheduled task not found' }); return; } + if (!requireScheduledTaskTier(existing.action, req, res)) return; await SchedulerService.getInstance().triggerTask(id); @@ -3973,7 +3997,7 @@ app.post('/api/scheduled-tasks/:id/run', async (req: Request, res: Response): Pr app.get('/api/scheduled-tasks/:id/runs/export', (req: Request, res: Response): void => { if (!requireAdmin(req, res)) return; - if (!requireAdmiral(req, res)) return; + if (!requirePro(req, res)) return; try { const id = parseInt(req.params.id as string, 10); if (isNaN(id)) { res.status(400).json({ error: 'Invalid task ID' }); return; } @@ -3981,6 +4005,7 @@ app.get('/api/scheduled-tasks/:id/runs/export', (req: Request, res: Response): v const db = DatabaseService.getInstance(); const task = db.getScheduledTask(id); if (!task) { res.status(404).json({ error: 'Scheduled task not found' }); return; } + if (!requireScheduledTaskTier(task.action, req, res)) return; const runs = db.getAllScheduledTaskRuns(id); @@ -4015,7 +4040,7 @@ app.get('/api/scheduled-tasks/:id/runs/export', (req: Request, res: Response): v app.get('/api/scheduled-tasks/:id/runs', (req: Request, res: Response): void => { if (!requireAdmin(req, res)) return; - if (!requireAdmiral(req, res)) return; + if (!requirePro(req, res)) return; try { const id = parseInt(req.params.id as string, 10); if (isNaN(id)) { res.status(400).json({ error: 'Invalid task ID' }); return; } @@ -4023,6 +4048,7 @@ app.get('/api/scheduled-tasks/:id/runs', (req: Request, res: Response): void => const db = DatabaseService.getInstance(); const existing = db.getScheduledTask(id); if (!existing) { res.status(404).json({ error: 'Scheduled task not found' }); return; } + if (!requireScheduledTaskTier(existing.action, req, res)) return; const limit = Math.min(parseInt(req.query.limit as string, 10) || 20, 100); const offset = Math.max(parseInt(req.query.offset as string, 10) || 0, 0); @@ -4429,6 +4455,10 @@ app.post('/api/image-updates/refresh', authMiddleware, (_req: Request, res: Resp } }); +app.get('/api/image-updates/status', authMiddleware, (_req: Request, res: Response) => { + res.json({ checking: ImageUpdateService.getInstance().isChecking() }); +}); + // ========================= // Node Management API // ========================= diff --git a/backend/src/services/DatabaseService.ts b/backend/src/services/DatabaseService.ts index b7116827..ccbd3c6b 100644 --- a/backend/src/services/DatabaseService.ts +++ b/backend/src/services/DatabaseService.ts @@ -155,7 +155,7 @@ export interface ScheduledTask { target_type: 'stack' | 'fleet' | 'system'; target_id: string | null; node_id: number | null; - action: 'restart' | 'snapshot' | 'prune'; + action: 'restart' | 'snapshot' | 'prune' | 'update'; cron_expression: string; enabled: number; created_by: string; @@ -851,6 +851,10 @@ export class DatabaseService { return result; } + public clearStackUpdateStatus(stackName: string): void { + this.db.prepare('DELETE FROM stack_update_status WHERE stack_name = ?').run(stackName); + } + // --- Webhooks --- public getWebhooks(): Webhook[] { diff --git a/backend/src/services/ImageUpdateService.ts b/backend/src/services/ImageUpdateService.ts index da93b7b1..e367c2c5 100644 --- a/backend/src/services/ImageUpdateService.ts +++ b/backend/src/services/ImageUpdateService.ts @@ -1,8 +1,10 @@ import https from 'https'; import http from 'http'; +import path from 'path'; import DockerController from './DockerController'; import { DatabaseService } from './DatabaseService'; import { RegistryService } from './RegistryService'; +import { NodeRegistry } from './NodeRegistry'; // ─── Image ref parsing ──────────────────────────────────────────────────────── @@ -230,14 +232,22 @@ export class ImageUpdateService { private async checkNode(nodeId: number, db: DatabaseService) { const docker = DockerController.getInstance(nodeId); const containers = await docker.getAllContainers(); + const composeDir = path.resolve(NodeRegistry.getInstance().getComposeDir(nodeId)); // stackName → set of image refs used by that stack + // Key by directory name (matching FileSystemService.getStacks()) rather than + // com.docker.compose.project label, which diverges when compose files set `name:`. const stackImages = new Map>(); for (const c of containers) { - const stackName: string | undefined = c.Labels?.['com.docker.compose.project']; - if (!stackName) continue; + const workingDir: string | undefined = c.Labels?.['com.docker.compose.project.working_dir']; + if (!workingDir) continue; + // Only consider containers managed under COMPOSE_DIR + const resolved = path.resolve(workingDir); + if (resolved !== composeDir && !resolved.startsWith(composeDir + path.sep)) continue; + + const stackName = path.basename(resolved); const imageRef: string = c.Image ?? ''; if (!imageRef || imageRef.startsWith('sha256:')) continue; @@ -270,7 +280,7 @@ export class ImageUpdateService { } } - private async checkImage(docker: DockerController, imageRef: string): Promise { + public async checkImage(docker: DockerController, imageRef: string): Promise { const parsed = parseImageRef(imageRef); if (!parsed) return false; diff --git a/backend/src/services/SchedulerService.ts b/backend/src/services/SchedulerService.ts index 52383ffe..25149265 100644 --- a/backend/src/services/SchedulerService.ts +++ b/backend/src/services/SchedulerService.ts @@ -3,7 +3,9 @@ import { DatabaseService } from './DatabaseService'; import type { ScheduledTask } from './DatabaseService'; import { LicenseService } from './LicenseService'; import DockerController from './DockerController'; +import { ComposeService } from './ComposeService'; import { FileSystemService } from './FileSystemService'; +import { ImageUpdateService } from './ImageUpdateService'; import { NodeRegistry } from './NodeRegistry'; import { NotificationService } from './NotificationService'; @@ -47,7 +49,9 @@ export class SchedulerService { this.isProcessing = true; try { const ls = LicenseService.getInstance(); - if (ls.getTier() !== 'pro' || ls.getVariant() !== 'team') return; + const isPro = ls.getTier() === 'pro'; + const isAdmiral = isPro && ls.getVariant() === 'team'; + if (!isPro) return; // No scheduled tasks for non-Pro tiers const db = DatabaseService.getInstance(); const now = Date.now(); @@ -57,6 +61,8 @@ export class SchedulerService { db.cleanupOldTaskRuns(30); for (const task of dueTasks) { + // Skipper users can only run 'update' tasks; other actions require Admiral + if (!isAdmiral && task.action !== 'update') continue; if (this.runningTasks.has(task.id)) continue; this.runningTasks.add(task.id); this.executeTask(task).finally(() => this.runningTasks.delete(task.id)); @@ -107,6 +113,9 @@ export class SchedulerService { case 'prune': output = await this.executePrune(task); break; + case 'update': + output = await this.executeUpdate(task); + break; } const nextRun = this.calculateNextRun(task.cron_expression); @@ -345,4 +354,91 @@ export class SchedulerService { const filterSuffix = labelFilter ? ` (label: ${labelFilter})` : ''; return `System prune completed${filterSuffix}: ${results.join('; ')}`; } + + private async executeUpdate(task: ScheduledTask): Promise { + if (!task.target_id || task.node_id == null) { + throw new Error('Auto-update requires target_id (stack name or "*") and node_id'); + } + + // Resolve target stacks: "*" means all stacks on the node + let stackNames: string[]; + if (task.target_id === '*') { + stackNames = await FileSystemService.getInstance(task.node_id).getStacks(); + if (stackNames.length === 0) { + return 'No stacks found on node — skipped.'; + } + } else { + stackNames = [task.target_id]; + } + + const docker = DockerController.getInstance(task.node_id); + const imageUpdateService = ImageUpdateService.getInstance(); + const compose = ComposeService.getInstance(task.node_id); + const db = DatabaseService.getInstance(); + const results: string[] = []; + + for (const stackName of stackNames) { + try { + const output = await this.executeUpdateForStack(stackName, docker, imageUpdateService, compose, db); + results.push(output); + } catch (e) { + const msg = e instanceof Error ? e.message : String(e); + results.push(`Stack "${stackName}" failed: ${msg}`); + console.error(`[SchedulerService] Auto-update failed for stack "${stackName}":`, e); + } + } + + return results.join('\n'); + } + + private async executeUpdateForStack( + stackName: string, + docker: DockerController, + imageUpdateService: ImageUpdateService, + compose: ComposeService, + db: DatabaseService + ): Promise { + const containers = await docker.getContainersByStack(stackName); + if (!containers || containers.length === 0) { + return `Stack "${stackName}": no containers found — skipped.`; + } + + const imageRefs = [...new Set( + containers + .map((c: { Image?: string }) => c.Image) + .filter((img): img is string => !!img && !img.startsWith('sha256:')) + )]; + + if (imageRefs.length === 0) { + return `Stack "${stackName}": no pullable images — skipped.`; + } + + let hasUpdate = false; + const updatedImages: string[] = []; + + for (const imageRef of imageRefs) { + try { + if (await imageUpdateService.checkImage(docker, imageRef)) { + hasUpdate = true; + updatedImages.push(imageRef); + } + } catch (e) { + console.warn(`[SchedulerService] Failed to check image ${imageRef}:`, e); + } + } + + if (!hasUpdate) { + return `Stack "${stackName}": all images up to date.`; + } + + await compose.updateStack(stackName, undefined, true); + db.clearStackUpdateStatus(stackName); + + NotificationService.getInstance().dispatchAlert( + 'info', + `Auto-update: stack "${stackName}" updated with new images` + ); + + return `Stack "${stackName}": updated (${updatedImages.join(', ')}).`; + } } diff --git a/docs/docs.json b/docs/docs.json index 0f402768..c9b6282e 100644 --- a/docs/docs.json +++ b/docs/docs.json @@ -111,6 +111,7 @@ "features/audit-log", "features/api-tokens", "features/private-registries", + "features/auto-update-policies", "features/scheduled-operations", "features/sso", "features/licensing" diff --git a/docs/features/auto-update-policies.mdx b/docs/features/auto-update-policies.mdx new file mode 100644 index 00000000..d2c457c6 --- /dev/null +++ b/docs/features/auto-update-policies.mdx @@ -0,0 +1,115 @@ +--- +title: "Auto-Update Policies" +description: "Automatically check for and apply container image updates on a schedule." +--- + + + Auto-Update Policies require a **Skipper** or **Admiral** license. + + +## Overview + +Auto-Update Policies let you define schedules for Sencho to automatically check your container images for updates and apply them when new versions are available. Think of it as a built-in Watchtower — but integrated directly into your Sencho dashboard with full visibility into what was updated and when. + +Each policy targets a specific stack and runs on a cron schedule. When triggered, Sencho: + +1. Inspects every container in the target stack +2. Compares local image digests against the remote registry +3. If any image has a newer version, pulls the update and recreates the stack with `docker compose up -d` +4. Records the result in run history for auditability + + + Auto-Update Policies view showing the policies list + + +## Creating a Policy + +Navigate to **Auto-Update** in the sidebar and click **New Policy**. + + + Create auto-update policy dialog + + +Fill in the following fields: + +| Field | Description | +|-------|-------------| +| **Name** | A descriptive name for the policy (e.g., "Nightly media stack update") | +| **Node** | The node where the target stack runs | +| **Stack** | The stack to monitor and update | +| **Schedule** | A cron expression or preset defining how often to check | + +### Schedule Presets + +For convenience, Sencho offers common schedule presets: + +| Preset | Cron Expression | Description | +|--------|----------------|-------------| +| Every 6 hours | `0 */6 * * *` | Check four times per day | +| Every 12 hours | `0 */12 * * *` | Check twice per day | +| Daily at 3 AM | `0 3 * * *` | Low-traffic window for most users | +| Daily at midnight | `0 0 * * *` | Start of each day | +| Weekly (Sunday 3 AM) | `0 3 * * 0` | Minimal disruption for stable stacks | +| Custom | User-defined | Any valid cron expression | + +## Managing Policies + +Each policy in the list shows: + +- **Name** and target stack +- **Schedule** in human-readable form (e.g., "Every 6 hours") +- **Status** — enabled or disabled +- **Last run** — when it last executed and whether it succeeded +- **Next run** — when it will execute next + +### Available Actions + +- **Toggle** — Enable or disable a policy without deleting it +- **Run Now** — Trigger an immediate check-and-update cycle +- **Edit** — Modify the policy name, target, or schedule +- **Delete** — Permanently remove the policy + +## Run History + +Click the clock icon on any policy to view its run history. Each entry shows: + +- **Timestamp** — When the run started +- **Status** — Success or failure +- **Output** — Detailed log of what was checked and whether updates were applied + +This gives you full auditability over what changed and when. + +## How It Works + +Under the hood, Auto-Update Policies are built on the same scheduling engine as [Scheduled Operations](/features/scheduled-operations). The key difference is that auto-update policies: + +- Are available to **Skipper** tier (Scheduled Operations requires Admiral) +- Always target a **stack** (not individual containers) +- Perform a **check-then-update** flow rather than a blind restart + +### The Check-Then-Update Flow + +1. **Enumerate images** — Sencho lists all unique images used by containers in the target stack +2. **Check digests** — For each image, Sencho compares the local `RepoDigests` against the remote registry manifest digest +3. **Conditional update** — Only if at least one image has a newer version does Sencho run `docker compose up -d` to pull and recreate +4. **Clear indicators** — After a successful update, the blue update indicator dot is automatically cleared + +If no updates are found, the run completes with a "No updates available" message and no containers are restarted. + +## Relationship to Image Update Detection + +Sencho has two complementary features for keeping your images current: + +| Feature | Purpose | Tier | +|---------|---------|------| +| **Image Update Detection** | Passive — shows a blue dot on stacks with available updates | All tiers | +| **Auto-Update Policies** | Active — automatically applies updates on a schedule | Skipper+ | + +Image Update Detection runs in the background every 6 hours and highlights stacks that have newer images available. Auto-Update Policies take this a step further by automatically applying those updates based on your defined schedule. + +## Best Practices + +- **Start with longer intervals** — Use "Daily at 3 AM" or "Weekly" for production stacks. Reserve shorter intervals for dev/staging environments. +- **Pin critical images** — If a stack uses `image: postgres:16.2` (pinned tag), auto-update will only detect updates to that exact tag. Use floating tags like `postgres:16` if you want minor version updates. +- **Monitor run history** — Check run history periodically to ensure updates are applying cleanly. Failed runs may indicate registry authentication issues or compose file problems. +- **Combine with notifications** — Sencho sends alert notifications when auto-updates are applied, so you stay informed even when updates happen automatically. diff --git a/docs/images/auto-update-policies/create-dialog.png b/docs/images/auto-update-policies/create-dialog.png new file mode 100644 index 0000000000000000000000000000000000000000..d865f8b7ae6be61d8175c3210c8d2b27561fff6a GIT binary patch literal 52138 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%0A-*>g*|uQn1WFh*ECQth zF!Xo^@uqqMW9CzM12ta`1VeDyul3}Y~?mI_EMvI9ajb(Lm)H*JH>8HSi~N+9E77n3B^7M z_TosW)6Glbw{jv#>KV0NlbxL&WYFyZl0GGdt9G>_kYeVaZ)Hq0y4iX61&M4Vyi%*A zA~DQuz^Ve-fJS}(m#Ei;DK#j~#HQG?ypB^qc9=C)q8mcrRlMJH;R)95X-k5s>eGb! z;BRU;iCq&=G1gA-S(i@0-e~k3D1yr3GLf+#*#b%JXJQxo=%4|mF;)nW5ZwVRes>0P zK-b5JGQK-((vCXadvUDtzFFYT!U|z?#I@j7nIdG$Q#tr(04u5ohL4Y`U7SE%e@Nyw zztXhv?8-{L1L(dEZE6?rv3~<2V+4OGh;Alq)nkTM-g- z(0v8|FK7=(l=+O~@It!#4N}Q{r$XYXe)En>*HD$!#O6pg1+@Y&b&&cgS>!#kqE_SiPjLbqPA+*(qrkQP_w=Q9bLmFI{0(fTN*CT*WPX&o0|cb~ zahB8YbMQoh4hP~2{i`~MHMIet@o52pIu_dt7A=-a60}CQAd40j6gbF(LJ2*PuC!=;`qlwiwRH*&^cd2C_DnqeU_T z-qf!K%|grsW{u7}8e7*c6B84_TyFHst}_@-J|XBB`?LA;&PbgSoobL-1DNR%=9toy z0B^gymF;WL3K8W3_ON@vPa3p%U&CUEyodwby{zs6+fT-3uS@gU0*AF8(&H`Ai*bXt z6WFp^!8+pm@rziFQWE?9?fUXWsG&0m8QVZpbcx}61*-0Oh*AjV}Czl9@;`HCVK#c$`dM`~>l53VMxSLB){O{ftUDN!>)!^0gxz=57M z`17Yk8;>xKueAu4EU5#WlhTODStN@(tGRFUB(S0PPhXv^eJvo=hF9!u{fPgq4<0h*6d&=%F?Nmx}9OCFSN1c92A>mrMKlJeYXt_4|8v-7J-xAhOR) zr~Lr_Fi=<0MZH|@O_=YRLl@~3lW=d|<+@9Jk^;H1o}+Inkwu$6Azs3lnI%Vvnw>u8 z<=>~k=yR)s#thV}x=)+n0SPw0zKnrU+T%g(Qt(}u2QKU5B3Q~NZ$Sq6G5{J0#VI*6 zU@w-`F5aBKmA{7(lK!*k7i6^#H&p^KuB*_Z39=EwJ4RAJ%p9i^s%& zC~Hm-A5Ku(AyD>mYx?7QM!6KyvsbK(VK_yfgQE8Nrdat?fdq3y- z@7X1>4gbzPsj)|6?R=dcY{D2nt4G$b~@;@NZJQ-(K2>on8< z{`s|-Zgu4-POy@_1D?W#_X5aoI3hdI@@^YVFgoa>xsUVS7kB>Fp}hLe@MUQP%!bdZ zw&9yaGx%hy_x1a;Zkw^{^~Gp%(+CYCPn?m~oscM^OaFv$3b-TOJQ5Ml`E7%I1Q8^a_M?0r^gT+^jE zRjNr{W|q}#0gO@VJ@%Gtzuf%~Up_p$t_T+du&{h4b8vnN;7`z+FkE(_>6+o`gW8Hm z&N?U`;3b-Ka`6wQ9XQl%L8*j;kh7!Nqu)QUILmYvsM8+H-2>vj*=7scGM#++DSx&# zW{?emnLDzX7RmkT2oflQp}Sc~v3hv|Kub^2GRoSCu#RFm>swnu0aW(xX!7I(!7jh! zgKZHdiW-s#z+Kh$Ik!etb8GHVa99|cy~Z}g5;uSrkd>?!)Ws5-G#sg@qDD4glS;sMfy6=*?eNhp=_)*9c31po0IcP z&H=oWCylegIb$)5D(C1KRpRM{hh@JrlU1vVxjwIiM|#An zMr}@h7*!9+q*cz=uF)oMTQ`=wRhOSAh2tSBZQl!7zLV$X{`0BUet<>pJ~Tc`n_BQi zZd)6pPKxDKhD+jCKZrIjQAPQM0K4n_t@RKa2OuwFVT9?#cHFmK8O?A0W*~_tk(^fu z$(3{(EreTs@5#^(8gyOI=!AF^XRej)$%MO0z*J*HG*=~GZCb4@W>wvk`Ct_egP+C* zfz~OM>ie9OFn{xp%uPSDB(pqRqn^MJyz%>_xOl26TjD%sJNQsMBybSfJ9&*9js^MbK!PX*A1JLfz@)R}K!oN_7JL z)(N!1M(x2toZk5cs^c(kRn(lcJnqSuDladO?!D#@&`!}DXysoS)l)vk_33IAr`7N) z`>sDWc)3sv>tjV&KUZ5g#!`H*Ewnm^R11sus4FZcL>Fd7d9N_@3~FW@i6JByxz)|R zpE4wG>l-QpGdK@j@EH_})P5K>6u^=`sV9@Z*|eAhsT#S1GxhM)ay1?=jTA19c2IUk znkgqvX=XFu?*Jsq@k}|JE(3qc7jEN?!kJQB^gh!m_1D6n{1OP60!6F0sy#Fl4Y4FY zA8*j~M4of-LQBmZ0|tu)O#dDONn2#~BSQacg23-?XteF+i}z0<(?HYe%6(4%-fn-k z748|5D}x_6M~+|I#}ioTg(@^`wqB~4Zr1$h=Yq%$>`-Ud7q$*aN)R+kGR}Ik%QP67 zvK0upNkTpn{tdqbN`9}ZN4c8(b#W+z1%oz&{|P~}p|Pm;_|d#KrEjckT#XAf6mk!L zI}KaCDf$q;o#V-1+V@QCFG(6a$nS!VO!Vj}mo^%4RxQk##V9^>f5=*BE2US98YAv~ zccIgF#=UXOThyX8lQK1l>E8P}t;VFNC6bn>see*b$YM_{Jly*Fp;bs4&+%Y+67#j= zwXDdEhaogFm0w{DLUJz_*Kx$*5T-`WPGWw%-Dru|Zw@&-P2gXma1_6dJQ~(pV*3%3 zfX0~tlTO;TocV{sl8x|kR~?9cGJe${v2z~GM7AklUHp<#tUGg$=p(KaWn^A=Sn94C zz7`X=GWj%w4)0ZDmw{bZis_|LnF=Zm`hJ~L0rno0qM_YWf=S7PVEy)qNhoE>{X1Rp zq6T!j+0C{obbr*+=g2sNkkiKznW!f4_#>lOLyo;2=)zJ*3KR?Oocr?m`Q2BV=tIa_ zQtxIzEmDb|zP~&hxT`}<&iJMc$AjL+2l+gmLYD;Ey=Af%54HZe_l^Z{fwZgpi8UX7 z3)pP7>Go5Z$;ps-{RZS0nAV9ATu>{;Ucf5SY}|(-`1<0mKv++W|w-nw-y_P zV%zx+q=grhcCI#=&CL;J^5^1z@BU7R<%|&Q#?@7&0dmA5ZfjRX{J8g!QPKC~bFE!3 zpedqP73w(maKe$)O9=|YM^_mP$23si*IV4qf3u@CtOi`Z%CMZQ+gD?dM3yF{Xkd*b z&FVRmxE;Y^DZA6n{q|dSjuancTJjn*V&Bip+gsoN;&T9vp2U+!XSs;3fv}=SA25{I zR-|NKCOkPWy^}>ih-5#k%OrQ{CxFP@x@J==3D0nD1ZpKpAHv!uZ*4J~+= z=3if5;*vsyWc$GaaTu~x+-E+{>|LPjUs66X54|!W{f6VX2Tz*^KwXZU8Q6;3uvbp+ zw>0q1$EYDnpaDLZhmJpLn`a5P_!<80pS^EY*7qr?zAE~TSH4Ds{euPQNYgs%3E{nt z7in1y#b*T$D>@`?ETt$hv=%CE!tewiH9ppMA6RU|kd(#VVCzuRuN||Gc!MJsii`IJ zx&xZUP=Xr4CQ4MFQ=d-rTo`eeM7UzOgNm3oyUPI}PD;PU!xLU~Gz|uI*3IcXod&Yc z-M>ijIyXwZP>O-_@0|$Hevg(K)(o7M z+C!Of45DO|iE$30Ex9lrM@VmkE7!)T?i=eH<+A!0OfReZX-6=5G=&ls00`a{ zNh~`x-o0*qetl0DIlA?njdz&Z#Scz}QH-Of<6|aUo$Uvq4wBY(j?8WVOW-24`X2_A zd2@Nx_V9Nbho4iE8n>M>kysJT5^(6tmwVs=lodnj4-B?%fqRiv9f=zn^djPi!_L}! zO5>u0hCxG#=ZAb*rt0p~2leCO+47)$Ni74c&f3huW;%)CYnVf_(z>vD%Y;8ueo=AF zI|N$p-p`1?%iNcf^+Rh(M@aGsgD(pb%XO9=WR+r^^^_;MAtQ}|`jbe#K-{?%!p_Dy zK+mx}?L2!BF>T$|-%$ygk$5Vr*-OMC=zHIyQY~7;| zKhnKG|z;l-;RawZK+P<|rXw?jyGnnFFe<4vb#>uMe1ntuIuBT%?Adm4p-Z z!~-9)DmYZ#jO?Y550C3M^3o?DPresML@MIcC>GdblSBdwVNmYKYgH0wjugJFi zwj%Ie_|TO%S>@QT?SC95G3Zn%7}_`Ge0%L^;??a<01xc*|AMypjRc87(>Pne8d5^G z+8W3@)f2HB3j_9#x;c&kiSimmc^!NjvJ+qGk>p&m5JOAorEXFp`7YQZ>ir$lY$NP> zTLAJg`*R3~1rWuM!vRa1K(kA#8V4*^Bs6@eouUi|%9X>1_%W1e!((S?4@rdbbiJ#3 z??XTz>L!VMbDh7oTy7Wd(HNO2RKg|JD`2fiw||(a+X|HVo;COBvv3E$nAIUJTN!1* z{$i)M^{DbC8tTLItev)>ig`b(Ree3}0#f#zzlt1O!nd{GF5oy+{+=+@vmfRBj;261 zaw{oo$N3&t;^Px2KbF;+=?U$*7PW4xJUpH+(ev95=;_notc$-U8Ffe3qw<)U~Eq52T2TwTL%;r7!J11;xzz_z9ve4TQ%h% znFKU#f%_ZD>>c;(g`tGRrg1yp;1Rihax}662_h6WL zYolVBcss-TJWi|kq8eT{Ox<31qW0)}4H&q4w+#T9V(=83uiBCM`Md;S?5@L*CyVPH zw>IJRL6Z;3eK`9D0WweOrU`N``Ufd*``vtinm|lsZY@XI$RNKDBTHQ5*E17`J=KnF@(vVmbp(Yd?GJ~DX3kN3H3&l9_-y#)0gkK_NSjvDD8rY zZPQad1Qt-;(nH-w(~i}&XDo%=yf!Q!hRNl4C+&yDmj}@g*>$}HxVqgI z#@F-s91zg)pLQa)PWERdw!!l=1BhM{W!N3ZI%)&4X}(AX`)%2D3S7K=C?mQa$Mz9as(fTPt83d;$r8t0xRW(B6E*mX`jZNV@X%L#5#nxlQ@vE8lZ{5iNau zhrQ;`o1T^ta?yig1EjByySW5K`ea5FMOTBItshO&S+Mm5pqVAJ1*(aL2L*o(Ge7uSzt0y-z{|cKJmJSezIO^c7KGAaF z_6;35KK;t2)(7;`lZ{NIkV9bmhG*!cAH;cJb?$SA5FEj@olw>@;K)W-9MZ8FXQCpp z7_M546ceM?X484#Ch9BB7OSgPJtg?X?pzVAt@OZ*$6L}TNsM)h57s6_>y8F2Pi=gD z1GQHAQ-kK2dIVhjr`!d{S`V;342cn!<}QIaJhd@z!cVw9!{U$B>}{TOz|rRnAn-d? zy|N?dlN7H~afFf3X$^!~R|4HA;X22Vq=sH4sH*{c%~ zW{vcr8jSkARet5p&xg~0A*e|z2bIL~FLob%T!_w647utHA(v|Ezm~WhWP&qaHQd4b zDZF|_8hUvC`?~8gVC1OI*=SCw1c3w@ZDt8&i1dR3f-LX+@5^?|rHb5SIF?vhUg0^_$+!{%0y z3Q({3OcGx3T1~H8{Chuw4ZSvp z0DK*NK2LV7t1viEn&*cW0ZP2Z8&p*dLEdhV;+H_}31q;w%r@eN{$~P>8H&lzKD>|C z&C8r#lnl8sEu|zD#--pyrEmEyloB9Qe1E&I{Y{|5Xwe&(1FE*iwM}ewik&Xi6A)v- z*{vi!6*0}o&-nDhkdFkRMsZwM*=opm9#P)1Yw^TKpc}3;Z~eJM?8HRIQN_$ zCiomOZ{;-=w`qGQojqBBnv^&R;~)L2lIHkOivTV$SPDMOl)X;@$RionhIashvZ2SC0C9t0a6^Hg;HkXIUD!$M$F%HC~OL>XOy@e0^AU} zQQxR^K&_BqI(c^Y#*{sfO3)QsC*)lMA~~jZ001WH>26B`N?ch_)t?3%kk@5J)J%}W zf#tW04`@1Zb8Gu>TiLK2wSI8Qe}pVAG`nVT{v!yBIq3sfEsy{5`v-si!|oH*B#-`z z_NIDU>@b))k9wj1?$z!8$Vg&>P!C%MH=AP2_2Ho@qkg7#SZU!N#$)jG@z)F2v096x zR0URD6yH|}!ilBk16ufNY?=cE#9~#zJSZxUuxA4N|0a?5STx=yB#`(QaVpo}kljA$ zhbRQM(rjuTqFY)$Ek23&|Dy!7DXs6FKh#&R= z8ph_Z0;>J|i{22xb{gh&7fi5kr}(wujT>_)%^QqeH~z2RnM&nUK9%DCp{nJ-EAamB zL&E=$REq!obc+q;7?wf%E6~cb8@x>xaCLk6r zQBPh#&lw&g*d-)hkyV64YM3j zF3n3(+H@5cF;f%i88%XXl$p+5PIuF5gp);q#n!${x=c8MrU-jxcLXK9+8+KPlU+*r z@a9`g)`4cv>EO^%9yBjdBh6eMP37g~DFE&ZMSE#^xw(|%qX+6o=gInPdx-N0Cv5=$ zB=ryjgFTloqIm@Xxw{E849p!)Ow9BE0-C=IY~IbixjGMQ=GvF$aeooYE})|6&bYtV zdw%h1u>umc z%!HuX<>Mt{w-=JGpAry8{*#*R1WUdku?qfhlje|HOoG_%aw*NQZ3m@{gFUNPpGawG-E#bdYy`8^>*xPKSW3n!@>*R%#};m{n_OXxX1#zBr} z1fRIa{tEMHpx=7lLQ!SBg8BLIkK2ozm1bE-RAZ#qf4tri_OaoOM*Vu4eSD*_bB0BD zc&l|PsQg9~j(^ze%W6VRvnfrYc9{0$3Dz?;SEf=8zjm#D2|en@Cmm*eZ7Y`I)E`#X zYdA>`8^fp*h?cYg{e0oyr5^B}%H`)gVI)P(5ZdWuO^#BE6X^?7{(cPUqjaz+H0p|* zcrF%824F9OrwL1zFL+P_q0V9b3u%OF+-nZEeXItpEkTP&j!P-`QAjMI9Vmgclsg|M z1EE@A8YE0f2PXZ`QxQJg>cpp}@=Y0-sdH9&5Zc+h+>|UPO&G2;1<(`>3f9}UJ6OqF zVx!NpR=Ttt;vXzP*~c1~A0(#$&1Wj6`oub3rsKG;+CS~eKv-g?A@(k z@ps{G%mJc3Z&gptai9ATS){9cfp^5HGG8IU=213XG0kx^98aqtn?iDg*dt<1to-99 z8NC6KwzYWFl+GaNEWY0!3>r4P;A5}q^tqDdkXyz#+RNPa2zZU6-JP^qI_k-;*7+w?{^*x#|x% zl&2fqU7Kr7RhF331#QZ-D)7L>b({Fv#>ZFm(qV5g{tHE=lDKy%FA{#+*plH|NhB&V z*T4TVj7~Seiio{+F1UUsy|3WDWmBnoGOMK34t0(D1<&*+7u}-5cIy~&T52arhPT*L zu8`9-j_~F?HGcV|oovoZ`AfE_OqZM@|3>vn>{Q@lL=8qWr|1Ob`Y}=pSMeM}%w&qz zmOr`YIZ5U7hF)=2`(rIajNDnJa?62e2T@X?2#w1Kp)~i{EO{_anTq|W-mN~Y-c2!Z z$c{eZ53D}#U1n{m3qsd;((7b1zoE73c3QkG)DBV}N~S$FJT_kTcZ}Z|wkQ3m`E=3O z&_bB7-au~W580B=Alm?kP;u9lB`Oki1LVbps`o2Y=L649_A}_LDY_9)GZQkOScaTj zPNnAaiKH4TP*jxP{as-ePwRoS!nd8nBOX|uoSOWkc=9`a7p*t@E=X8j6MSI!icH^| z15m*HXNb!q}KFRd6A2S+pC`6dQ8QD8QGLy)@#<5myixED@Gk_=IhhT^u{n- zUpq)yyExA$$X-l*ONEJD$_~$^_Y)a==rK+6SReTYO@@MW>Lu|_HtVL%Ps*GtDF$Bj zPO?jhVs#=YvV3VpCaK$0+Z)uU!0;`VEk15A(@*?jIcz-KOqu&2_F8Umz2Uw4Z%cX@ zgguGrgbJ0RQwU9pGxBmR9yawK4q8&~qEOV!^)DCDt!tnp6)7bxc?&~t<_m*hpKrFR z@)HP0r*B`>%P>OP=vwZH2`=8HBcF;(VR~$rHHo)9A85SVd&PfWLhO0tRusxmH1&I1 zU*gNrx1tdgHX$51La648D$L`fZf0vYU#pOeBhJel`@B|}%|S;V#~NIYoEwlJ2@-Pl zy_t5wfir_m>)=(@yDpQSweDIJi#PM)N1O9jr+=NTe_;dAhtumd>^Zq5_88v?XC@GE zj0qdJpd$6-kt}zqIW9Ke2QAnh9|IVcUhkr0d?qZXt~d8x;uXJ?Dkg)A?9Fo=HlRCi z2v5#1bzDr~_kQvt`maf3s55lRQ@riP4oO$0(Hfb@&n;eIwr&;T(b`0E;xeZQCiqkq zcWql-(UVV?oKg$+x`tcmM8j+yb;X`8z?V{l6uEmlX?Z{VP z@oOxhD=PjH_NC`M)ax5t+%5bY1i|&{K^(nG=-X;sVfCNi1qIO1OFgUL7MgG&DO(Pw zjLl0_?ER*h)OMBGG+je1m6Dv>arilVfV|n4!e=j3&h$)ce#pzo*!V3qJ_}<^pyMTo zYAPs*=}D%Rn05EA-!&VOGPTYrpcKXtdy7OPH&TgWB5Xv|x{`W7>Yd#9m|=uAJFJ>S z4RZK*xJEx+!$!y}a|x#icADmnew7aUnC}#X_qhx&M++^pIB1Fv!^bW3k+t3SdIKT! zn)RfdrTKs#qN$aX{P?>99wF>w+h5RU-{>1x&-80p_VvDb4;_X6DwOzLp@@LE?Fc(! z5JOk_SkG-HbLx%bjF#ee|GRJR{fbiQ-Va7&_BNG7r-FN!w;XdBo1h^=ytc(8AfGZe zIJ@C6r_u4+LdgfcKZ>%t8c1)6H%hi4k0QjHo}~Djg<*DCoT7eQ3$9(Se_jCN7Dh~S z=uj4x0t`^i_vdR;etCJC^K2 zG|kl#liPIUT7}Rxtl}Hy45F-N(~H~sf5Z_T+TC9SEKRB&lej1)JCfxfhdV{(w8ZSf ztwu(u{Q|3o30@}QEC_eF zE_aF%2%At!_hsldrMWUSSh5UR!MX#Q=$Q$S*Xgi!T2T&ASrj0%TiRk+xr$QCNBnPVj{fgWTcK*e1V^vH1AV(BM}klaprX-$aqXK z{X+{L1{X||wQQf?u41KS@{ay?z|q}smbL34sVa7xwZACj;r@n>`L-xk-^TSi<_{zU z&O^ypNIv5sH-cr%lCG)(ZEf0Aq(tGj{Xa2uYu^HUe!-SVf+qJJ+g{Jx5VQ_QSe} zgp~5+oBWYgB({c564=W7(?9vsxtM~?tXp;O2`k?>unJ6nLKK$y9cNRSV~nVKRf(&IqsFqZY*L$9`CjjuFISnh=b?KK151fJdA11tSpWoa~eI zU9i*7hLRi$X7BN*ikdaSYsyi@`$K;XCu@DQ2sepqmIal$Ro9f!I)5hnceSLS&mIeB zY!@0dQN)I&zQ2{L?vZLblSTL9Q7l6(ZiUj%$tJAcXyrxFVaE01if+4FE0C&uefyx- zgy$aTF^CrjaicoFg2FA}MY53oE9N75s$KYU-2$x7hy^JZ`F7FC(w7nK*O}j|^1g6y zH!}*HS)H+vYA|_5Y6Z6C3+#-0PuOR~pN4UzH|g}5UrNvQ`n4{haS*MKs`1r89N$|R zU-Ze1>x?M=dg8>O5SW06dCO9@X)+pm&UtwTZ!PV@z_;9DG-L~2ZJ#^Gf&XIsRylXd|RzUw{U~G~! z`-C#;+$S#8)}y7|1+UHGb*x>exK4B2F$ML$%Pz3CE!BSZy!X)s-@6wqFDKwxu$-r# zPuEv%5?kPD`XQ!SvQA7J7SzQTm08!5I9am&T<6Hf(C=s8xmRrqRnFSa?;;Ct&e3KVo7aI6@6oZ;C;N$9D`F<3N8Bcg8AS88fu2z`{o$JFDl#7igO9TvO`1cl^na19y-7plDEQ-i~=U4lkx`N|L z-!5wfj#vKQ*stgIw!;M?DEje??$oZI-Trxga<8j%3|(kr`CQM5oRvc>nTEb&nY`5+ zAc!92KkaC)JtQb`*)1$RLf2o%GmTp?=mZDitnCTQaC_62Y=AV-!00FFS|VZ!9#eN@ z(x^m{bP87o9`3-50v@L~&_u$Ffz7#~ree)vm5Ka)(O*9dEE4()ncbuQzigLxsGt-W zd?J@!3atiFb-vDL3Y1!AP z$(5FxItF+MRzm^!C}`^UKwtqLfvC#+{n6`M3;$BZV!UCt2fN#^ zbY#KPvcEpF4=rCp+Y2>tgWY%n*0Y9tFJZt|y)ErBK|s46^0EeaDDaRix3gT2s%6U>o$?6fjE1Tm4Fm&tgSDyrEBn48zWIh)(;#G%&O)XYOwGQ_7Q@36E?)xbh z+ZBcKvl>94X<2DyMds;ZEWE;V94~2yNxy zI(z_F3NK35xYe)bg{9s1qG;AQZB+|!37mg{9rVxdua4o~Y6aa2ymY4aSbd%2TD{NKJOcg^V1)9UKk1Tw3 zLjNST9fTh-USxfxL3&h5@eQ#V&k3#47L_^g0Q-GNqSqHorTPz~_JND=qL*_UZi0yF zsp`>3I#p^+&xH{_s=uH)>3kA*Y+7sA4|vUBPFy@KxrAexUcrX^6lmAZ1-7nc!PA2U zm-p`V!sHlTH>#C2v!a>$wgliGpkif zjPNqAw!+z#+3coWvIdq2&aq{qn!!uE5&mmt~ifLc@gQd8tQ)1wHb`zCS6cOVo4{l-77nWEF}`{C!=@Xr&x z`WJV@!$9=+<6-hg)_MQap76V=3*6{B{KMot{xbtfUCsmd_$ogt>_dmhsy6xu3&0+B zO2ub9kH9ArSFBR6_T>vgAYPF9fybK0nlB?ZuzdU4mjxow+)}sDGh}6CL@iXn9=wYy z+}2GO8in(3wN`2Lg3zrnolA}N)W=6Y1>ffzq>B{u*!nqn4@O0Bj66Om3@1}YFEo*D z;Lr){%YVFh@uG%hob9_`|9(IJ^&Y(MiI_IH!Gaj@6zJZ-=>q2;DBNVRN=dHH@M$Kln5vIKo1>>bDunkyi^HY!~0!Ocs16qz?V!+-+@-qXsvxG z<2hB!Q!{Uzl*cx4+2RWrt%WRF5r+wsM|h(M%xFLuONrds9S6VB%tY0$#e%S86o7N=LCs%K zLVu~vCd-yP?YESfk~T_7sajd@c7%*)D&n@OzNEU;Ytz^eeoYhRRjf6 zQ}Edc`^6g1nT0{ryT6+!tUi4lPIG{#qCa)ug=Gy;CeF{W&-6x*wygZQOyD-Vn8+f$ zke<>vJXyd+#%EK^apNb2s5d25A|HpoU8Z`qo;DvjI1hIlt^cD;*ER%i2Aq#3yJ8xJ zj;jP?6!@^i!b)yD`V+t?X2EG!&qogL~$UfHPANz0TnEXijoH`c= zpfCUNRnkPC2>h4bFD5AJe`CP^@6h)DOUOC9HBN3`-g<*&U)%;Y1#Mm{IrRC*k zAgOkm2Jc2SOoO`$2ZRi*U{Ztb!lVy&K5l$#V;VDsx{b9=VdIFZgzHnfocWY(tL~Ms#PCJNwg1{JO9puo zOZ=x6=$93@ESkLLLBiS<&S5WjyYd)BuUoFb!EEmr`)zN7xoe1=IuIXGWD0iR3vVi3 z<0TP}LO(gYc*~;Jju82;ZLa@1{2HZiFDOmzh%z1k_-Xp~zB^zG65`@r;YB8J3rxeO z48QR0S4N4px(hw^xFrBbtXjx@YxeyPWZEU8*D-Ys5@3Puye$u93LGK%mil9>j{g*W zWt!NCi(c?SLS8^DiP}){^>GDdE*-rp@Hbzb0ev8+>r+uni#HBB!=6Cx!zYcNljp$J zq_b)0GO1^Pm4WL$-s*mSgw(x6Jp@l~R+hkjZQ(8M<8Hn15Z*Byi#1SvgR67r9smdP zg6XME2R@Ku7V&ynj||R*wc`QU`E`N8;h%wnwX(EyK@_Xn0FMp~pXtzmg7=r~%qy8y z!R^^^(0U$!9Q)@^KoytL4pqecR_&x)-)-HyH!D-cL;K;wgGZ{)DS3HA5lPp1p}lIf z?#BD~uZ@~2hhGG10_;b1v0B+CM+;JEOdT}7dB5-VU$42%U;K~r7X&nP|NrF}|0fqQ zgR`@Y z1ECH9Q7>4fSwnp6A3*R<9B_PJ!KWS^99*~6e<#i#x|)w*dz-^L zwo-PFe`+z%$T%1M{t|rAw;bk_rP5I=(6f;>1)cY$I5oFgh_yYvhkQe?C!j^X!S%vd zkC;@G1uMjXAO_$~DQ-=J;}yF<5xJ&CW5D6_vWJISb>NmfX(6*ACyS~td84Jk3s%wL zy=qap$)GO^E!FBTGDkG3&%SI1y`a#=cJ=?dU+Q440LFIRS39)ix7i&lMH5xfPC#nO z;w-{?s}Q?@6Z{xHN3n%A$vDCljwAU?d-0<05I`D2=e36w2beXbwDkO1&?4KQ@#f7r zr#o!MBWwSvB&PGKNZ&hckJ$&w9LWaqd8J?=Z^NcFJnI7n$Sa{R6nt@@)A6`qh6e6d zgrDCSZZ5d~I7JfGCMBWegijux7{#;WU6^my0s%fc_dx?hXYYdEG*0k*S13MaoxRNM$G4-vsoBP$xVL< zH z0uMP@8{WK*>Rz9l2GxPb?7OeS6ny6qj%1Mw$}d~s?IUW9Jnh0i9Qv2#fsM{}d*ms!1M446t%pS_j9O5F(;S-0W3A49wz9~fo+Z(dIT8k)(!r8?ZztHcIitGAsD$YAZ6P)%$~q ztp-!lE)L(T$opwyoOkv4>m=?fKW2Q0Z(J$@|9E`K_}nB8A_neM)H#V_(*WDP-uIoW zooY(_dmsUUl6M@@tRjXvg`yaNh%rt-IokiO3?Z02onm8yUz}&(fvFiDOJQySarfld zaE3A9$reiWtvP);F#NpNf#=kCyj1-hTN0G0EBdV$?ygUN95j8ttZQsR*@8v|qz+{MhTc#Kj;cPjy1R@42uwL#?RE=}B*v|E)uE8XY2 zi@R(f-6%piW*Yj>%du6?5D^8v7IQ(FB6omA#rCyVZWx`Qc?e}-pPPh4v zNHM|1HZ!0q-D}Dvq2~CbB5?e$L<&?H?hmB6qmdf~6%&kzu^m2elg;lVO_m-j+^@8e zx0JMU3I8koi>;BBBdcj%AU^eO-JUbKZe2N7<~*yy!xX{c83xWJlFNg)f^kdwIc%RZ zzdE!sy_YWq_J*fAb_YMw{Kxe>)w)VL&G%k~(b?ZpGLO7VBS|~$2+2@=!otzK^X05= zhZ)GU62ep*GL4=qoJ3uQ9m{u7OP6d~I2zea6x9Yxhgmp;MM`7SC|;8LvlYMAsOwW; z?d%hKqAA8o>NB8u+%AA07g;>_aa-=#ru)81L2$G zMDpr4X|_{Bw1&**D(^&VYvg2Vtm5z$Da&b!Bp^60U(0`COjUn6u9!5&P}axFAfQb3 z4%aM7p+jDZgmzaR8hCC&V?1M7V?rqAf6Dtw-6#`YLip`_68Z!tAy&7y6jcJBZS=IW zCHsX;gzyX}#=bmy6pNjnp?W~5!q-79A%qB;>5(YoOxK|O+NIV=%CG2B1o1yu08M09 ziq!ozF|9P*W{VrTMiUA68oOqdE=n(JQjBhdF9@3Us$^cM5#w2G$zc{ zI24h@i)_>mQ?>R0L){B7?Sb_P>v{tcj^Kj8&nfDNwvDH+jyfb?|tUkWz2J-3p!sO`&Xy%P`}X_!DhJVTutuExth=bN?aaJ*c_z5p6%`nGv2 zO^rwG?Hz|E^?)nK5q=rYgm$r48hLd}#4SZg6Y#VJw3!SqEHTIyr8ae4dm0Q@k@*pV z%dwS(aYDKSR732oCD(RG6QHtUsxqz+r5tZL`e6=YJCr06eku#-OWG7VXe&jQGhLOX z!d=LfaMZ1iHDHn)vyv0_Nmh@j^A!R2_oja7eL;8c==g8@YS%>;N@xc8q&ZFgs0jww zZKU$L_^*^nSY9)ESIZ+o&8y8mBHtj%`q1RBpvluAbuapuxJsE*{9Mzy?|deFn!zA# zu5cQ=(!Is19h3Hqq@8cP%Cf!vcv%!SrG`-}$8h7}UVKGx)RA#`IQiZ*fiL6-gk>i;J;dKWPT1}joJml&j@AYyxDk_GLrd`|mk zL>x0E=h8P?7r8V|%aQR*O_XJ_!XwPK;RZw-!=SB22JE>V#o~2m#h9KNx6RiPHsEKw z7L$H6dN6~u)<|ujbX*$qd2$1{W3{(Q{X^#8#MHY*(t-=)lxfVL6kEFPOV$3VTp{8r zy=+U2*;RI1LQmgvBVh2p8qYi(=6ICe&v@4dMXMzo1cuX7W}FA1bguMKjwH`$a&-!2 zCLL(*LzYYM5IW0)n1XfW5iLiP@s(Zsq2FX$5N1hM+7{AA1 zoi;Z;kBtS6E}ctDzF7hoG{># zYyQkz<#7|&&BKVrmPWE6^A--zk1NxkSvGd|t%jE>zP0{M-t@($_)HSp%BzVr+#98g z<_V>wW?rj7xIN=hyg2*1yHZ(EB0hOJv}mwE)%a3!p3bpSc}<1w>$OgrcOm(G-ZC_Q z=W`1Msa{3)36RM)@+5I69){RZE>L%jc8YQSaT<24MLhy2{(>H!@DKKz*#!|VZoHLO z`=h2Ud7MO%!|>~r$LQJ)oA^nb&tf^me=Ri115@Sg<8{SFP z!nLi=y58lNFB;A?2C00PM%(R?X=((jZ|_L8Q*Zwu(4@SqYxU)29WtNzC-b2TX@c8@ z0!Eu|MdsvlNqElQn_KSL1t}3J+G7@PP>Z0x3%kR8_Nj1?%R%TsO2C|Yn;LKEhvH|# zXH2A*#7Y-AC2rnZ^AJ-@{yt(MyJ^oqal4vpdeORfd%vxQ?b%#^%*S6;p)x90^p37k z`o|z{_4(LPcRWT8y0bCbHuWdDxXX{6~?XGu;=| zG1%oR4eyV^(%25!I5yl6!}F9%wuJGd2a*yzTesCrZ}|ZKVGgc^yZ6zMcDDrUV4v)Q zeShD%JP%Wq&a3ki*56A0z#5Dh zE0+fvHVFRru~}=XF@<|;lc`fO?|0zVlH)-6M~m2ZhF$z^)#)y;^HPZ*1QvB~&bBK)NbHL8v#+5S z$ZNhpnBaE5N2E5=&JKCoT0;rZszIQxbHJ0ClS|H+_c(I5!7t65PMw-T?d84o>B}}P zMxJ$Ef*M-ByELgp!a|NFzz1@8`X)J|6;i6t8Wo88Q&u;9AeLK$f-)w zg-jwSjW%i zHR&CKLG+3@zjXYDfr(Hg=b1ofWJ=%)iU`vK6aEvqA&-%DOOSjwWyMXOV*WSQ?RiAv# zY@8=BYB8wWF9%Sh1^(5UZg^NGbh^IF<{upQ#-9Rrg=pUYcV_A#IM?`ThD7B{g;lU= zK3MW5ibHrn(+|@pd(~^jXiblC5-yHwEISD$RKmu`KQODk&$kM@XB$c{P3R#UlO03w zZcq5e{<#;q*T&JW8UQt@_?D-iLTLa>dm!6VBVXY4S*@l#`s@*Y?3gB07@}51XP!3QS*I@2+4Qi8)g)uQITo{BDu3-r@0Zsz~=9IU2~WK&X+u ziA>RWn9p(6pe3^`WFkBUo9+>@eo8h(l4G~P5hUAYT%EovpfU`}l5%dMP!bN?Nj8H# zECbdrqeRcHY(BFYhH%dTzaUu^D(;__=e4cD0UmxD&yN)uDzM~R44gZm%;HnMiEMf5 zMxDkv`t*(i3F{`-mkfS!U4m2_E4oo<80#lb(P1@Dr@UgVaIFQcF5CD-zGjg4%o#-d zd0JWn29iW;JHbmoHX!}sWv-*iVd_leql;>TW^em(U1{I+Urj^F8$TPnP|a}V?ijaD z3xuPP9Ht2E5v7Oo%KX?L4V^^piI-aP&wQJzi<4EUP8pozvVHtC*UB(^Nd4Rwt_xzJ zO7zp7ULV1uotGw0V2Uco_oemG&B&aW@=rltkmc&r6ha%hCUSerpjs&T z_mQj4-3av|N%!f;QJn>=ICyd|y&-pIBIm?~ji~lz!^O6mSJJJW>aFeDs15lah_qLn z-0JbqtjC4229f|t$gwZxnI5MM(?O&HH%B zxvUZkbxcFM%X_+lnfFP5@XP20dlB9c32<;GKi0zWuTv3M%bk}q;aGak9H!5o7L(zT zf;h?CeW=Sy4O;~(uE#drXmC-Vb7b5&n|QY|f|>zzVAV*+y!gjXM=i#$nM zg#0T7h=TAUJDVpDsV?6D(bvi`V5$c*vlb%Bxc^`Qr+W~ExCtq3n10yDvjwYE1Hg*d zoN-;o_q{+lI{o?C33t*qc{*Jg6+UET3LYLVvYo9GZ?Xd*ap{bV?W(+AS_kCEy|h1o zRA`RqyHm&UC*F{_8KoXZ{}(c)v5r-5OSzi%&RV^OeuATvN7Mp%C)Gody$53Tk)6CT z2S2!G@_2?fO&lLu&Ga}6XGsWWRt6bPyz94qb2P7M`_g6>6-X&P%|ltEca-US+2-6| z-|z09T%KIJsv=bG_fk4SMzkg@qTI=!Y^s?rH-$Vg!i=?~Z`yl;dUo$#u+C=*p|7Q@a_G|=W6b<&W}tmnT0l`Zz+6#HOiO93 z_+orUjZGO-4O_Rv8}dg}j*W|(O@Rw*+r_l9_h(J(mTYcT9qtjgDFx5j?9mb`%qbNs z&_LsB;>iCVU)wWMsC0T0OWh1akDIGKOy3t-R3r;Nd4rg2B;Cft_8nI@u$w6m*OGkl z-|f_m>vre3y!3;SlHht2e%TPgV2KapfgXt3is=lX9yUQtF}ei-E@&1v<65d(D)wsg);;Naw$5!|HVoM8a+qM;U7J zMya=yn^al#{?M&c5mVUpXwpjM|@H0AoSKL?6xL%|o*)@snPOyX%*PG6&9HOO)KJOm9 zqSY#cZ%y7@abw^5+8|~NQr5I*{#SeF9!>R{#_{c>iw231j!@cNT?%KZTvKi-GKEBT zg*uK~)OH_R?Cv5-DwlnnQAzBI~E6mjH|h$8IseMQrmwPx0twPxnbAO2~r zcCq*V{eJKByzldTpU-!>EhLWsw9N{=4b9p}H;wZ*blfZ~^0U?~5nizV(hyXbM;6`R zi1NEP94vJ$C2i|PYS3`_NN-^;RnZ9&Kg+p7sMx*Ocps9FU8v!?Sn)8g`TWhkPfy9t zQvzXVw(AgRL7hg7(a@-u8+>7W2NB5+5qy`nX62w>%y?g|JAOg`>bKsw31x3(heZb< zi&OgL3W3pgh4LC*?B(A5P(oZsa-~(OW20$V_EBv?K~BxVOpAlg2^OhxG2`C5=gVY6 zvi3@5&GlAM{h-YSd!%^O9%(OnMCBj7sO|(hTwds~g6uJ^WwZ!QhC;Rtf1}juN{~Jp zA{NW>pE*5ZmX)^Uw!>NU0+~fD0c^PJY7@lRc8nUC5Qiy~f_cVXlSt#g|8+$SrIx>K zx{dcAlMDQRb@9J;Mffj>1pe8t*AwAtLQ@TA|06awJ-`YhmqCz2r*6UK9s?GN05(l` zmY1jK&*$<1DBuOQ2Vh5*R94c^y~3a?DPNiig2u85q`9xUm-i@aPp7x@ciJarcM1h{ zH*Z#Q?>d!b5@AnsPb+O{d-iO=)Y)ph=)I|aq@=W(9BrJ zGho_2=9*_<5bUksu5CEnAN`Kzfki^-ZIHxI04(znM(Sly7UCN~WBFv&7?5WDo#6kg z!_to91Ia^QE@uKQXIWZ~PetS=pz3ZF*5HeqS$W!`aTdvf1{4F`-K$PmU$YN~1_AV4 z94*2X9~X$dBE$|1vF_y8JHKe83%Ig7*5S49M0Cf09>+01ixHV63B2Z`i-V+OV3*KBMl>^a+y8Q zN)UGkbOQ@bkk8&|i|*g13f)q7RZIbGVnDRQ=(@ZwhDp>qeD2e{VkCb10LEbx5Ev7RlKALvK6shRBe9e_wc~>>T_$U9 znw2I!ucj8F2Z@lA0tRpcORowv2{8K9#Dm2*xnh(RL8Zv#e9%qi{_-7+91wE*mf*;o!jEfCPW65ku8?k82E75?x=HfNSM!u)=lv z0sj;Z6O!hEm&e0Hn%lEWKL!WTI8CQ_M60Gl%ZsOzS|qI#i07>qRA}9xV4xbw4hh9( zBn(ENw;K`$?z0=uGprrLBTj8{Ed~9DR8)ZFh6vzJ?DFYrd!M0SKP&5qZeP58ONqn9 z-mG%ghteORNfptgW=U8DpeB0zsEs~R^g7M1P_XMc^(~7qnx`OjtFEz}b96BdYnV#!R z=ok2xRtukF?{8fK{3qQD;2|>U1Tnc1zJp55VUu6m?rG4p1K=P@)8Qu==wT3 z`}SRLyep1I8JW0-GHNV*&mGg=GtxnEP_Ag1r9iRo+>!(RZ9@j#{s9HF=N`DRqX|#s zTr8AV8KA*p??j#p44JDKTg6TJE+`o9+NC)NS=6m55v9y-{Qk?LOG>ChpJ)`qCdFv^ z2#&vPx$O{ar$faXSH)y!03tTge_8|a`+JBO7UDoN+yMRmIPi>r!ODi0f+3oj6#%3} zk@SKisUkQpY`!0?jc@8}aqXB3$D)w1urOw_?dPENSRd z%-y}^XaOG_Bc-$}H1IF@`p2|Z@If|_FC57GL^o0G{$&2%c8yXXJSZ$JvRfpemP)0L zip7GdzBJt~Y;791UH!e6NV%CF?+!g!o$-NW-c!a*?}Egdi79a9W{)<2WI+i$ z*QgrV8Czabl2nTHVgEdSh?M<}kh=>tnh*gY%p-Ut>|N9VEkq~Y9caQmasBqq0%ZXE zRDf+9Qsv%DJcN8WoVc)kx|Mdvrr`*RE1`u9-1~#A;_@9A>N-; z9F6eN*ofqb=E*lP((#KG^POU)s-nxQZN6^+fEc(sE31ecWbG0y(*-YJ@6rb)w#u?g zftCxVwp}nx(xG&A_SC;0iygbrcr4s;b#b{nkDyQ823c7?hS`e@dNms;?N)@|1D$|w zT~IEgY9Tbf)Bme$uTfV2@X~mwTO#ST<{O!y8wQUiLaXVOp+1*7wa0oL3Bh0Gc+p-5hZ6f*#5qEjKaHSs%rI>9W$h7z-RgeBwWP>mA z?AK9DwMX7=?d*)<#IiXJ6^&!p7^%MM5gg023K)LInMec8il-1yLaHPpZ$-R(j!xh~ zTMDP8li-XE^}j5D@O23FVckF*6x+y;kOr3b+^ca8sh!|J4RzI!K{*DlZF4UwniL>2>F60EZ^FMmYjcE*bYf>l_zk^ldw-+QO&7)BC1Pw{Jm2tAD4PB=FV9?)nFnHKHOz#9 uneh0>=Z1W2mpGg%Ro1uoEOW?Kdltpr*vv@XQOXLhQ1rD8x3RSj`2PwqYnHwM literal 0 HcmV?d00001 diff --git a/frontend/src/components/AutoUpdatePoliciesView.tsx b/frontend/src/components/AutoUpdatePoliciesView.tsx new file mode 100644 index 00000000..24693ddb --- /dev/null +++ b/frontend/src/components/AutoUpdatePoliciesView.tsx @@ -0,0 +1,570 @@ +import { useState, useEffect, useCallback } from 'react'; +import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from '@/components/ui/table'; +import { Button } from '@/components/ui/button'; +import { Input } from '@/components/ui/input'; +import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select'; +import { Combobox } from '@/components/ui/combobox'; +import { Badge } from '@/components/ui/badge'; +import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card'; +import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogFooter } from '@/components/ui/dialog'; +import { AlertDialog, AlertDialogAction, AlertDialogCancel, AlertDialogContent, AlertDialogDescription, AlertDialogFooter, AlertDialogHeader, AlertDialogTitle } from '@/components/ui/alert-dialog'; +import { Sheet, SheetContent, SheetHeader, SheetTitle } from '@/components/ui/sheet'; +import { Switch } from '@/components/ui/switch'; +import { Label } from '@/components/ui/label'; +import { RefreshCw, Plus, Pencil, Trash2, History, Play, ChevronLeft, ChevronRight, Download } from 'lucide-react'; +import { toast } from '@/components/ui/toast-store'; +import { apiFetch, fetchForNode } from '@/lib/api'; +import { ProGate } from '@/components/ProGate'; +import cronstrue from 'cronstrue'; + +interface ScheduledTask { + id: number; + name: string; + target_type: 'stack' | 'fleet' | 'system'; + target_id: string | null; + node_id: number | null; + action: 'restart' | 'snapshot' | 'prune' | 'update'; + cron_expression: string; + enabled: number; + created_by: string; + created_at: number; + updated_at: number; + last_run_at: number | null; + next_run_at: number | null; + last_status: string | null; + last_error: string | null; +} + +interface TaskRun { + id: number; + task_id: number; + started_at: number; + completed_at: number | null; + status: 'running' | 'success' | 'failure'; + output: string | null; + error: string | null; + triggered_by: 'scheduler' | 'manual'; +} + +interface NodeOption { + id: number; + name: string; +} + +const CRON_PRESETS = [ + { label: 'Every 6 hours', value: '0 */6 * * *' }, + { label: 'Every 12 hours', value: '0 */12 * * *' }, + { label: 'Daily at 3 AM', value: '0 3 * * *' }, + { label: 'Daily at midnight', value: '0 0 * * *' }, + { label: 'Weekly (Sunday 3 AM)', value: '0 3 * * 0' }, + { label: 'Custom', value: 'custom' }, +]; + +function getCronDescription(expression: string): string { + try { + return cronstrue.toString(expression); + } catch { + return 'Invalid expression'; + } +} + +function formatTimestamp(ts: number | null): string { + if (!ts) return '-'; + return new Date(ts).toLocaleString(); +} + +function AutoUpdatePoliciesContent() { + const [policies, setPolicies] = useState([]); + const [loading, setLoading] = useState(true); + const [dialogOpen, setDialogOpen] = useState(false); + const [editingPolicy, setEditingPolicy] = useState(null); + const [deleteTarget, setDeleteTarget] = useState(null); + const [runsTask, setRunsTask] = useState(null); + const [runs, setRuns] = useState([]); + const [runsLoading, setRunsLoading] = useState(false); + + // Form state + const [formName, setFormName] = useState(''); + const [formTargetId, setFormTargetId] = useState(''); + const [formNodeId, setFormNodeId] = useState(''); + const [formCron, setFormCron] = useState('0 3 * * *'); + const [formCronPreset, setFormCronPreset] = useState('0 3 * * *'); + const [formEnabled, setFormEnabled] = useState(true); + const [saving, setSaving] = useState(false); + const [runningPolicyId, setRunningPolicyId] = useState(null); + const [runsPage, setRunsPage] = useState(1); + const [runsTotal, setRunsTotal] = useState(0); + const runsLimit = 20; + + // Available stacks and nodes + const [stacks, setStacks] = useState([]); + const [nodes, setNodes] = useState([]); + + const fetchPolicies = useCallback(async () => { + setLoading(true); + try { + const res = await apiFetch('/scheduled-tasks', { localOnly: true }); + if (res.ok) { + const all: ScheduledTask[] = await res.json(); + setPolicies(all.filter(t => t.action === 'update')); + } + } catch { + // Non-critical + } finally { + setLoading(false); + } + }, []); + + const fetchStacks = useCallback(async (nodeId?: string) => { + try { + const res = nodeId + ? await fetchForNode('/stacks', parseInt(nodeId, 10)) + : await apiFetch('/stacks'); + if (res.ok) setStacks(await res.json()); + else setStacks([]); + } catch { setStacks([]); } + }, []); + + const fetchNodes = useCallback(async () => { + try { + const res = await apiFetch('/nodes', { localOnly: true }); + if (res.ok) { + const data = await res.json(); + setNodes(data.map((n: { id: number; name: string }) => ({ id: n.id, name: n.name }))); + } + } catch { /* Non-critical */ } + }, []); + + useEffect(() => { + fetchPolicies(); + fetchStacks(); + fetchNodes(); + }, [fetchPolicies, fetchStacks, fetchNodes]); + + // Re-fetch stacks when selected node changes in the dialog + useEffect(() => { + if (dialogOpen && formNodeId) { + fetchStacks(formNodeId); + setFormTargetId(''); + } + }, [formNodeId, dialogOpen, fetchStacks]); + + const openCreate = () => { + setEditingPolicy(null); + setFormName(''); + setFormTargetId(''); + setFormNodeId(''); + setFormCron('0 3 * * *'); + setFormCronPreset('0 3 * * *'); + setFormEnabled(true); + setDialogOpen(true); + }; + + const openEdit = (policy: ScheduledTask) => { + setEditingPolicy(policy); + setFormName(policy.name); + setFormTargetId(policy.target_id || ''); + setFormNodeId(policy.node_id != null ? String(policy.node_id) : ''); + setFormCron(policy.cron_expression); + const matchingPreset = CRON_PRESETS.find(p => p.value === policy.cron_expression); + setFormCronPreset(matchingPreset ? matchingPreset.value : 'custom'); + setFormEnabled(policy.enabled === 1); + setDialogOpen(true); + }; + + const handleSave = async () => { + const body: Record = { + name: formName, + target_type: 'stack', + action: 'update', + target_id: formTargetId, + node_id: formNodeId ? parseInt(formNodeId, 10) : null, + cron_expression: formCron, + enabled: formEnabled, + }; + + setSaving(true); + try { + const res = editingPolicy + ? await apiFetch(`/scheduled-tasks/${editingPolicy.id}`, { method: 'PUT', body: JSON.stringify(body), localOnly: true }) + : await apiFetch('/scheduled-tasks', { method: 'POST', body: JSON.stringify(body), localOnly: true }); + + if (res.ok) { + toast.success(editingPolicy ? 'Policy updated' : 'Policy created'); + setDialogOpen(false); + fetchPolicies(); + } else { + const data = await res.json().catch(() => ({})); + toast.error(data?.error || 'Failed to save policy'); + } + } catch (error: unknown) { + const msg = error instanceof Error ? error.message : 'Something went wrong.'; + toast.error(msg); + } finally { + setSaving(false); + } + }; + + const handleToggle = async (policy: ScheduledTask) => { + try { + const res = await apiFetch(`/scheduled-tasks/${policy.id}/toggle`, { method: 'PATCH', localOnly: true }); + if (res.ok) { + fetchPolicies(); + } else { + const data = await res.json().catch(() => ({})); + toast.error(data?.error || 'Failed to toggle policy'); + } + } catch { + toast.error('Something went wrong.'); + } + }; + + const handleDelete = async () => { + if (!deleteTarget) return; + try { + const res = await apiFetch(`/scheduled-tasks/${deleteTarget.id}`, { method: 'DELETE', localOnly: true }); + if (res.ok) { + toast.success('Policy deleted'); + setDeleteTarget(null); + fetchPolicies(); + } else { + const data = await res.json().catch(() => ({})); + toast.error(data?.error || 'Failed to delete policy'); + } + } catch { + toast.error('Something went wrong.'); + } + }; + + const openRuns = async (task: ScheduledTask, page = 1) => { + setRunsTask(task); + setRunsPage(page); + setRunsLoading(true); + const offset = (page - 1) * runsLimit; + try { + const res = await apiFetch(`/scheduled-tasks/${task.id}/runs?limit=${runsLimit}&offset=${offset}`, { localOnly: true }); + if (res.ok) { + const data = await res.json(); + setRuns(data.runs); + setRunsTotal(data.total); + } + } catch { /* Non-critical */ } + finally { setRunsLoading(false); } + }; + + const handleRunNow = async (policy: ScheduledTask) => { + setRunningPolicyId(policy.id); + try { + const res = await apiFetch(`/scheduled-tasks/${policy.id}/run`, { method: 'POST', localOnly: true }); + if (res.ok) { + toast.success(`Checking for updates on "${policy.target_id}"...`); + fetchPolicies(); + } else { + const data = await res.json().catch(() => ({})); + toast.error(data?.error || 'Failed to run policy'); + } + } catch { + toast.error('Something went wrong.'); + } finally { + setRunningPolicyId(null); + } + }; + + const cronDescription = getCronDescription(formCron); + + return ( +

+ + +
+
+ + Auto-Update Policies +
+
+ + +
+
+

+ Automatically check for new images and update your stacks on a schedule. +

+
+ + {loading && policies.length === 0 ? ( +
Loading...
+ ) : policies.length === 0 ? ( +
+ No auto-update policies yet. Create one to keep your stacks up to date automatically. +
+ ) : ( + + + + Name + Stack + Schedule + Status + Last Run + Next Run + Enabled + Actions + + + + {policies.map((policy) => ( + + {policy.name} + {policy.target_id === '*' ? 'All Stacks' : policy.target_id} + +
{getCronDescription(policy.cron_expression)}
+
{policy.cron_expression}
+
+ + {policy.last_status === 'success' ? ( + Success + ) : policy.last_status === 'failure' ? ( + Failed + ) : ( + Never run + )} + + + {formatTimestamp(policy.last_run_at)} + + + {formatTimestamp(policy.next_run_at)} + + + handleToggle(policy)} + /> + + +
+ + + + +
+
+
+ ))} +
+
+ )} +
+
+ + {/* Create/Edit Dialog */} + + + + {editingPolicy ? 'Edit Auto-Update Policy' : 'New Auto-Update Policy'} + +
+
+ + setFormName(e.target.value)} /> +
+ +
+ + ({ value: String(n.id), label: n.name }))} + value={formNodeId} + onValueChange={setFormNodeId} + placeholder="Select node..." + searchPlaceholder="Search nodes..." + emptyText="No nodes found." + /> +
+ +
+ + ({ value: s, label: s })), + ]} + value={formTargetId} + onValueChange={setFormTargetId} + placeholder={formNodeId ? "Select stack..." : "Select a node first"} + searchPlaceholder="Search stacks..." + emptyText="No stacks found." + disabled={!formNodeId} + /> +
+ +
+ + + {formCronPreset === 'custom' && ( + setFormCron(e.target.value)} + className="font-mono" + /> + )} +

{cronDescription}

+
+ +
+ + +
+
+ + + + +
+
+ + {/* Delete Confirmation */} + { if (!open) setDeleteTarget(null); }}> + + + Delete Auto-Update Policy + + Are you sure you want to delete “{deleteTarget?.name}”? This will also remove all execution history. This action cannot be undone. + + + + Cancel + + Delete + + + + + + {/* Run History Sheet */} + { if (!open) setRunsTask(null); }}> + + +
+ Update History - {runsTask?.name} + {runsTask && runs.length > 0 && ( + + )} +
+
+
+ {runsLoading ? ( +
Loading...
+ ) : runs.length === 0 ? ( +
No executions yet.
+ ) : ( + <> + + + + Time + Source + Status + Duration + Details + + + + {runs.map((run) => { + const duration = run.completed_at && run.started_at + ? `${((run.completed_at - run.started_at) / 1000).toFixed(1)}s` + : '-'; + return ( + + + {new Date(run.started_at).toLocaleString()} + + + + {run.triggered_by === 'manual' ? 'Manual' : 'Scheduled'} + + + + {run.status === 'success' ? ( + Success + ) : run.status === 'failure' ? ( + Failed + ) : ( + Running + )} + + {duration} + + {run.error || run.output || '-'} + + + ); + })} + +
+ {Math.ceil(runsTotal / runsLimit) > 1 && runsTask && ( +
+

+ Page {runsPage} of {Math.ceil(runsTotal / runsLimit)} +

+
+ + +
+
+ )} + + )} +
+
+
+
+ ); +} + +export default function AutoUpdatePoliciesView() { + return ( + + + + ); +} diff --git a/frontend/src/components/EditorLayout.tsx b/frontend/src/components/EditorLayout.tsx index 7cd7d9f9..860141ea 100644 --- a/frontend/src/components/EditorLayout.tsx +++ b/frontend/src/components/EditorLayout.tsx @@ -43,6 +43,7 @@ import { GlobalObservabilityView } from './GlobalObservabilityView'; import { FleetView } from './FleetView'; import { AuditLogView } from './AuditLogView'; import ScheduledOperationsView from './ScheduledOperationsView'; +import AutoUpdatePoliciesView from './AutoUpdatePoliciesView'; import { useNodes } from '@/context/NodeContext'; import type { Node } from '@/context/NodeContext'; import { useAuth } from '@/context/AuthContext'; @@ -126,7 +127,7 @@ export default function EditorLayout() { window.matchMedia('(prefers-color-scheme: dark)').matches ); const isDarkMode = theme === 'dark' || (theme === 'auto' && systemDark); - const [activeView, setActiveView] = useState<'dashboard' | 'editor' | 'host-console' | 'resources' | 'templates' | 'global-observability' | 'fleet' | 'audit-log' | 'scheduled-ops'>('dashboard'); + const [activeView, setActiveView] = useState<'dashboard' | 'editor' | 'host-console' | 'resources' | 'templates' | 'global-observability' | 'fleet' | 'audit-log' | 'scheduled-ops' | 'auto-updates'>('dashboard'); const [isEditing, setIsEditing] = useState(false); const [searchQuery, setSearchQuery] = useState(''); const [stackStatuses, setStackStatuses] = useState({}); @@ -168,6 +169,9 @@ export default function EditorLayout() { { value: 'templates', label: 'App Store', icon: CloudDownload }, { value: 'global-observability', label: 'Logs', icon: Activity }, ); + if (isPro && isAdmin) { + items.push({ value: 'auto-updates', label: 'Auto-Update', icon: RefreshCw }); + } if (isPro && license?.variant === 'team') { if (isAdmin) items.push({ value: 'host-console', label: 'Console', icon: Terminal }); if (can('system:audit')) items.push({ value: 'audit-log', label: 'Audit', icon: ScrollText }); @@ -445,6 +449,10 @@ export default function EditorLayout() { refreshStacks(); fetchImageUpdates(); + + // Poll for image update results every 5 minutes so background checks are picked up + const imageUpdateInterval = setInterval(fetchImageUpdates, 5 * 60 * 1000); + return () => clearInterval(imageUpdateInterval); }, [activeNode?.id]); // eslint-disable-line react-hooks/exhaustive-deps const fetchNotifications = async () => { @@ -979,6 +987,7 @@ export default function EditorLayout() { setContainers(Array.isArray(conts) ? conts : []); } await refreshStacks(true); + if (action === 'update') fetchImageUpdates(); if (action === 'deploy' && isPro) { try { const backupRes = await apiFetch(`/stacks/${stackName}/backup`); @@ -999,7 +1008,25 @@ export default function EditorLayout() { const res = await apiFetch('/image-updates/refresh', { method: 'POST' }); if (res.ok) { toast.success('Checking for image updates...'); - setTimeout(() => fetchImageUpdates(), 3000); + // Poll until the background check completes instead of using a fixed timeout + let elapsed = 0; + const poll = setInterval(async () => { + elapsed += 2000; + try { + const statusRes = await apiFetch('/image-updates/status'); + if (statusRes.ok) { + const { checking } = await statusRes.json(); + if (!checking || elapsed >= 60000) { + clearInterval(poll); + await fetchImageUpdates(); + if (!checking) toast.success('Image update check complete.'); + } + } + } catch { + clearInterval(poll); + await fetchImageUpdates(); + } + }, 2000); } else { const data = await res.json().catch(() => ({})); toast.error(data.error || 'Failed to check for updates'); @@ -1809,6 +1836,8 @@ export default function EditorLayout() { }} /> ) : activeView === 'audit-log' ? ( + ) : activeView === 'auto-updates' ? ( + ) : activeView === 'scheduled-ops' ? ( ) : ( diff --git a/frontend/src/components/ScheduledOperationsView.tsx b/frontend/src/components/ScheduledOperationsView.tsx index 5130564e..881162a7 100644 --- a/frontend/src/components/ScheduledOperationsView.tsx +++ b/frontend/src/components/ScheduledOperationsView.tsx @@ -2,7 +2,6 @@ import { useState, useEffect, useCallback } from 'react'; import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from '@/components/ui/table'; import { Button } from '@/components/ui/button'; import { Input } from '@/components/ui/input'; -import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select'; import { Badge } from '@/components/ui/badge'; import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card'; import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogFooter } from '@/components/ui/dialog'; @@ -13,7 +12,8 @@ import { Label } from '@/components/ui/label'; import { Checkbox } from '@/components/ui/checkbox'; import { Clock, Plus, Pencil, Trash2, History, RefreshCw, Play, ChevronLeft, ChevronRight, Download } from 'lucide-react'; import { toast } from '@/components/ui/toast-store'; -import { apiFetch } from '@/lib/api'; +import { apiFetch, fetchForNode } from '@/lib/api'; +import { Combobox } from '@/components/ui/combobox'; import cronstrue from 'cronstrue'; interface ScheduledTask { @@ -117,9 +117,11 @@ export default function ScheduledOperationsView() { } }, []); - const fetchStacks = useCallback(async () => { + const fetchStacks = useCallback(async (nodeId?: string) => { try { - const res = await apiFetch('/stacks'); + const res = nodeId + ? await fetchForNode('/stacks', parseInt(nodeId, 10)) + : await apiFetch('/stacks'); if (res.ok) { setStacks(await res.json()); } @@ -166,6 +168,17 @@ export default function ScheduledOperationsView() { return () => { cancelled = true; }; }, [formAction, formTargetId]); + // Re-fetch stacks when node changes + useEffect(() => { + if (!dialogOpen) return; + if (formNodeId) { + fetchStacks(formNodeId); + setFormTargetId(''); + } else { + setStacks([]); + } + }, [formNodeId, dialogOpen, fetchStacks]); + const openCreate = () => { setEditingTask(null); setFormName(''); @@ -435,45 +448,34 @@ export default function ScheduledOperationsView() {
- + ({ value: o.value, label: o.label }))} + value={formAction} + onValueChange={(val) => { setFormAction(val); setFormTargetId(''); setFormNodeId(''); setFormTargetServices([]); setFormPruneLabelFilter(''); }} + placeholder="Select action..." + />
{targetType === 'stack' && ( <>
- - + + ({ value: String(n.id), label: n.name }))} + value={formNodeId} + onValueChange={setFormNodeId} + placeholder="Select node..." + />
- - + + ({ value: s, label: s }))} + value={formTargetId} + onValueChange={setFormTargetId} + placeholder={formNodeId ? "Select stack..." : "Select a node first"} + disabled={!formNodeId} + />
{formAction === 'restart' && formTargetId && availableServices.length > 0 && (
diff --git a/frontend/src/components/ui/combobox.tsx b/frontend/src/components/ui/combobox.tsx new file mode 100644 index 00000000..260acf11 --- /dev/null +++ b/frontend/src/components/ui/combobox.tsx @@ -0,0 +1,148 @@ +import * as React from "react" +import { Check, ChevronsUpDown } from "lucide-react" + +import { cn } from "@/lib/utils" + +export interface ComboboxOption { + value: string + label: string +} + +interface ComboboxProps { + options: ComboboxOption[] + value: string + onValueChange: (value: string) => void + placeholder?: string + searchPlaceholder?: string + emptyText?: string + disabled?: boolean + className?: string +} + +export function Combobox({ + options, + value, + onValueChange, + placeholder = "Select...", + searchPlaceholder, + emptyText = "No results found.", + disabled = false, + className, +}: ComboboxProps) { + const [open, setOpen] = React.useState(false) + const [search, setSearch] = React.useState("") + const wrapperRef = React.useRef(null) + const inputRef = React.useRef(null) + + const selectedLabel = options.find((o) => o.value === value)?.label + + const filtered = search + ? options.filter((o) => + o.label.toLowerCase().includes(search.toLowerCase()) + ) + : options + + React.useEffect(() => { + if (!open) return + const handler = (e: MouseEvent) => { + if (wrapperRef.current && !wrapperRef.current.contains(e.target as Node)) { + setOpen(false) + setSearch("") + } + } + document.addEventListener("mousedown", handler) + return () => document.removeEventListener("mousedown", handler) + }, [open]) + + React.useEffect(() => { + if (!open) return + const handler = (e: KeyboardEvent) => { + if (e.key === "Escape") { + e.stopPropagation() + setOpen(false) + setSearch("") + } + } + document.addEventListener("keydown", handler, true) + return () => document.removeEventListener("keydown", handler, true) + }, [open]) + + const handleSelect = (option: ComboboxOption) => { + onValueChange(option.value === value ? "" : option.value) + setOpen(false) + setSearch("") + } + + return ( +
+ {/* Trigger: static button when closed, inline search input when open */} + {open ? ( +
+ setSearch(e.target.value)} + placeholder={searchPlaceholder ?? selectedLabel ?? placeholder} + className="h-full w-full bg-transparent text-sm outline-none placeholder:text-muted-foreground" + autoFocus + /> + +
+ ) : ( + + )} + + {/* Options list — absolutely positioned overlay */} + {open && ( +
+
+ {filtered.length === 0 ? ( +
+ {emptyText} +
+ ) : ( + filtered.map((option) => ( + + )) + )} +
+
+ )} +
+ ) +} diff --git a/frontend/src/components/ui/toast.tsx b/frontend/src/components/ui/toast.tsx index c83470b8..c9223d45 100644 --- a/frontend/src/components/ui/toast.tsx +++ b/frontend/src/components/ui/toast.tsx @@ -113,7 +113,7 @@ function ToastItem({ id, type, message }: { id: string; type: ToastType; message animate={{ opacity: 1, x: 0 }} exit={{ opacity: 0, x: 100 }} transition={{ duration: 0.3 }} - className="relative w-full max-w-sm rounded-xl p-4 backdrop-blur-xl bg-white/15 dark:bg-black/15 border border-gray-300/60 dark:border-gray-700/60 overflow-hidden ring-1 ring-gray-200/40 dark:ring-gray-700/40 drop-shadow-xl transition-all duration-300 ease-in-out transform hover:scale-105" + className="relative w-full max-w-sm rounded-xl p-4 backdrop-blur-xl bg-white/15 dark:bg-black/15 border border-gray-300/60 dark:border-gray-700/60 overflow-hidden ring-1 ring-gray-200/40 dark:ring-gray-700/40 drop-shadow-xl transition-all duration-300 ease-in-out transform hover:scale-105 font-[family-name:var(--font-sans)]" onMouseEnter={() => setHovered(true)} onMouseLeave={() => setHovered(false)} >