import React, { useMemo, useRef, useState, useCallback, useEffect, } from "react"; import { useLockFn } from "ahooks"; import { Virtuoso } from "react-virtuoso"; import { useTranslation } from "react-i18next"; import { useConnectionSetting } from "@/services/states"; import { useVisibility } from "@/hooks/use-visibility"; import { useAppData } from "@/providers/app-data-provider"; import { closeAllConnections } from "@/services/api"; import parseTraffic from "@/utils/parse-traffic"; import { cn } from "@root/lib/utils"; import { BaseEmpty } from "@/components/base"; import { ConnectionItem } from "@/components/connection/connection-item"; import { ConnectionTable } from "@/components/connection/connection-table"; import { ConnectionDetail, ConnectionDetailRef, } from "@/components/connection/connection-detail"; import { BaseSearchBox } from "@/components/base/base-search-box"; import { Button } from "@/components/ui/button"; import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue, } from "@/components/ui/select"; import { Separator } from "@/components/ui/separator"; import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger, } from "@/components/ui/tooltip"; import { List, Table2, PlayCircle, PauseCircle, ArrowDown, ArrowUp, } from "lucide-react"; import { SidebarTrigger } from "@/components/ui/sidebar"; interface IConnectionsItem { id: string; metadata: { host: string; destinationIP: string; process?: string; }; start?: string; curUpload?: number; curDownload?: number; } interface IConnections { uploadTotal: number; downloadTotal: number; connections: IConnectionsItem[]; data: IConnectionsItem[]; } type OrderFunc = (list: IConnectionsItem[]) => IConnectionsItem[]; const initConn: IConnections = { uploadTotal: 0, downloadTotal: 0, connections: [], data: [], }; const ConnectionsPage = () => { const { t } = useTranslation(); const pageVisible = useVisibility(); const [match, setMatch] = useState(() => (_: string) => true); const [curOrderOpt, setOrderOpt] = useState("Default"); const { connections } = useAppData(); const [setting, setSetting] = useConnectionSetting(); const isTableLayout = setting.layout === "table"; const orderOpts: Record = { Default: (list) => list.sort( (a, b) => new Date(b.start || "0").getTime()! - new Date(a.start || "0").getTime()!, ), "Upload Speed": (list) => list.sort((a, b) => (b.curUpload ?? 0) - (a.curUpload ?? 0)), "Download Speed": (list) => list.sort((a, b) => (b.curDownload ?? 0) - (a.curDownload ?? 0)), }; const [isPaused, setIsPaused] = useState(false); const [frozenData, setFrozenData] = useState(null); const displayData = useMemo(() => { if (!pageVisible) return initConn; const currentData = { uploadTotal: connections.uploadTotal, downloadTotal: connections.downloadTotal, connections: connections.data, data: connections.data, }; if (isPaused) return frozenData ?? currentData; return currentData; }, [isPaused, frozenData, connections, pageVisible]); const filterConn = useMemo(() => { const orderFunc = orderOpts[curOrderOpt]; let conns = displayData.connections.filter((conn) => { const { host, destinationIP, process } = conn.metadata; return ( match(host || "") || match(destinationIP || "") || match(process || "") ); }); if (orderFunc) conns = orderFunc(conns); return conns; }, [displayData, match, curOrderOpt]); const [scrollingElement, setScrollingElement] = useState< HTMLElement | Window | null >(null); const [isScrolled, setIsScrolled] = useState(false); const scrollerRefCallback = useCallback( (node: HTMLElement | Window | null) => { setScrollingElement(node); }, [], ); useEffect(() => { if (!scrollingElement) return; const handleScroll = () => { const scrollTop = scrollingElement instanceof Window ? scrollingElement.scrollY : scrollingElement.scrollTop; setIsScrolled(scrollTop > 5); }; scrollingElement.addEventListener("scroll", handleScroll); return () => scrollingElement.removeEventListener("scroll", handleScroll); }, [scrollingElement]); const onCloseAll = useLockFn(closeAllConnections); const detailRef = useRef(null!); const handleSearch = useCallback( (m: (content: string) => boolean) => setMatch(() => m), [], ); const handlePauseToggle = useCallback(() => { setIsPaused((prev) => { if (!prev) { setFrozenData({ uploadTotal: connections.uploadTotal, downloadTotal: connections.downloadTotal, connections: connections.data, data: connections.data, }); } else { setFrozenData(null); } return !prev; }); }, [connections]); const headerHeight = "7rem"; return (

{t("Connections")}

{parseTraffic(displayData.downloadTotal)}
{parseTraffic(displayData.uploadTotal)}

{isTableLayout ? t("List View") : t("Table View")}

{isPaused ? t("Resume") : t("Pause")}

{!isTableLayout && ( )}
{filterConn.length === 0 ? ( ) : isTableLayout ? (
detailRef.current?.open(detail)} scrollerRef={scrollerRefCallback} />
) : ( ( detailRef.current?.open(item)} /> )} /> )}
); }; export default ConnectionsPage;