mirror of
https://github.com/Studio-Saelix/sencho.git
synced 2026-08-06 08:58:05 +00:00
b33a0e8422
* fix(deploy-enforcement): surface scan-policy blocks on update and sidebar deploys A blocked deploy only opened the policy dialog from the editor deploy button. The update action and the sidebar context-menu deploy/update fell through to a generic error toast, so an admin could not review the violations or bypass the block from those entry points. Route the 409 policy response through a shared handler on all three paths and make the "Deploy anyway" bypass retry the originating action (deploy or update) so an update bypass still re-pulls images. Also: - Correct the "Block on deploy" policy-editor helper text, which described post-deploy alerting rather than the pre-flight rejection it actually performs. - Dispatch the documented scan_finding warning (policy name and the offending images) when a scheduled auto-update or auto-start is blocked, instead of recording an opaque failure. - Add a standard log line when the gate blocks a deploy, plus developer-mode diagnostics for the matched policy and per-image severity decision. - Fix deploy-enforcement docs: complete the enforced entry-point list, correct the policy-precedence wording, and remove inaccurate tier and audit-actor claims. * fix(deploy-enforcement): surface policy block on rollback and name images in remote auto-update alert Addresses two gaps found in independent review: - Rollback is a policy-gated deploy path (it restores the saved files then re-runs the gate before redeploying), but the frontend treated a blocked rollback as a generic error toast. Route the 409 through the same handler as deploy and update so the block dialog opens, and let an admin "Deploy anyway" retry the rollback with the bypass flag (the rollback route already honors it). - The remote auto-update path dispatched its policy-block warning without the offending image refs, unlike the local scheduler. Append the images so the alert matches the documented contract on every node. Also list rollback as an enforced entry point in the docs and clarify that Git Source enforcement covers both the create-time deploy and a manual apply-with-deploy.
106 lines
4.2 KiB
TypeScript
106 lines
4.2 KiB
TypeScript
import { useState, useCallback, useEffect } from 'react';
|
|
import { SENCHO_OPEN_LOGS_EVENT } from '@/lib/events';
|
|
import type { SenchoOpenLogsDetail } from '@/lib/events';
|
|
import type { PolicyBlockPayload, PolicyBlockableAction } from '../../stack/PolicyBlockDialog';
|
|
import type { Node } from '@/context/NodeContext';
|
|
|
|
type DiffPreview = {
|
|
mode: 'save' | 'save-and-deploy';
|
|
language: 'yaml' | 'ini';
|
|
original: string;
|
|
modified: string;
|
|
fileName: string;
|
|
};
|
|
|
|
type PolicyBlock = {
|
|
stackName: string;
|
|
stackFile: string;
|
|
action: PolicyBlockableAction;
|
|
payload: PolicyBlockPayload;
|
|
};
|
|
type Container = { id: string; name: string };
|
|
|
|
export function useOverlayState() {
|
|
const [createDialogOpen, setCreateDialogOpen] = useState(false);
|
|
|
|
const [deleteDialogOpen, setDeleteDialogOpen] = useState(false);
|
|
const [stackToDelete, setStackToDelete] = useState<string | null>(null);
|
|
const openDeleteDialog = useCallback((stackName: string) => {
|
|
setStackToDelete(stackName);
|
|
setDeleteDialogOpen(true);
|
|
}, []);
|
|
const closeDeleteDialog = useCallback(() => {
|
|
setDeleteDialogOpen(false);
|
|
setStackToDelete(null);
|
|
}, []);
|
|
|
|
const [pendingUnsavedLoad, setPendingUnsavedLoad] = useState<string | null>(null);
|
|
const [pendingUnsavedNode, setPendingUnsavedNode] = useState<Node | null>(null);
|
|
|
|
const [bashModalOpen, setBashModalOpen] = useState(false);
|
|
const [selectedContainer, setSelectedContainer] = useState<Container | null>(null);
|
|
const openBashModal = useCallback((container: Container) => {
|
|
setSelectedContainer(container);
|
|
setBashModalOpen(true);
|
|
}, []);
|
|
const closeBashModal = useCallback(() => {
|
|
setBashModalOpen(false);
|
|
setSelectedContainer(null);
|
|
}, []);
|
|
|
|
const [logViewerOpen, setLogViewerOpen] = useState(false);
|
|
const [logContainer, setLogContainer] = useState<Container | null>(null);
|
|
const openLogViewer = useCallback((container: Container) => {
|
|
setLogContainer(container);
|
|
setLogViewerOpen(true);
|
|
}, []);
|
|
const closeLogViewer = useCallback(() => {
|
|
setLogViewerOpen(false);
|
|
setLogContainer(null);
|
|
}, []);
|
|
|
|
// Listen for topology click-to-logs events and open the log viewer.
|
|
// openLogViewer is stable (useCallback with empty deps), so this effect
|
|
// mounts/unmounts once and never re-registers.
|
|
useEffect(() => {
|
|
const handler = (e: Event) => {
|
|
const { containerId, containerName } = (e as CustomEvent<SenchoOpenLogsDetail>).detail;
|
|
openLogViewer({ id: containerId, name: containerName });
|
|
};
|
|
window.addEventListener(SENCHO_OPEN_LOGS_EVENT, handler);
|
|
return () => window.removeEventListener(SENCHO_OPEN_LOGS_EVENT, handler);
|
|
}, [openLogViewer]); // openLogViewer is stable (useCallback with empty deps)
|
|
|
|
const [stackMonitor, setStackMonitor] = useState<{ stackName: string; tab: 'alerts' | 'auto-heal' } | null>(null);
|
|
const openAlertSheet = useCallback((stackName: string) => {
|
|
setStackMonitor({ stackName, tab: 'alerts' });
|
|
}, []);
|
|
const openAutoHeal = useCallback((stackName: string) => {
|
|
setStackMonitor({ stackName, tab: 'auto-heal' });
|
|
}, []);
|
|
const closeStackMonitor = useCallback(() => setStackMonitor(null), []);
|
|
|
|
const [policyBlock, setPolicyBlock] = useState<PolicyBlock | null>(null);
|
|
const [policyBypassing, setPolicyBypassing] = useState(false);
|
|
|
|
const [stackMisconfigScanId, setStackMisconfigScanId] = useState<number | null>(null);
|
|
|
|
const [diffPreview, setDiffPreview] = useState<DiffPreview | null>(null);
|
|
const [diffPreviewConfirming, setDiffPreviewConfirming] = useState(false);
|
|
|
|
return {
|
|
createDialogOpen, setCreateDialogOpen,
|
|
deleteDialogOpen, stackToDelete, openDeleteDialog, closeDeleteDialog,
|
|
pendingUnsavedLoad, setPendingUnsavedLoad,
|
|
pendingUnsavedNode, setPendingUnsavedNode,
|
|
bashModalOpen, selectedContainer, openBashModal, closeBashModal,
|
|
logViewerOpen, logContainer, openLogViewer, closeLogViewer,
|
|
stackMonitor, openAlertSheet, openAutoHeal, closeStackMonitor,
|
|
policyBlock, setPolicyBlock, policyBypassing, setPolicyBypassing,
|
|
stackMisconfigScanId, setStackMisconfigScanId,
|
|
diffPreview, setDiffPreview, diffPreviewConfirming, setDiffPreviewConfirming,
|
|
} as const;
|
|
}
|
|
|
|
export type OverlayState = ReturnType<typeof useOverlayState>;
|