diff --git a/CHANGELOG.md b/CHANGELOG.md index 735df026..5356747c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -29,6 +29,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Fixed +* **fleet:** "Open in Editor" button in Fleet View's stack drill-down now correctly navigates to the stack editor instead of the dashboard. Also passes the stack name through the callback chain so the correct file is loaded. * **scheduled-ops:** `prune_targets` field was silently dropped when creating scheduled tasks due to missing column in the INSERT statement ### Security diff --git a/docs/images/fleet-view/fleet-drill-down.png b/docs/images/fleet-view/fleet-drill-down.png index d5bd99d7..9fc039e0 100644 Binary files a/docs/images/fleet-view/fleet-drill-down.png and b/docs/images/fleet-view/fleet-drill-down.png differ diff --git a/frontend/src/components/EditorLayout.tsx b/frontend/src/components/EditorLayout.tsx index b3de34ba..fc081f6f 100644 --- a/frontend/src/components/EditorLayout.tsx +++ b/frontend/src/components/EditorLayout.tsx @@ -108,6 +108,7 @@ export default function EditorLayout() { const rawBytesRef = useRef>({}); const [activeTab, setActiveTab] = useState<'compose' | 'env'>('compose'); const monacoEditorRef = useRef(null); + const pendingStackLoadRef = useRef(null); const [createDialogOpen, setCreateDialogOpen] = useState(false); const [deleteDialogOpen, setDeleteDialogOpen] = useState(false); const [newStackName, setNewStackName] = useState(''); @@ -425,6 +426,9 @@ export default function EditorLayout() { // Also clears any stale editor/container state that belonged to the previous node. useEffect(() => { if (!activeNode) return; + const pendingStack = pendingStackLoadRef.current; + pendingStackLoadRef.current = null; + setSelectedFile(null); setContent(''); setOriginalContent(''); @@ -432,7 +436,13 @@ export default function EditorLayout() { setOriginalEnvContent(''); setContainers([]); setIsEditing(false); - setActiveView('dashboard'); + + if (pendingStack) { + loadFile(pendingStack); + } else { + setActiveView('dashboard'); + } + refreshStacks(); fetchImageUpdates(); }, [activeNode?.id]); // eslint-disable-line react-hooks/exhaustive-deps @@ -1786,11 +1796,15 @@ export default function EditorLayout() { ) : activeView === 'global-observability' ? ( ) : activeView === 'fleet' ? ( - { + { const node = nodes.find(n => n.id === nodeId); if (node) { - setActiveNode(node); - setActiveView('dashboard'); + if (activeNode?.id === nodeId) { + loadFile(stackName); + } else { + pendingStackLoadRef.current = stackName; + setActiveNode(node); + } } }} /> ) : activeView === 'audit-log' ? ( diff --git a/frontend/src/components/FleetView.tsx b/frontend/src/components/FleetView.tsx index 4266911a..27544638 100644 --- a/frontend/src/components/FleetView.tsx +++ b/frontend/src/components/FleetView.tsx @@ -189,7 +189,7 @@ function ContainerRow({ container, nodeId, onNavigate }: { function StackSection({ stackName, nodeId, onNavigate }: { stackName: string; nodeId: number; - onNavigate: (nodeId: number) => void; + onNavigate: (nodeId: number, stackName: string) => void; }) { const [expanded, setExpanded] = useState(false); const [containers, setContainers] = useState(null); @@ -247,7 +247,7 @@ function StackSection({ stackName, nodeId, onNavigate }: { key={c.Id ?? containerName(c)} container={c} nodeId={nodeId} - onNavigate={onNavigate} + onNavigate={(nid) => onNavigate(nid, stackName)} /> )) ) : ( @@ -259,7 +259,7 @@ function StackSection({ stackName, nodeId, onNavigate }: { ); } -function NodeCard({ node, onNavigate }: { node: FleetNode; onNavigate: (nodeId: number) => void }) { +function NodeCard({ node, onNavigate }: { node: FleetNode; onNavigate: (nodeId: number, stackName: string) => void }) { const { isPro } = useLicense(); const [expanded, setExpanded] = useState(false); const [stacks, setStacks] = useState(node.stacks); @@ -431,7 +431,7 @@ function NodeCard({ node, onNavigate }: { node: FleetNode; onNavigate: (nodeId: // --- Main Component --- interface FleetViewProps { - onNavigateToNode: (nodeId: number) => void; + onNavigateToNode: (nodeId: number, stackName: string) => void; } export function FleetView({ onNavigateToNode }: FleetViewProps) {