From 0c7af34ad2d9b9dff96d7ca5e94edb7618a86446 Mon Sep 17 00:00:00 2001 From: "courtmanr@gmail.com" Date: Sun, 23 Nov 2025 22:53:33 +0000 Subject: [PATCH] Implement status bars for Storage, Backups, and Hosts tabs #744 --- VERSION | 2 +- .../src/components/Backups/BackupsFilter.tsx | 145 ++- .../src/components/Backups/UnifiedBackups.tsx | 70 +- .../src/components/Dashboard/Dashboard.tsx | 14 +- .../components/Dashboard/DashboardFilter.tsx | 87 +- .../components/Docker/DockerSummaryStats.tsx | 22 +- .../src/components/Storage/Storage.tsx | 1008 +++++++++-------- .../src/components/Storage/StorageFilter.tsx | 68 +- 8 files changed, 768 insertions(+), 648 deletions(-) diff --git a/VERSION b/VERSION index 20b5160..13d83d1 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -4.32.4 +4.32.5 diff --git a/frontend-modern/src/components/Backups/BackupsFilter.tsx b/frontend-modern/src/components/Backups/BackupsFilter.tsx index df4a385..0146cb6 100644 --- a/frontend-modern/src/components/Backups/BackupsFilter.tsx +++ b/frontend-modern/src/components/Backups/BackupsFilter.tsx @@ -21,6 +21,8 @@ interface BackupsFilterProps { setSortDirection: (value: 'asc' | 'desc') => void; sortOptions?: { value: string; label: string }[]; onReset?: () => void; + statusFilter?: () => 'all' | 'verified' | 'unverified'; + setStatusFilter?: (value: 'all' | 'verified' | 'unverified') => void; } export const BackupsFilter: Component = (props) => { @@ -305,44 +307,40 @@ export const BackupsFilter: Component = (props) => { @@ -363,44 +361,40 @@ export const BackupsFilter: Component = (props) => { @@ -413,22 +407,20 @@ export const BackupsFilter: Component = (props) => { @@ -476,6 +468,42 @@ export const BackupsFilter: Component = (props) => { + {/* Status Filter */} +
+ + + +
+ + + {/* Reset Button */} @@ -1695,9 +1702,8 @@ const UnifiedBackups: Component = () => { let tooltipText = `${formattedDate}`; if (d.total > 0) { - tooltipText += `\nAvailable: ${d.total} backup${ - d.total > 1 ? 's' : '' - }`; + tooltipText += `\nAvailable: ${d.total} backup${d.total > 1 ? 's' : '' + }`; const breakdown: string[] = []; if (d.snapshots > 0) breakdown.push(`Snapshots: ${d.snapshots}`); @@ -1846,6 +1852,8 @@ const UnifiedBackups: Component = () => { searchInputRef={(el) => (searchInputRef = el)} typeFilter={typeFilter} setTypeFilter={setTypeFilter} + statusFilter={statusFilter} + setStatusFilter={setStatusFilter} hasHostBackups={hasHostBackups} sortOptions={sortKeyOptions} sortKey={sortKey} @@ -1945,11 +1953,10 @@ const UnifiedBackups: Component = () => {
{item.type} @@ -1960,13 +1967,12 @@ const UnifiedBackups: Component = () => {
{item.backupType === 'snapshot' ? 'SNAP' @@ -2157,13 +2163,12 @@ const UnifiedBackups: Component = () => { {item.vmid} {item.type} @@ -2196,13 +2201,12 @@ const UnifiedBackups: Component = () => {
{item.backupType === 'snapshot' ? 'Snapshot' diff --git a/frontend-modern/src/components/Dashboard/Dashboard.tsx b/frontend-modern/src/components/Dashboard/Dashboard.tsx index f783b93..7bbdcff 100644 --- a/frontend-modern/src/components/Dashboard/Dashboard.tsx +++ b/frontend-modern/src/components/Dashboard/Dashboard.tsx @@ -196,7 +196,7 @@ interface DashboardProps { } type ViewMode = 'all' | 'vm' | 'lxc'; -type StatusMode = 'all' | 'running' | 'stopped'; +type StatusMode = 'all' | 'running' | 'degraded' | 'stopped'; type GroupingMode = 'grouped' | 'flat'; export function Dashboard(props: DashboardProps) { @@ -232,7 +232,9 @@ export function Dashboard(props: DashboardProps) { const [statusMode, setStatusMode] = usePersistentSignal('dashboardStatusMode', 'all', { deserialize: (raw) => - raw === 'all' || raw === 'running' || raw === 'stopped' ? raw : ('all' as StatusMode), + raw === 'all' || raw === 'running' || raw === 'degraded' || raw === 'stopped' + ? (raw as StatusMode) + : 'all', }); // Grouping mode - grouped by node or flat list @@ -487,6 +489,14 @@ export function Dashboard(props: DashboardProps) { // Filter by status if (statusMode() === 'running') { guests = guests.filter((g) => g.status === 'running'); + } else if (statusMode() === 'degraded') { + guests = guests.filter((g) => { + const status = (g.status || '').toLowerCase(); + return ( + DEGRADED_HEALTH_STATUSES.has(status) || + (status !== 'running' && !OFFLINE_HEALTH_STATUSES.has(status)) + ); + }); } else if (statusMode() === 'stopped') { guests = guests.filter((g) => g.status !== 'running'); } diff --git a/frontend-modern/src/components/Dashboard/DashboardFilter.tsx b/frontend-modern/src/components/Dashboard/DashboardFilter.tsx index 54296ec..46195e8 100644 --- a/frontend-modern/src/components/Dashboard/DashboardFilter.tsx +++ b/frontend-modern/src/components/Dashboard/DashboardFilter.tsx @@ -11,8 +11,8 @@ interface DashboardFilterProps { isSearchLocked: () => boolean; viewMode: () => 'all' | 'vm' | 'lxc'; setViewMode: (value: 'all' | 'vm' | 'lxc') => void; - statusMode: () => 'all' | 'running' | 'stopped'; - setStatusMode: (value: 'all' | 'running' | 'stopped') => void; + statusMode: () => 'all' | 'running' | 'degraded' | 'stopped'; + setStatusMode: (value: 'all' | 'running' | 'degraded' | 'stopped') => void; groupingMode: () => 'grouped' | 'flat'; setGroupingMode: (value: 'grouped' | 'flat') => void; setSortKey: (value: string) => void; @@ -292,33 +292,30 @@ export const DashboardFilter: Component = (props) => { @@ -331,33 +328,40 @@ export const DashboardFilter: Component = (props) => { + @@ -370,11 +374,10 @@ export const DashboardFilter: Component = (props) => { @@ -322,6 +322,41 @@ export const StorageFilter: Component = (props) => { + {/* Status Filter */} +
+ + + +
+ + {/* Sort controls */}
@@ -368,16 +403,17 @@ export const StorageFilter: Component = (props) => { props.setSortKey('name'); props.setSortDirection('asc'); if (props.setGroupBy) props.setGroupBy('node'); + if (props.setStatusFilter) props.setStatusFilter('all'); }} title="Reset all filters" - class={`flex items-center justify-center px-2.5 py-1 text-xs font-medium rounded-lg transition-colors ${ - props.search().trim() !== '' || - props.sortKey() !== 'name' || - props.sortDirection() !== 'asc' || - (props.groupBy && props.groupBy!() !== 'node') + class={`flex items-center justify-center px-2.5 py-1 text-xs font-medium rounded-lg transition-colors ${props.search().trim() !== '' || + props.sortKey() !== 'name' || + props.sortDirection() !== 'asc' || + (props.groupBy && props.groupBy!() !== 'node') || + (props.statusFilter && props.statusFilter!() !== 'all') ? 'text-blue-700 dark:text-blue-300 bg-blue-100 dark:bg-blue-900/50 hover:bg-blue-200 dark:hover:bg-blue-900/70' : 'text-gray-600 dark:text-gray-400 bg-gray-100 dark:bg-gray-700 hover:bg-gray-200 dark:hover:bg-gray-600' - }`} + }`} >