import { useEffect, useMemo, useRef, useState } from 'react'; import { Search, ArrowUpDown, AlertTriangle, Play, Square, LayoutGrid, Network, SlidersHorizontal, ChartNetwork, RefreshCcwDot, } from 'lucide-react'; import { Button } from '@/components/ui/button'; import { Badge } from '@/components/ui/badge'; import { Input } from '@/components/ui/input'; import { Combobox } from '@/components/ui/combobox'; import { Popover, PopoverContent, PopoverTrigger } from '@/components/ui/popover'; import { MultiSelectCombobox } from '@/components/ui/multi-select-combobox'; import { SegmentedControl, type SegmentedControlOption } from '@/components/ui/segmented-control'; import { LabelDot } from '../LabelPill'; import type { LabelColor } from '../label-types'; import type { ViewMode, SortField, FilterStatus, FilterType, FilterNetworking, FleetPreferences, FleetPaletteEntry } from './types'; const FILTER_SECTION_LABEL_CLASS = 'text-[10px] leading-3 font-mono uppercase tracking-[0.18em] text-stat-subtitle'; const SORT_OPTIONS = [ { value: 'name', label: 'Name' }, { value: 'cpu', label: 'CPU Usage' }, { value: 'memory', label: 'Memory Usage' }, { value: 'containers', label: 'Containers' }, { value: 'status', label: 'Status' }, ]; const VIEW_MODE_OPTIONS: SegmentedControlOption[] = [ { value: 'grid', label: 'Grid', icon: LayoutGrid }, { value: 'topology', label: 'Topology', icon: Network }, ]; function renderPaletteOption(option: { label: string; color?: string }) { return ( {option.label} ); } interface OverviewToolbarProps { viewMode: ViewMode; onViewModeChange: (mode: ViewMode) => void; searchQuery: string; onSearchQueryChange: (q: string) => void; prefs: FleetPreferences; onPrefsChange: (update: Partial) => void; fleetPalette: FleetPaletteEntry[]; labelFilters: Set; onLabelFiltersChange: (filters: Set) => void; onClearFilters: () => void; onAddNode?: () => void; onCheckUpdates?: () => void; checkingUpdates?: boolean; } export function OverviewToolbar({ viewMode, onViewModeChange, searchQuery, onSearchQueryChange, prefs, onPrefsChange, fleetPalette, labelFilters, onLabelFiltersChange, onClearFilters, onAddNode, onCheckUpdates, checkingUpdates, }: OverviewToolbarProps) { const showGridControls = viewMode === 'grid'; const activeFilterCount = (prefs.filterStatus !== 'all' ? 1 : 0) + (prefs.filterType !== 'all' ? 1 : 0) + (prefs.filterCritical ? 1 : 0) + (prefs.filterNetworking !== 'all' ? 1 : 0) + (labelFilters.size > 0 ? 1 : 0); const paletteOptions = useMemo( () => fleetPalette.map(p => ({ value: p.key, label: p.name, color: p.color })), [fleetPalette], ); // Collapsed by default to a single icon button; expands to the full input on // click and collapses again on blur once the query is cleared. An active // query keeps it open so the filter stays visible and editable. const [searchExpanded, setSearchExpanded] = useState(false); const searchInputRef = useRef(null); const searchOpen = searchExpanded || searchQuery !== ''; useEffect(() => { if (searchExpanded) searchInputRef.current?.focus(); }, [searchExpanded]); return (
{showGridControls && ( <> {searchOpen ? (
onSearchQueryChange(e.target.value)} onBlur={() => { if (searchQuery === '') setSearchExpanded(false); }} className="pl-9 h-9" />
) : ( )}
onPrefsChange({ sortBy: v as SortField })} placeholder="Sort by..." className="[&>button]:!bg-background" />
{(['all', 'online', 'offline'] as FilterStatus[]).map(status => ( ))}
{(['all', 'local', 'remote'] as FilterType[]).map(type => ( ))}
{([['all', 'All'], ['exposed', 'Exposed'], ['unknown', 'Unknown'], ['drift', 'Drift']] as [FilterNetworking, string][]).map(([value, label]) => ( ))}
{fleetPalette.length > 0 && (
)} {activeFilterCount > 0 && ( )}
)} {onCheckUpdates && ( )} {onAddNode && ( )}
); }