diff --git a/frontend/src/components/FleetView.tsx b/frontend/src/components/FleetView.tsx index d091c199..4ec4a628 100644 --- a/frontend/src/components/FleetView.tsx +++ b/frontend/src/components/FleetView.tsx @@ -3,7 +3,7 @@ import { Server, Cpu, MemoryStick, HardDrive, RefreshCw, ChevronDown, ChevronRight, Layers, Wifi, WifiOff, Search, ArrowUpDown, AlertTriangle, Play, Square, RotateCcw, ExternalLink, Camera, Download, Loader2, Check, - CircleCheck, CircleAlert, Globe, Monitor, X, LayoutGrid, Network, + CircleCheck, CircleAlert, Globe, Monitor, X, LayoutGrid, Network, SlidersHorizontal, } from 'lucide-react'; import { FleetMasthead } from './fleet/FleetMasthead'; import { FleetTopology } from './fleet/FleetTopology'; @@ -12,6 +12,7 @@ import { Badge } from '@/components/ui/badge'; import { Skeleton } from '@/components/ui/skeleton'; import { Input } from '@/components/ui/input'; import { Combobox } from '@/components/ui/combobox'; +import { Popover, PopoverContent, PopoverTrigger } from '@/components/ui/popover'; import { ScrollArea } from '@/components/ui/scroll-area'; import { AlertDialog, AlertDialogAction, AlertDialogCancel, AlertDialogContent, @@ -39,6 +40,8 @@ interface FleetPaletteEntry { function labelPaletteKey(name: string, color: LabelColor): string { return `${name.trim().toLowerCase()}|${color}`; } + +const FILTER_SECTION_LABEL_CLASS = 'text-[11px] font-medium uppercase tracking-wider text-muted-foreground'; import { MultiSelectCombobox } from '@/components/ui/multi-select-combobox'; import { formatVersion } from '@/lib/version'; import { CursorProvider, Cursor, CursorFollow, CursorContainer } from '@/components/animate-ui/primitives/animate/cursor'; @@ -993,6 +996,18 @@ export function FleetView({ onNavigateToNode }: FleetViewProps) { critical: n.status === 'online' && isCritical(n), })), [processedNodes]); + const showPaidControls = isPaid && viewMode === 'grid'; + const activeFilterCount = + (prefs.filterStatus !== 'all' ? 1 : 0) + + (prefs.filterType !== 'all' ? 1 : 0) + + (prefs.filterCritical ? 1 : 0) + + (labelFilters.size > 0 ? 1 : 0); + const clearFilters = useCallback(() => { + updatePrefs({ filterStatus: 'all', filterType: 'all', filterCritical: false }); + setLabelFilters(new Set()); + }, [updatePrefs]); + const allNodes = localNode ? [localNode, ...remoteNodes] : remoteNodes; + return (
-
-
- - -
-
- {isPaid && ( + +
+ + + + Overview + + {isPaid && ( + + + Snapshots + + + )} + + +
+ {isPaid && ( + + )} - )} - +
-
- - - - - - Overview - - {isPaid && ( - - - Snapshots - - - )} - - {/* Loading State */} @@ -1110,147 +1102,183 @@ export function FleetView({ onNavigateToNode }: FleetViewProps) { {/* Fleet Content */} {!loading && nodes.length > 0 && ( <> - {/* Paid: Search, Sort & Filter Toolbar */} - {isPaid && viewMode === 'grid' && ( -
- {/* Search */} -
- - setSearchQuery(e.target.value)} - className="pl-9 h-9" - /> -
- - {/* Sort */} - updatePrefs({ sortBy: v as SortField })} - placeholder="Sort by..." - /> - - - - {/* Filter pills */} -
- {(['all', 'online', 'offline'] as FilterStatus[]).map(status => ( - - ))} -
- -
- {(['all', 'local', 'remote'] as FilterType[]).map(type => ( - - ))} -
- - - - {fleetPalette.length > 0 && ( - <> -
- ({ value: p.key, label: p.name, color: p.color }))} - selected={labelFilters} - onSelectionChange={setLabelFilters} - placeholder="Tags" - renderOption={(option) => ( - - - {option.label} - - )} + {/* Overview Toolbar: Search, Sort, Filters, View Mode */} +
+ {showPaidControls && ( + <> +
+ + setSearchQuery(e.target.value)} + className="pl-9 h-9" /> - - )} -
- )} +
+
+ updatePrefs({ sortBy: v as SortField })} + placeholder="Sort by..." + /> +
+ + + + + + +
+ +
+ {(['all', 'online', 'offline'] as FilterStatus[]).map(status => ( + + ))} +
+
+
+ +
+ {(['all', 'local', 'remote'] as FilterType[]).map(type => ( + + ))} +
+
+
+ + +
+ {fleetPalette.length > 0 && ( +
+ + ({ value: p.key, label: p.name, color: p.color }))} + selected={labelFilters} + onSelectionChange={setLabelFilters} + placeholder="Tags" + renderOption={(option) => ( + + + {option.label} + + )} + /> +
+ )} + {activeFilterCount > 0 && ( + + )} +
+
+ + )} + +
+ + +
+
- {/* Node Grid or Topology */} {viewMode === 'topology' && processedNodes.length > 0 ? ( onNavigateToNode(id, '')} /> ) : processedNodes.length > 0 ? ( -
- {localNode && ( -
- -
- )} - {remoteNodes.length > 0 && ( -
- {remoteNodes.map(node => ( - - ))} -
- )} +
+ {allNodes.map(node => ( + + ))}
) : (
@@ -1263,8 +1291,7 @@ export function FleetView({ onNavigateToNode }: FleetViewProps) { className="mt-3" onClick={() => { setSearchQuery(''); - updatePrefs({ filterStatus: 'all', filterType: 'all', filterCritical: false }); - setLabelFilters(new Set()); + clearFilters(); }} >