diff --git a/frontend-modern/src/components/Dashboard/EnhancedCPUBar.tsx b/frontend-modern/src/components/Dashboard/EnhancedCPUBar.tsx new file mode 100644 index 0000000..fc6e2b6 --- /dev/null +++ b/frontend-modern/src/components/Dashboard/EnhancedCPUBar.tsx @@ -0,0 +1,154 @@ +import { Show, createMemo, createSignal } from 'solid-js'; +import { Portal } from 'solid-js/web'; +import { formatPercent } from '@/utils/format'; +import { useMetricsViewMode } from '@/stores/metricsViewMode'; +import { getMetricHistory } from '@/stores/metricsHistory'; +import { Sparkline } from '@/components/shared/Sparkline'; + +interface EnhancedCPUBarProps { + usage: number; // CPU Usage % (0-100) + loadAverage?: number; // 1-minute load average + cores?: number; // Number of cores + model?: string; // CPU Model name (for tooltip) + resourceId?: string; // For sparkline history +} + +export function EnhancedCPUBar(props: EnhancedCPUBarProps) { + const [showTooltip, setShowTooltip] = createSignal(false); + const [tooltipPos, setTooltipPos] = createSignal({ x: 0, y: 0 }); + let containerRef: HTMLDivElement | undefined; + + // Calculate Load % relative to core count + const loadPercent = createMemo(() => { + if (props.loadAverage === undefined || !props.cores || props.cores === 0) return 0; + return (props.loadAverage / props.cores) * 100; + }); + + const isOverloaded = createMemo(() => loadPercent() > 100); + + // Bar color based on usage + const barColor = createMemo(() => { + if (props.usage >= 90) return 'bg-red-500/60 dark:bg-red-500/50'; + if (props.usage >= 80) return 'bg-yellow-500/60 dark:bg-yellow-500/50'; + return 'bg-green-500/60 dark:bg-green-500/50'; + }); + + // Load marker position (capped at 100%) + const markerPosition = createMemo(() => Math.min(loadPercent(), 100)); + + const handleMouseEnter = (e: MouseEvent) => { + const rect = (e.currentTarget as HTMLElement).getBoundingClientRect(); + setTooltipPos({ x: rect.left + rect.width / 2, y: rect.top }); + setShowTooltip(true); + }; + + const handleMouseLeave = () => { + setShowTooltip(false); + }; + + const { viewMode } = useMetricsViewMode(); + + // Get metric history for sparkline + const metricHistory = createMemo(() => { + if (viewMode() !== 'sparklines' || !props.resourceId) return []; + return getMetricHistory(props.resourceId); + }); + + return ( + +
+ {/* Usage Bar */} +
+ + {/* Load Average Marker */} + +
+ + + {/* Label */} + + {formatPercent(props.usage)} + +
+ + {/* Tooltip */} + + +
+
+
+ CPU Details +
+ + +
+ {props.model} +
+
+ +
+ Usage + 90 ? 'text-red-400' : 'text-gray-200'}`}> + {formatPercent(props.usage)} + +
+ + +
+ Load (1m) + + {props.loadAverage?.toFixed(2)} + +
+
+ Capacity + + {loadPercent().toFixed(0)}% of {props.cores} cores + +
+
+
+
+
+
+
+ } + > + {/* Sparkline mode */} +
+
+ +
+ + {formatPercent(props.usage)} + +
+ + ); +} diff --git a/frontend-modern/src/components/Docker/DockerHostSummaryTable.tsx b/frontend-modern/src/components/Docker/DockerHostSummaryTable.tsx index ab1f1d2..5fb54dc 100644 --- a/frontend-modern/src/components/Docker/DockerHostSummaryTable.tsx +++ b/frontend-modern/src/components/Docker/DockerHostSummaryTable.tsx @@ -10,7 +10,8 @@ import { buildMetricKey } from '@/utils/metricsKeys'; import { StatusDot } from '@/components/shared/StatusDot'; import { getDockerHostStatusIndicator } from '@/utils/status'; import { useBreakpoint } from '@/hooks/useBreakpoint'; -import { ResponsiveMetricCell } from '@/components/shared/responsive'; +import { ResponsiveMetricCell, MetricText } from '@/components/shared/responsive'; +import { EnhancedCPUBar } from '@/components/Dashboard/EnhancedCPUBar'; export interface DockerHostSummary { host: DockerHost; @@ -283,13 +284,19 @@ export const DockerHostSummaryTable: Component = (p
- + +
+ +
+
+ = (props) => { case 'cpu': return (
- + +
+ +
+
+
); case 'memory': diff --git a/frontend-modern/src/components/shared/NodeSummaryTable.tsx b/frontend-modern/src/components/shared/NodeSummaryTable.tsx index ee04b1c..2e0d005 100644 --- a/frontend-modern/src/components/shared/NodeSummaryTable.tsx +++ b/frontend-modern/src/components/shared/NodeSummaryTable.tsx @@ -13,6 +13,7 @@ import { getNodeStatusIndicator, getPBSStatusIndicator } from '@/utils/status'; import { type ColumnPriority } from '@/hooks/useBreakpoint'; import { ResponsiveMetricCell, MetricText, useGridTemplate } from '@/components/shared/responsive'; import { StackedMemoryBar } from '@/components/Dashboard/StackedMemoryBar'; +import { EnhancedCPUBar } from '@/components/Dashboard/EnhancedCPUBar'; import { TemperatureGauge } from '@/components/shared/TemperatureGauge'; // Icons for mobile headers @@ -699,15 +700,31 @@ export const NodeSummaryTable: Component = (props) => { case 'cpu': return (
- + +
+ +
+
+
);