"use client"; import { useState, useEffect, useCallback, useMemo, useRef } from "react"; 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"; const STATUS_FILTERS = [ { key: "all", label: "All" }, { key: "error", label: "Errors", icon: "error" }, { key: "ok", label: "Success", icon: "check_circle" }, { key: "timeout", label: "Timeout", icon: "timer_off" }, ]; const COLUMNS = [ { key: "status", label: "Status" }, { key: "proxy", label: "Proxy" }, { key: "tls", label: "TLS" }, { key: "type", label: "Type" }, { key: "level", label: "Level" }, { key: "provider", label: "Provider" }, { key: "target", label: "Target" }, { key: "latency", label: "Latency" }, { key: "ip", label: "Public IP" }, { key: "time", label: "Time" }, ]; const DEFAULT_VISIBLE = Object.fromEntries(COLUMNS.map((c) => [c.key, true])); export default function 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 intervalRef = useRef(null); const hasLoadedRef = useRef(false); 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(); 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]); 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 (
{/* Header Bar */}
{/* Recording Toggle */} {/* Search */}
search setSearch(e.target.value)} className="w-full pl-10 pr-4 py-2 rounded-lg bg-bg-subtle border border-border text-sm text-text-primary placeholder:text-text-muted focus:outline-none focus:border-primary" />
{/* Type Dropdown */} {/* Level Dropdown */} {/* Provider Dropdown */} {/* Stats */}
{totalCount} total {okCount} OK {errorCount > 0 && ( {errorCount} ERR )} {timeoutCount > 0 && ( {timeoutCount} TMO )} {directCount > 0 && ( {directCount} direct )} {tlsCount > 0 && ( 🔒 {tlsCount} TLS )}
{/* Sort */} {/* Refresh */}
{/* Quick Filters */}
{STATUS_FILTERS.map((f) => ( ))} {uniqueProviders.length > 0 && } {uniqueProviders.map((p) => { const pc = PROVIDER_COLORS[p] || { bg: "#374151", text: "#fff", label: p.toUpperCase() }; const isActive = selectedProvider === p; return ( ); })}
{/* Column Visibility Toggles */}
Columns {COLUMNS.map((col) => ( ))}
{/* Table */}
{loading && logs.length === 0 ? (
Loading proxy logs...
) : logs.length === 0 ? (
vpn_lock No proxy logs yet. Configure proxies and make API calls to see them here.
) : sortedLogs.length === 0 ? (
No logs match the current filters.
) : ( {visibleColumns.status && ( )} {visibleColumns.proxy && ( )} {visibleColumns.tls && ( )} {visibleColumns.type && ( )} {visibleColumns.level && ( )} {visibleColumns.provider && ( )} {visibleColumns.target && ( )} {visibleColumns.latency && ( )} {visibleColumns.ip && ( )} {visibleColumns.time && ( )} {sortedLogs.map((log) => { const statusStyle = getStatusStyle(log.status); const typeColor = TYPE_COLORS[log.proxy?.type] || { bg: "#6B7280", text: "#fff", label: log.proxy?.type || "-", }; const levelColor = LEVEL_COLORS[log.level] || LEVEL_COLORS.direct; const providerColor = PROVIDER_COLORS[log.provider] || { bg: "#374151", text: "#fff", label: (log.provider || "-").toUpperCase(), }; const isError = log.status === "error" || log.status === "timeout"; return ( setSelectedLog(selectedLog?.id === log.id ? null : log)} className={`cursor-pointer hover:bg-primary/5 transition-colors ${isError ? "bg-red-500/5" : ""}`} > {visibleColumns.status && ( )} {visibleColumns.proxy && ( )} {visibleColumns.tls && ( )} {visibleColumns.type && ( )} {visibleColumns.level && ( )} {visibleColumns.provider && ( )} {visibleColumns.target && ( )} {visibleColumns.latency && ( )} {visibleColumns.ip && ( )} {visibleColumns.time && ( )} ); })}
Status Proxy TLS Type Level Provider Target Latency Public IP Time
{log.status} {log.proxy ? `${log.proxy.host}:${log.proxy.port}` : "—"} {log.tlsFingerprint ? ( 🔒 TLS ) : ( — )} {typeColor.label} {levelColor.label} {log.provider ? ( {providerColor.label} ) : ( — )} {truncateUrl(log.targetUrl)} {formatLatency(log.latencyMs)} {log.publicIp || "—"} {formatTime(log.timestamp)}
)}
{/* Detail Panel */} {selectedLog && setSelectedLog(null)} />}
); }