feat(fleet): reapply Compose configuration without a version update (#1716)

* feat(fleet): reapply Compose configuration without a version update

Add a distinct Fleet Reapply configuration path so Compose-managed nodes can recreate Sencho from the current on-disk project when already up to date, without pulling or rewriting the image reference.

* fix(fleet): confirm remote reapply and close concurrent tracker race

Require confirmation for remote compose reapply, and lock dispatch before the remote POST so a second request cannot overwrite a successful in-flight tracker.

* fix(ui): icon-only Reapply control so Up to date badge can breathe

Collapse the Node updates Reapply label into a tooltip so the status pill no longer wraps in the Status column.

* feat(editor): Save & Reapply self-stack via fleet compose reapply (#1726)

* feat(editor): Save & Reapply self-stack via fleet compose reapply

Eligible admins can apply on-disk Compose edits to Sencho's own stack from the editor using the same confirm, dispatch, and reconnect path as Fleet Node Updates.

* fix(editor): gate Save & Reapply label to self-stack only

Ordinary stacks were labeled Save & Reapply whenever the node was
reapply-eligible. Require the selected file to be the self-stack for the
toolbar label and diff confirm CTA.

* fix(ui): move compose diff action label helper out of dialog module

Keep ComposeDiffPreviewDialog component-only so react-refresh Fast Refresh
lint passes after the Save and reapply stacked merge.
This commit is contained in:
Anso
2026-07-28 14:26:46 -04:00
committed by GitHub
parent 543e4ef256
commit b0b423b234
42 changed files with 1770 additions and 168 deletions
@@ -1,3 +1,5 @@
export type FleetOperationKind = 'update' | 'reapply_configuration';
export interface UpdateTracker {
status: 'updating' | 'completed' | 'timeout' | 'failed';
startedAt: number;
@@ -11,6 +13,8 @@ export interface UpdateTracker {
wasOffline: boolean;
/** Timestamp when the tracker transitioned to a terminal state (completed/failed/timeout). */
resolvedAt?: number;
/** Distinguishes version updates from compose reapply so poll heuristics stay correct. */
operationKind: FleetOperationKind;
}
export type TerminalStatus = 'completed' | 'failed' | 'timeout';
@@ -61,13 +65,15 @@ export class FleetUpdateTrackerService {
return this.trackers.size;
}
/** Create a new tracker with `startedAt=now` and resolvedAt set if terminal. */
/** Create a new tracker with `startedAt=now` and resolvedAt set if terminal.
* `operationKind` defaults to `'update'` so existing call sites stay unchanged. */
public create(
status: UpdateTracker['status'],
previousVersion: string | null,
previousProcessStart: number | null,
error?: string,
code?: string,
operationKind: FleetOperationKind = 'update',
): UpdateTracker {
const now = Date.now();
return {
@@ -78,6 +84,7 @@ export class FleetUpdateTrackerService {
wasOffline: false,
error,
code,
operationKind,
resolvedAt: status !== 'updating' ? now : undefined,
};
}