| "use client"; |
|
|
| import { useState, useEffect } from "react"; |
| import Card from "./Card"; |
|
|
| export default function RequestLogger() { |
| const [logs, setLogs] = useState([]); |
| const [loading, setLoading] = useState(true); |
| const [autoRefresh, setAutoRefresh] = useState(true); |
|
|
| useEffect(() => { |
| fetchLogs(); |
| }, []); |
|
|
| useEffect(() => { |
| let interval; |
| if (autoRefresh) { |
| interval = setInterval(() => { |
| fetchLogs(false); |
| }, 3000); |
| } |
| return () => clearInterval(interval); |
| }, [autoRefresh]); |
|
|
| const fetchLogs = async (showLoading = true) => { |
| if (showLoading) setLoading(true); |
| try { |
| const res = await fetch("/api/usage/request-logs"); |
| if (res.ok) { |
| const data = await res.json(); |
| setLogs(data); |
| } |
| } catch (error) { |
| console.error("Failed to fetch logs:", error); |
| } finally { |
| if (showLoading) setLoading(false); |
| } |
| }; |
|
|
| return ( |
| <div className="flex flex-col gap-4"> |
| <div className="flex items-center justify-between"> |
| <h2 className="text-xl font-semibold">Request Logs</h2> |
| <div className="flex items-center gap-2"> |
| <label className="text-sm font-medium text-text-muted flex items-center gap-2 cursor-pointer"> |
| <span>Auto Refresh (3s)</span> |
| <div |
| onClick={() => setAutoRefresh(!autoRefresh)} |
| className={`relative inline-flex h-5 w-9 items-center rounded-full transition-colors focus:outline-none ${autoRefresh ? "bg-primary" : "bg-bg-subtle border border-border" |
| }`} |
| > |
| <span |
| className={`inline-block h-3 w-3 transform rounded-full bg-white transition-transform ${autoRefresh ? "translate-x-5" : "translate-x-1" |
| }`} |
| /> |
| </div> |
| </label> |
| </div> |
| </div> |
| |
| <Card className="overflow-hidden bg-black/5 dark:bg-black/20"> |
| <div className="p-0 overflow-x-auto max-h-[600px] overflow-y-auto font-mono text-xs"> |
| {loading && logs.length === 0 ? ( |
| <div className="p-8 text-center text-text-muted">Loading logs...</div> |
| ) : logs.length === 0 ? ( |
| <div className="p-8 text-center text-text-muted">No logs recorded yet.</div> |
| ) : ( |
| <table className="w-full text-left border-collapse whitespace-nowrap"> |
| <thead className="sticky top-0 bg-bg-subtle border-b border-border z-10"> |
| <tr> |
| <th className="px-3 py-2 border-r border-border">DateTime</th> |
| <th className="px-3 py-2 border-r border-border">Model</th> |
| <th className="px-3 py-2 border-r border-border">Provider</th> |
| <th className="px-3 py-2 border-r border-border">Account</th> |
| <th className="px-3 py-2 border-r border-border">In</th> |
| <th className="px-3 py-2 border-r border-border">Out</th> |
| <th className="px-3 py-2">Status</th> |
| </tr> |
| </thead> |
| <tbody className="divide-y divide-border/50"> |
| {logs.map((log, i) => { |
| const parts = log.split(" | "); |
| if (parts.length < 7) return null; |
| |
| const status = parts[6]; |
| const isPending = status.includes("PENDING"); |
| const isFailed = status.includes("FAILED"); |
| const isSuccess = status.includes("OK"); |
| |
| return ( |
| <tr key={i} className={`hover:bg-primary/5 transition-colors ${isPending ? 'bg-primary/5' : ''}`}> |
| <td className="px-3 py-1.5 border-r border-border text-text-muted">{parts[0]}</td> |
| <td className="px-3 py-1.5 border-r border-border font-medium">{parts[1]}</td> |
| <td className="px-3 py-1.5 border-r border-border"> |
| <span className="px-1.5 py-0.5 rounded bg-bg-subtle border border-border text-[10px] uppercase font-bold"> |
| {parts[2]} |
| </span> |
| </td> |
| <td className="px-3 py-1.5 border-r border-border truncate max-w-[150px]" title={parts[3]}>{parts[3]}</td> |
| <td className="px-3 py-1.5 border-r border-border text-right text-primary">{parts[4]}</td> |
| <td className="px-3 py-1.5 border-r border-border text-right text-success">{parts[5]}</td> |
| <td className={`px-3 py-1.5 font-bold ${isSuccess ? 'text-success' : |
| isFailed ? 'text-error' : |
| 'text-primary animate-pulse' |
| }`}> |
| {status} |
| </td> |
| </tr> |
| ); |
| })} |
| </tbody> |
| </table> |
| )} |
| </div> |
| </Card> |
| <div className="text-[10px] text-text-muted italic"> |
| Logs are loaded from the request history database. |
| </div> |
| </div> |
| ); |
| } |
|
|