"use client"; import { useState, useEffect, useCallback, useMemo, useRef } from "react"; import { useTranslations } from "next-intl"; import Card from "./Card"; import ProxyLogDetail from "./ProxyLogDetail"; import { TYPE_COLORS, LEVEL_COLORS, PROVIDER_COLORS, getProxyStatusStyle as getStatusStyle, } from "@/shared/constants/colors"; import { formatTime, formatDuration as formatLatency, truncateUrl, } from "@/shared/utils/formatting"; import { getProviderDisplayLabel } from "@/shared/utils/providerDisplayLabel"; import { LOG_TABLE_CLASS, LOG_TABLE_HEAD_CLASS, LOG_TABLE_HEADER_BG_STYLE, LOG_TABLE_HEADER_CELL_CLASS, LOG_TABLE_HEADER_CELL_RIGHT_CLASS, LOG_TABLE_ROW_CLASS, } from "./logTableStyles"; const PROXY_COLUMN_KEYS = [ "status", "proxy", "tls", "type", "level", "provider", "target", "latency", "ip", "time", ]; const DEFAULT_VISIBLE = Object.fromEntries(PROXY_COLUMN_KEYS.map((key) => [key, true])); export default function ProxyLogger() { const t = useTranslations("proxyLogger"); const [logs, setLogs] = useState([]); const [loading, setLoading] = useState(true); const [recording, setRecording] = useState(true); const [search, setSearch] = useState(""); const [activeFilter, setActiveFilter] = useState("all"); const [selectedType, setSelectedType] = useState(""); const [selectedProvider, setSelectedProvider] = useState(""); const [selectedLevel, setSelectedLevel] = useState(""); const [sortBy, setSortBy] = useState("newest"); const [selectedLog, setSelectedLog] = useState(null); const [providerNodes, setProviderNodes] = useState< Array<{ id?: string; prefix?: string; name?: string }> >([]); const intervalRef = useRef(null); const hasLoadedRef = useRef(false); const logsSignatureRef = useRef(""); const statusFilters = useMemo( () => [ { key: "all", label: t("filterAll") }, { key: "error", label: t("filterErrors"), icon: "error" }, { key: "ok", label: t("filterSuccess"), icon: "check_circle" }, { key: "timeout", label: t("filterTimeout"), icon: "timer_off" }, ], [t] ); const columns = useMemo( () => [ { key: "status", label: t("colStatus") }, { key: "proxy", label: t("colProxy") }, { key: "tls", label: t("colTls") }, { key: "type", label: t("colType") }, { key: "level", label: t("colLevel") }, { key: "provider", label: t("colProvider") }, { key: "target", label: t("colTarget") }, { key: "latency", label: t("colLatency") }, { key: "ip", label: t("colClientIp") }, { key: "time", label: t("colTime") }, ], [t] ); const [visibleColumns, setVisibleColumns] = useState(() => { if (typeof window === "undefined") return DEFAULT_VISIBLE; try { const saved = localStorage.getItem("proxyLoggerVisibleColumns"); return saved ? { ...DEFAULT_VISIBLE, ...JSON.parse(saved) } : DEFAULT_VISIBLE; } catch { return DEFAULT_VISIBLE; } }); const toggleColumn = useCallback((key) => { setVisibleColumns((prev) => { const next = { ...prev, [key]: !prev[key] }; try { localStorage.setItem("proxyLoggerVisibleColumns", JSON.stringify(next)); } catch {} return next; }); }, []); const fetchLogs = useCallback( async (showLoading = false) => { if (showLoading) setLoading(true); try { const params = new URLSearchParams(); if (search) params.set("search", search); if (activeFilter === "error") params.set("status", "error"); if (activeFilter === "ok") params.set("status", "ok"); if (activeFilter === "timeout") params.set("status", "timeout"); if (selectedType) params.set("type", selectedType); if (selectedProvider) params.set("provider", selectedProvider); if (selectedLevel) params.set("level", selectedLevel); params.set("limit", "300"); const res = await fetch(`/api/usage/proxy-logs?${params}`); if (res.ok) { const data = await res.json(); // Skip re-render if data hasn't changed (#1369 GPU perf) const sig = JSON.stringify(data.map?.((l: any) => l.id) ?? []); if (sig !== logsSignatureRef.current) { logsSignatureRef.current = sig; setLogs(data); } } } catch (error) { console.error("Failed to fetch proxy logs:", error); } finally { if (showLoading) setLoading(false); } }, [search, activeFilter, selectedType, selectedProvider, selectedLevel] ); useEffect(() => { const showLoading = !hasLoadedRef.current; hasLoadedRef.current = true; fetchLogs(showLoading); }, [fetchLogs]); // Fetch provider nodes for display labels useEffect(() => { fetch("/api/provider-nodes") .then((r) => (r.ok ? r.json() : { nodes: [] })) .then((d) => setProviderNodes(d.nodes || [])) .catch(() => {}); }, []); useEffect(() => { if (intervalRef.current) clearInterval(intervalRef.current); if (recording) { intervalRef.current = setInterval(() => fetchLogs(false), 3000); } return () => { if (intervalRef.current) clearInterval(intervalRef.current); }; }, [recording, fetchLogs]); const sortedLogs = useMemo(() => { const arr = [...logs]; arr.sort((a, b) => { switch (sortBy) { case "oldest": return new Date(a.timestamp).getTime() - new Date(b.timestamp).getTime(); case "latency_desc": return (b.latencyMs || 0) - (a.latencyMs || 0); case "latency_asc": return (a.latencyMs || 0) - (b.latencyMs || 0); case "newest": default: return new Date(b.timestamp).getTime() - new Date(a.timestamp).getTime(); } }); return arr; }, [logs, sortBy]); const uniqueProviders = [...new Set(logs.map((l) => l.provider).filter(Boolean))].sort(); const uniqueTypes = [...new Set(logs.map((l) => l.proxy?.type).filter(Boolean))].sort(); const uniqueLevels = [...new Set(logs.map((l) => l.level).filter(Boolean))].sort(); const totalCount = logs.length; const okCount = logs.filter((l) => l.status === "success").length; const errorCount = logs.filter((l) => l.status === "error").length; const timeoutCount = logs.filter((l) => l.status === "timeout").length; const directCount = logs.filter((l) => l.level === "direct").length; const tlsCount = logs.filter((l) => l.tlsFingerprint).length; return (
| {t("colStatus")} | )} {visibleColumns.proxy && ({t("colProxy")} | )} {visibleColumns.tls && ({t("colTls")} | )} {visibleColumns.type && ({t("colType")} | )} {visibleColumns.level && ({t("colLevel")} | )} {visibleColumns.provider && ({t("colProvider")} | )} {visibleColumns.target && ({t("colTarget")} | )} {visibleColumns.latency && ({t("colLatency")} | )} {visibleColumns.ip && ({t("colClientIp")} | )} {visibleColumns.time && ({t("colTime")} | )}
|---|---|---|---|---|---|---|---|---|---|
| {log.status} | )} {visibleColumns.proxy && ({log.proxy ? `${log.proxy.host}:${log.proxy.port}` : "—"} | )} {visibleColumns.tls && ({log.tlsFingerprint ? ( 🔒 TLS ) : ( — )} | )} {visibleColumns.type && ({typeColor.label} | )} {visibleColumns.level && ({levelColor.label} | )} {visibleColumns.provider && ({log.provider ? ( {providerColor.label} ) : ( — )} | )} {visibleColumns.target && ({truncateUrl(log.targetUrl)} | )} {visibleColumns.latency && ({formatLatency(log.latencyMs)} | )} {visibleColumns.ip && ({log.clientIp || "—"} | )} {visibleColumns.time && ({formatTime(log.timestamp)} | )}