diff --git a/frontend-modern/src/components/Dashboard/Dashboard.tsx b/frontend-modern/src/components/Dashboard/Dashboard.tsx index b6a9e92..2df7232 100644 --- a/frontend-modern/src/components/Dashboard/Dashboard.tsx +++ b/frontend-modern/src/components/Dashboard/Dashboard.tsx @@ -20,6 +20,7 @@ import { getNodeDisplayName } from '@/utils/nodes'; import { logger } from '@/utils/logger'; import { usePersistentSignal } from '@/hooks/usePersistentSignal'; import { STORAGE_KEYS } from '@/utils/localStorage'; +import { getBackupInfo } from '@/utils/format'; type GuestMetadataRecord = Record; type IdleCallbackHandle = number; @@ -196,6 +197,7 @@ interface DashboardProps { type ViewMode = 'all' | 'vm' | 'lxc'; type StatusMode = 'all' | 'running' | 'degraded' | 'stopped'; +type BackupMode = 'all' | 'needs-backup'; type GroupingMode = 'grouped' | 'flat'; export function Dashboard(props: DashboardProps) { @@ -236,6 +238,11 @@ export function Dashboard(props: DashboardProps) { : 'all', }); + // Backup filter mode - filter by backup status + const [backupMode, setBackupMode] = usePersistentSignal('dashboardBackupMode', 'all', { + deserialize: (raw) => (raw === 'all' || raw === 'needs-backup' ? raw : 'all'), + }); + // Grouping mode - grouped by node or flat list const [groupingMode, setGroupingMode] = usePersistentSignal( 'dashboardGroupingMode', @@ -426,7 +433,8 @@ export function Dashboard(props: DashboardProps) { sortDirection() !== 'asc' || selectedNode() !== null || viewMode() !== 'all' || - statusMode() !== 'all'; + statusMode() !== 'all' || + backupMode() !== 'all'; if (hasActiveFilters) { // Clear ALL filters including search text, tag filters, node selection, and view modes @@ -437,6 +445,7 @@ export function Dashboard(props: DashboardProps) { setSelectedNode(null); setViewMode('all'); setStatusMode('all'); + setBackupMode('all'); // Blur the search input if it's focused if (searchInputRef && document.activeElement === searchInputRef) { @@ -502,6 +511,17 @@ export function Dashboard(props: DashboardProps) { guests = guests.filter((g) => g.status !== 'running'); } + // Filter by backup status + if (backupMode() === 'needs-backup') { + guests = guests.filter((g) => { + // Skip templates - they don't need backups + if (g.template) return false; + const backupInfo = getBackupInfo(g.lastBackup); + // Show guests that need backup: stale, critical, or never backed up + return backupInfo.status === 'stale' || backupInfo.status === 'critical' || backupInfo.status === 'never'; + }); + } + // Apply search/filter const searchTerm = search().trim(); if (searchTerm) { @@ -800,6 +820,8 @@ export function Dashboard(props: DashboardProps) { setViewMode={setViewMode} statusMode={statusMode} setStatusMode={setStatusMode} + backupMode={backupMode} + setBackupMode={setBackupMode} groupingMode={groupingMode} setGroupingMode={setGroupingMode} setSortKey={setSortKey} diff --git a/frontend-modern/src/components/Dashboard/DashboardFilter.tsx b/frontend-modern/src/components/Dashboard/DashboardFilter.tsx index 46195e8..d110057 100644 --- a/frontend-modern/src/components/Dashboard/DashboardFilter.tsx +++ b/frontend-modern/src/components/Dashboard/DashboardFilter.tsx @@ -13,6 +13,8 @@ interface DashboardFilterProps { setViewMode: (value: 'all' | 'vm' | 'lxc') => void; statusMode: () => 'all' | 'running' | 'degraded' | 'stopped'; setStatusMode: (value: 'all' | 'running' | 'degraded' | 'stopped') => void; + backupMode: () => 'all' | 'needs-backup'; + setBackupMode: (value: 'all' | 'needs-backup') => void; groupingMode: () => 'grouped' | 'flat'; setGroupingMode: (value: 'grouped' | 'flat') => void; setSortKey: (value: string) => void; @@ -369,6 +371,25 @@ export const DashboardFilter: Component = (props) => { + {/* Backup Filter */} + + + + {/* Grouping Mode Toggle */}