mirror of
https://github.com/jhd3197/ServerKit.git
synced 2026-11-02 18:17:11 +09:00
Enhance metrics graph visuals and tooltip UI
Switches from LineChart to AreaChart for main metrics graph, adds animated gradients and glow effects, and updates legend styling. Tooltip UI is improved with clearer layout, colored dots, and value formatting. Chart colors now use explicit hex values for better SVG compatibility, and LESS styles are updated for improved appearance and interactivity.
This commit is contained in:
1 parent
9bb434c6af
commit
44f3abe8d2
3 files changed
+190
-50
No files matched your search
Vendored
+2
-2
@@ -6,8 +6,8 @@
|
||||
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>React Dashboard Template</title>
|
||||
<script type="module" crossorigin src="/assets/index-Bdew6YNt.js"></script>
|
||||
<link rel="stylesheet" crossorigin href="/assets/index-BLkkqmRP.css">
|
||||
<script type="module" crossorigin src="/assets/index-DYaRythr.js"></script>
|
||||
<link rel="stylesheet" crossorigin href="/assets/index-W4e3hCos.css">
|
||||
</head>
|
||||
|
||||
<body>
|
||||
|
||||
@@ -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 }) => {
|
||||
<div className="metrics-tooltip">
|
||||
<div className="tooltip-time">{label}</div>
|
||||
{payload.map((entry, index) => (
|
||||
<div key={index} className="tooltip-row" style={{ color: entry.color }}>
|
||||
<span>{entry.name}:</span>
|
||||
<span>{entry.value}%</span>
|
||||
<div key={index} className="tooltip-row">
|
||||
<span className="tooltip-dot" style={{ backgroundColor: entry.stroke || entry.color }} />
|
||||
<span className="tooltip-label">{entry.name}:</span>
|
||||
<span className="tooltip-value" style={{ color: entry.stroke || entry.color }}>{entry.value?.toFixed(1)}%</span>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
@@ -129,9 +137,9 @@ const MetricsGraph = ({ compact = false }) => {
|
||||
<ResponsiveContainer width="100%" height={120}>
|
||||
<AreaChart data={chartData} margin={{ top: 5, right: 5, left: -20, bottom: 0 }}>
|
||||
<defs>
|
||||
<linearGradient id="cpuGradient" x1="0" y1="0" x2="0" y2="1">
|
||||
<stop offset="5%" stopColor="var(--success)" stopOpacity={0.3} />
|
||||
<stop offset="95%" stopColor="var(--success)" stopOpacity={0} />
|
||||
<linearGradient id="cpuGradientCompact" x1="0" y1="0" x2="0" y2="1">
|
||||
<stop offset="5%" stopColor={CHART_COLORS.cpu} stopOpacity={0.4} />
|
||||
<stop offset="95%" stopColor={CHART_COLORS.cpu} stopOpacity={0} />
|
||||
</linearGradient>
|
||||
</defs>
|
||||
<XAxis dataKey="time" tick={false} axisLine={false} />
|
||||
@@ -140,8 +148,8 @@ const MetricsGraph = ({ compact = false }) => {
|
||||
<Area
|
||||
type="monotone"
|
||||
dataKey="cpu"
|
||||
stroke="var(--success)"
|
||||
fill="url(#cpuGradient)"
|
||||
stroke={CHART_COLORS.cpu}
|
||||
fill="url(#cpuGradientCompact)"
|
||||
strokeWidth={2}
|
||||
name="CPU"
|
||||
/>
|
||||
@@ -224,63 +232,97 @@ const MetricsGraph = ({ compact = false }) => {
|
||||
</div>
|
||||
|
||||
<div className="metrics-chart-container">
|
||||
<ResponsiveContainer width="100%" height={250}>
|
||||
<LineChart data={chartData} margin={{ top: 10, right: 10, left: -10, bottom: 0 }}>
|
||||
<CartesianGrid strokeDasharray="3 3" stroke="var(--border-subtle)" />
|
||||
<ResponsiveContainer width="100%" height={280}>
|
||||
<AreaChart data={chartData} margin={{ top: 10, right: 10, left: -10, bottom: 0 }}>
|
||||
<defs>
|
||||
{/* CPU Gradient - Green */}
|
||||
<linearGradient id="cpuGradient" x1="0" y1="0" x2="0" y2="1">
|
||||
<stop offset="5%" stopColor={CHART_COLORS.cpu} stopOpacity={0.3} />
|
||||
<stop offset="95%" stopColor={CHART_COLORS.cpu} stopOpacity={0} />
|
||||
</linearGradient>
|
||||
{/* Memory Gradient - Purple */}
|
||||
<linearGradient id="memoryGradient" x1="0" y1="0" x2="0" y2="1">
|
||||
<stop offset="5%" stopColor={CHART_COLORS.memory} stopOpacity={0.3} />
|
||||
<stop offset="95%" stopColor={CHART_COLORS.memory} stopOpacity={0} />
|
||||
</linearGradient>
|
||||
{/* Disk Gradient - Amber */}
|
||||
<linearGradient id="diskGradient" x1="0" y1="0" x2="0" y2="1">
|
||||
<stop offset="5%" stopColor={CHART_COLORS.disk} stopOpacity={0.3} />
|
||||
<stop offset="95%" stopColor={CHART_COLORS.disk} stopOpacity={0} />
|
||||
</linearGradient>
|
||||
{/* Glow filters for lines */}
|
||||
<filter id="glow" x="-20%" y="-20%" width="140%" height="140%">
|
||||
<feGaussianBlur stdDeviation="2" result="coloredBlur" />
|
||||
<feMerge>
|
||||
<feMergeNode in="coloredBlur" />
|
||||
<feMergeNode in="SourceGraphic" />
|
||||
</feMerge>
|
||||
</filter>
|
||||
</defs>
|
||||
<CartesianGrid strokeDasharray="3 3" stroke="#27272a" opacity={0.5} />
|
||||
<XAxis
|
||||
dataKey="time"
|
||||
tick={{ fontSize: 11, fill: 'var(--text-secondary)' }}
|
||||
axisLine={{ stroke: 'var(--border-subtle)' }}
|
||||
tick={{ fontSize: 11, fill: '#a1a1aa' }}
|
||||
axisLine={{ stroke: '#27272a' }}
|
||||
tickLine={false}
|
||||
interval="preserveStartEnd"
|
||||
/>
|
||||
<YAxis
|
||||
domain={[0, 100]}
|
||||
tick={{ fontSize: 11, fill: 'var(--text-secondary)' }}
|
||||
axisLine={{ stroke: 'var(--border-subtle)' }}
|
||||
tick={{ fontSize: 11, fill: '#a1a1aa' }}
|
||||
axisLine={{ stroke: '#27272a' }}
|
||||
tickLine={false}
|
||||
tickFormatter={(value) => `${value}%`}
|
||||
/>
|
||||
<Tooltip content={<CustomTooltip />} />
|
||||
<Line
|
||||
<Area
|
||||
type="monotone"
|
||||
dataKey="cpu"
|
||||
stroke="var(--success)"
|
||||
strokeWidth={2}
|
||||
stroke={CHART_COLORS.cpu}
|
||||
fill="url(#cpuGradient)"
|
||||
strokeWidth={2.5}
|
||||
dot={false}
|
||||
activeDot={{ r: 6, strokeWidth: 2, stroke: CHART_COLORS.cpu, fill: '#18181b' }}
|
||||
name="CPU"
|
||||
filter="url(#glow)"
|
||||
/>
|
||||
<Line
|
||||
<Area
|
||||
type="monotone"
|
||||
dataKey="memory"
|
||||
stroke="var(--accent-primary)"
|
||||
strokeWidth={2}
|
||||
stroke={CHART_COLORS.memory}
|
||||
fill="url(#memoryGradient)"
|
||||
strokeWidth={2.5}
|
||||
dot={false}
|
||||
activeDot={{ r: 6, strokeWidth: 2, stroke: CHART_COLORS.memory, fill: '#18181b' }}
|
||||
name="Memory"
|
||||
filter="url(#glow)"
|
||||
/>
|
||||
<Line
|
||||
<Area
|
||||
type="monotone"
|
||||
dataKey="disk"
|
||||
stroke="var(--warning)"
|
||||
strokeWidth={2}
|
||||
stroke={CHART_COLORS.disk}
|
||||
fill="url(#diskGradient)"
|
||||
strokeWidth={2.5}
|
||||
dot={false}
|
||||
activeDot={{ r: 6, strokeWidth: 2, stroke: CHART_COLORS.disk, fill: '#18181b' }}
|
||||
name="Disk"
|
||||
filter="url(#glow)"
|
||||
/>
|
||||
</LineChart>
|
||||
</AreaChart>
|
||||
</ResponsiveContainer>
|
||||
</div>
|
||||
|
||||
<div className="metrics-legend">
|
||||
<div className="legend-item">
|
||||
<span className="legend-line" style={{ background: 'var(--success)' }} />
|
||||
<span className="legend-line cpu" />
|
||||
<span>CPU</span>
|
||||
</div>
|
||||
<div className="legend-item">
|
||||
<span className="legend-line" style={{ background: 'var(--accent-primary)' }} />
|
||||
<span className="legend-line memory" />
|
||||
<span>Memory</span>
|
||||
</div>
|
||||
<div className="legend-item">
|
||||
<span className="legend-line" style={{ background: 'var(--warning)' }} />
|
||||
<span className="legend-line disk" />
|
||||
<span>Disk</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -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 {
|
||||
|
||||
Reference in new issue
Block a user