"use client" import { useEffect, useState } from "react" import { useParams } from "next/navigation" import axios from "axios" import Chart from 'chart.js/auto' import { AppSidebar } from "@/components/app-sidebar" import { Breadcrumb, BreadcrumbItem, BreadcrumbList, BreadcrumbPage, BreadcrumbSeparator, } from "@/components/ui/breadcrumb" import { SidebarInset, SidebarProvider, SidebarTrigger } from "@/components/ui/sidebar" import { Separator } from "@/components/ui/separator" import { Link, Cpu, MicroscopeIcon as Microchip, MemoryStick, HardDrive, MonitorIcon as MonitorCog, FileDigit, History } from "lucide-react" import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card" import { StatusIndicator } from "@/components/status-indicator" import { DynamicIcon } from "lucide-react/dynamic" import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select" import { ScrollArea } from "@/components/ui/scroll-area" import { Button } from "@/components/ui/button" import NextLink from "next/link" import { useTranslations } from "next-intl" interface ServerHistory { labels: string[]; datasets: { cpu: (number | null)[]; ram: (number | null)[]; disk: (number | null)[]; online: (boolean | null)[]; gpu: (number | null)[]; temp: (number | null)[]; } } interface Server { id: number; name: string; icon: string; host: boolean; hostServer: number | null; os?: string; ip?: string; url?: string; cpu?: string; gpu?: string; ram?: string; disk?: string; hostedVMs?: Server[]; isVM?: boolean; monitoring?: boolean; monitoringURL?: string; online?: boolean; cpuUsage: number; ramUsage: number; diskUsage: number; gpuUsage: number; temp: number; history?: ServerHistory; port: number; uptime?: string; } interface GetServersResponse { servers: Server[]; maxPage: number; } export default function ServerDetail() { const t = useTranslations() const params = useParams() const serverId = params.server_id as string const [server, setServer] = useState(null) const [timeRange, setTimeRange] = useState<'1h' | '1d' | '7d' | '30d'>('1h') const [loading, setLoading] = useState(true) // Chart references const cpuChartRef = { current: null as Chart | null } const ramChartRef = { current: null as Chart | null } const diskChartRef = { current: null as Chart | null } const gpuChartRef = { current: null as Chart | null } const tempChartRef = { current: null as Chart | null } const fetchServerDetails = async () => { try { setLoading(true) const response = await axios.post("/api/servers/get", { serverId: parseInt(serverId), timeRange: timeRange }) if (response.data.servers && response.data.servers.length > 0) { setServer(response.data.servers[0]) } setLoading(false) } catch (error) { console.error("Failed to fetch server details:", error) setLoading(false) } } useEffect(() => { fetchServerDetails() }, [serverId, timeRange]) useEffect(() => { if (!server || !server.history) return; // Clean up existing charts if (cpuChartRef.current) cpuChartRef.current.destroy(); if (ramChartRef.current) ramChartRef.current.destroy(); if (diskChartRef.current) diskChartRef.current.destroy(); if (gpuChartRef.current) gpuChartRef.current.destroy(); if (tempChartRef.current) tempChartRef.current.destroy(); // Wait for DOM to be ready const initTimer = setTimeout(() => { const history = server.history as ServerHistory; // Format time labels based on the selected time range const timeLabels = history.labels.map((date: string) => { const d = new Date(date) if (timeRange === '1h') { return d.toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' }) } else if (timeRange === '1d') { // For 1 day, show hours and minutes return d.toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' }) } else if (timeRange === '7d') { // For 7 days, show day and time return d.toLocaleDateString([], { weekday: 'short', month: 'numeric', day: 'numeric' }) + ' ' + d.toLocaleTimeString([], { hour: '2-digit' }) } else { // For 30 days return d.toLocaleDateString([], { month: 'numeric', day: 'numeric' }) } }) // Create a time range title for the chart const getRangeTitle = () => { const now = new Date() const startDate = new Date(history.labels[0]) if (timeRange === '1h') { return `Last Hour (${startDate.toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' })} - ${now.toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' })})` } else if (timeRange === '1d') { return `Last 24 Hours (${startDate.toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' })} - ${now.toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' })})` } else if (timeRange === '7d') { return `Last 7 Days (${startDate.toLocaleDateString([], { month: 'short', day: 'numeric' })} - ${now.toLocaleDateString([], { month: 'short', day: 'numeric' })})` } else { return `Last 30 Days (${startDate.toLocaleDateString([], { month: 'short', day: 'numeric' })} - ${now.toLocaleDateString([], { month: 'short', day: 'numeric' })})` } } // Directly hardcode the y-axis maximum in each chart option const commonOptions = { responsive: true, maintainAspectRatio: false, interaction: { mode: 'nearest' as const, axis: 'x' as const, intersect: false }, scales: { y: { min: 0, max: 100, beginAtZero: true, ticks: { stepSize: 25, autoSkip: false, callback: function(value: any) { return value + '%'; } }, title: { display: true, text: 'Usage %' } }, x: { grid: { display: false } } }, elements: { point: { radius: 0 }, line: { tension: 0.4, spanGaps: true } } }; // Create charts with very explicit y-axis max values const cpuCanvas = document.getElementById(`cpu-chart`) as HTMLCanvasElement if (cpuCanvas) { cpuChartRef.current = new Chart(cpuCanvas, { type: 'line', data: { labels: timeLabels, datasets: [{ label: t('Common.Server.CPU') + ' ' + t('Common.Server.Usage'), data: history.datasets.cpu, borderColor: 'rgb(75, 192, 192)', backgroundColor: 'rgba(75, 192, 192, 0.1)', fill: true, spanGaps: false }] }, options: { ...commonOptions, plugins: { title: { display: true, text: t('Common.Server.CPU') + ' ' + t('Server.UsageHistory'), font: { size: 14 } }, tooltip: { callbacks: { title: function(tooltipItems: any) { return timeLabels[tooltipItems[0].dataIndex]; } } }, legend: { display: false } }, scales: { ...commonOptions.scales, y: { ...commonOptions.scales.y, max: 100 // Force this to ensure it's applied } } } }) } const ramCanvas = document.getElementById(`ram-chart`) as HTMLCanvasElement if (ramCanvas) { ramChartRef.current = new Chart(ramCanvas, { type: 'line', data: { labels: timeLabels, datasets: [{ label: t('Common.Server.RAM') + ' ' + t('Common.Server.Usage'), data: history.datasets.ram, borderColor: 'rgb(153, 102, 255)', backgroundColor: 'rgba(153, 102, 255, 0.1)', fill: true, spanGaps: false }] }, options: { ...commonOptions, plugins: { title: { display: true, text: t('Common.Server.RAM') + ' ' + t('Server.UsageHistory'), font: { size: 14 } }, tooltip: { callbacks: { title: function(tooltipItems: any) { return timeLabels[tooltipItems[0].dataIndex]; } } }, legend: { display: false } }, scales: { ...commonOptions.scales, y: { ...commonOptions.scales.y, max: 100 // Force this to ensure it's applied } } } }) } const diskCanvas = document.getElementById(`disk-chart`) as HTMLCanvasElement if (diskCanvas) { diskChartRef.current = new Chart(diskCanvas, { type: 'line', data: { labels: timeLabels, datasets: [{ label: t('Common.Server.Disk') + ' ' + t('Common.Server.Usage'), data: history.datasets.disk, borderColor: 'rgb(255, 159, 64)', backgroundColor: 'rgba(255, 159, 64, 0.1)', fill: true, spanGaps: false }] }, options: { ...commonOptions, plugins: { title: { display: true, text: t('Common.Server.Disk') + ' ' + t('Server.UsageHistory'), font: { size: 14 } }, tooltip: { callbacks: { title: function(tooltipItems: any) { return timeLabels[tooltipItems[0].dataIndex]; } } }, legend: { display: false } }, scales: { ...commonOptions.scales, y: { ...commonOptions.scales.y, max: 100 // Force this to ensure it's applied } } } }) } const gpuCanvas = document.getElementById(`gpu-chart`) as HTMLCanvasElement if (gpuCanvas) { gpuChartRef.current = new Chart(gpuCanvas, { type: 'line', data: { labels: timeLabels, datasets: [{ label: t('Common.Server.GPU') + ' ' + t('Common.Server.Usage'), data: history.datasets.gpu, borderColor: 'rgb(255, 99, 132)', backgroundColor: 'rgba(255, 99, 132, 0.1)', fill: true, spanGaps: false }] }, options: { ...commonOptions, plugins: { title: { display: true, text: t('Common.Server.GPU') + ' ' + t('Common.Server.UsageHistory'), font: { size: 14 } }, tooltip: { callbacks: { title: function(tooltipItems: any) { return timeLabels[tooltipItems[0].dataIndex]; } } }, legend: { display: false } }, scales: { ...commonOptions.scales, y: { ...commonOptions.scales.y, max: 100 } } } }) } const tempCanvas = document.getElementById(`temp-chart`) as HTMLCanvasElement if (tempCanvas) { tempChartRef.current = new Chart(tempCanvas, { type: 'line', data: { labels: timeLabels, datasets: [{ label: t('Common.Server.Temperature') + ' ' + t('Common.Server.Usage'), data: history.datasets.temp, borderColor: 'rgb(255, 159, 64)', backgroundColor: 'rgba(255, 159, 64, 0.1)', fill: true, spanGaps: false }] }, options: { ...commonOptions, plugins: { title: { display: true, text: t('Common.Server.Temperature') + ' ' + t('Server.UsageHistory'), font: { size: 14 } }, tooltip: { callbacks: { title: function(tooltipItems: any) { return timeLabels[tooltipItems[0].dataIndex]; } } }, legend: { display: false } }, scales: { ...commonOptions.scales, y: { ...commonOptions.scales.y, max: 100, ticks: { callback: function(value: any) { return value + '°C'; } } } } } }) } }, 100); return () => { clearTimeout(initTimer); if (cpuChartRef.current) cpuChartRef.current.destroy(); if (ramChartRef.current) ramChartRef.current.destroy(); if (diskChartRef.current) diskChartRef.current.destroy(); if (gpuChartRef.current) gpuChartRef.current.destroy(); if (tempChartRef.current) tempChartRef.current.destroy(); }; }, [server, timeRange]); // Function to refresh data const refreshData = () => { fetchServerDetails() } return (
/ {t('Servers.MyInfrastructure')} {t('Servers.Title')} {server && ( <> {server.name} )}
{loading ? (
{t('Common.Loading')}
) : server ? (
{/* Server header card */}
{server.icon && }
{server.name} {server.os || t('Common.Server.OS')} • {server.isVM ? t('Server.VM') : t('Server.Physical')} {server.isVM && server.hostServer && ( <> • {t('Server.HostedOn')} {server.hostedVMs?.[0]?.name} )}
{server.monitoring && (
{server.online && server.uptime && ( {t('Common.since', { date: server.uptime })} )}
)}

{t('Server.Hardware')}

{t('Common.Server.CPU')}:
{server.cpu || "-"}
{t('Common.Server.GPU')}:
{server.gpu || "-"}
{t('Common.Server.RAM')}:
{server.ram || "-"}
{t('Common.Server.Disk')}:
{server.disk || "-"}

{t('Server.Network')}

{t('Common.Server.IP')}:
{server.ip || "-"}
{t('Server.ManagementURL')}:
{server.url ? ( {server.url} ) : ( "-" )}
{server.monitoring && (

{t('Server.CurrentUsage')}

{t('Common.Server.CPU')}:
80 ? "bg-destructive" : server.cpuUsage > 60 ? "bg-amber-500" : "bg-emerald-500"}`} style={{ width: `${server.cpuUsage}%` }} />
{server.cpuUsage !== null && server.cpuUsage !== undefined ? `${server.cpuUsage}%` : t('Common.noData')}
{t('Common.Server.RAM')}:
80 ? "bg-destructive" : server.ramUsage > 60 ? "bg-amber-500" : "bg-emerald-500"}`} style={{ width: `${server.ramUsage}%` }} />
{server.ramUsage !== null && server.ramUsage !== undefined ? `${server.ramUsage}%` : t('Common.noData')}
{t('Common.Server.Disk')}:
80 ? "bg-destructive" : server.diskUsage > 60 ? "bg-amber-500" : "bg-emerald-500"}`} style={{ width: `${server.diskUsage}%` }} />
{server.diskUsage !== null && server.diskUsage !== undefined ? `${server.diskUsage}%` : t('Common.noData')}
{server.gpuUsage && server.gpuUsage !== null && server.gpuUsage !== undefined && server.gpuUsage.toString() !== "0" && ( <>
{t('Common.Server.GPU')}:
80 ? "bg-destructive" : server.gpuUsage && server.gpuUsage > 60 ? "bg-amber-500" : "bg-emerald-500"}`} style={{ width: `${server.gpuUsage || 0}%` }} />
{server.gpuUsage && server.gpuUsage !== null && server.gpuUsage !== undefined ? `${server.gpuUsage}%` : t('Common.noData')}
)} {server.temp && server.temp !== null && server.temp !== undefined && server.temp.toString() !== "0" && ( <>
{t('Common.Server.Temperature')}:
80 ? "bg-destructive" : server.temp && server.temp > 60 ? "bg-amber-500" : "bg-emerald-500"}`} style={{ width: `${Math.min(server.temp || 0, 100)}%` }} />
{server.temp !== null && server.temp !== undefined && server.temp !== 0 ? `${server.temp}°C` : t('Common.noData')}
)}
)}
{/* Charts */} {server.monitoring && server.history && (
{t('Server.ResourceUsageHistory')} {timeRange === '1h' ? t('Server.TimeRange.LastHour') : timeRange === '1d' ? t('Server.TimeRange.Last24Hours') : timeRange === '7d' ? t('Server.TimeRange.Last7Days') : t('Server.TimeRange.Last30Days')}
{server.history?.datasets.gpu.some(value => value !== null && value !== 0) && (
)} {server.history?.datasets.temp.some(value => value !== null && value !== 0) && (
)}
)} {/* Virtual Machines */} {server.hostedVMs && server.hostedVMs.length > 0 && ( {t('Server.VirtualMachines')} {t('Server.VirtualMachinesDescription')}
{server.hostedVMs.map((hostedVM) => (
{hostedVM.icon && ( )}
{hostedVM.icon && "・ "} {hostedVM.name}
{hostedVM.monitoring && (
{hostedVM.online && hostedVM.uptime && ( {t('Common.since', { date: hostedVM.uptime })} )}
)}
{t('Common.Server.OS')}: {hostedVM.os || "-"}
{t('Common.Server.IP')}: {hostedVM.ip || t('Common.notSet')}

{t('Server.HardwareInformation')}

{t('Common.Server.CPU')}: {hostedVM.cpu || "-"}
{t('Common.Server.GPU')}: {hostedVM.gpu || "-"}
{t('Common.Server.RAM')}: {hostedVM.ram || "-"}
{t('Common.Server.Disk')}: {hostedVM.disk || "-"}
{hostedVM.monitoring && ( <>
{t('Common.Server.CPU')}
{hostedVM.cpuUsage || 0}%
80 ? "bg-destructive" : hostedVM.cpuUsage && hostedVM.cpuUsage > 60 ? "bg-amber-500" : "bg-emerald-500"}`} style={{ width: `${hostedVM.cpuUsage || 0}%` }} />
{t('Common.Server.RAM')}
{hostedVM.ramUsage || 0}%
80 ? "bg-destructive" : hostedVM.ramUsage && hostedVM.ramUsage > 60 ? "bg-amber-500" : "bg-emerald-500"}`} style={{ width: `${hostedVM.ramUsage || 0}%` }} />
{t('Common.Server.Disk')}
{hostedVM.diskUsage || 0}%
80 ? "bg-destructive" : hostedVM.diskUsage && hostedVM.diskUsage > 60 ? "bg-amber-500" : "bg-emerald-500"}`} style={{ width: `${hostedVM.diskUsage || 0}%` }} />
)}
))}
)}
) : (

{t('Server.NotFound')}

{t('Server.NotFoundDescription')}

)}
) }