import GlobalMap from "@/components/GlobalMap" import GroupSwitch from "@/components/GroupSwitch" import ServerCard from "@/components/ServerCard" import ServerCardInline from "@/components/ServerCardInline" import ServerOverview from "@/components/ServerOverview" import { ServiceTracker } from "@/components/ServiceTracker" import { Loader } from "@/components/loading/Loader" import { Label } from "@/components/ui/label" import { Popover, PopoverContent, PopoverTrigger } from "@/components/ui/popover" import { SORT_ORDERS, SORT_TYPES } from "@/context/sort-context" import { useSort } from "@/hooks/use-sort" import { useStatus } from "@/hooks/use-status" import { useWebSocketContext } from "@/hooks/use-websocket-context" import { fetchServerGroup } from "@/lib/nezha-api" import { cn, formatNezhaInfo } from "@/lib/utils" import { NezhaWebsocketResponse } from "@/types/nezha-api" import { ServerGroup } from "@/types/nezha-api" import { ArrowDownIcon, ArrowUpIcon, ArrowsUpDownIcon, ChartBarSquareIcon, MapIcon, ViewColumnsIcon, } from "@heroicons/react/20/solid" import { useQuery } from "@tanstack/react-query" import { useEffect, useState } from "react" import { useTranslation } from "react-i18next" import { toast } from "sonner" export default function Servers() { const { t } = useTranslation() const { sortType, sortOrder, setSortOrder, setSortType } = useSort() const { data: groupData } = useQuery({ queryKey: ["server-group"], queryFn: () => fetchServerGroup(), }) const { lastMessage, connected } = useWebSocketContext() const { status } = useStatus() const [showServices, setShowServices] = useState("0") const [showMap, setShowMap] = useState("0") const [inline, setInline] = useState("0") const [settingsOpen, setSettingsOpen] = useState(false) const [currentGroup, setCurrentGroup] = useState("All") useEffect(() => { const showServicesState = localStorage.getItem("showServices") if (showServicesState !== null) { setShowServices(showServicesState) } }, []) useEffect(() => { const inlineState = localStorage.getItem("inline") if (inlineState !== null) { setInline(inlineState) } }, []) const groupTabs = ["All", ...(groupData?.data?.map((item: ServerGroup) => item.group.name) || [])] useEffect(() => { const hasShownToast = sessionStorage.getItem("websocket-connected-toast") if (connected && !hasShownToast) { toast.success(t("info.websocketConnected")) sessionStorage.setItem("websocket-connected-toast", "true") } }, [connected]) if (!connected) { return (
{t("info.websocketConnecting")}
) } const nezhaWsData = lastMessage ? (JSON.parse(lastMessage.data) as NezhaWebsocketResponse) : null if (!nezhaWsData) { return (

{t("info.processing")}

) } let filteredServers = nezhaWsData?.servers?.filter((server) => { if (currentGroup === "All") return true const group = groupData?.data?.find( (g: ServerGroup) => g.group.name === currentGroup && Array.isArray(g.servers) && g.servers.includes(server.id), ) return !!group }) || [] const totalServers = filteredServers.length || 0 const onlineServers = filteredServers.filter((server) => formatNezhaInfo(nezhaWsData.now, server).online)?.length || 0 const offlineServers = filteredServers.filter((server) => !formatNezhaInfo(nezhaWsData.now, server).online)?.length || 0 const up = filteredServers.reduce( (total, server) => formatNezhaInfo(nezhaWsData.now, server).online ? total + (server.state?.net_out_transfer ?? 0) : total, 0, ) || 0 const down = filteredServers.reduce( (total, server) => formatNezhaInfo(nezhaWsData.now, server).online ? total + (server.state?.net_in_transfer ?? 0) : total, 0, ) || 0 const upSpeed = filteredServers.reduce( (total, server) => formatNezhaInfo(nezhaWsData.now, server).online ? total + (server.state?.net_out_speed ?? 0) : total, 0, ) || 0 const downSpeed = filteredServers.reduce( (total, server) => formatNezhaInfo(nezhaWsData.now, server).online ? total + (server.state?.net_in_speed ?? 0) : total, 0, ) || 0 filteredServers = status === "all" ? filteredServers : filteredServers.filter((server) => [status].includes(formatNezhaInfo(nezhaWsData.now, server).online ? "online" : "offline"), ) filteredServers = filteredServers.sort((a, b) => { const serverAInfo = formatNezhaInfo(nezhaWsData.now, a) const serverBInfo = formatNezhaInfo(nezhaWsData.now, b) if (sortType !== "name" && sortType !== "system") { // 仅在非 "name" 排序时,先按在线状态排序 if (!serverAInfo.online && serverBInfo.online) return 1 if (serverAInfo.online && !serverBInfo.online) return -1 if (!serverAInfo.online && !serverBInfo.online) { // 如果两者都离线,可以继续按照其他条件排序,或者保持原序 // 这里选择保持原序 return 0 } } let comparison = 0 switch (sortType) { case "name": comparison = a.name.localeCompare(b.name) break case "uptime": comparison = (a.state?.uptime ?? 0) - (b.state?.uptime ?? 0) break case "system": comparison = a.host.platform.localeCompare(b.host.platform) break case "cpu": comparison = (a.state?.cpu ?? 0) - (b.state?.cpu ?? 0) break case "mem": comparison = (a.state?.mem_used ?? 0) - (b.state?.mem_used ?? 0) break case "stg": comparison = (a.state?.disk_used ?? 0) - (b.state?.disk_used ?? 0) break case "up": comparison = (a.state?.net_out_speed ?? 0) - (b.state?.net_out_speed ?? 0) break case "down": comparison = (a.state?.net_in_speed ?? 0) - (b.state?.net_in_speed ?? 0) break case "up total": comparison = (a.state?.net_out_transfer ?? 0) - (b.state?.net_out_transfer ?? 0) break case "down total": comparison = (a.state?.net_in_transfer ?? 0) - (b.state?.net_in_transfer ?? 0) break default: comparison = 0 } return sortOrder === "asc" ? comparison : -comparison }) return (
{SORT_TYPES.map((type) => ( ))}
{SORT_ORDERS.map((order) => ( ))}
{showMap === "1" && ( )} {showServices === "1" && } {inline === "1" && (
{filteredServers.map((serverInfo) => ( ))}
)} {inline === "0" && (
{filteredServers.map((serverInfo) => ( ))}
)}
) }