mirror of
https://github.com/diegosouzapw/OmniRoute.git
synced 2026-09-14 02:42:24 +03:00
fix(dashboard): pass nodeMap into Runtime QuotaGroup (#12868)
Validado numa worktree combinada com as 16 PRs desta leva sobre `release/v3.8.51`: typecheck:core limpo, check-file-size e check-changelog-integrity OK, complexity 2788/3218 e cognitive 1261/1437 (ambos sob a baseline), ESLint 0 erros nos 152 arquivos alterados, 771 testes unitários focados, 49 de integração e a suíte vitest:ui completa (2149) verdes. O `nodeMap` lido do closure de `RuntimePageClient` por uma função de nível de módulo é uma bomba-relógio silenciosa: só explode quando um monitor entra em error/exhausted/alerting, e o teste existente só alimentava listas vazias. Tirar o arquivo do exclude do vitest vale tanto quanto o fix — confirmei aqui que `tests/unit/ui/runtime-page-client.test.tsx` agora roda na `test:vitest:ui` e passa. A anotação sobre o "内部服务器错误" ser o catálogo RSC da página, e não o crash, poupou o próximo a caçar fantasma.
This commit is contained in:
1
changelog.d/fixes/12868-runtime-quotagroup-nodemap.md
Normal file
1
changelog.d/fixes/12868-runtime-quotagroup-nodemap.md
Normal file
@@ -0,0 +1 @@
|
||||
- **fix(dashboard):** pass `nodeMap` into Runtime `QuotaGroup` so a quota monitor in error/exhausted/alerting no longer throws `ReferenceError: nodeMap is not defined`. ([#12868](https://github.com/diegosouzapw/OmniRoute/pull/12868))
|
||||
@@ -1,4 +1,5 @@
|
||||
{
|
||||
"_rebaseline_2026_09_06_runtime_quotagroup_nodemap": "Own growth: src/app/(dashboard)/dashboard/runtime/RuntimePageClient.tsx 1201->1222 (+21, check-file-size split-newline). QuotaGroup is a module-level sibling and was reading nodeMap from RuntimePageClient's closure; that identifier is not in scope, so a quota monitor with status error/exhausted/alerting throws ReferenceError. Fix threads nodeMap as a prop (3 call sites + parameter + ProviderNodeEntry import). Prettier wraps the long import and the three QuotaGroup JSX tags. Covered by tests/unit/ui/runtime-page-client.test.tsx (empty monitors stay green; error+exhausted fixtures mount QuotaGroup).",
|
||||
"_rebaseline_2026_09_03_reset_aware_model_family": "Own growth: open-sse/services/combo.ts 4036->4041 (+5). buildAutoCandidates now keys the reset-aware quota cache by getQuotaFetchScope and spreads requestedModel onto the connection so Gemini windows stay off a Claude-empty Antigravity account. Irreducible wiring at the existing fetchResetAwareQuotaWithCache call site; the family helper itself lives in antigravityQuotaFamily.ts. Covered by tests/unit/reset-aware-request-scope-12600.test.ts.",
|
||||
"_rebaseline_2026_09_03_overloaded_not_provider_breaker": "fix/overloaded-not-provider-breaker own growth: open-sse/services/combo.ts 4036->4075 (check-file-size split-newline, +39). Circuit-open pre-skip now records the breaker retryAfter and, when every target was skipped that way, waits the short reset via resolveCircuitOpenWaitDecision (new leaf in comboCooldownRetry.ts) instead of crystallizing ALL_TARGETS_SKIPPED in ~43ms. skippedForCircuitOpen / earliestCircuitOpenRetryMs reset each setTry so a later iteration cannot inherit a stale retryAfter. Irreducible at the existing ALL_TARGETS_SKIPPED chokepoint (same pattern as #7301/#8213 cooldown-wait). Predicate itself lives in circuitBreaker.ts / comboPredicates.ts / chatPredicates.ts, all under cap. Covered by tests/unit/overloaded-not-provider-breaker.test.ts + combo-cooldown-retry.test.ts.",
|
||||
"_rebaseline_2026_09_03_12649_free_tier_reaudit_gateways": "PR #12649 (fix/free-tier-quota-reaudit) own growth: src/shared/constants/providers/apikey/gateways.ts 1459->1462 (+3 = the nara authHint rewritten for the re-audited 7M/day plan now wraps to two lines, plus the Prettier reflow of two pre-existing >100-col authHint lines (oneminai, freebuff) that lint-staged enforces on any touch of the file; additive text at the existing registry chokepoint, same god-file no-split rationale as prior gateways.ts rebaselines: #11786 seekai, #10987 logfare, #10531 freebuff). Covered by tests/unit/free-tier-reaudit-2026-09.test.ts and tests/unit/free-providers-batch-2026-07.test.ts.",
|
||||
@@ -439,7 +440,7 @@
|
||||
"src/app/(dashboard)/dashboard/endpoint/EndpointPageClient.tsx": 2491,
|
||||
"src/app/(dashboard)/dashboard/providers/[id]/components/modals/EditConnectionModal.tsx": 1631,
|
||||
"src/app/(dashboard)/dashboard/providers/page.tsx": 2025,
|
||||
"src/app/(dashboard)/dashboard/runtime/RuntimePageClient.tsx": 1201,
|
||||
"src/app/(dashboard)/dashboard/runtime/RuntimePageClient.tsx": 1222,
|
||||
"src/app/(dashboard)/dashboard/settings/components/ProxyRegistryManager.tsx": 1475,
|
||||
"src/app/(dashboard)/dashboard/settings/components/ResilienceTab.tsx": 1271,
|
||||
"src/app/(dashboard)/dashboard/settings/components/RoutingTab.tsx": 1607,
|
||||
@@ -575,7 +576,7 @@
|
||||
"src/app/(dashboard)/dashboard/health/page.tsx": "1165",
|
||||
"src/app/(dashboard)/dashboard/providers/[id]/components/modals/EditConnectionModal.tsx": "1324",
|
||||
"src/app/(dashboard)/dashboard/providers/page.tsx": "1944",
|
||||
"src/app/(dashboard)/dashboard/runtime/RuntimePageClient.tsx": "1201",
|
||||
"src/app/(dashboard)/dashboard/runtime/RuntimePageClient.tsx": "1222",
|
||||
"src/app/(dashboard)/dashboard/settings/components/PricingTab.tsx": "1019",
|
||||
"src/app/(dashboard)/dashboard/settings/components/ProxyRegistryManager.tsx": "1470",
|
||||
"src/app/(dashboard)/dashboard/settings/components/ResilienceTab.tsx": "1123",
|
||||
|
||||
@@ -6,7 +6,11 @@ import Link from "next/link";
|
||||
import Card from "@/shared/components/Card";
|
||||
import ProviderIcon from "@/shared/components/ProviderIcon";
|
||||
import ModelCooldownsCard from "./components/ModelCooldownsCard";
|
||||
import { useProviderNodeMap, resolveProviderName } from "@/lib/display/useProviderNodeMap";
|
||||
import {
|
||||
useProviderNodeMap,
|
||||
resolveProviderName,
|
||||
type ProviderNodeEntry,
|
||||
} from "@/lib/display/useProviderNodeMap";
|
||||
|
||||
type KnownBreakerState = "CLOSED" | "OPEN" | "HALF_OPEN" | "DEGRADED";
|
||||
type BreakerState = KnownBreakerState | (string & {});
|
||||
@@ -1002,13 +1006,28 @@ export default function RuntimePageClient() {
|
||||
return (
|
||||
<div className="mt-2 flex flex-col gap-3">
|
||||
{exhausted.length > 0 && (
|
||||
<QuotaGroup tone="red" label={t("statusExhausted")} items={exhausted} />
|
||||
<QuotaGroup
|
||||
tone="red"
|
||||
label={t("statusExhausted")}
|
||||
items={exhausted}
|
||||
nodeMap={nodeMap}
|
||||
/>
|
||||
)}
|
||||
{alerting.length > 0 && (
|
||||
<QuotaGroup tone="amber" label={t("statusAlerting")} items={alerting} />
|
||||
<QuotaGroup
|
||||
tone="amber"
|
||||
label={t("statusAlerting")}
|
||||
items={alerting}
|
||||
nodeMap={nodeMap}
|
||||
/>
|
||||
)}
|
||||
{errors.length > 0 && (
|
||||
<QuotaGroup tone="orange" label={t("statusError")} items={errors} />
|
||||
<QuotaGroup
|
||||
tone="orange"
|
||||
label={t("statusError")}
|
||||
items={errors}
|
||||
nodeMap={nodeMap}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
@@ -1148,10 +1167,12 @@ function QuotaGroup({
|
||||
tone,
|
||||
label,
|
||||
items,
|
||||
nodeMap,
|
||||
}: {
|
||||
tone: "red" | "amber" | "orange";
|
||||
label: string;
|
||||
items: QuotaMonitor[];
|
||||
nodeMap: Map<string, ProviderNodeEntry>;
|
||||
}) {
|
||||
const t = useTranslations("runtime");
|
||||
const toneMap = {
|
||||
|
||||
@@ -133,6 +133,9 @@ const runtimePageClientMessages: Record<string, string> = {
|
||||
openQuota: "Open Quota",
|
||||
allQuotasHealthy: "All quotas healthy",
|
||||
moreSuffix: "+{count} more",
|
||||
statusExhausted: "EXHAUSTED",
|
||||
statusAlerting: "ALERTING",
|
||||
statusError: "ERROR",
|
||||
};
|
||||
|
||||
function runtimePageClientTranslate(key: string, values?: Record<string, unknown>): string {
|
||||
@@ -196,6 +199,63 @@ describe("RuntimePageClient", () => {
|
||||
await waitForText("future-provider");
|
||||
await waitForText("DEG");
|
||||
await waitForText("UNK");
|
||||
await waitForText("All quotas healthy");
|
||||
expect(document.body.textContent).not.toContain("Internal Server Error");
|
||||
});
|
||||
|
||||
it("renders error and exhausted quota monitors without a nodeMap ReferenceError", async () => {
|
||||
fetchMock.mockImplementation((input: RequestInfo | URL) => {
|
||||
const path = getRequestPath(input);
|
||||
if (path === "/api/monitoring/health") {
|
||||
return Promise.resolve(
|
||||
jsonResponse({
|
||||
providerBreakers: [],
|
||||
lockouts: {},
|
||||
sessions: { activeCount: 0, stickyBoundCount: 0, byApiKey: {}, top: [] },
|
||||
quotaMonitor: {
|
||||
active: 2,
|
||||
alerting: 0,
|
||||
exhausted: 1,
|
||||
errors: 1,
|
||||
monitors: [
|
||||
{
|
||||
accountId: "acct-error",
|
||||
provider: "agy",
|
||||
window: "5h",
|
||||
status: "error",
|
||||
remainingPercent: 3,
|
||||
},
|
||||
{
|
||||
accountId: "acct-exhausted",
|
||||
provider: "volcengine-coding-plan",
|
||||
window: "5h",
|
||||
status: "exhausted",
|
||||
remainingPercent: 0,
|
||||
},
|
||||
],
|
||||
},
|
||||
})
|
||||
);
|
||||
}
|
||||
if (path === "/api/providers/client") {
|
||||
return Promise.resolve(jsonResponse({ connections: [] }));
|
||||
}
|
||||
if (path === "/api/provider-nodes") {
|
||||
return Promise.resolve(jsonResponse({ nodes: [] }));
|
||||
}
|
||||
return Promise.resolve(jsonResponse({}));
|
||||
});
|
||||
|
||||
await renderRuntimePage();
|
||||
|
||||
await waitForText("Runtime");
|
||||
await waitForText("acct-error");
|
||||
await waitForText("acct-exhausted");
|
||||
await waitForText("ERROR");
|
||||
await waitForText("EXHAUSTED");
|
||||
expect(document.body.textContent).toContain("agy");
|
||||
expect(document.body.textContent).toContain("volcengine-coding-plan");
|
||||
expect(document.body.textContent).not.toContain("Internal Server Error");
|
||||
expect(document.body.textContent).not.toContain("All quotas healthy");
|
||||
});
|
||||
});
|
||||
|
||||
@@ -58,7 +58,6 @@ export default defineConfig({
|
||||
"tests/unit/ui/livePage.test.tsx", // #8618 — pre-existing failure; remove this exclusion when fixed
|
||||
"tests/unit/ui/diffPane.test.tsx", // #8618 — pre-existing failure; remove this exclusion when fixed
|
||||
"tests/unit/ui/CliCodePage.test.tsx", // #8618 — pre-existing failure; remove this exclusion when fixed
|
||||
"tests/unit/ui/runtime-page-client.test.tsx", // #8618 — pre-existing failure; remove this exclusion when fixed
|
||||
"tests/unit/ui/engineConfigPage.test.tsx", // #8618 — pre-existing failure; remove this exclusion when fixed
|
||||
"open-sse/services/autoCombo/__tests__/autoCombo.test.ts", // #8618 — pre-existing failure; remove this exclusion when fixed
|
||||
"tests/unit/ui/agent-card-risk-modal.test.tsx", // #8618 — pre-existing failure; remove this exclusion when fixed
|
||||
|
||||
Reference in New Issue
Block a user