diff --git a/frontend-modern/src/components/Dashboard/Dashboard.tsx b/frontend-modern/src/components/Dashboard/Dashboard.tsx index 98b2352..1dcede5 100644 --- a/frontend-modern/src/components/Dashboard/Dashboard.tsx +++ b/frontend-modern/src/components/Dashboard/Dashboard.tsx @@ -1082,7 +1082,7 @@ export function Dashboard(props: DashboardProps) {
- +
@@ -1097,10 +1097,18 @@ export function Dashboard(props: DashboardProps) { class={`py-1 text-[11px] sm:text-xs font-medium uppercase tracking-wider whitespace-nowrap ${isFirst() ? 'pl-4 pr-2 text-left' : 'px-2 text-center'} ${isSortable ? 'cursor-pointer hover:bg-gray-200 dark:hover:bg-gray-600' : ''}`} - style={col.width ? { width: col.width } : undefined} + style={col.width ? { "min-width": col.width } : undefined} onClick={() => isSortable && handleSort(sortKeyForCol!)} + title={col.icon ? col.label : undefined} > - {col.label} {isSorted() && (sortDirection() === 'asc' ? '▲' : '▼')} + + {col.icon ? ( + + ) : ( + col.label + )} + {isSorted() && (sortDirection() === 'asc' ? ' ▲' : ' ▼')} + ); }} diff --git a/frontend-modern/src/components/Dashboard/GuestRow.tsx b/frontend-modern/src/components/Dashboard/GuestRow.tsx index 6a73e18..e365397 100644 --- a/frontend-modern/src/components/Dashboard/GuestRow.tsx +++ b/frontend-modern/src/components/Dashboard/GuestRow.tsx @@ -410,6 +410,7 @@ function BackupStatusCell(props: { lastBackup: string | number | null | undefine export interface GuestColumnDef { id: string; label: string; + icon?: string; // SVG icon markup for compact column headers priority: ColumnPriority; toggleable?: boolean; width?: string; // Fixed width for consistent column sizing @@ -420,31 +421,31 @@ export interface GuestColumnDef { } export const GUEST_COLUMNS: GuestColumnDef[] = [ - // Essential - always visible - { id: 'name', label: 'Name', priority: 'essential', sortKey: 'name' }, + // Essential - always visible (fixed widths ensure no overlap) + { id: 'name', label: 'Name', priority: 'essential', width: '120px', sortKey: 'name' }, { id: 'type', label: 'Type', priority: 'essential', width: '40px', sortKey: 'type' }, - { id: 'vmid', label: 'VMID', priority: 'essential', width: '45px', sortKey: 'vmid' }, + { id: 'vmid', label: 'ID', priority: 'essential', width: '45px', sortKey: 'vmid' }, - // Core metrics - percentage width for proportional sizing - { id: 'cpu', label: 'CPU', priority: 'essential', width: '15%', sortKey: 'cpu' }, - { id: 'memory', label: 'Memory', priority: 'essential', width: '15%', sortKey: 'memory' }, - { id: 'disk', label: 'Disk', priority: 'essential', width: '15%', sortKey: 'disk' }, + // Core metrics - fixed minimum widths to prevent content overlap + { id: 'cpu', label: 'CPU', priority: 'essential', width: '140px', sortKey: 'cpu' }, + { id: 'memory', label: 'Mem', priority: 'essential', width: '140px', sortKey: 'memory' }, + { id: 'disk', label: 'Disk', priority: 'essential', width: '140px', sortKey: 'disk' }, - // Secondary - visible on md+ (768px), user toggleable - { id: 'ip', label: 'IP', priority: 'secondary', width: '90px', toggleable: true }, - { id: 'uptime', label: 'Uptime', priority: 'secondary', width: '65px', toggleable: true, sortKey: 'uptime' }, - { id: 'node', label: 'Node', priority: 'secondary', width: '60px', toggleable: true, sortKey: 'node' }, + // Secondary - visible on md+ (768px), user toggleable - use icons + { id: 'ip', label: 'IP', icon: '', priority: 'secondary', width: '45px', toggleable: true }, + { id: 'uptime', label: 'Uptime', icon: '', priority: 'secondary', width: '60px', toggleable: true, sortKey: 'uptime' }, + { id: 'node', label: 'Node', icon: '', priority: 'secondary', width: '55px', toggleable: true, sortKey: 'node' }, // Supplementary - visible on lg+ (1024px), user toggleable - { id: 'backup', label: 'Backup', priority: 'supplementary', width: '55px', toggleable: true }, - { id: 'tags', label: 'Tags', priority: 'supplementary', width: '80px', toggleable: true }, + { id: 'backup', label: 'Backup', icon: '', priority: 'supplementary', width: '50px', toggleable: true }, + { id: 'tags', label: 'Tags', icon: '', priority: 'supplementary', width: '60px', toggleable: true }, // Detailed - visible on xl+ (1280px), user toggleable { id: 'os', label: 'OS', priority: 'detailed', width: '45px', toggleable: true }, - { id: 'diskRead', label: 'D Read', priority: 'detailed', width: '55px', toggleable: true, sortKey: 'diskRead' }, - { id: 'diskWrite', label: 'D Write', priority: 'detailed', width: '55px', toggleable: true, sortKey: 'diskWrite' }, - { id: 'netIn', label: 'Net In', priority: 'detailed', width: '55px', toggleable: true, sortKey: 'networkIn' }, - { id: 'netOut', label: 'Net Out', priority: 'detailed', width: '55px', toggleable: true, sortKey: 'networkOut' }, + { id: 'diskRead', label: 'D Read', icon: '', priority: 'detailed', width: '55px', toggleable: true, sortKey: 'diskRead' }, + { id: 'diskWrite', label: 'D Write', icon: '', priority: 'detailed', width: '55px', toggleable: true, sortKey: 'diskWrite' }, + { id: 'netIn', label: 'Net In', icon: '', priority: 'detailed', width: '55px', toggleable: true, sortKey: 'networkIn' }, + { id: 'netOut', label: 'Net Out', icon: '', priority: 'detailed', width: '55px', toggleable: true, sortKey: 'networkOut' }, ]; interface GuestRowProps { @@ -937,17 +938,7 @@ export function GuestRow(props: GuestRowProps) { - {/* Show tags inline only if tags column is hidden */} - - - + event.stopPropagation()}> diff --git a/frontend-modern/src/components/Dashboard/TagBadges.tsx b/frontend-modern/src/components/Dashboard/TagBadges.tsx index ecb544b..bf6dc2f 100644 --- a/frontend-modern/src/components/Dashboard/TagBadges.tsx +++ b/frontend-modern/src/components/Dashboard/TagBadges.tsx @@ -12,7 +12,8 @@ interface TagBadgesProps { } export const TagBadges: Component = (props) => { - const maxVisible = () => props.maxVisible ?? 3; + // maxVisible: 0 means show all, undefined defaults to 3 + const maxVisible = () => props.maxVisible === 0 ? Infinity : (props.maxVisible ?? 3); const darkModeSignal = useDarkMode(); const isDark = () => props.isDarkMode ?? darkModeSignal(); diff --git a/frontend-modern/src/components/shared/NodeSummaryTable.tsx b/frontend-modern/src/components/shared/NodeSummaryTable.tsx index cb8c880..ec64028 100644 --- a/frontend-modern/src/components/shared/NodeSummaryTable.tsx +++ b/frontend-modern/src/components/shared/NodeSummaryTable.tsx @@ -342,7 +342,7 @@ export const NodeSummaryTable: Component = (props) => { return (
-
+
- - - - - - - - - - diff --git a/frontend-modern/src/components/shared/Sparkline.tsx b/frontend-modern/src/components/shared/Sparkline.tsx index 7c428b5..54d5697 100644 --- a/frontend-modern/src/components/shared/Sparkline.tsx +++ b/frontend-modern/src/components/shared/Sparkline.tsx @@ -253,10 +253,10 @@ export const Sparkline: Component = (props) => { setHoveredPoint(null); }; - // Format timestamp for tooltip + // Format timestamp for tooltip (24-hour clock) const formatTime = (timestamp: number) => { const date = new Date(timestamp); - return date.toLocaleTimeString([], { hour: '2-digit', minute: '2-digit', second: '2-digit' }); + return date.toLocaleTimeString([], { hour: '2-digit', minute: '2-digit', second: '2-digit', hour12: false }); }; return ( diff --git a/frontend-modern/src/hooks/useColumnVisibility.ts b/frontend-modern/src/hooks/useColumnVisibility.ts index a17c6af..eecf6c1 100644 --- a/frontend-modern/src/hooks/useColumnVisibility.ts +++ b/frontend-modern/src/hooks/useColumnVisibility.ts @@ -5,6 +5,7 @@ import { useBreakpoint, type ColumnPriority, PRIORITY_BREAKPOINTS, type Breakpoi export interface ColumnDef { id: string; label: string; + icon?: string; // SVG icon markup for compact column headers priority: ColumnPriority; toggleable?: boolean; width?: string; // Fixed width for consistent column sizing
= (props) => { > {props.currentTab === 'backups' ? 'Node / PBS' : 'Node'} {renderSortIndicator('name')} handleSort('uptime')}> + handleSort('uptime')}> Uptime {renderSortIndicator('uptime')} handleSort('cpu')}> + handleSort('cpu')}> CPU {renderSortIndicator('cpu')} handleSort('memory')}> + handleSort('memory')}> Memory {renderSortIndicator('memory')} handleSort('disk')}> + handleSort('disk')}> Disk {renderSortIndicator('disk')} handleSort('temperature')}> + handleSort('temperature')}> Temp {renderSortIndicator('temperature')} handleSort('vmCount')}> + handleSort('vmCount')}> VMs {renderSortIndicator('vmCount')} handleSort('containerCount')}> + handleSort('containerCount')}> CTs {renderSortIndicator('containerCount')} handleSort('storageCount')}> + handleSort('storageCount')}> Storage {renderSortIndicator('storageCount')} handleSort('diskCount')}> + handleSort('diskCount')}> Disks {renderSortIndicator('diskCount')} handleSort('backupCount')}> + handleSort('backupCount')}> Backups {renderSortIndicator('backupCount')}