Refactor ServerDetail component to improve chart configuration and cleanup logic. Added common chart options for CPU, RAM, and Disk usage, ensuring consistent y-axis settings. Updated time range selection for better clarity and usability.

This commit is contained in:
headlessdev 2025-04-23 19:16:49 +02:00
parent 4e58dc5a0b
commit cd8379407a

View File

@ -98,16 +98,16 @@ export default function ServerDetail() {
}, [serverId, timeRange]) }, [serverId, timeRange])
useEffect(() => { useEffect(() => {
if (!server || !server.history) return if (!server || !server.history) return;
// Clean up existing charts // Clean up existing charts
if (cpuChartRef.current) cpuChartRef.current.destroy() if (cpuChartRef.current) cpuChartRef.current.destroy();
if (ramChartRef.current) ramChartRef.current.destroy() if (ramChartRef.current) ramChartRef.current.destroy();
if (diskChartRef.current) diskChartRef.current.destroy() if (diskChartRef.current) diskChartRef.current.destroy();
// Wait for DOM to be ready // Wait for DOM to be ready
const initTimer = setTimeout(() => { const initTimer = setTimeout(() => {
const history = server.history as ServerHistory const history = server.history as ServerHistory;
// Format time labels based on the selected time range // Format time labels based on the selected time range
const timeLabels = history.labels.map((date: string) => { const timeLabels = history.labels.map((date: string) => {
@ -146,7 +146,8 @@ export default function ServerDetail() {
} }
} }
const chartConfig = { // Directly hardcode the y-axis maximum in each chart option
const commonOptions = {
responsive: true, responsive: true,
maintainAspectRatio: false, maintainAspectRatio: false,
interaction: { interaction: {
@ -154,26 +155,17 @@ export default function ServerDetail() {
axis: 'x' as const, axis: 'x' as const,
intersect: false intersect: false
}, },
plugins: {
title: {
display: true,
text: getRangeTitle(),
font: {
size: 14
}
},
tooltip: {
callbacks: {
title: function(tooltipItems: any) {
return timeLabels[tooltipItems[0].dataIndex];
}
}
}
},
scales: { scales: {
y: { y: {
beginAtZero: true, min: 0,
max: 100, max: 100,
beginAtZero: true,
ticks: {
stepSize: 20,
callback: function(value: any) {
return value + '%';
}
},
title: { title: {
display: true, display: true,
text: 'Usage %' text: 'Usage %'
@ -190,45 +182,13 @@ export default function ServerDetail() {
radius: 0 radius: 0
}, },
line: { line: {
tension: 0.4 tension: 0.4,
} spanGaps: true
}
}
// Add individual chart configs
const cpuChartConfig = {
...chartConfig,
plugins: {
...chartConfig.plugins,
title: {
...chartConfig.plugins.title,
text: 'CPU Usage History'
}
}
};
const ramChartConfig = {
...chartConfig,
plugins: {
...chartConfig.plugins,
title: {
...chartConfig.plugins.title,
text: 'RAM Usage History'
}
}
};
const diskChartConfig = {
...chartConfig,
plugins: {
...chartConfig.plugins,
title: {
...chartConfig.plugins.title,
text: 'Disk Usage History'
} }
} }
}; };
// Create charts with very explicit y-axis max values
const cpuCanvas = document.getElementById(`cpu-chart`) as HTMLCanvasElement const cpuCanvas = document.getElementById(`cpu-chart`) as HTMLCanvasElement
if (cpuCanvas) { if (cpuCanvas) {
cpuChartRef.current = new Chart(cpuCanvas, { cpuChartRef.current = new Chart(cpuCanvas, {
@ -237,13 +197,38 @@ export default function ServerDetail() {
labels: timeLabels, labels: timeLabels,
datasets: [{ datasets: [{
label: 'CPU Usage', label: 'CPU Usage',
data: history.datasets.cpu.filter(value => value !== null), data: history.datasets.cpu,
borderColor: 'rgb(75, 192, 192)', borderColor: 'rgb(75, 192, 192)',
backgroundColor: 'rgba(75, 192, 192, 0.1)', backgroundColor: 'rgba(75, 192, 192, 0.1)',
fill: true fill: true
}] }]
}, },
options: cpuChartConfig options: {
...commonOptions,
plugins: {
title: {
display: true,
text: 'CPU Usage History',
font: {
size: 14
}
},
tooltip: {
callbacks: {
title: function(tooltipItems: any) {
return timeLabels[tooltipItems[0].dataIndex];
}
}
}
},
scales: {
...commonOptions.scales,
y: {
...commonOptions.scales.y,
max: 100 // Force this to ensure it's applied
}
}
}
}) })
} }
@ -255,13 +240,38 @@ export default function ServerDetail() {
labels: timeLabels, labels: timeLabels,
datasets: [{ datasets: [{
label: 'RAM Usage', label: 'RAM Usage',
data: history.datasets.ram.filter(value => value !== null), data: history.datasets.ram,
borderColor: 'rgb(153, 102, 255)', borderColor: 'rgb(153, 102, 255)',
backgroundColor: 'rgba(153, 102, 255, 0.1)', backgroundColor: 'rgba(153, 102, 255, 0.1)',
fill: true fill: true
}] }]
}, },
options: ramChartConfig options: {
...commonOptions,
plugins: {
title: {
display: true,
text: 'RAM Usage History',
font: {
size: 14
}
},
tooltip: {
callbacks: {
title: function(tooltipItems: any) {
return timeLabels[tooltipItems[0].dataIndex];
}
}
}
},
scales: {
...commonOptions.scales,
y: {
...commonOptions.scales.y,
max: 100 // Force this to ensure it's applied
}
}
}
}) })
} }
@ -273,24 +283,49 @@ export default function ServerDetail() {
labels: timeLabels, labels: timeLabels,
datasets: [{ datasets: [{
label: 'Disk Usage', label: 'Disk Usage',
data: history.datasets.disk.filter(value => value !== null), data: history.datasets.disk,
borderColor: 'rgb(255, 159, 64)', borderColor: 'rgb(255, 159, 64)',
backgroundColor: 'rgba(255, 159, 64, 0.1)', backgroundColor: 'rgba(255, 159, 64, 0.1)',
fill: true fill: true
}] }]
}, },
options: diskChartConfig options: {
...commonOptions,
plugins: {
title: {
display: true,
text: 'Disk Usage History',
font: {
size: 14
}
},
tooltip: {
callbacks: {
title: function(tooltipItems: any) {
return timeLabels[tooltipItems[0].dataIndex];
}
}
}
},
scales: {
...commonOptions.scales,
y: {
...commonOptions.scales.y,
max: 100 // Force this to ensure it's applied
}
}
}
}) })
} }
}, 100) }, 100);
return () => { return () => {
clearTimeout(initTimer) clearTimeout(initTimer);
if (cpuChartRef.current) cpuChartRef.current.destroy() if (cpuChartRef.current) cpuChartRef.current.destroy();
if (ramChartRef.current) ramChartRef.current.destroy() if (ramChartRef.current) ramChartRef.current.destroy();
if (diskChartRef.current) diskChartRef.current.destroy() if (diskChartRef.current) diskChartRef.current.destroy();
} };
}, [server, timeRange]) }, [server, timeRange]);
// Function to refresh data // Function to refresh data
const refreshData = () => { const refreshData = () => {
@ -384,19 +419,6 @@ export default function ServerDetail() {
</CardDescription> </CardDescription>
</div> </div>
</div> </div>
<div className="flex gap-2">
<Select value={timeRange} onValueChange={(value: '1h' | '7d' | '30d') => setTimeRange(value)}>
<SelectTrigger className="w-[180px]">
<SelectValue placeholder="Time range" />
</SelectTrigger>
<SelectContent>
<SelectItem value="1h">Last Hour</SelectItem>
<SelectItem value="7d">Last 7 Days (Hourly)</SelectItem>
<SelectItem value="30d">Last 30 Days (4h Intervals)</SelectItem>
</SelectContent>
</Select>
<Button variant="outline" onClick={refreshData}>Refresh Data</Button>
</div>
</div> </div>
</CardHeader> </CardHeader>
<CardContent> <CardContent>
@ -490,10 +512,22 @@ export default function ServerDetail() {
: 'Last 30 days, 4-hour intervals'} : 'Last 30 days, 4-hour intervals'}
</CardDescription> </CardDescription>
</div> </div>
<div className="flex gap-2">
<Select value={timeRange} onValueChange={(value: '1h' | '7d' | '30d') => setTimeRange(value)}>
<SelectTrigger className="w-[180px]">
<SelectValue placeholder="Time range" />
</SelectTrigger>
<SelectContent>
<SelectItem value="1h">Last Hour (per minute)</SelectItem>
<SelectItem value="7d">Last 7 Days (hourly)</SelectItem>
<SelectItem value="30d">Last 30 Days (4h intervals)</SelectItem>
</SelectContent>
</Select>
<Button variant="outline" onClick={refreshData}>Refresh</Button>
</div>
</div> </div>
</CardHeader> </CardHeader>
<CardContent> <CardContent>
<ScrollArea className="h-[600px] pr-4">
<div className="grid grid-cols-1 gap-8"> <div className="grid grid-cols-1 gap-8">
<div className="h-[200px] relative bg-background"> <div className="h-[200px] relative bg-background">
<canvas id="cpu-chart" /> <canvas id="cpu-chart" />
@ -505,7 +539,6 @@ export default function ServerDetail() {
<canvas id="disk-chart" /> <canvas id="disk-chart" />
</div> </div>
</div> </div>
</ScrollArea>
</CardContent> </CardContent>
</Card> </Card>
</div> </div>