import GlobalMap from "@/components/GlobalMap" import GroupSwitch from "@/components/GroupSwitch" import ServerCard from "@/components/ServerCard" 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 { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select" 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, fetchService } 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 } from "@heroicons/react/20/solid" import { useQuery } from "@tanstack/react-query" import { useEffect, useRef, useState } from "react" import { useTranslation } from "react-i18next" 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 containerRef = useRef(null) const [settingsOpen, setSettingsOpen] = useState(false) const [currentGroup, setCurrentGroup] = useState("All") const customBackgroundImage = (window.CustomBackgroundImage as string) !== "" ? window.CustomBackgroundImage : undefined const restoreScrollPosition = () => { const savedPosition = sessionStorage.getItem("scrollPosition") if (savedPosition && containerRef.current) { containerRef.current.scrollTop = Number(savedPosition) } } const handleTagChange = (newGroup: string) => { setCurrentGroup(newGroup) sessionStorage.setItem("selectedGroup", newGroup) sessionStorage.setItem("scrollPosition", String(containerRef.current?.scrollTop || 0)) } useEffect(() => { const showServicesState = localStorage.getItem("showServices") if (window.ForceShowServices) { setShowServices("1") } else if (showServicesState !== null) { setShowServices(showServicesState) } else { localStorage.setItem("showServices", "0") setShowServices("0") } }, []) useEffect(() => { const showMapState = localStorage.getItem("showMap") if (window.ForceShowMap) { setShowMap("1") } else if (showMapState !== null) { setShowMap(showMapState) } }, []) useEffect(() => { const savedGroup = sessionStorage.getItem("selectedGroup") || "All" setCurrentGroup(savedGroup) restoreScrollPosition() }, []) const nezhaWsData = lastMessage ? (JSON.parse(lastMessage.data) as NezhaWebsocketResponse) : null const groupTabs = [ "All", ...(groupData?.data ?.filter((item: ServerGroup) => { return Array.isArray(item.servers) && item.servers.some((serverId) => nezhaWsData?.servers?.some((server) => server.id === serverId)) }) ?.map((item: ServerGroup) => item.group.name) || []), ] // 获取cycle_transfer_stats数据 const { data: serviceData } = useQuery({ queryKey: ["service"], queryFn: () => fetchService(), refetchOnMount: true, refetchOnWindowFocus: true, refetchInterval: 10000, }) const cycleTransferStats = serviceData?.data?.cycle_transfer_stats if (!connected && !lastMessage) { return (
{t("info.websocketConnecting")}
) } 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") { // 仅在非 "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 (
{showMap === "1" && } {showServices === "1" && }
{filteredServers.map((serverInfo) => ( ))}
) }