import { useState, useEffect } from "react"; import { useQuery } from "@tanstack/react-query"; import { Dialog, DialogContent, DialogHeader, DialogTitle } from "@/components/ui/dialog"; import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"; import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs"; import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select"; import { ChartContainer, ChartTooltip, ChartTooltipContent } from "@/components/ui/chart"; import { LineChart, Line, XAxis, YAxis, CartesianGrid, ResponsiveContainer, AreaChart, Area } from "recharts"; import { DockerContainer, DockerMetrics } from "@/types/docker.types"; import { dockerService } from "@/services/dockerService"; import { Loader2, Cpu, HardDrive, Network, MemoryStick } from "lucide-react"; import { useTheme } from "@/contexts/ThemeContext"; import { useLanguage } from "@/contexts/LanguageContext"; interface DockerMetricsDialogProps { container: DockerContainer | null; open: boolean; onOpenChange: (open: boolean) => void; } type TimeRange = '60m' | '1d' | '7d' | '1m' | '3m'; const timeRangeOptions = [ { value: '60m' as TimeRange, label: 'minutes60', hours: 1 }, { value: '1d' as TimeRange, label: 'day1', hours: 24 }, { value: '7d' as TimeRange, label: 'days7', hours: 24 * 7 }, { value: '1m' as TimeRange, label: 'month1', hours: 24 * 30 }, { value: '3m' as TimeRange, label: 'months3', hours: 24 * 90 }, ]; export const DockerMetricsDialog = ({ container, open, onOpenChange }: DockerMetricsDialogProps) => { const [timeRange, setTimeRange] = useState("1d"); const { theme } = useTheme(); const { t } = useLanguage(); const { data: metrics = [], isLoading, error } = useQuery({ queryKey: ['docker-metrics', container?.docker_id, timeRange], queryFn: () => container ? dockerService.getContainerMetrics(container.docker_id) : Promise.resolve([]), enabled: !!container && open, refetchInterval: 30000 }); const formatBytes = (bytes: number, decimals = 2) => { if (bytes === 0) return '0 B'; const k = 1024; const dm = decimals < 0 ? 0 : decimals; 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(dm)) + ' ' + sizes[i]; }; const parseValueWithUnit = (value: string | number): { numeric: number; unit: string; original: string } => { if (typeof value === 'number') { return { numeric: value, unit: 'B', original: value.toString() }; } const str = value.toString(); const match = str.match(/^([\d.]+)\s*([A-Za-z%]*)/); if (match) { const numeric = parseFloat(match[1]); const unit = match[2] || ''; return { numeric, unit, original: str }; } return { numeric: 0, unit: '', original: str }; }; const convertToBytes = (value: string | number): number => { if (typeof value === 'number') return value; const parsed = parseValueWithUnit(value); const multipliers: { [key: string]: number } = { 'B': 1, 'KB': 1024, 'MB': 1024 * 1024, 'GB': 1024 * 1024 * 1024, 'TB': 1024 * 1024 * 1024 * 1024 }; const multiplier = multipliers[parsed.unit.toUpperCase()] || 1; return parsed.numeric * multiplier; }; const filterMetricsByTimeRange = (metrics: DockerMetrics[], timeRange: TimeRange): DockerMetrics[] => { const now = new Date(); const selectedRange = timeRangeOptions.find(opt => opt.value === timeRange); if (!selectedRange) return metrics; const cutoffTime = new Date(now.getTime() - (selectedRange.hours * 60 * 60 * 1000)); return metrics.filter(metric => { const metricTime = new Date(metric.timestamp); return metricTime >= cutoffTime; }); }; const formatChartData = (metrics: DockerMetrics[]) => { const filteredMetrics = filterMetricsByTimeRange(metrics, timeRange); return filteredMetrics.slice(0, 100).reverse().map((metric, index) => { // Parse CPU usage const cpuUsage = typeof metric.cpu_usage === 'string' ? parseFloat(metric.cpu_usage.replace('%', '')) : parseFloat(metric.cpu_usage) || 0; // Parse memory values const ramUsedBytes = convertToBytes(metric.ram_used); const ramTotalBytes = convertToBytes(metric.ram_total); const ramFreeBytes = convertToBytes(metric.ram_free); const ramUsagePercent = ramTotalBytes > 0 ? (ramUsedBytes / ramTotalBytes) * 100 : 0; // Parse disk values const diskUsedBytes = convertToBytes(metric.disk_used); const diskTotalBytes = convertToBytes(metric.disk_total); const diskFreeBytes = convertToBytes(metric.disk_free); const diskUsagePercent = diskTotalBytes > 0 ? (diskUsedBytes / diskTotalBytes) * 100 : 0; // Network values const networkRxBytes = metric.network_rx_bytes || 0; const networkTxBytes = metric.network_tx_bytes || 0; const networkRxSpeed = metric.network_rx_speed || 0; const networkTxSpeed = metric.network_tx_speed || 0; return { timestamp: new Date(metric.timestamp).toLocaleTimeString('en-US', { hour: '2-digit', minute: '2-digit' }), // CPU cpuUsage: Math.round(cpuUsage * 100) / 100, cpuCores: parseInt(metric.cpu_cores) || 0, cpuFree: 100 - cpuUsage, // Memory ramUsedBytes, ramTotalBytes, ramFreeBytes, ramUsed: formatBytes(ramUsedBytes), ramTotal: formatBytes(ramTotalBytes), ramFree: formatBytes(ramFreeBytes), ramUsagePercent: Math.round(ramUsagePercent * 100) / 100, // Disk diskUsedBytes, diskTotalBytes, diskFreeBytes, diskUsed: formatBytes(diskUsedBytes), diskTotal: formatBytes(diskTotalBytes), diskFree: formatBytes(diskFreeBytes), diskUsagePercent: Math.round(diskUsagePercent * 100) / 100, // Network networkRxBytes, networkTxBytes, networkRx: formatBytes(networkRxBytes), networkTx: formatBytes(networkTxBytes), networkRxSpeed: Math.round(networkRxSpeed * 100) / 100, networkTxSpeed: Math.round(networkTxSpeed * 100) / 100, }; }); }; const chartData = formatChartData(metrics); const latestMetric = chartData[chartData.length - 1]; const chartConfig = { cpuUsage: { label: "CPU Usage (%)", color: theme === 'dark' ? "#3b82f6" : "#2563eb", }, ramUsagePercent: { label: "RAM Usage (%)", color: theme === 'dark' ? "#10b981" : "#059669", }, diskUsagePercent: { label: "Disk Usage (%)", color: theme === 'dark' ? "#f59e0b" : "#d97706", }, networkRx: { label: "Network RX", color: theme === 'dark' ? "#8b5cf6" : "#7c3aed", }, networkTx: { label: "Network TX", color: theme === 'dark' ? "#ef4444" : "#dc2626", }, }; const getGridColor = () => theme === 'dark' ? '#374151' : '#e5e7eb'; const getAxisColor = () => theme === 'dark' ? '#9ca3af' : '#6b7280'; const MetricCard = ({ title, used, total, free, percentage, icon: Icon, color }: { title: string; used: string; total: string; free: string; percentage: number; icon: any; color: string; }) => (
{title}
Used: {used}
Free: {free}
Total: {total}
Usage: {percentage.toFixed(1)}%
); if (!container) return null; return (
{t('containerMetricsTitle', 'docker', { name: container.name })}

{t('dockerId', 'docker')}: {container.docker_id} • {container.hostname}

{isLoading ? (
{t('loadingMetrics', 'docker')}
) : error ? (

{t('errorLoadingMetrics', 'docker')}: {String((error as any)?.message ?? '')}

) : chartData.length === 0 ? (

{t('noMetricsAvailable', 'docker')}

) : ( <> {/* Current Metrics Summary */} {latestMetric && (
)} {t('cpu', 'docker')} {t('memoryTitle', 'docker')} {t('diskTitle', 'docker')} {t('network', 'docker')}
{t('cpuUsagePct', 'docker')} } cursor={{ stroke: getGridColor() }} /> {t('cpuUsageVsAvailable', 'docker')} } cursor={{ stroke: getGridColor() }} />
{t('ramUsagePct', 'docker')} } cursor={{ stroke: getGridColor() }} /> {t('memoryUsageBytes', 'docker')} formatBytes(value)} /> } cursor={{ stroke: getGridColor() }} formatter={(value, name) => [ name === t('usedMemory', 'docker') ? formatBytes(Number(value)) : name === t('totalMemory', 'docker') ? formatBytes(Number(value)) : value, name ]} />
{t('diskUsagePct', 'docker')} } cursor={{ stroke: getGridColor() }} /> {t('diskUsageBytes', 'docker')} formatBytes(value)} /> } cursor={{ stroke: getGridColor() }} formatter={(value, name) => [ name === t('usedDisk', 'docker') ? formatBytes(Number(value)) : name === t('totalDisk', 'docker') ? formatBytes(Number(value)) : value, name ]} />
{t('networkTraffic', 'docker')} } cursor={{ stroke: getGridColor() }} /> {t('networkSpeedKbs', 'docker')} } cursor={{ stroke: getGridColor() }} />
)}
); };