Fix: Server history chart and date filter
Ensure the historical performance chart correctly displays server metric data and that the date filter dropdown functions as expected.
This commit is contained in:
@@ -1,20 +1,23 @@
|
||||
import { useQuery } from "@tanstack/react-query";
|
||||
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
|
||||
import { ChartContainer, ChartTooltip, ChartTooltipContent } from "@/components/ui/chart";
|
||||
import { LineChart, Line, XAxis, YAxis, CartesianGrid, ResponsiveContainer } from "recharts";
|
||||
import { LineChart, Line, AreaChart, Area, XAxis, YAxis, CartesianGrid, ResponsiveContainer } from "recharts";
|
||||
import { serverService } from "@/services/serverService";
|
||||
import { Loader2, TrendingUp, Cpu, HardDrive, Wifi, MemoryStick } from "lucide-react";
|
||||
import { useTheme } from "@/contexts/ThemeContext";
|
||||
import { DateRangeFilter } from "@/components/services/DateRangeFilter";
|
||||
import { TimeRangeSelector } from "./charts/TimeRangeSelector";
|
||||
import { useState } from "react";
|
||||
import { formatChartData, filterMetricsByTimeRange, formatBytes } from "./charts/dataUtils";
|
||||
|
||||
interface ServerHistoryChartsProps {
|
||||
serverId: string;
|
||||
}
|
||||
|
||||
type TimeRange = '60m' | '1d' | '7d' | '1m' | '3m';
|
||||
|
||||
export const ServerHistoryCharts = ({ serverId }: ServerHistoryChartsProps) => {
|
||||
const { theme } = useTheme();
|
||||
const [dateRange, setDateRange] = useState({ start: new Date(Date.now() - 24 * 60 * 60 * 1000), end: new Date() });
|
||||
const [timeRange, setTimeRange] = useState<TimeRange>("1d");
|
||||
|
||||
console.log('ServerHistoryCharts: Rendering with serverId:', serverId);
|
||||
|
||||
@@ -23,117 +26,135 @@ export const ServerHistoryCharts = ({ serverId }: ServerHistoryChartsProps) => {
|
||||
isLoading,
|
||||
error
|
||||
} = useQuery({
|
||||
queryKey: ['server-metrics-history', serverId],
|
||||
queryKey: ['server-metrics-history', serverId, timeRange],
|
||||
queryFn: async () => {
|
||||
console.log('ServerHistoryCharts: Fetching metrics for serverId:', serverId);
|
||||
const result = await serverService.getServerMetrics(serverId);
|
||||
const result = await serverService.getServerMetrics(serverId, timeRange);
|
||||
console.log('ServerHistoryCharts: Raw metrics result:', result);
|
||||
return result;
|
||||
},
|
||||
enabled: !!serverId,
|
||||
refetchInterval: 60000, // Refresh every minute
|
||||
refetchInterval: 60000,
|
||||
retry: 1
|
||||
});
|
||||
|
||||
const handleDateRangeChange = (startDate: Date, endDate: Date, option: any) => {
|
||||
setDateRange({ start: startDate, end: endDate });
|
||||
console.log('Date range changed:', { startDate, endDate, option });
|
||||
};
|
||||
|
||||
console.log('ServerHistoryCharts: Query state:', {
|
||||
metricsCount: metrics.length,
|
||||
isLoading,
|
||||
error: error?.message,
|
||||
firstMetric: metrics[0],
|
||||
serverId
|
||||
serverId,
|
||||
timeRange
|
||||
});
|
||||
|
||||
const formatBytes = (bytes: number) => {
|
||||
if (bytes === 0) return '0 B';
|
||||
const k = 1024;
|
||||
const sizes = ['B', 'KB', 'MB', 'GB', 'TB'];
|
||||
const i = Math.floor(Math.log(bytes) / Math.log(k));
|
||||
return parseFloat((bytes / Math.pow(k, i)).toFixed(1)) + ' ' + sizes[i];
|
||||
};
|
||||
|
||||
const parseValueWithUnit = (value: string | number) => {
|
||||
if (typeof value === 'number') return value;
|
||||
const match = value.toString().match(/^([\d.]+)/);
|
||||
return match ? parseFloat(match[1]) : 0;
|
||||
};
|
||||
|
||||
const convertToBytes = (value: string | number): number => {
|
||||
if (typeof value === 'number') return value;
|
||||
|
||||
const str = value.toString();
|
||||
const numMatch = str.match(/^([\d.]+)/);
|
||||
const unitMatch = str.match(/([A-Za-z]+)$/);
|
||||
|
||||
if (!numMatch) return 0;
|
||||
|
||||
const num = parseFloat(numMatch[1]);
|
||||
const unit = unitMatch ? unitMatch[1].toUpperCase() : 'B';
|
||||
|
||||
const multipliers: { [key: string]: number } = {
|
||||
'B': 1,
|
||||
'KB': 1024,
|
||||
'MB': 1024 * 1024,
|
||||
'GB': 1024 * 1024 * 1024,
|
||||
'TB': 1024 * 1024 * 1024 * 1024
|
||||
};
|
||||
|
||||
return num * (multipliers[unit] || 1);
|
||||
};
|
||||
|
||||
const formatChartData = (metrics: any[]) => {
|
||||
console.log('ServerHistoryCharts: Formatting chart data for', metrics.length, 'metrics');
|
||||
|
||||
if (!Array.isArray(metrics) || metrics.length === 0) {
|
||||
console.log('ServerHistoryCharts: No metrics to format');
|
||||
return [];
|
||||
}
|
||||
|
||||
const formattedData = metrics.slice(-50).reverse().map((metric, index) => {
|
||||
console.log(`ServerHistoryCharts: Processing metric ${index}:`, metric);
|
||||
|
||||
const cpuUsage = parseValueWithUnit(metric.cpu_usage || 0);
|
||||
const ramUsedBytes = convertToBytes(metric.ram_used || 0);
|
||||
const ramTotalBytes = convertToBytes(metric.ram_total || 0);
|
||||
const diskUsedBytes = convertToBytes(metric.disk_used || 0);
|
||||
const diskTotalBytes = convertToBytes(metric.disk_total || 0);
|
||||
|
||||
const ramUsagePercent = ramTotalBytes > 0 ? (ramUsedBytes / ramTotalBytes) * 100 : 0;
|
||||
const diskUsagePercent = diskTotalBytes > 0 ? (diskUsedBytes / diskTotalBytes) * 100 : 0;
|
||||
|
||||
return {
|
||||
timestamp: new Date(metric.timestamp || metric.created).toLocaleTimeString('en-US', {
|
||||
hour: '2-digit',
|
||||
minute: '2-digit'
|
||||
}),
|
||||
fullTimestamp: new Date(metric.timestamp || metric.created).toLocaleString(),
|
||||
cpuUsage: Math.round(cpuUsage * 100) / 100,
|
||||
ramUsagePercent: Math.round(ramUsagePercent * 100) / 100,
|
||||
diskUsagePercent: Math.round(diskUsagePercent * 100) / 100,
|
||||
ramUsedBytes,
|
||||
ramTotalBytes,
|
||||
diskUsedBytes,
|
||||
diskTotalBytes,
|
||||
networkRxBytes: metric.network_rx_bytes || 0,
|
||||
networkTxBytes: metric.network_tx_bytes || 0,
|
||||
networkRxSpeed: parseValueWithUnit(metric.network_rx_speed || 0),
|
||||
networkTxSpeed: parseValueWithUnit(metric.network_tx_speed || 0),
|
||||
};
|
||||
});
|
||||
|
||||
console.log('ServerHistoryCharts: Formatted chart data:', formattedData);
|
||||
return formattedData;
|
||||
};
|
||||
|
||||
const chartData = formatChartData(metrics);
|
||||
const chartData = formatChartData(metrics, timeRange);
|
||||
|
||||
const getGridColor = () => theme === 'dark' ? '#374151' : '#e5e7eb';
|
||||
const getAxisColor = () => theme === 'dark' ? '#9ca3af' : '#6b7280';
|
||||
|
||||
// Custom tooltip content with detailed information and full date/time
|
||||
const DetailedTooltipContent = ({ active, payload, label }: any) => {
|
||||
if (active && payload && payload.length) {
|
||||
const data = payload[0]?.payload;
|
||||
return (
|
||||
<div className="bg-popover/95 border border-border backdrop-blur-sm rounded-lg p-3 shadow-lg">
|
||||
<p className="font-medium text-popover-foreground mb-2">{label}</p>
|
||||
{data?.fullTimestamp && (
|
||||
<p className="text-xs text-muted-foreground mb-2">
|
||||
{new Date(data.fullTimestamp).toLocaleString('en-US', {
|
||||
weekday: 'short',
|
||||
year: 'numeric',
|
||||
month: 'short',
|
||||
day: 'numeric',
|
||||
hour: '2-digit',
|
||||
minute: '2-digit',
|
||||
second: '2-digit'
|
||||
})}
|
||||
</p>
|
||||
)}
|
||||
{payload.map((entry: any, index: number) => {
|
||||
const data = entry.payload;
|
||||
return (
|
||||
<div key={index} className="space-y-1">
|
||||
<div className="flex items-center gap-2">
|
||||
<div
|
||||
className="w-3 h-3 rounded-full"
|
||||
style={{ backgroundColor: entry.color }}
|
||||
/>
|
||||
<span className="text-sm font-medium">{entry.name}: {entry.value}%</span>
|
||||
</div>
|
||||
{entry.dataKey === 'cpuUsage' && (
|
||||
<div className="text-xs text-muted-foreground ml-5">
|
||||
<div>CPU Cores: {data.cpuCores}</div>
|
||||
<div>Free: {data.cpuFree}%</div>
|
||||
</div>
|
||||
)}
|
||||
{entry.dataKey === 'ramUsagePercent' && (
|
||||
<div className="text-xs text-muted-foreground ml-5">
|
||||
<div>Used: {data.ramUsed}</div>
|
||||
<div>Total: {data.ramTotal}</div>
|
||||
<div>Free: {data.ramFree}</div>
|
||||
</div>
|
||||
)}
|
||||
{entry.dataKey === 'diskUsagePercent' && (
|
||||
<div className="text-xs text-muted-foreground ml-5">
|
||||
<div>Used: {data.diskUsed}</div>
|
||||
<div>Total: {data.diskTotal}</div>
|
||||
<div>Free: {data.diskFree}</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
return null;
|
||||
};
|
||||
|
||||
// Network tooltip with detailed info and full date/time
|
||||
const NetworkTooltipContent = ({ active, payload, label }: any) => {
|
||||
if (active && payload && payload.length) {
|
||||
const data = payload[0]?.payload;
|
||||
return (
|
||||
<div className="bg-popover/95 border border-border backdrop-blur-sm rounded-lg p-3 shadow-lg">
|
||||
<p className="font-medium text-popover-foreground mb-2">{label}</p>
|
||||
{data?.fullTimestamp && (
|
||||
<p className="text-xs text-muted-foreground mb-2">
|
||||
{new Date(data.fullTimestamp).toLocaleString('en-US', {
|
||||
weekday: 'short',
|
||||
year: 'numeric',
|
||||
month: 'short',
|
||||
day: 'numeric',
|
||||
hour: '2-digit',
|
||||
minute: '2-digit',
|
||||
second: '2-digit'
|
||||
})}
|
||||
</p>
|
||||
)}
|
||||
{payload.map((entry: any, index: number) => (
|
||||
<div key={index} className="flex items-center gap-2 mb-1">
|
||||
<div
|
||||
className="w-3 h-3 rounded-full"
|
||||
style={{ backgroundColor: entry.color }}
|
||||
/>
|
||||
<span className="text-sm font-medium">
|
||||
{entry.name}: {entry.dataKey.includes('Speed') ? `${entry.value} KB/s` : formatBytes(entry.value)}
|
||||
</span>
|
||||
</div>
|
||||
))}
|
||||
{data && (
|
||||
<div className="text-xs text-muted-foreground mt-2 pt-2 border-t border-border">
|
||||
<div>Total RX: {data.networkRx}</div>
|
||||
<div>Total TX: {data.networkTx}</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
return null;
|
||||
};
|
||||
|
||||
if (isLoading) {
|
||||
return (
|
||||
<div className="space-y-6">
|
||||
@@ -143,6 +164,7 @@ export const ServerHistoryCharts = ({ serverId }: ServerHistoryChartsProps) => {
|
||||
<h2 className="text-lg font-medium">Historical Performance</h2>
|
||||
<Loader2 className="h-4 w-4 animate-spin ml-2" />
|
||||
</div>
|
||||
<TimeRangeSelector value={timeRange} onChange={setTimeRange} />
|
||||
</div>
|
||||
<div className="grid grid-cols-1 lg:grid-cols-2 gap-6">
|
||||
{[1, 2, 3, 4].map((index) => (
|
||||
@@ -174,7 +196,7 @@ export const ServerHistoryCharts = ({ serverId }: ServerHistoryChartsProps) => {
|
||||
<TrendingUp className="h-5 w-5" />
|
||||
<h2 className="text-lg font-medium">Historical Performance</h2>
|
||||
</div>
|
||||
<DateRangeFilter onRangeChange={handleDateRangeChange} />
|
||||
<TimeRangeSelector value={timeRange} onChange={setTimeRange} />
|
||||
</div>
|
||||
<Card>
|
||||
<CardContent className="flex items-center justify-center py-12">
|
||||
@@ -197,16 +219,16 @@ export const ServerHistoryCharts = ({ serverId }: ServerHistoryChartsProps) => {
|
||||
<TrendingUp className="h-5 w-5" />
|
||||
<h2 className="text-lg font-medium">Historical Performance</h2>
|
||||
</div>
|
||||
<DateRangeFilter onRangeChange={handleDateRangeChange} />
|
||||
<TimeRangeSelector value={timeRange} onChange={setTimeRange} />
|
||||
</div>
|
||||
<Card>
|
||||
<CardContent className="flex items-center justify-center py-12">
|
||||
<div className="text-center">
|
||||
<p className="text-muted-foreground">No historical data available</p>
|
||||
<p className="text-xs mt-2">Metrics count: {metrics.length}</p>
|
||||
<p className="text-muted-foreground">No historical data available for {timeRange}</p>
|
||||
<p className="text-xs mt-2">Raw metrics count: {metrics.length}</p>
|
||||
<p className="text-xs mt-1">Server ID: {serverId}</p>
|
||||
<p className="text-xs mt-1 text-muted-foreground">
|
||||
{metrics.length > 0 ? 'Data exists but failed to format' : 'No metrics data found'}
|
||||
{metrics.length > 0 ? 'Data exists but filtered out by time range' : 'No metrics data found'}
|
||||
</p>
|
||||
</div>
|
||||
</CardContent>
|
||||
@@ -215,7 +237,10 @@ export const ServerHistoryCharts = ({ serverId }: ServerHistoryChartsProps) => {
|
||||
);
|
||||
}
|
||||
|
||||
console.log('ServerHistoryCharts: Rendering individual charts with', chartData.length, 'data points');
|
||||
console.log('ServerHistoryCharts: Rendering charts with', chartData.length, 'data points for time range:', timeRange);
|
||||
|
||||
// Calculate summary stats from latest data point
|
||||
const latestData = chartData[chartData.length - 1];
|
||||
|
||||
return (
|
||||
<div className="space-y-6">
|
||||
@@ -223,20 +248,28 @@ export const ServerHistoryCharts = ({ serverId }: ServerHistoryChartsProps) => {
|
||||
<div className="flex items-center gap-2">
|
||||
<TrendingUp className="h-5 w-5" />
|
||||
<h2 className="text-lg font-medium">Historical Performance</h2>
|
||||
<span className="text-xs text-muted-foreground">({chartData.length} data points)</span>
|
||||
<span className="text-xs text-muted-foreground">({chartData.length} data points • {timeRange})</span>
|
||||
</div>
|
||||
<DateRangeFilter onRangeChange={handleDateRangeChange} />
|
||||
<TimeRangeSelector value={timeRange} onChange={setTimeRange} />
|
||||
</div>
|
||||
|
||||
<div className="grid grid-cols-1 lg:grid-cols-2 gap-6">
|
||||
{/* CPU Usage Chart */}
|
||||
{/* CPU Usage Chart - Smooth Area Chart with Blue Gradient */}
|
||||
<Card className="bg-gradient-to-br from-background to-muted/20 border-border/50 shadow-lg hover:shadow-xl transition-all duration-300 backdrop-blur-sm">
|
||||
<CardHeader className="pb-2">
|
||||
<CardTitle className="flex items-center gap-2 text-foreground">
|
||||
<div className="p-2 rounded-lg bg-blue-500/15">
|
||||
<Cpu className="h-5 w-5 text-blue-500" />
|
||||
<CardTitle className="flex items-center justify-between text-foreground">
|
||||
<div className="flex items-center gap-2">
|
||||
<div className="p-2 rounded-lg bg-blue-500/15">
|
||||
<Cpu className="h-5 w-5 text-blue-500" />
|
||||
</div>
|
||||
CPU Usage
|
||||
</div>
|
||||
CPU Usage
|
||||
{latestData && (
|
||||
<div className="text-right text-sm">
|
||||
<div className="text-blue-500 font-semibold">{latestData.cpuUsage}%</div>
|
||||
<div className="text-xs text-muted-foreground">{latestData.cpuCores} cores</div>
|
||||
</div>
|
||||
)}
|
||||
</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent className="pt-2">
|
||||
@@ -246,10 +279,10 @@ export const ServerHistoryCharts = ({ serverId }: ServerHistoryChartsProps) => {
|
||||
color: theme === 'dark' ? "#3b82f6" : "#2563eb",
|
||||
}
|
||||
}} className="h-80">
|
||||
<LineChart data={chartData}>
|
||||
<AreaChart data={chartData}>
|
||||
<defs>
|
||||
<linearGradient id="cpuGradient" x1="0" y1="0" x2="0" y2="1">
|
||||
<stop offset="5%" stopColor={theme === 'dark' ? "#3b82f6" : "#2563eb"} stopOpacity={0.3}/>
|
||||
<stop offset="5%" stopColor={theme === 'dark' ? "#3b82f6" : "#2563eb"} stopOpacity={0.4}/>
|
||||
<stop offset="95%" stopColor={theme === 'dark' ? "#1e40af" : "#1d4ed8"} stopOpacity={0.1}/>
|
||||
</linearGradient>
|
||||
</defs>
|
||||
@@ -266,10 +299,10 @@ export const ServerHistoryCharts = ({ serverId }: ServerHistoryChartsProps) => {
|
||||
label={{ value: 'CPU %', angle: -90, position: 'insideLeft' }}
|
||||
/>
|
||||
<ChartTooltip
|
||||
content={<ChartTooltipContent className="bg-popover border-border" />}
|
||||
content={<DetailedTooltipContent />}
|
||||
cursor={{ stroke: getGridColor() }}
|
||||
/>
|
||||
<Line
|
||||
<Area
|
||||
type="monotone"
|
||||
dataKey="cpuUsage"
|
||||
stroke={theme === 'dark' ? "#60a5fa" : "#2563eb"}
|
||||
@@ -279,19 +312,27 @@ export const ServerHistoryCharts = ({ serverId }: ServerHistoryChartsProps) => {
|
||||
fillOpacity={1}
|
||||
name="CPU Usage (%)"
|
||||
/>
|
||||
</LineChart>
|
||||
</AreaChart>
|
||||
</ChartContainer>
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
{/* Memory Usage Chart */}
|
||||
{/* Memory Usage Chart - Basis Area Chart with Green Gradient */}
|
||||
<Card className="bg-gradient-to-br from-background to-muted/20 border-border/50 shadow-lg hover:shadow-xl transition-all duration-300 backdrop-blur-sm">
|
||||
<CardHeader className="pb-2">
|
||||
<CardTitle className="flex items-center gap-2 text-foreground">
|
||||
<div className="p-2 rounded-lg bg-green-500/15">
|
||||
<MemoryStick className="h-5 w-5 text-green-500" />
|
||||
<CardTitle className="flex items-center justify-between text-foreground">
|
||||
<div className="flex items-center gap-2">
|
||||
<div className="p-2 rounded-lg bg-green-500/15">
|
||||
<MemoryStick className="h-5 w-5 text-green-500" />
|
||||
</div>
|
||||
Memory Usage
|
||||
</div>
|
||||
Memory Usage
|
||||
{latestData && (
|
||||
<div className="text-right text-sm">
|
||||
<div className="text-green-500 font-semibold">{latestData.ramUsagePercent}%</div>
|
||||
<div className="text-xs text-muted-foreground">{latestData.ramUsed} / {latestData.ramTotal}</div>
|
||||
</div>
|
||||
)}
|
||||
</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent className="pt-2">
|
||||
@@ -301,10 +342,10 @@ export const ServerHistoryCharts = ({ serverId }: ServerHistoryChartsProps) => {
|
||||
color: theme === 'dark' ? "#10b981" : "#059669",
|
||||
}
|
||||
}} className="h-80">
|
||||
<LineChart data={chartData}>
|
||||
<AreaChart data={chartData}>
|
||||
<defs>
|
||||
<linearGradient id="memoryGradient" x1="0" y1="0" x2="0" y2="1">
|
||||
<stop offset="5%" stopColor={theme === 'dark' ? "#10b981" : "#059669"} stopOpacity={0.3}/>
|
||||
<stop offset="5%" stopColor={theme === 'dark' ? "#10b981" : "#059669"} stopOpacity={0.5}/>
|
||||
<stop offset="95%" stopColor={theme === 'dark' ? "#047857" : "#065f46"} stopOpacity={0.1}/>
|
||||
</linearGradient>
|
||||
</defs>
|
||||
@@ -321,11 +362,11 @@ export const ServerHistoryCharts = ({ serverId }: ServerHistoryChartsProps) => {
|
||||
label={{ value: 'Memory %', angle: -90, position: 'insideLeft' }}
|
||||
/>
|
||||
<ChartTooltip
|
||||
content={<ChartTooltipContent className="bg-popover border-border" />}
|
||||
content={<DetailedTooltipContent />}
|
||||
cursor={{ stroke: getGridColor() }}
|
||||
/>
|
||||
<Line
|
||||
type="monotone"
|
||||
<Area
|
||||
type="basis"
|
||||
dataKey="ramUsagePercent"
|
||||
stroke={theme === 'dark' ? "#34d399" : "#059669"}
|
||||
strokeWidth={3}
|
||||
@@ -334,19 +375,27 @@ export const ServerHistoryCharts = ({ serverId }: ServerHistoryChartsProps) => {
|
||||
fillOpacity={1}
|
||||
name="Memory Usage (%)"
|
||||
/>
|
||||
</LineChart>
|
||||
</AreaChart>
|
||||
</ChartContainer>
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
{/* Disk Usage Chart */}
|
||||
{/* Disk Usage Chart - Stepped Area Chart with Orange Gradient */}
|
||||
<Card className="bg-gradient-to-br from-background to-muted/20 border-border/50 shadow-lg hover:shadow-xl transition-all duration-300 backdrop-blur-sm">
|
||||
<CardHeader className="pb-2">
|
||||
<CardTitle className="flex items-center gap-2 text-foreground">
|
||||
<div className="p-2 rounded-lg bg-amber-500/15">
|
||||
<HardDrive className="h-5 w-5 text-amber-500" />
|
||||
<CardTitle className="flex items-center justify-between text-foreground">
|
||||
<div className="flex items-center gap-2">
|
||||
<div className="p-2 rounded-lg bg-amber-500/15">
|
||||
<HardDrive className="h-5 w-5 text-amber-500" />
|
||||
</div>
|
||||
Disk Usage
|
||||
</div>
|
||||
Disk Usage
|
||||
{latestData && (
|
||||
<div className="text-right text-sm">
|
||||
<div className="text-amber-500 font-semibold">{latestData.diskUsagePercent}%</div>
|
||||
<div className="text-xs text-muted-foreground">{latestData.diskUsed} / {latestData.diskTotal}</div>
|
||||
</div>
|
||||
)}
|
||||
</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent className="pt-2">
|
||||
@@ -356,10 +405,10 @@ export const ServerHistoryCharts = ({ serverId }: ServerHistoryChartsProps) => {
|
||||
color: theme === 'dark' ? "#f59e0b" : "#d97706",
|
||||
}
|
||||
}} className="h-80">
|
||||
<LineChart data={chartData}>
|
||||
<AreaChart data={chartData}>
|
||||
<defs>
|
||||
<linearGradient id="diskGradient" x1="0" y1="0" x2="0" y2="1">
|
||||
<stop offset="5%" stopColor={theme === 'dark' ? "#f59e0b" : "#d97706"} stopOpacity={0.3}/>
|
||||
<stop offset="5%" stopColor={theme === 'dark' ? "#f59e0b" : "#d97706"} stopOpacity={0.6}/>
|
||||
<stop offset="95%" stopColor={theme === 'dark' ? "#d97706" : "#b45309"} stopOpacity={0.1}/>
|
||||
</linearGradient>
|
||||
</defs>
|
||||
@@ -376,11 +425,11 @@ export const ServerHistoryCharts = ({ serverId }: ServerHistoryChartsProps) => {
|
||||
label={{ value: 'Disk %', angle: -90, position: 'insideLeft' }}
|
||||
/>
|
||||
<ChartTooltip
|
||||
content={<ChartTooltipContent className="bg-popover border-border" />}
|
||||
content={<DetailedTooltipContent />}
|
||||
cursor={{ stroke: getGridColor() }}
|
||||
/>
|
||||
<Line
|
||||
type="monotone"
|
||||
<Area
|
||||
type="step"
|
||||
dataKey="diskUsagePercent"
|
||||
stroke={theme === 'dark' ? "#fbbf24" : "#d97706"}
|
||||
strokeWidth={3}
|
||||
@@ -389,19 +438,27 @@ export const ServerHistoryCharts = ({ serverId }: ServerHistoryChartsProps) => {
|
||||
fillOpacity={1}
|
||||
name="Disk Usage (%)"
|
||||
/>
|
||||
</LineChart>
|
||||
</AreaChart>
|
||||
</ChartContainer>
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
{/* Network Traffic Chart */}
|
||||
{/* Network Traffic Chart - Dual Line Chart with Purple/Red Theme */}
|
||||
<Card className="bg-gradient-to-br from-background to-muted/20 border-border/50 shadow-lg hover:shadow-xl transition-all duration-300 backdrop-blur-sm">
|
||||
<CardHeader className="pb-2">
|
||||
<CardTitle className="flex items-center gap-2 text-foreground">
|
||||
<div className="p-2 rounded-lg bg-purple-500/15">
|
||||
<Wifi className="h-5 w-5 text-purple-500" />
|
||||
<CardTitle className="flex items-center justify-between text-foreground">
|
||||
<div className="flex items-center gap-2">
|
||||
<div className="p-2 rounded-lg bg-purple-500/15">
|
||||
<Wifi className="h-5 w-5 text-purple-500" />
|
||||
</div>
|
||||
Network Traffic
|
||||
</div>
|
||||
Network Traffic
|
||||
{latestData && (
|
||||
<div className="text-right text-sm">
|
||||
<div className="text-purple-500 font-semibold">{latestData.networkRxSpeed} KB/s ↓</div>
|
||||
<div className="text-red-500 font-semibold">{latestData.networkTxSpeed} KB/s ↑</div>
|
||||
</div>
|
||||
)}
|
||||
</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent className="pt-2">
|
||||
@@ -421,6 +478,13 @@ export const ServerHistoryCharts = ({ serverId }: ServerHistoryChartsProps) => {
|
||||
<stop offset="5%" stopColor={theme === 'dark' ? "#8b5cf6" : "#7c3aed"} stopOpacity={0.2}/>
|
||||
<stop offset="95%" stopColor={theme === 'dark' ? "#7c3aed" : "#6d28d9"} stopOpacity={0.05}/>
|
||||
</linearGradient>
|
||||
<filter id="glow">
|
||||
<feGaussianBlur stdDeviation="2" result="coloredBlur"/>
|
||||
<feMerge>
|
||||
<feMergeNode in="coloredBlur"/>
|
||||
<feMergeNode in="SourceGraphic"/>
|
||||
</feMerge>
|
||||
</filter>
|
||||
</defs>
|
||||
<CartesianGrid strokeDasharray="3 3" stroke={getGridColor()} opacity={0.3} />
|
||||
<XAxis
|
||||
@@ -431,13 +495,11 @@ export const ServerHistoryCharts = ({ serverId }: ServerHistoryChartsProps) => {
|
||||
<YAxis
|
||||
tick={{ fontSize: 10, fill: getAxisColor() }}
|
||||
axisLine={{ stroke: getGridColor() }}
|
||||
tickFormatter={(value) => formatBytes(value) + '/s'}
|
||||
label={{ value: 'Network Speed', angle: -90, position: 'insideLeft' }}
|
||||
label={{ value: 'Network Speed (KB/s)', angle: -90, position: 'insideLeft' }}
|
||||
/>
|
||||
<ChartTooltip
|
||||
content={<ChartTooltipContent className="bg-popover border-border" />}
|
||||
content={<NetworkTooltipContent />}
|
||||
cursor={{ stroke: getGridColor() }}
|
||||
formatter={(value, name) => [formatBytes(Number(value)) + '/s', name]}
|
||||
/>
|
||||
<Line
|
||||
type="monotone"
|
||||
@@ -445,9 +507,9 @@ export const ServerHistoryCharts = ({ serverId }: ServerHistoryChartsProps) => {
|
||||
stroke={theme === 'dark' ? "#a78bfa" : "#7c3aed"}
|
||||
strokeWidth={3}
|
||||
dot={false}
|
||||
fill="url(#networkGradient)"
|
||||
fillOpacity={0.5}
|
||||
name="RX Speed"
|
||||
filter="url(#glow)"
|
||||
strokeDasharray="0"
|
||||
/>
|
||||
<Line
|
||||
type="monotone"
|
||||
@@ -456,6 +518,7 @@ export const ServerHistoryCharts = ({ serverId }: ServerHistoryChartsProps) => {
|
||||
strokeWidth={3}
|
||||
dot={false}
|
||||
name="TX Speed"
|
||||
strokeDasharray="5 5"
|
||||
/>
|
||||
</LineChart>
|
||||
</ChartContainer>
|
||||
|
||||
@@ -26,7 +26,7 @@ export const ServerMetricsCharts = ({ serverId }: ServerMetricsChartsProps) => {
|
||||
error
|
||||
} = useQuery({
|
||||
queryKey: ['server-metrics', serverId, timeRange],
|
||||
queryFn: () => serverService.getServerMetrics(serverId),
|
||||
queryFn: () => serverService.getServerMetrics(serverId, timeRange),
|
||||
enabled: !!serverId,
|
||||
refetchInterval: 30000
|
||||
});
|
||||
@@ -53,7 +53,7 @@ export const ServerMetricsCharts = ({ serverId }: ServerMetricsChartsProps) => {
|
||||
if (chartData.length === 0) {
|
||||
return (
|
||||
<div className="flex items-center justify-center h-96 text-muted-foreground">
|
||||
<p>No server metrics data available</p>
|
||||
<p>No server metrics data available for {timeRange}</p>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -63,7 +63,7 @@ export const ServerMetricsCharts = ({ serverId }: ServerMetricsChartsProps) => {
|
||||
<div className="flex items-center justify-between">
|
||||
<div className="flex items-center gap-2">
|
||||
<h2 className="text-lg font-medium">Server Metrics</h2>
|
||||
<span className="text-xs text-muted-foreground">({chartData.length} data points)</span>
|
||||
<span className="text-xs text-muted-foreground">({chartData.length} data points • {timeRange})</span>
|
||||
</div>
|
||||
<TimeRangeSelector value={timeRange} onChange={setTimeRange} />
|
||||
</div>
|
||||
|
||||
@@ -57,11 +57,43 @@ export const filterMetricsByTimeRange = (metrics: any[], timeRange: TimeRange):
|
||||
const cutoffTime = new Date(now.getTime() - (selectedRange.hours * 60 * 60 * 1000));
|
||||
|
||||
return metrics.filter(metric => {
|
||||
const metricTime = new Date(metric.timestamp);
|
||||
const metricTime = new Date(metric.created || metric.timestamp);
|
||||
return metricTime >= cutoffTime;
|
||||
});
|
||||
};
|
||||
|
||||
const formatTimestamp = (timestamp: string, timeRange: TimeRange): string => {
|
||||
const date = new Date(timestamp);
|
||||
|
||||
if (timeRange === '60m') {
|
||||
// For 60 minutes, show time with seconds
|
||||
return date.toLocaleString('en-US', {
|
||||
month: 'short',
|
||||
day: 'numeric',
|
||||
hour: '2-digit',
|
||||
minute: '2-digit',
|
||||
second: '2-digit'
|
||||
});
|
||||
} else if (timeRange === '1d') {
|
||||
// For 1 day, show date and time
|
||||
return date.toLocaleString('en-US', {
|
||||
month: 'short',
|
||||
day: 'numeric',
|
||||
hour: '2-digit',
|
||||
minute: '2-digit'
|
||||
});
|
||||
} else {
|
||||
// For longer periods, show date and time
|
||||
return date.toLocaleString('en-US', {
|
||||
month: 'short',
|
||||
day: 'numeric',
|
||||
year: 'numeric',
|
||||
hour: '2-digit',
|
||||
minute: '2-digit'
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
export const formatChartData = (metrics: any[], timeRange: TimeRange) => {
|
||||
const filteredMetrics = filterMetricsByTimeRange(metrics, timeRange);
|
||||
|
||||
@@ -85,11 +117,11 @@ export const formatChartData = (metrics: any[], timeRange: TimeRange) => {
|
||||
const networkRxSpeed = metric.network_rx_speed || 0;
|
||||
const networkTxSpeed = metric.network_tx_speed || 0;
|
||||
|
||||
const timestamp = metric.created || metric.timestamp;
|
||||
|
||||
return {
|
||||
timestamp: new Date(metric.timestamp).toLocaleTimeString('en-US', {
|
||||
hour: '2-digit',
|
||||
minute: '2-digit'
|
||||
}),
|
||||
timestamp: formatTimestamp(timestamp, timeRange),
|
||||
fullTimestamp: timestamp,
|
||||
cpuUsage: Math.round(cpuUsage * 100) / 100,
|
||||
cpuCores: parseInt(metric.cpu_cores) || 0,
|
||||
cpuFree: 100 - cpuUsage,
|
||||
|
||||
Reference in New Issue
Block a user