"use client" import { useMemo } from "react" import { type ColumnDef, getCoreRowModel, getSortedRowModel, useReactTable, } from "@tanstack/react-table" import type { Server } from "@/lib/data" import { Flag } from "@/components/flag" import { Sparkline } from "@/components/sparkline" import { StatusDot } from "@/components/status-dot" import { cn } from "@/lib/utils" import { DataGridShell } from "@/components/data-grids/shared/data-grid-shell" import { DATA_GRID_CELL_PAD, DATA_GRID_CELL_PAD_FIRST, DATA_GRID_CELL_PAD_LAST, } from "@/components/data-grids/shared/data-grid-layout" import { DataGridSortHeader } from "@/components/data-grids/shared/data-grid-sort-header" import { EmptyState } from "@/components/empty-state" import { AlertCircleIcon, ClockIcon, CpuIcon, HardDriveIcon, ServerIcon, SearchIcon, ThermometerIcon, } from "lucide-react" export interface UptimeResourceRow { serverId: string server: Server hasData: boolean cpu: number cpuHistory: number[] ramUsed: number ramTotal: number ramPct: number hddUsed: number hddTotal: number hddPct: number uptimeSeconds: number boardName: string temp?: number } function TypeChip({ type }: { type: "jump-host" | "exit-node" | "home-router" }) { return ( {type === "jump-host" ? "JH" : type === "home-router" ? "HR" : "EN"} ) } function fmtMB(mb: number): string { if (mb >= 1024) return `${(mb / 1024).toFixed(mb >= 10240 ? 0 : 1)} ГБ` return `${mb.toFixed(1)} МБ` } function fmtUptime(sec: number): string { const d = Math.floor(sec / 86400) const h = Math.floor((sec % 86400) / 3600) const m = Math.floor((sec % 3600) / 60) if (d > 0) return `${d}д ${h}ч` if (h > 0) return `${h}ч ${m}м` return `${m}м` } function resPctColor(pct: number, warn = 70, crit = 85): string { if (pct >= crit) return "text-red-600 dark:text-red-400" if (pct >= warn) return "text-amber-600 dark:text-amber-400" return "text-emerald-600 dark:text-emerald-400" } function resBarColor(pct: number, warn = 70, crit = 85): string { if (pct >= crit) return "bg-red-500" if (pct >= warn) return "bg-amber-500" return "bg-emerald-500" } function MiniBar({ pct, warn = 70, crit = 85, className, }: { pct: number warn?: number crit?: number className?: string }) { return (
) } interface UptimeResourcesDataGridProps { rows: UptimeResourceRow[] } function UptimeResourcesDataGrid({ rows }: UptimeResourcesDataGridProps) { const columns = useMemo[]>( () => [ { id: "name", accessorFn: (row) => row.server.name, header: ({ column }) => ( ), cell: ({ row }) => { const r = row.original const srv = r.server const offline = srv.status !== "online" const noMetrics = offline || !r.hasData const isCrit = !noMetrics && (r.cpu >= 85 || r.ramPct >= 85 || r.hddPct >= 85 || (r.temp ?? 0) >= 70) return (
{isCrit && } {!isCrit && } {srv.name} {srv.site} {!offline && !r.hasData && ( нет данных )}
) }, meta: { headerTitle: "Сервер", headerClassName: DATA_GRID_CELL_PAD_FIRST, cellClassName: DATA_GRID_CELL_PAD_FIRST, }, }, { id: "board", accessorKey: "boardName", header: () => ( Модель · ROS ), enableSorting: false, cell: ({ row }) => { const r = row.original return (
{r.hasData ? r.boardName : "—"} {r.server.os}
) }, meta: { headerClassName: "hidden md:table-cell", cellClassName: "hidden md:table-cell px-4 py-3" }, }, { id: "cpu", accessorKey: "cpu", header: ({ column }) => ( ), cell: ({ row }) => { const r = row.original const offline = r.server.status !== "online" const noMetrics = offline || !r.hasData if (noMetrics) { return ( {offline ? "—" : "нет опроса"} ) } const cpuColor = r.cpu >= 85 ? "hsl(0 84% 60%)" : r.cpu >= 70 ? "hsl(38 92% 50%)" : "hsl(142 76% 36%)" return (
{r.cpu}%
) }, meta: { headerTitle: "CPU", headerClassName: cn(DATA_GRID_CELL_PAD, "min-w-[160px]"), cellClassName: cn(DATA_GRID_CELL_PAD, "min-w-[160px]"), }, }, { id: "ram", accessorKey: "ramPct", header: ({ column }) => ( ), cell: ({ row }) => { const r = row.original const offline = r.server.status !== "online" const noMetrics = offline || !r.hasData if (noMetrics) { return {offline ? "—" : "нет опроса"} } return (
{r.ramPct}% {fmtMB(r.ramUsed)}/{fmtMB(r.ramTotal)}
) }, meta: { headerTitle: "RAM", headerClassName: cn(DATA_GRID_CELL_PAD, "min-w-[175px]"), cellClassName: cn(DATA_GRID_CELL_PAD, "min-w-[175px]"), }, }, { id: "hdd", accessorKey: "hddPct", header: ({ column }) => ( ), cell: ({ row }) => { const r = row.original const offline = r.server.status !== "online" const noMetrics = offline || !r.hasData if (noMetrics) { return {offline ? "—" : "нет опроса"} } return (
{r.hddPct}% {fmtMB(r.hddUsed)}/{fmtMB(r.hddTotal)}
) }, meta: { headerTitle: "Диск", headerClassName: cn(DATA_GRID_CELL_PAD, "min-w-[175px]"), cellClassName: cn(DATA_GRID_CELL_PAD, "min-w-[175px]"), }, }, { id: "uptime", accessorKey: "uptimeSeconds", header: ({ column }) => ( ), cell: ({ row }) => { const r = row.original const offline = r.server.status !== "online" const noMetrics = offline || !r.hasData return ( {noMetrics ? "—" : fmtUptime(r.uptimeSeconds)} ) }, meta: { headerTitle: "Uptime", headerClassName: DATA_GRID_CELL_PAD, cellClassName: DATA_GRID_CELL_PAD, }, }, { id: "temp", accessorKey: "temp", header: ({ column }) => ( ), cell: ({ row }) => { const r = row.original const offline = r.server.status !== "online" const noMetrics = offline || !r.hasData if (r.temp !== undefined && !noMetrics) { return ( = 70 ? "text-red-600 dark:text-red-400" : r.temp >= 55 ? "text-amber-600 dark:text-amber-400" : "text-muted-foreground", )} > {r.temp}°C ) } return }, meta: { headerTitle: "°C", headerClassName: DATA_GRID_CELL_PAD_LAST, cellClassName: DATA_GRID_CELL_PAD_LAST, }, }, ], [], ) const table = useReactTable({ data: rows, columns, getCoreRowModel: getCoreRowModel(), getSortedRowModel: getSortedRowModel(), getRowId: (row) => row.serverId, }) if (rows.length === 0) { return ( } title="Ничего не найдено" className="border-0 py-12" /> ) } return ( ) } export { UptimeResourcesDataGrid, type UptimeResourcesDataGridProps }