feat(topology): add click navigation to provider page and filter inactive providers (#9024)

Validated in local merge-train (devbox-vm-06-dev002) @ combined-tip (FAST gates green: static + changed tests + vitest — only pre-existing audit.test.ts flake). Evidence: /home/diegosouzapw/dev/proxys/OmniRoute/.claude/worktrees/merge-train-20260805-213228-suite.log
This commit is contained in:
everson-junior
2026-08-05 21:42:44 -03:00
committed by GitHub
parent 2cfd672f79
commit 9971dbd51a
3 changed files with 76 additions and 6 deletions

View File

@@ -498,6 +498,12 @@ export default function HomePageClient({ machineId }: HomePageClientProps) {
const canonicalProviderId = normalizeProviderId(rawProviderId);
if (!canonicalProviderId || byProvider.has(canonicalProviderId)) return;
// Exclude providers with no active connections (or where all connections are deactivated)
const hasActiveConn = providerConnections.some(
(c) => normalizeProviderId(c.provider) === canonicalProviderId && c.isActive !== false
);
if (!hasActiveConn) return;
const resolvedName =
getProviderDisplayLabel(rawProviderId, providerNodes) ||
name ||
@@ -515,10 +521,11 @@ export default function HomePageClient({ machineId }: HomePageClientProps) {
providerStats
.filter((provider) => provider.total > 0)
.forEach((provider) => addProvider(provider.id, provider.provider.name));
providerConnections.forEach((conn) => addProvider(conn.provider));
Object.keys(providerMetrics).forEach((provider) => addProvider(provider));
return Array.from(byProvider.values());
}, [providerStats, providerMetrics, providerNodes]);
}, [providerStats, providerMetrics, providerNodes, providerConnections]);
const { lastProvider, errorProvider } = providerTopology;

View File

@@ -1,6 +1,7 @@
"use client";
import { useMemo } from "react";
import { useMemo, useCallback } from "react";
import { useRouter } from "next/navigation";
import { useTranslations } from "next-intl";
import { Handle, Position, type Node, type Edge, type NodeTypes } from "@xyflow/react";
import { AI_PROVIDERS } from "@/shared/constants/providers";
@@ -58,7 +59,7 @@ function ProviderNode({ data }: { data: ProviderNodeData }) {
return (
<div
className="flex items-center gap-2 px-2.5 py-1.5 rounded-lg border-2 transition-all duration-300 bg-bg"
className="flex items-center gap-2 px-2.5 py-1.5 rounded-lg border-2 transition-all duration-300 bg-bg cursor-pointer hover:scale-105 active:scale-95 shadow-sm hover:shadow-md"
style={{
borderColor: error ? RED : active ? color : healthy ? GREEN : "var(--color-border)",
boxShadow: error
@@ -216,9 +217,7 @@ function buildLayout(
return 4;
};
const d = rank(a) - rank(b);
return d !== 0
? d
: a.provider.toLowerCase().localeCompare(b.provider.toLowerCase()); // ASCII kasıtlı
return d !== 0 ? d : a.provider.toLowerCase().localeCompare(b.provider.toLowerCase()); // ASCII kasıtlı
});
let provIdx = 0;
@@ -330,6 +329,21 @@ export default function ProviderTopology({
[providers]
);
const router = useRouter();
const handleNodeClick = useCallback(
(_event: React.MouseEvent, node: Node) => {
if (node.type !== "provider") return;
const providerId =
(node.data as ProviderNodeData | undefined)?.providerId ||
node.id.replace(/^provider-/, "");
if (providerId) {
router.push(`/dashboard/providers/${providerId}`);
}
},
[router]
);
const containerClass =
"h-[300px] w-full min-w-0 rounded-xl border border-border bg-bg-subtle/20 overflow-hidden sm:h-[420px]";
@@ -351,6 +365,7 @@ export default function ProviderTopology({
nodeTypes={nodeTypes}
fitKey={providersKey}
className={containerClass}
onNodeClick={handleNodeClick}
/>
);
}