feat: sidebar reorganization + CLI fingerprint to Agents page

- New CLI sidebar section: Tools + Agents
- Move Media to Debug section
- Move Health + Logs to System section
- Move CLI Fingerprint card from Settings > Security to Agents page
- Add cliToolsShort i18n key to all 30 languages
This commit is contained in:
diegosouzapw
2026-03-07 11:55:18 -03:00
parent 8963c62adb
commit 40ba5b02e4
35 changed files with 172 additions and 110 deletions

4
package-lock.json generated
View File

@@ -1,12 +1,12 @@
{
"name": "omniroute",
"version": "2.0.10",
"version": "2.0.11",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "omniroute",
"version": "2.0.10",
"version": "2.0.11",
"hasInstallScript": true,
"license": "MIT",
"workspaces": [

View File

@@ -1,6 +1,6 @@
{
"name": "omniroute",
"version": "2.0.11",
"version": "2.0.12",
"description": "Smart AI Router with auto fallback — route to FREE & cheap models, zero downtime. Works with Cursor, Cline, Claude Desktop, Codex, and any OpenAI-compatible tool.",
"type": "module",
"bin": {

View File

@@ -3,6 +3,7 @@
import { useState, useEffect, useCallback } from "react";
import { Card, Button, Input } from "@/shared/components";
import { useTranslations } from "next-intl";
import { AI_PROVIDERS } from "@/shared/constants/config";
interface AgentInfo {
id: string;
@@ -29,6 +30,7 @@ export default function AgentsPage() {
const [refreshing, setRefreshing] = useState(false);
const [showAddForm, setShowAddForm] = useState(false);
const [addLoading, setAddLoading] = useState(false);
const [settings, setSettings] = useState<Record<string, any>>({});
const [newAgent, setNewAgent] = useState({
name: "",
binary: "",
@@ -36,6 +38,7 @@ export default function AgentsPage() {
spawnArgs: "",
});
const t = useTranslations("agents");
const ts = useTranslations("settings");
const fetchAgents = useCallback(async () => {
try {
@@ -52,8 +55,26 @@ export default function AgentsPage() {
useEffect(() => {
fetchAgents();
// Also fetch settings for CLI fingerprint
fetch("/api/settings")
.then((r) => r.json())
.then((d) => setSettings(d))
.catch(() => {});
}, [fetchAgents]);
const updateSetting = async (key: string, value: any) => {
try {
const res = await fetch("/api/settings", {
method: "PATCH",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ [key]: value }),
});
if (res.ok) setSettings((prev) => ({ ...prev, [key]: value }));
} catch (err) {
console.error("Failed to update setting:", err);
}
};
const handleRefresh = async () => {
setRefreshing(true);
try {
@@ -64,7 +85,7 @@ export default function AgentsPage() {
});
const data = await res.json();
setAgents(data.agents || []);
await fetchAgents(); // Re-fetch for summary
await fetchAgents();
} catch (err) {
console.error("Failed to refresh:", err);
} finally {
@@ -156,6 +177,70 @@ export default function AgentsPage() {
</div>
)}
{/* CLI Fingerprint Matching */}
<Card>
<div className="flex items-center gap-3 mb-4">
<div className="p-2 rounded-lg bg-emerald-500/10 text-emerald-600 dark:text-emerald-400">
<span className="material-symbols-outlined text-[20px]" aria-hidden="true">
fingerprint
</span>
</div>
<h3 className="text-lg font-semibold">{ts("cliFingerprint")}</h3>
</div>
<div className="flex flex-col gap-4">
<p className="text-sm text-text-muted">{ts("cliFingerprintDesc")}</p>
<div className="flex flex-wrap gap-2">
{(["codex", "claude", "github", "antigravity"] as const).map((providerId) => {
const providerMeta = Object.values(AI_PROVIDERS).find(
(p: any) => p.id === providerId
) as any;
const isEnabled = (settings.cliCompatProviders || []).includes(providerId);
const displayName = providerMeta?.name || providerId;
const icon = providerMeta?.icon || "terminal";
const color = providerMeta?.color || "#888";
return (
<button
key={providerId}
onClick={() => {
const current: string[] = settings.cliCompatProviders || [];
const updated = current.includes(providerId)
? current.filter((p) => p !== providerId)
: [...current, providerId];
updateSetting("cliCompatProviders", updated);
}}
className={`inline-flex items-center gap-1.5 px-2.5 py-1.5 rounded-lg text-xs font-medium transition-all border ${
isEnabled
? "bg-emerald-500/10 border-emerald-500/30 text-emerald-600 dark:text-emerald-400"
: "bg-black/[0.02] dark:bg-white/[0.02] border-transparent text-text-muted hover:bg-black/[0.05] dark:hover:bg-white/[0.05]"
}`}
>
<span
className="material-symbols-outlined text-[14px]"
style={{ color: isEnabled ? undefined : color }}
>
{isEnabled ? "fingerprint" : icon}
</span>
{displayName}
{isEnabled && (
<span className="material-symbols-outlined text-[12px] text-emerald-500">
check
</span>
)}
</button>
);
})}
</div>
{(settings.cliCompatProviders || []).length > 0 && (
<p className="text-xs text-emerald-600 dark:text-emerald-400 mt-1 flex items-center gap-1">
<span className="material-symbols-outlined text-[14px]">verified</span>
{ts("cliFingerprintEnabled", {
count: (settings.cliCompatProviders || []).length,
})}
</p>
)}
</div>
</Card>
{/* Agent Grid */}
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
{agents.map((agent) => (

View File

@@ -248,76 +248,6 @@ export default function SecurityTab() {
</div>
</Card>
{/* CLI Fingerprint Matching */}
<Card>
<div className="flex items-center gap-3 mb-4">
<div className="p-2 rounded-lg bg-emerald-500/10 text-emerald-600 dark:text-emerald-400">
<span className="material-symbols-outlined text-[20px]" aria-hidden="true">
fingerprint
</span>
</div>
<h3 className="text-lg font-semibold">{t("cliFingerprint")}</h3>
</div>
<div className="flex flex-col gap-4">
<p className="text-sm text-text-muted">{t("cliFingerprintDesc")}</p>
<div className="flex flex-wrap gap-2">
{(["codex", "claude", "github", "antigravity"] as const).map((providerId) => {
const providerMeta = Object.values(AI_PROVIDERS).find(
(p: any) => p.id === providerId
) as any;
const isEnabled = (settings.cliCompatProviders || []).includes(providerId);
const displayName = providerMeta?.name || providerId;
const icon = providerMeta?.icon || "terminal";
const color = providerMeta?.color || "#888";
return (
<button
key={providerId}
onClick={() => {
const current: string[] = settings.cliCompatProviders || [];
const updated = current.includes(providerId)
? current.filter((p) => p !== providerId)
: [...current, providerId];
updateSetting("cliCompatProviders", updated);
}}
disabled={loading}
className={`inline-flex items-center gap-1.5 px-2.5 py-1.5 rounded-lg text-xs font-medium transition-all border ${
isEnabled
? "bg-emerald-500/10 border-emerald-500/30 text-emerald-600 dark:text-emerald-400"
: "bg-black/[0.02] dark:bg-white/[0.02] border-transparent text-text-muted hover:bg-black/[0.05] dark:hover:bg-white/[0.05]"
}`}
title={
isEnabled
? t("disableFingerprintTitle", { provider: displayName })
: t("enableFingerprintTitle", { provider: displayName })
}
>
<span
className="material-symbols-outlined text-[14px]"
style={{ color: isEnabled ? undefined : color }}
>
{isEnabled ? "fingerprint" : icon}
</span>
{displayName}
{isEnabled && (
<span className="material-symbols-outlined text-[12px] text-emerald-500">
check
</span>
)}
</button>
);
})}
</div>
{(settings.cliCompatProviders || []).length > 0 && (
<p className="text-xs text-emerald-600 dark:text-emerald-400 mt-1 flex items-center gap-1">
<span className="material-symbols-outlined text-[14px]">verified</span>
{t("cliFingerprintEnabled", {
count: (settings.cliCompatProviders || []).length,
})}
</p>
)}
</div>
</Card>
<SessionInfoCard />
<IPFilterSection />
</div>

View File

@@ -102,7 +102,8 @@
"themeCyan": "كيان",
"endpoints": "نقاط النهاية",
"playground": "ملعب النماذج",
"agents": "وكلاء"
"agents": "وكلاء",
"cliToolsShort": "أدوات"
},
"themesPage": {
"title": "المواضيع",

View File

@@ -102,7 +102,8 @@
"themeCyan": "Циан",
"endpoints": "Крайни точки",
"playground": "Площадка",
"agents": "Агенти"
"agents": "Агенти",
"cliToolsShort": "Инструменти"
},
"themesPage": {
"title": "Теми",

View File

@@ -102,7 +102,8 @@
"themeCyan": "Turkis",
"endpoints": "Endpoints",
"playground": "Legeplads",
"agents": "Agenter"
"agents": "Agenter",
"cliToolsShort": "Værktøjer"
},
"themesPage": {
"title": "Temaer",

View File

@@ -102,7 +102,8 @@
"themeCyan": "Cyan",
"endpoints": "Endpunkte",
"playground": "Spielwiese",
"agents": "Agenten"
"agents": "Agenten",
"cliToolsShort": "Werkzeuge"
},
"themesPage": {
"title": "Themen",

View File

@@ -102,7 +102,8 @@
"themeGreen": "Green",
"themeViolet": "Violet",
"themeOrange": "Orange",
"themeCyan": "Cyan"
"themeCyan": "Cyan",
"cliToolsShort": "Tools"
},
"themesPage": {
"title": "Themes",

View File

@@ -102,7 +102,8 @@
"themeCyan": "cian",
"endpoints": "Endpoints",
"playground": "Playground",
"agents": "Agentes"
"agents": "Agentes",
"cliToolsShort": "Herramientas"
},
"themesPage": {
"title": "Temas",

View File

@@ -102,7 +102,8 @@
"themeCyan": "Syaani",
"endpoints": "Päätepisteet",
"playground": "Leikkipaikka",
"agents": "Agentit"
"agents": "Agentit",
"cliToolsShort": "Työkalut"
},
"themesPage": {
"title": "Teemat",

View File

@@ -102,7 +102,8 @@
"themeCyan": "Cyan",
"endpoints": "Points d'accès",
"playground": "Playground",
"agents": "Agents"
"agents": "Agents",
"cliToolsShort": "Outils"
},
"themesPage": {
"title": "Thèmes",

View File

@@ -102,7 +102,8 @@
"themeCyan": "Cyan",
"endpoints": "נקודות קצה",
"playground": "Playground",
"agents": "סוכנים"
"agents": "סוכנים",
"cliToolsShort": "כלים"
},
"themesPage": {
"title": "Themes",

View File

@@ -102,7 +102,8 @@
"themeCyan": "Cián",
"endpoints": "Végpontok",
"playground": "Játszótér",
"agents": "Ügynökök"
"agents": "Ügynökök",
"cliToolsShort": "Eszközök"
},
"themesPage": {
"title": "Témák",

View File

@@ -102,7 +102,8 @@
"themeCyan": "Cyan",
"endpoints": "Endpoint",
"playground": "Playground",
"agents": "Agen"
"agents": "Agen",
"cliToolsShort": "Alat"
},
"themesPage": {
"title": "Themes",

View File

@@ -102,7 +102,8 @@
"themeCyan": "Cyan",
"endpoints": "Endpoint",
"playground": "Playground",
"agents": "एजेंट"
"agents": "एजेंट",
"cliToolsShort": "उपकरण"
},
"themesPage": {
"title": "Themes",

View File

@@ -102,7 +102,8 @@
"themeCyan": "Cyan",
"endpoints": "Endpoint",
"playground": "Playground",
"agents": "Agenti"
"agents": "Agenti",
"cliToolsShort": "Strumenti"
},
"themesPage": {
"title": "Themes",

View File

@@ -102,7 +102,8 @@
"themeCyan": "Cyan",
"endpoints": "エンドポイント",
"playground": "プレイグラウンド",
"agents": "エージェント"
"agents": "エージェント",
"cliToolsShort": "ツール"
},
"themesPage": {
"title": "Themes",

View File

@@ -102,7 +102,8 @@
"themeCyan": "Cyan",
"endpoints": "엔드포인트",
"playground": "플레이그라운드",
"agents": "에이전트"
"agents": "에이전트",
"cliToolsShort": "도구"
},
"themesPage": {
"title": "Themes",

View File

@@ -102,7 +102,8 @@
"themeCyan": "Cyan",
"endpoints": "Titik Akhir",
"playground": "Playground",
"agents": "Ejen"
"agents": "Ejen",
"cliToolsShort": "Alat"
},
"themesPage": {
"title": "Themes",

View File

@@ -102,7 +102,8 @@
"themeCyan": "Cyan",
"endpoints": "Eindpunten",
"playground": "Speeltuin",
"agents": "Agenten"
"agents": "Agenten",
"cliToolsShort": "Gereedschap"
},
"themesPage": {
"title": "Themes",

View File

@@ -102,7 +102,8 @@
"themeCyan": "Cyan",
"endpoints": "Endepunkter",
"playground": "Lekeplass",
"agents": "Agenter"
"agents": "Agenter",
"cliToolsShort": "Verktøy"
},
"themesPage": {
"title": "Themes",

View File

@@ -102,7 +102,8 @@
"themeCyan": "Cyan",
"endpoints": "Mga Endpoint",
"playground": "Playground",
"agents": "Mga Agent"
"agents": "Mga Agent",
"cliToolsShort": "Mga Tool"
},
"themesPage": {
"title": "Themes",

View File

@@ -102,7 +102,8 @@
"themeCyan": "Cyan",
"endpoints": "Punkty końcowe",
"playground": "Plac zabaw",
"agents": "Agenci"
"agents": "Agenci",
"cliToolsShort": "Narzędzia"
},
"themesPage": {
"title": "Themes",

View File

@@ -102,7 +102,8 @@
"themeCyan": "Cyan",
"endpoints": "Endpoints",
"playground": "Playground",
"agents": "Agentes"
"agents": "Agentes",
"cliToolsShort": "Ferramentas"
},
"themesPage": {
"title": "Themes",

View File

@@ -102,7 +102,8 @@
"themeOrange": "Laranja",
"themeCyan": "Ciano",
"playground": "Playground",
"agents": "Agentes"
"agents": "Agentes",
"cliToolsShort": "Ferramentas"
},
"themesPage": {
"title": "Themes",

View File

@@ -102,7 +102,8 @@
"themeCyan": "Cyan",
"endpoints": "Puncte finale",
"playground": "Playground",
"agents": "Agenți"
"agents": "Agenți",
"cliToolsShort": "Instrumente"
},
"themesPage": {
"title": "Themes",

View File

@@ -102,7 +102,8 @@
"themeCyan": "Голубой",
"endpoints": "Конечные точки",
"playground": "Площадка",
"agents": "Агенты"
"agents": "Агенты",
"cliToolsShort": "Инструменты"
},
"themesPage": {
"title": "Темы",

View File

@@ -102,7 +102,8 @@
"themeCyan": "Cyan",
"endpoints": "Koncové body",
"playground": "Ihrisko",
"agents": "Agenti"
"agents": "Agenti",
"cliToolsShort": "Nástroje"
},
"themesPage": {
"title": "Themes",

View File

@@ -102,7 +102,8 @@
"themeCyan": "Cyan",
"endpoints": "Ändpunkter",
"playground": "Lekplats",
"agents": "Agenter"
"agents": "Agenter",
"cliToolsShort": "Verktyg"
},
"themesPage": {
"title": "Themes",

View File

@@ -102,7 +102,8 @@
"themeCyan": "Cyan",
"endpoints": "จุดปลายทาง",
"playground": "Playground",
"agents": "เอเจนต์"
"agents": "เอเจนต์",
"cliToolsShort": "เครื่องมือ"
},
"themesPage": {
"title": "ธีมส์",

View File

@@ -102,7 +102,8 @@
"themeCyan": "Cyan",
"endpoints": "Кінцеві точки",
"playground": "Playground",
"agents": "Агенти"
"agents": "Агенти",
"cliToolsShort": "Інструменти"
},
"themesPage": {
"title": "Themes",

View File

@@ -102,7 +102,8 @@
"themeCyan": "Cyan",
"endpoints": "Điểm cuối",
"playground": "Playground",
"agents": "Tác nhân"
"agents": "Tác nhân",
"cliToolsShort": "Công cụ"
},
"themesPage": {
"title": "Themes",

View File

@@ -102,7 +102,8 @@
"themeCyan": "Cyan",
"endpoints": "端点",
"playground": "模型试验场",
"agents": "代理"
"agents": "代理",
"cliToolsShort": "工具"
},
"themesPage": {
"title": "Themes",

View File

@@ -18,22 +18,27 @@ const navItemDefs = [
{ href: "/dashboard/api-manager", i18nKey: "apiManager", icon: "vpn_key" },
{ href: "/dashboard/providers", i18nKey: "providers", icon: "dns" },
{ href: "/dashboard/combos", i18nKey: "combos", icon: "layers" },
{ href: "/dashboard/logs", i18nKey: "logs", icon: "description" },
{ href: "/dashboard/costs", i18nKey: "costs", icon: "account_balance_wallet" },
{ href: "/dashboard/analytics", i18nKey: "analytics", icon: "analytics" },
{ href: "/dashboard/limits", i18nKey: "limits", icon: "tune" },
{ href: "/dashboard/health", i18nKey: "health", icon: "health_and_safety" },
{ href: "/dashboard/cli-tools", i18nKey: "cliTools", icon: "terminal" },
{ href: "/dashboard/media", i18nKey: "media", icon: "auto_awesome" },
];
const cliItemDefs = [
{ href: "/dashboard/cli-tools", i18nKey: "cliToolsShort", icon: "terminal" },
{ href: "/dashboard/agents", i18nKey: "agents", icon: "smart_toy" },
];
const debugItemDefs = [
{ href: "/dashboard/translator", i18nKey: "translator", icon: "translate" },
{ href: "/dashboard/playground", i18nKey: "playground", icon: "science" },
{ href: "/dashboard/agents", i18nKey: "agents", icon: "smart_toy" },
{ href: "/dashboard/media", i18nKey: "media", icon: "auto_awesome" },
];
const systemItemDefs = [{ href: "/dashboard/settings", i18nKey: "settings", icon: "settings" }];
const systemItemDefs = [
{ href: "/dashboard/health", i18nKey: "health", icon: "health_and_safety" },
{ href: "/dashboard/logs", i18nKey: "logs", icon: "description" },
{ href: "/dashboard/settings", i18nKey: "settings", icon: "settings" },
];
const helpItemDefs = [
{ href: "/docs", i18nKey: "docs", icon: "menu_book" },
@@ -110,6 +115,7 @@ export default function Sidebar({
// Resolve i18n keys → labels
const resolveItems = (defs) => defs.map((d) => ({ ...d, label: t(d.i18nKey) }));
const navItems = resolveItems(navItemDefs);
const cliItems = resolveItems(cliItemDefs);
const debugItems = resolveItems(debugItemDefs);
const systemItems = resolveItems(systemItemDefs);
const helpItems = resolveItems(helpItemDefs);
@@ -238,6 +244,17 @@ export default function Sidebar({
>
{navItems.map(renderNavLink)}
{/* CLI section */}
<div className="pt-4 mt-2">
{!collapsed && (
<p className="px-4 text-xs font-semibold text-text-muted/60 uppercase tracking-wider mb-2">
CLI
</p>
)}
{collapsed && <div className="border-t border-black/5 dark:border-white/5 mb-2" />}
{cliItems.map(renderNavLink)}
</div>
{/* Debug section */}
{showDebug && (
<div className="pt-4 mt-2">