feat: sortable resource tables and richer dashboard stack-health columns (#1498)

Make the Resources Images, Volumes, and Networks tables sortable with a
shared useTableSort hook and SortableTableHead, move the Images scan-history
control into the Images tab header, and keep the network List/Topology toggle
anchored with Create Network visible in both modes.

Rework the dashboard Stack health table: drop the redundant Host column, add
sortable Stack/Up/CPU/Mem headers, and add Source (local/git) and Port columns.
The status endpoint now labels each stack with its git/local source, computed
outside the cache so linking changes show immediately.

Extract a reusable CreateNetworkDialog and add a create-network action to the
stack-detail Networking tab.
This commit is contained in:
Anso
2026-06-28 05:06:04 -04:00
committed by GitHub
parent cf0db36e78
commit 60536aa614
11 changed files with 512 additions and 190 deletions
@@ -1,9 +1,10 @@
import { useEffect, useState, useCallback } from 'react';
import { Network, Globe, Lock, ShieldQuestion, RefreshCw, ArrowRight } from 'lucide-react';
import { Network, Globe, Lock, ShieldQuestion, RefreshCw, ArrowRight, Plus } from 'lucide-react';
import { apiFetch } from '@/lib/api';
import { cn } from '@/lib/utils';
import { toast } from '@/components/ui/toast-store';
import { useNodes } from '@/context/NodeContext';
import { CreateNetworkDialog } from '@/components/resources/CreateNetworkDialog';
// Mirrors the backend networking payload shapes (the frontend never imports
// backend). IntentEntry intentionally keeps only the fields this panel reads.
@@ -110,6 +111,7 @@ export default function StackNetworkingPanel({ stackName, canEdit, doctorEnabled
const [loadError, setLoadError] = useState(false);
const [refreshing, setRefreshing] = useState(false);
const [reloadKey, setReloadKey] = useState(0);
const [showCreateNetwork, setShowCreateNetwork] = useState(false);
useEffect(() => {
let cancelled = false;
@@ -197,11 +199,24 @@ export default function StackNetworkingPanel({ stackName, canEdit, doctorEnabled
<div data-testid="networking-panel" className="flex-1 min-h-0 overflow-y-auto px-3 py-3 flex flex-col gap-4">
<div className="flex items-center justify-between gap-2">
<span className={LABEL_CLASS}>networking</span>
<button type="button" onClick={() => setReloadKey(k => k + 1)} disabled={refreshing} className={ACTION_CLASS}>
<RefreshCw className={cn('h-3 w-3', refreshing && 'animate-spin')} strokeWidth={1.5} /> refresh
</button>
<div className="flex items-center gap-3">
{canEdit && (
<button type="button" onClick={() => setShowCreateNetwork(true)} className={ACTION_CLASS}>
<Plus className="h-3 w-3" strokeWidth={1.5} /> create network
</button>
)}
<button type="button" onClick={() => setReloadKey(k => k + 1)} disabled={refreshing} className={ACTION_CLASS}>
<RefreshCw className={cn('h-3 w-3', refreshing && 'animate-spin')} strokeWidth={1.5} /> refresh
</button>
</div>
</div>
<CreateNetworkDialog
open={showCreateNetwork}
onOpenChange={setShowCreateNetwork}
onCreated={() => setReloadKey(k => k + 1)}
/>
{/* Exposure intent */}
<section className="flex flex-col gap-2">
<div className={LABEL_CLASS}>exposure intent</div>