"use client"; import { Badge } from "@workspace/ui/components/badge"; import { Button } from "@workspace/ui/components/button"; import { ConfirmButton } from "@workspace/ui/composed/confirm-button"; import { ProTable, type ProTableActions, } from "@workspace/ui/composed/pro-table/pro-table"; import { cn } from "@workspace/ui/lib/utils"; import { createServer, deleteServer, filterServerList, resetSortWithServer, updateServer, } from "@workspace/ui/services/admin/server"; import { useRef, useState } from "react"; import { useTranslation } from "react-i18next"; import { toast } from "sonner"; import { useNode } from "@/stores/node"; import { useServer } from "@/stores/server"; import DynamicMultiplier from "./dynamic-multiplier"; import OnlineUsersCell from "./online-users-cell"; import ServerConfig from "./server-config"; import ServerForm from "./server-form"; import ServerInstall from "./server-install"; function PctBar({ value }: { value: number }) { const v = value.toFixed(2); const widthClass = value >= 90 ? "w-[90%]" : value >= 80 ? "w-4/5" : value >= 70 ? "w-[70%]" : value >= 60 ? "w-3/5" : value >= 50 ? "w-1/2" : value >= 40 ? "w-2/5" : value >= 30 ? "w-[30%]" : value >= 20 ? "w-1/5" : value >= 10 ? "w-[10%]" : "w-0"; return (
{v}%
); } function RegionIpCell({ country, city, ip, notAvailableText, }: { country?: string; city?: string; ip?: string; notAvailableText: string; }) { const region = [country, city].filter(Boolean).join(" / ") || notAvailableText; return (
{region} {ip || notAvailableText}
); } export default function Servers() { const { t } = useTranslation("servers"); const { isServerReferencedByNodes } = useNode(); const { fetchServers } = useServer(); const [loading, setLoading] = useState(false); const ref = useRef(null); return (
action={ref} actions={{ render: (row) => [ { setLoading(true); try { await updateServer({ id: row.id, ...(values as unknown as Omit< API.UpdateServerRequest, "id" >), }); toast.success(t("updated", "Updated")); ref.current?.refresh(); fetchServers(); setLoading(false); return true; } catch { setLoading(false); return false; } }} title={t("drawerEditTitle", "Edit Server")} trigger={t("edit", "Edit")} />, , { await deleteServer({ id: row.id } as API.DeleteServerRequest); toast.success(t("deleted", "Deleted")); ref.current?.refresh(); fetchServers(); }} title={t("confirmDeleteTitle", "Delete this server?")} trigger={ } />, , ], batchRender(rows) { const hasReferencedServers = rows.some((row) => isServerReferencedByNodes(row.id) ); return [ { await Promise.all( rows.map((r) => deleteServer({ id: r.id })) ); toast.success(t("deleted", "Deleted")); ref.current?.refresh(); fetchServers(); }} title={t("confirmDeleteTitle", "Delete this server?")} trigger={ } />, ]; }, }} columns={[ { accessorKey: "id", header: t("id", "ID"), cell: ({ row }) => {row.getValue("id")}, }, { accessorKey: "name", header: t("name", "Name") }, { id: "region_ip", header: t("address", "Address"), cell: ({ row }) => ( ), }, { accessorKey: "protocols", header: t("protocols", "Protocols"), cell: ({ row }) => { const list = row.original.protocols.filter( (p) => p.enable ) as API.Protocol[]; if (!list.length) return "—"; return (
{list.map((p, idx) => { const ratio = Number(p.ratio ?? 1) || 1; return (
{ratio.toFixed(2)}x {p.type} {p.port}
); })}
); }, }, { id: "status", header: t("status", "Status"), cell: ({ row }) => { const offline = row.original.status.status === "offline"; return (
{offline ? t("offline", "Offline") : t("online", "Online")}
); }, }, { id: "cpu", header: t("cpu", "CPU"), cell: ({ row }) => ( ), }, { id: "mem", header: t("memory", "Memory"), cell: ({ row }) => ( ), }, { id: "disk", header: t("disk", "Disk"), cell: ({ row }) => ( ), }, { id: "online_users", header: t("onlineUsers", "Online Users"), cell: ({ row }) => ( ), }, ]} header={{ title: t("pageTitle", "Servers"), toolbar: (
{ setLoading(true); try { await createServer( values as unknown as API.CreateServerRequest ); toast.success(t("created", "Created")); ref.current?.refresh(); fetchServers(); setLoading(false); return true; } catch { setLoading(false); return false; } }} title={t("drawerCreateTitle", "Create Server")} trigger={t("create", "Create")} />
), }} onSort={async (source, target, items) => { const sourceIndex = items.findIndex( (item) => String(item.id) === source ); const targetIndex = items.findIndex( (item) => String(item.id) === target ); const originalSorts = items.map((item) => item.sort); const [movedItem] = items.splice(sourceIndex, 1); items.splice(targetIndex, 0, movedItem!); const updatedItems = items.map((item, index) => { const originalSort = originalSorts[index]; const newSort = originalSort !== undefined ? originalSort : item.sort; return { ...item, sort: newSort }; }); const changedItems = updatedItems.filter( (item, index) => item.sort !== items[index]?.sort ); if (changedItems.length > 0) { resetSortWithServer({ sort: changedItems.map((item) => ({ id: item.id, sort: item.sort, })) as API.SortItem[], }); toast.success(t("sorted_success", "Sorted successfully")); } return updatedItems; }} params={[{ key: "search" }]} request={async (pagination, filter) => { const { data } = await filterServerList({ page: pagination.page, size: pagination.size, search: filter?.search || undefined, }); const list = (data?.data?.list || []) as API.Server[]; const total = (data?.data?.total ?? list.length) as number; return { list, total }; }} />
); }