From 17c99377f64c7f1ad1234ded3ec652d3c4b220f7 Mon Sep 17 00:00:00 2001 From: rcourtman Date: Wed, 26 Nov 2025 09:32:58 +0000 Subject: [PATCH] fix: restore I/O column styling to match v4.32.5 release - Restore green/yellow/red color thresholds for disk and network I/O - Use consistent text-xs font size across all columns - Expand column widths to fit full header text at larger breakpoints - Show full header names (Disk Read, VMID, Uptime, Memory) at xl+ screens - Use shared GUEST_COLUMNS config for header/row grid alignment - Add whitespace-nowrap to prevent header text wrapping --- .../src/components/Dashboard/Dashboard.tsx | 76 +++++++++++++------ .../src/components/Dashboard/GuestRow.tsx | 52 ++++++------- .../src/components/Dashboard/IOMetric.tsx | 6 +- 3 files changed, 82 insertions(+), 52 deletions(-) diff --git a/frontend-modern/src/components/Dashboard/Dashboard.tsx b/frontend-modern/src/components/Dashboard/Dashboard.tsx index 9c8df7f..ef72430 100644 --- a/frontend-modern/src/components/Dashboard/Dashboard.tsx +++ b/frontend-modern/src/components/Dashboard/Dashboard.tsx @@ -1,7 +1,8 @@ import { createSignal, createMemo, createEffect, For, Show, onMount } from 'solid-js'; import { useNavigate } from '@solidjs/router'; import type { VM, Container, Node } from '@/types/api'; -import { GuestRow } from './GuestRow'; +import { GuestRow, GUEST_COLUMNS } from './GuestRow'; +import { useGridTemplate } from '@/components/shared/responsive'; import { useWebSocket } from '@/App'; import { getAlertStyles } from '@/utils/alerts'; import { useAlertsActivation } from '@/stores/alertsActivation'; @@ -258,6 +259,9 @@ export function Dashboard(props: DashboardProps) { const [sortKey, setSortKey] = createSignal('vmid'); const [sortDirection, setSortDirection] = createSignal<'asc' | 'desc'>('asc'); + // Use the same grid template as GuestRow for header alignment + const { gridTemplate: headerGridTemplate } = useGridTemplate({ columns: GUEST_COLUMNS }); + // Load all guest metadata on mount (single API call for all guests) onMount(async () => { @@ -928,85 +932,111 @@ export function Dashboard(props: DashboardProps) {
{/* Desktop Header */} -
+
{/* Name Header */}
handleSort('name')} > - Name - {sortKey() === 'name' && (sortDirection() === 'asc' ? '▲' : '▼')} + Name {sortKey() === 'name' && (sortDirection() === 'asc' ? '▲' : '▼')}
{/* Type */}
handleSort('type')} + title="Type" > - Type {sortKey() === 'type' && (sortDirection() === 'asc' ? '▲' : '▼')} + + T + {sortKey() === 'type' && (sortDirection() === 'asc' ? ' ▲' : ' ▼')}
{/* VMID */}
handleSort('vmid')} + title="VMID" > - ID {sortKey() === 'vmid' && (sortDirection() === 'asc' ? '▲' : '▼')} + + ID + {sortKey() === 'vmid' && (sortDirection() === 'asc' ? ' ▲' : ' ▼')}
{/* Uptime */}
handleSort('uptime')} + title="Uptime" > - Up {sortKey() === 'uptime' && (sortDirection() === 'asc' ? '▲' : '▼')} + + Up + {sortKey() === 'uptime' && (sortDirection() === 'asc' ? ' ▲' : ' ▼')}
{/* CPU */}
handleSort('cpu')} > CPU {sortKey() === 'cpu' && (sortDirection() === 'asc' ? '▲' : '▼')}
{/* Memory */}
handleSort('memory')} + title="Memory" > - Mem {sortKey() === 'memory' && (sortDirection() === 'asc' ? '▲' : '▼')} + + Mem + {sortKey() === 'memory' && (sortDirection() === 'asc' ? ' ▲' : ' ▼')}
{/* Disk */}
handleSort('disk')} > Disk {sortKey() === 'disk' && (sortDirection() === 'asc' ? '▲' : '▼')}
{/* Disk Read */}
handleSort('diskRead')} + title="Disk Read" > - D Rd {sortKey() === 'diskRead' && (sortDirection() === 'asc' ? '▲' : '▼')} + + D Rd + {sortKey() === 'diskRead' && (sortDirection() === 'asc' ? ' ▲' : ' ▼')}
{/* Disk Write */}
handleSort('diskWrite')} + title="Disk Write" > - D Wr {sortKey() === 'diskWrite' && (sortDirection() === 'asc' ? '▲' : '▼')} + + D Wr + {sortKey() === 'diskWrite' && (sortDirection() === 'asc' ? ' ▲' : ' ▼')}
{/* Net In */}
handleSort('networkIn')} + title="Net In" > - N In {sortKey() === 'networkIn' && (sortDirection() === 'asc' ? '▲' : '▼')} + + N In + {sortKey() === 'networkIn' && (sortDirection() === 'asc' ? ' ▲' : ' ▼')}
{/* Net Out */}
handleSort('networkOut')} + title="Net Out" > - N Out {sortKey() === 'networkOut' && (sortDirection() === 'asc' ? '▲' : '▼')} + + N Out + {sortKey() === 'networkOut' && (sortDirection() === 'asc' ? ' ▲' : ' ▼')}
diff --git a/frontend-modern/src/components/Dashboard/GuestRow.tsx b/frontend-modern/src/components/Dashboard/GuestRow.tsx index e08edaf..58e803d 100644 --- a/frontend-modern/src/components/Dashboard/GuestRow.tsx +++ b/frontend-modern/src/components/Dashboard/GuestRow.tsx @@ -17,14 +17,14 @@ type Guest = VM | Container; /** * Get color class for I/O values based on throughput (bytes/sec) - * Higher throughput = more prominent color to draw attention + * Uses color intensity to indicate activity level (green/yellow/red) */ function getIOColorClass(bytesPerSec: number): string { const mbps = bytesPerSec / (1024 * 1024); - if (mbps < 1) return 'text-gray-400 dark:text-gray-500'; - if (mbps < 10) return 'text-gray-600 dark:text-gray-300'; - if (mbps < 50) return 'text-gray-700 dark:text-gray-200 font-medium'; - return 'text-gray-900 dark:text-white font-semibold'; + if (mbps < 1) return 'text-gray-300 dark:text-gray-400'; + if (mbps < 10) return 'text-green-600 dark:text-green-400'; + if (mbps < 50) return 'text-yellow-600 dark:text-yellow-400'; + return 'text-red-600 dark:text-red-400'; } // Global state for currently expanded drawer (only one drawer open at a time) @@ -59,19 +59,19 @@ interface ColumnDef { flex?: number; } -const GUEST_COLUMNS: ColumnDef[] = [ +export const GUEST_COLUMNS: ColumnDef[] = [ { id: 'name', label: 'Name', priority: 'essential', minWidth: '100px', flex: 1.5 }, - { id: 'type', label: 'Type', priority: 'essential', minWidth: '24px', maxWidth: '32px' }, - { id: 'vmid', label: 'VMID', priority: 'essential', minWidth: '28px', maxWidth: '36px' }, - { id: 'uptime', label: 'Uptime', priority: 'essential', minWidth: '28px', maxWidth: '50px' }, + { id: 'type', label: 'Type', priority: 'essential', minWidth: '24px', maxWidth: '50px' }, + { id: 'vmid', label: 'VMID', priority: 'essential', minWidth: '28px', maxWidth: '55px' }, + { id: 'uptime', label: 'Uptime', priority: 'essential', minWidth: '28px', maxWidth: '65px' }, { id: 'cpu', label: 'CPU', priority: 'essential', minWidth: '50px', flex: 1 }, { id: 'memory', label: 'Memory', priority: 'essential', minWidth: '50px', flex: 1 }, { id: 'disk', label: 'Disk', priority: 'essential', minWidth: '50px', flex: 1 }, - // I/O columns - fixed width, no flex - { id: 'diskRead', label: 'D Read', priority: 'essential', minWidth: '44px', maxWidth: '54px' }, - { id: 'diskWrite', label: 'D Write', priority: 'essential', minWidth: '44px', maxWidth: '54px' }, - { id: 'netIn', label: 'Net In', priority: 'essential', minWidth: '44px', maxWidth: '54px' }, - { id: 'netOut', label: 'Net Out', priority: 'essential', minWidth: '44px', maxWidth: '54px' }, + // I/O columns - fixed width matching v4.32.5 (grows at breakpoints to fit full header text) + { id: 'diskRead', label: 'Disk Read', priority: 'essential', minWidth: '56px', maxWidth: '90px' }, + { id: 'diskWrite', label: 'Disk Write', priority: 'essential', minWidth: '56px', maxWidth: '90px' }, + { id: 'netIn', label: 'Net In', priority: 'essential', minWidth: '56px', maxWidth: '70px' }, + { id: 'netOut', label: 'Net Out', priority: 'essential', minWidth: '56px', maxWidth: '70px' }, ]; interface GuestRowProps { @@ -662,36 +662,36 @@ export function GuestRow(props: GuestRowProps) { case 'diskRead': return ( -
- -}> - {formatSpeed(diskRead())} +
+ -}> + {formatSpeed(diskRead())}
); case 'diskWrite': return ( -
- -}> - {formatSpeed(diskWrite())} +
+ -}> + {formatSpeed(diskWrite())}
); case 'netIn': return ( -
- -}> - {formatSpeed(networkIn())} +
+ -}> + {formatSpeed(networkIn())}
); case 'netOut': return ( -
- -}> - {formatSpeed(networkOut())} +
+ -}> + {formatSpeed(networkOut())}
); diff --git a/frontend-modern/src/components/Dashboard/IOMetric.tsx b/frontend-modern/src/components/Dashboard/IOMetric.tsx index f48475b..a0f1e5b 100644 --- a/frontend-modern/src/components/Dashboard/IOMetric.tsx +++ b/frontend-modern/src/components/Dashboard/IOMetric.tsx @@ -24,7 +24,7 @@ export function IOMetric(props: IOMetricProps) { } }); - // Color based on speed (MB/s) - matching current dashboard + // Color based on speed (MB/s) - uses color intensity to indicate activity level const colorClass = createMemo(() => { if (props.disabled) return 'text-gray-400 dark:text-gray-500'; @@ -38,9 +38,9 @@ export function IOMetric(props: IOMetricProps) { return ( -
} + fallback={
-
} > -
+
{formatSpeed(currentValue(), 0)}