mirror of
https://github.com/diegosouzapw/OmniRoute.git
synced 2026-08-01 12:52:11 +03:00
OmniRoute is an intelligent API gateway that unifies 20+ AI providers behind a single OpenAI-compatible endpoint. Features include intelligent routing with 6 strategies, multi-format translation (OpenAI/Claude/Gemini/Responses API), circuit breakers, semantic caching, combo fallback chains, real-time health monitoring, and a full dashboard with provider management, analytics, and CLI tool integration. Key highlights: - 20+ providers (Claude Code, Codex, Gemini CLI, GitHub Copilot, iFlow, Qwen, Kiro, etc.) - 6 routing strategies (Fill First, Round Robin, P2C, Random, Least Used, Cost Optimized) - Export/Import database backup with full archive support - Translator Playground with 4 modes (Playground, Chat Tester, Test Bench, Live Monitor) - 100% TypeScript across src/ and open-sse/ - Docker support with multi-stage builds - Comprehensive documentation and 9 dashboard screenshots
193 lines
7.3 KiB
TypeScript
193 lines
7.3 KiB
TypeScript
"use client";
|
|
|
|
import { useState, useEffect } from "react";
|
|
import {
|
|
TYPE_COLORS,
|
|
LEVEL_COLORS,
|
|
PROVIDER_COLORS,
|
|
getProxyStatusStyle as getStatusStyle,
|
|
} from "@/shared/constants/colors";
|
|
import { formatDuration as formatLatency } from "@/shared/utils/formatting";
|
|
|
|
/**
|
|
* Proxy log detail modal — shows full proxy event metadata, error info, and config.
|
|
* Extracted from ProxyLogger.js for maintainability.
|
|
*/
|
|
export default function ProxyLogDetail({ log, onClose }) {
|
|
useEffect(() => {
|
|
const handler = (e) => {
|
|
if (e.key === "Escape") onClose();
|
|
};
|
|
window.addEventListener("keydown", handler);
|
|
return () => window.removeEventListener("keydown", handler);
|
|
}, [onClose]);
|
|
|
|
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 formatDate = (iso) => {
|
|
try {
|
|
const d = new Date(iso);
|
|
return (
|
|
d.toLocaleDateString("pt-BR") + ", " + d.toLocaleTimeString("en-US", { hour12: false })
|
|
);
|
|
} catch {
|
|
return iso;
|
|
}
|
|
};
|
|
|
|
return (
|
|
<div
|
|
className="fixed inset-0 z-50 flex items-start justify-center pt-[5vh]"
|
|
onClick={onClose}
|
|
role="dialog"
|
|
aria-modal="true"
|
|
aria-label="Proxy log detail"
|
|
>
|
|
<div className="absolute inset-0 bg-black/60 backdrop-blur-sm" />
|
|
<div
|
|
className="relative bg-bg-primary border border-border rounded-xl w-full max-w-[700px] max-h-[90vh] overflow-y-auto shadow-2xl"
|
|
onClick={(e) => e.stopPropagation()}
|
|
>
|
|
{/* Header */}
|
|
<div className="sticky top-0 z-10 flex items-center justify-between px-6 py-4 border-b border-border bg-bg-primary/95 backdrop-blur-sm rounded-t-xl">
|
|
<div className="flex items-center gap-3">
|
|
<span
|
|
className="inline-block px-2.5 py-1 rounded text-xs font-bold uppercase"
|
|
style={{ backgroundColor: statusStyle.bg, color: statusStyle.text }}
|
|
>
|
|
{log.status}
|
|
</span>
|
|
<span className="font-bold text-lg">Proxy Event</span>
|
|
</div>
|
|
<button
|
|
onClick={onClose}
|
|
className="p-1.5 rounded-lg hover:bg-bg-subtle text-text-muted hover:text-text-primary transition-colors"
|
|
aria-label="Close proxy detail modal"
|
|
>
|
|
<span className="material-symbols-outlined">close</span>
|
|
</button>
|
|
</div>
|
|
|
|
<div className="p-6 flex flex-col gap-6">
|
|
{/* Metadata Grid */}
|
|
<div className="grid grid-cols-2 md:grid-cols-3 gap-4 p-4 bg-bg-subtle rounded-xl border border-border">
|
|
<div>
|
|
<div className="text-[10px] text-text-muted uppercase tracking-wider mb-1">Time</div>
|
|
<div className="text-sm font-medium">{formatDate(log.timestamp)}</div>
|
|
</div>
|
|
<div>
|
|
<div className="text-[10px] text-text-muted uppercase tracking-wider mb-1">
|
|
Latency
|
|
</div>
|
|
<div className="text-sm font-medium">{formatLatency(log.latencyMs)}</div>
|
|
</div>
|
|
<div>
|
|
<div className="text-[10px] text-text-muted uppercase tracking-wider mb-1">
|
|
Public IP
|
|
</div>
|
|
<div className="text-sm font-medium font-mono text-emerald-400">
|
|
{log.publicIp || "—"}
|
|
</div>
|
|
</div>
|
|
<div>
|
|
<div className="text-[10px] text-text-muted uppercase tracking-wider mb-1">Proxy</div>
|
|
<div className="text-sm font-medium font-mono text-primary">
|
|
{log.proxy ? `${log.proxy.type}://${log.proxy.host}:${log.proxy.port}` : "Direct"}
|
|
</div>
|
|
</div>
|
|
<div>
|
|
<div className="text-[10px] text-text-muted uppercase tracking-wider mb-1">Type</div>
|
|
<span
|
|
className="inline-block px-2.5 py-1 rounded text-[10px] font-bold uppercase"
|
|
style={{ backgroundColor: typeColor.bg, color: typeColor.text }}
|
|
>
|
|
{typeColor.label}
|
|
</span>
|
|
</div>
|
|
<div>
|
|
<div className="text-[10px] text-text-muted uppercase tracking-wider mb-1">Level</div>
|
|
<span
|
|
className="inline-block px-2.5 py-1 rounded text-[10px] font-bold uppercase"
|
|
style={{ backgroundColor: levelColor.bg, color: levelColor.text }}
|
|
>
|
|
{levelColor.label}
|
|
</span>
|
|
</div>
|
|
<div>
|
|
<div className="text-[10px] text-text-muted uppercase tracking-wider mb-1">
|
|
Provider
|
|
</div>
|
|
{log.provider ? (
|
|
<span
|
|
className="inline-block px-2.5 py-1 rounded text-[10px] font-bold uppercase"
|
|
style={{ backgroundColor: providerColor.bg, color: providerColor.text }}
|
|
>
|
|
{providerColor.label}
|
|
</span>
|
|
) : (
|
|
<div className="text-sm text-text-muted">—</div>
|
|
)}
|
|
</div>
|
|
<div>
|
|
<div className="text-[10px] text-text-muted uppercase tracking-wider mb-1">
|
|
TLS Fingerprint
|
|
</div>
|
|
{log.tlsFingerprint ? (
|
|
<span
|
|
className="inline-flex items-center gap-1 px-2.5 py-1 rounded text-[10px] font-bold uppercase"
|
|
style={{ backgroundColor: "rgba(6, 182, 212, 0.15)", color: "#22d3ee" }}
|
|
>
|
|
<span style={{ fontSize: "12px" }}>🔒</span> Chrome 124
|
|
</span>
|
|
) : (
|
|
<div className="text-sm text-text-muted">Direct (native)</div>
|
|
)}
|
|
</div>
|
|
<div className="col-span-2">
|
|
<div className="text-[10px] text-text-muted uppercase tracking-wider mb-1">
|
|
Target URL
|
|
</div>
|
|
<div className="text-sm font-medium font-mono text-text-muted break-all">
|
|
{log.targetUrl || "—"}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
{/* Error */}
|
|
{log.error && (
|
|
<div className="p-4 rounded-xl bg-red-500/10 border border-red-500/30">
|
|
<div className="text-[10px] text-red-400 uppercase tracking-wider mb-1 font-bold">
|
|
Error
|
|
</div>
|
|
<div className="text-sm text-red-300 font-mono">{log.error}</div>
|
|
</div>
|
|
)}
|
|
|
|
{/* Proxy Config Details */}
|
|
{log.proxy && (
|
|
<div className="p-4 rounded-xl bg-bg-subtle border border-border">
|
|
<div className="text-[10px] text-text-muted uppercase tracking-wider mb-2 font-bold">
|
|
Proxy Configuration
|
|
</div>
|
|
<pre className="text-xs font-mono text-text-primary bg-black/20 rounded-lg p-3 overflow-x-auto">
|
|
{JSON.stringify(log.proxy, null, 2)}
|
|
</pre>
|
|
</div>
|
|
)}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|