"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 (
)
},
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 }