* feat(scheduler): schedule container restart, stop, and start
Add container as a scheduled-task target type so operators can automate lifecycle actions against standalone containers by node and name, with matching UI pickers, validation, execution on local and remote nodes, and tests.
* fix(scheduler): stack service matching and container picker hygiene
Backfill Service on smartFallback containers so per-service stack restarts work when container_name is set. Match services by compose label and container name in stack routes and scheduled restarts. Exclude Sencho from GET /api/containers lists. Hide the Restart Stack service picker when a stack has only one service.
* test(scheduler): scope service checkbox assertion to Services block
The create dialog also has a Delete after run checkbox. Count checkboxes only inside the Services section so CI does not include unrelated form controls.
* fix(scheduler): narrow closest() result to HTMLElement in schedule test
The service-checkbox assertion passed an Element from closest() into
within(), which requires an HTMLElement, failing tsc -b in the frontend
build and Docker build stages. Use the closest<HTMLElement>() type
argument so the value type-checks without an unsafe cast.
* fix(scheduler): hide Sencho container on remote node picker lists
Remote container lists are proxied from peer Sencho instances, so id-only self filtering missed peers on older builds. Await SelfIdentity init, match ImageID, and drop official saelix/sencho images. Apply the same heuristic in the scheduled-operations UI and when the hub fetches remote containers for scheduled runs.
* test(monitor): add missing DatabaseService mocks for scan history cleanup
* test(scheduler): add missing markStaleScansAsFailed mock
SchedulerService.tick() calls db.markStaleScansAsFailed() to sweep stale
vulnerability scans. The scheduler-service test was missing this method in
its DatabaseService mock, causing TypeError failures during test initialization.
Added mockMarkStaleScansAsFailed to hoisted mocks and DatabaseService mock
object, returning safe default of 0 scans marked as failed.
* test(compose): add missing FileSystemService mocks for getStackContent/getEnvContent
* test(containers-route): mock SelfIdentityService to prevent initialize() crash
The excludeSelfContainers() helper calls SelfIdentityService.initialize(), which tries to access DockerController. Without a proper SelfIdentityService mock, the initialize() call fails silently, causing a 500 error on GET /api/containers.
Added SelfIdentityService mock with initialize(), isOwnContainer(), and isOwnImage() methods to prevent the crash.
Timeline pills and the mobile schedule list now identify what each
scheduled run acts on instead of repeating the task name. Pills stay
compact (firing time plus a category-aware target) and carry the full
detail on hover:
- Stack actions show the stack name.
- Fleet snapshots show "Entire fleet".
- Fleet auto-updates and node-scoped prune/scan show the selected node.
- The hover tooltip adds the action label, task name, and node.
The mobile list resolves node names too, so prune and scan rows name the
node rather than the literal "system". A shared scheduleTargetDescriptor
helper removes the target-label logic that was duplicated across the
desktop and mobile views. The lifecycle lane is renamed "Stack lifecycle"
to match the action-picker category wording.
* feat(scheduler): add helper text and risk badges to scheduled action picker
Add a concise helper text and risk level badge to every scheduled action
in the create/edit modal. The six risk levels (Safe, Read-only, Interruptive,
Runtime change, Removes containers, Destructive) map to the four existing
design-system tones and render as a small dot+label chip next to the helper
text, following the same pattern as SeverityBadge.
Fix an ambiguous mobile label: update + target_type: fleet now resolves
through resolveTaskAction and renders 'update node stacks' instead of the
misleading 'update fleet'.
Add exact helper-text and risk-level assertions for all 10 actions, plus
component tests for default modal state, action-switch scenarios, and
mobile update+fleet rendering.
* docs: update stale scheduled-operations alt text for changed helper text
* feat(scheduler): group schedule action picker by operator intent
Reorganize the New Schedule action picker from a flat dropdown to a
category-grouped list (Lifecycle, Updates, Security, Maintenance, Backups).
- Extend Combobox component with optional group field on ComboboxOption,
rendering grouped sections with non-interactive headers when groups are
present. Flat rendering is unchanged for all other callers.
- Reorder SCHEDULED_ACTIONS by category group and update seven action
labels per the operator-intent spec.
- Add DEFAULT_SCHEDULED_ACTION_ID constant so picker order and form
defaults are independently controllable.
- Wire grouped actionOptions into ScheduledOperationsView.
- Update all label references in docs and tests.
- Add Combobox grouping tests, registry order test, and default-constant
test.
* fix(scheduler): correct Combobox grouping for interleaved groups, docs labels
- Replace last-group-append with Map-based group partitioning so
interleaved or mixed-group options land in the correct group.
- Add interleaved-groups test and restore non-interactivity test.
- Update stale "Start Stack" references to "Start / Bring Up Stack"
in doc action-label contexts.
- Update action-picker alt text to describe the new grouped order.
Give every scheduled action an explicit, predictable target model
(Action then Node then Stack then Options then Schedule):
- System Prune now exposes a Node picker and requires a node, so it can
no longer run silently on the default node.
- Vulnerability Scan and System Prune list local nodes only; both run on
the hub-local Docker daemon and reject remote nodes on the backend.
- Restart Stack service discovery loads services from the selected node
via fetchForNode instead of the active or local node.
- Fleet Snapshot shows a read-only "Scope: Entire fleet" summary.
Backend gains a shared local-node guard and prune node validation on
create and update, plus an executor-level remote-node guard, so the
frontend and backend validation now agree for every action.
Scheduled-operation action metadata was duplicated across the backend route
validator, the DatabaseService action union, the desktop action picker, the
Timeline lanes, and the mobile labels/tones. Adding or renaming one action meant
editing all of them.
Introduce one registry per package as the single source within that package:
- backend/src/services/scheduledActionRegistry.ts owns the action list and
target-type validation; routes/scheduledTasks.ts and DatabaseService import
from it (BackendScheduledAction type, VALID_ACTIONS, validateActionTarget).
- frontend/src/lib/scheduledActions.ts owns the UI metadata (labels, short
labels, categories, tones, target/node/stack/service flags, helper text) and
drives the create-flow picker, the All Tasks label, the Timeline lanes, and
the mobile schedule view.
Timeline lanes now group by semantic category (Lifecycle, Updates, Security,
Maintenance, Backups) sourced from the registry. The update-fleet UI alias is
made explicit via a backendAction field. Backend validation stays authoritative;
parity tests on each side keep the action sets in lockstep.