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
+15 -1
View File
@@ -23,6 +23,9 @@ import { ThemeQuickSwitch } from './theme/ThemeQuickSwitch';
import { useNotifications } from './EditorLayout/hooks/useNotifications';
import { useContainerStats } from './EditorLayout/hooks/useContainerStats';
import { useSidebarContextMenu } from './EditorLayout/hooks/useSidebarContextMenu';
import { useActiveNodeReapplyEligibility } from './EditorLayout/hooks/useActiveNodeReapplyEligibility';
import { resolveCanSaveAndReapply } from './EditorLayout/resolveCanSaveAndReapply';
import { useComposeReapplyAction } from './FleetView/hooks/useComposeReapplyAction';
import { NodeSwitcher } from './NodeSwitcher';
import {
GlobalCommandPalette,
@@ -186,6 +189,12 @@ export default function EditorLayout() {
createDialogOpen, setCreateDialogOpen,
} = overlayState;
const { canReapply: canReapplyCompose } = useActiveNodeReapplyEligibility(activeNode?.id);
const composeReapply = useComposeReapplyAction();
const isSelfStackSelected = selectedFile ? stackSelfFlags[selectedFile] === true : false;
// Ordinary stacks keep Save & Deploy even when the node supports compose reapply.
const canSaveAndReapply = resolveCanSaveAndReapply(isAdmin, canReapplyCompose, isSelfStackSelected);
// Which mode the create dialog opens on (always empty after import tab removal).
const [createDialogInitialMode, setCreateDialogInitialMode] = useState<CreateMode>('empty');
const [adoptDialogOpen, setAdoptDialogOpen] = useState(false);
@@ -292,6 +301,8 @@ export default function EditorLayout() {
canOfferVolumeRemoval,
onDeletedOpenStack: () => onDeletedOpenStackRef.current(),
removeNotificationsForStack,
isAdmin,
canReapplyCompose,
});
// Wire the ref now that stackActions is available
@@ -692,7 +703,8 @@ export default function EditorLayout() {
requestDeleteStack={stackActions.requestDeleteStack}
requestTakeDownStack={stackActions.requestTakeDownStack}
showTakeDown={selectedFile ? stackActions.getStackMenuVisibility(selectedFile).showTakeDown : false}
isSelfStack={selectedFile ? stackSelfFlags[selectedFile] === true : false}
isSelfStack={isSelfStackSelected}
canSaveAndReapply={canSaveAndReapply}
recoveryResult={selectedFile ? lastActionResult[selectedFile] : undefined}
onRefreshState={async () => {
if (!selectedFile) return;
@@ -1050,6 +1062,8 @@ export default function EditorLayout() {
gitSourceOpen={gitSourceOpen}
setGitSourceOpen={setGitSourceOpen}
canSelfUpdate={hasCapability('self-update')}
composeReapply={composeReapply}
canSaveAndReapply={canSaveAndReapply}
canOfferVolumeRemoval={canOfferVolumeRemoval}
onOpenFleetNodeUpdates={() => {
if (isMobile) {