diff --git a/frontend/dist/index.html b/frontend/dist/index.html index d91f330d..25c4a44b 100644 --- a/frontend/dist/index.html +++ b/frontend/dist/index.html @@ -6,8 +6,8 @@ React Dashboard Template - - + + diff --git a/frontend/src/components/MetricsGraph.jsx b/frontend/src/components/MetricsGraph.jsx index 18791671..f98d48b6 100644 --- a/frontend/src/components/MetricsGraph.jsx +++ b/frontend/src/components/MetricsGraph.jsx @@ -1,11 +1,18 @@ import React, { useState, useEffect } from 'react'; import { LineChart, Line, XAxis, YAxis, CartesianGrid, - Tooltip, ResponsiveContainer, Area, AreaChart + Tooltip, ResponsiveContainer, Area, AreaChart, Legend } from 'recharts'; import { Cpu, MemoryStick, HardDrive, Clock, TrendingUp } from 'lucide-react'; import api from '../services/api'; +// Chart colors - using actual hex values for SVG compatibility +const CHART_COLORS = { + cpu: '#10b981', // Green + memory: '#6366f1', // Purple/Indigo + disk: '#f59e0b' // Amber/Orange +}; + const MetricsGraph = ({ compact = false }) => { const [data, setData] = useState(null); const [period, setPeriod] = useState('1h'); @@ -46,13 +53,13 @@ const MetricsGraph = ({ compact = false }) => { function getColor(value, type) { if (type === 'cpu') { - if (value > 80) return 'var(--danger)'; - if (value > 60) return 'var(--warning)'; - return 'var(--success)'; + if (value > 80) return '#ef4444'; // danger + if (value > 60) return '#f59e0b'; // warning + return '#10b981'; // success } - if (value > 90) return 'var(--danger)'; - if (value > 80) return 'var(--warning)'; - return 'var(--accent-primary)'; + if (value > 90) return '#ef4444'; // danger + if (value > 80) return '#f59e0b'; // warning + return '#6366f1'; // accent } if (loading && !data) { @@ -94,9 +101,10 @@ const MetricsGraph = ({ compact = false }) => {
{label}
{payload.map((entry, index) => ( -
- {entry.name}: - {entry.value}% +
+ + {entry.name}: + {entry.value?.toFixed(1)}%
))}
@@ -129,9 +137,9 @@ const MetricsGraph = ({ compact = false }) => { - - - + + + @@ -140,8 +148,8 @@ const MetricsGraph = ({ compact = false }) => { @@ -224,63 +232,97 @@ const MetricsGraph = ({ compact = false }) => {
- - - + + + + {/* CPU Gradient - Green */} + + + + + {/* Memory Gradient - Purple */} + + + + + {/* Disk Gradient - Amber */} + + + + + {/* Glow filters for lines */} + + + + + + + + + `${value}%`} /> } /> - - - - +
- + CPU
- + Memory
- + Disk
diff --git a/frontend/src/styles/components/_metrics-graph.less b/frontend/src/styles/components/_metrics-graph.less index 891f9e3a..b0b1153f 100644 --- a/frontend/src/styles/components/_metrics-graph.less +++ b/frontend/src/styles/components/_metrics-graph.less @@ -85,19 +85,51 @@ // Chart Container .metrics-chart-container { margin-bottom: @space-4; + background: linear-gradient(180deg, rgba(39, 39, 42, 0.3) 0%, transparent 100%); + border-radius: @radius-lg; + padding: @space-2 0; &.compact { margin-bottom: @space-3; + background: transparent; + padding: 0; + } + + // Recharts path animation + .recharts-area-area { + animation: fadeIn 0.6s ease-out; + } + + .recharts-area-curve { + animation: drawLine 1s ease-out; + } +} + +@keyframes fadeIn { + from { opacity: 0; } + to { opacity: 1; } +} + +@keyframes drawLine { + from { + stroke-dasharray: 1000; + stroke-dashoffset: 1000; + } + to { + stroke-dasharray: 1000; + stroke-dashoffset: 0; } } // Tooltip .metrics-tooltip { - background: @bg-elevated; + background: rgba(24, 24, 27, 0.95); + backdrop-filter: blur(8px); border: 1px solid @border-subtle; - border-radius: @radius-md; - padding: @space-3; - box-shadow: @shadow-lg; + border-radius: @radius-lg; + padding: @space-3 @space-4; + box-shadow: 0 8px 32px rgba(0, 0, 0, 0.4); + min-width: 140px; .tooltip-time { font-size: @font-size-xs; @@ -105,19 +137,34 @@ margin-bottom: @space-2; padding-bottom: @space-2; border-bottom: 1px solid @border-subtle; + text-align: center; } .tooltip-row { display: flex; - justify-content: space-between; - gap: @space-4; + align-items: center; + gap: @space-2; font-size: @font-size-sm; padding: @space-1 0; + } - span:last-child { - font-family: @font-mono; - font-weight: @font-weight-semibold; - } + .tooltip-dot { + width: 8px; + height: 8px; + border-radius: 50%; + flex-shrink: 0; + } + + .tooltip-label { + color: @text-secondary; + flex: 1; + } + + .tooltip-value { + font-family: @font-mono; + font-weight: @font-weight-bold; + min-width: 48px; + text-align: right; } } @@ -126,23 +173,60 @@ display: flex; align-items: center; justify-content: center; - gap: @space-6; + gap: @space-8; padding-top: @space-4; + margin-top: @space-2; border-top: 1px solid @border-subtle; } .legend-item { display: flex; align-items: center; - gap: @space-2; + gap: @space-3; font-size: @font-size-sm; color: @text-secondary; + padding: @space-1 @space-3; + border-radius: @radius-full; + transition: @transition-fast; + + &:hover { + background: @bg-hover; + color: @text-primary; + } } .legend-line { - width: 20px; + width: 24px; height: 3px; border-radius: 2px; + position: relative; + + &::after { + content: ''; + position: absolute; + top: 50%; + left: 50%; + transform: translate(-50%, -50%); + width: 6px; + height: 6px; + border-radius: 50%; + background: inherit; + } + + &.cpu { + background: #10b981; + box-shadow: 0 0 8px rgba(16, 185, 129, 0.5); + } + + &.memory { + background: #6366f1; + box-shadow: 0 0 8px rgba(99, 102, 241, 0.5); + } + + &.disk { + background: #f59e0b; + box-shadow: 0 0 8px rgba(245, 158, 11, 0.5); + } } // Compact Version @@ -192,12 +276,26 @@ .recharts-cartesian-grid-horizontal line, .recharts-cartesian-grid-vertical line { stroke: @border-subtle; + opacity: 0.5; } .recharts-text { fill: @text-secondary; } +.recharts-area-curve { + filter: drop-shadow(0 0 4px currentColor); +} + +.recharts-active-dot { + filter: drop-shadow(0 0 6px currentColor); +} + +.recharts-tooltip-cursor { + stroke: @text-tertiary; + stroke-dasharray: 4 4; +} + // Responsive @media (max-width: @breakpoint-md) { .metrics-summary-row {