mirror of
https://github.com/diegosouzapw/OmniRoute.git
synced 2026-08-05 06:42:12 +03:00
Integrated into release/v3.8.41 (restore active/total model badge, #5264). Test: modelVisibilityToolbarActiveCount green (2/2).
This commit is contained in:
committed by
GitHub
parent
48983fcf31
commit
619e925002
@@ -10,7 +10,7 @@ _In development — bullets added per PR; finalized at release._
|
||||
|
||||
### 🔧 Bug Fixes
|
||||
|
||||
- **cli:** `omniroute serve` no longer discards a user-set `NODE_OPTIONS=--max-old-space-size=…`. It used to unconditionally overwrite `NODE_OPTIONS` (and pass an explicit CLI arg) with the calibrated default, so a user who exported `--max-old-space-size=8192` still OOM`'`d at the old cap. Now mirrors the Electron/standalone launchers: a user-pinned heap flag wins (duplicate CLI arg suppressed); otherwise the calibrated value is appended, preserving unrelated flags. Regression guard: `tests/unit/serve-node-options-preserve-5238.test.ts`. (Defect C of [#5238](https://github.com/diegosouzapw/OmniRoute/issues/5238))
|
||||
- **dashboard:** restore the `{active}/{total} active` model-count badge in a provider's **Available Models** toolbar. It was dropped during the v3.8.13 god-file decomposition (#3327) — `ModelVisibilityToolbar` still received the counts but they were orphaned as unused `_`-prefixed params and the rendering `<span>` was never carried over. Re-wired the existing props to the still-present `modelsActiveCount` key. Regression guard: `modelVisibilityToolbarActiveCount.test.tsx`. ([#5264](https://github.com/diegosouzapw/OmniRoute/issues/5264))
|
||||
- **authz/dashboard:** stop rejecting every dashboard mutation with `403 INVALID_ORIGIN` when the dashboard is reached over a LAN IP / non-localhost host. The origin-pinning check (#5278) only accepted the configured `*_PUBLIC_BASE_URL` (typically `http://localhost:20128`) plus the internal `request.url` origin — which Next.js standalone reports as the bind host, not the real `Host`. So opening the dashboard at e.g. `http://192.168.0.15:20128` made the browser's same-origin `Origin` match no candidate, and **every** POST/PUT/DELETE (save API key, save provider, test connection) failed while GETs still worked. Two fixes: **(a)** the request `Host` (or a trusted `X-Forwarded-Host`) is now accepted as a valid mutation origin, gated by two independent checks — the token-stamped socket peer must be loopback/private-LAN **and** the Host itself must be a loopback/private-LAN IP literal, so a DNS-rebinding domain (which classifies as `remote`) can never become a trusted origin and the protocol is pinned to the actual connection; **(b)** the `INVALID_ORIGIN` response now carries an actionable message (set `OMNIROUTE_PUBLIC_BASE_URL`) and the dashboard surfaces API error `.message` via a shared `extractApiErrorMessage` helper instead of rendering the raw error object. Regression guards: `tests/unit/authz/public-origin.test.ts` (direct LAN/loopback + DNS-rebinding defense), `tests/unit/api-error-message-5340.test.ts`. ([#5340](https://github.com/diegosouzapw/OmniRoute/issues/5340))
|
||||
|
||||
---
|
||||
|
||||
@@ -0,0 +1,82 @@
|
||||
// @vitest-environment jsdom
|
||||
//
|
||||
// Regression test for issue #5264 — the "{active}/{total} active" model-count
|
||||
// badge in the provider DETAIL page's "Available Models" toolbar.
|
||||
//
|
||||
// During the god-file decomposition in v3.8.13 (commit a25d5f1ef / PR #3327),
|
||||
// ModelVisibilityToolbar kept receiving `activeCount`/`totalCount` props but the
|
||||
// <span> that rendered them was never carried over. The props were left
|
||||
// orphaned (destructured as `_activeCount`/`_totalCount` to silence lint), so
|
||||
// the count badge silently disappeared from the toolbar.
|
||||
//
|
||||
// This test renders the toolbar with activeCount={3} totalCount={5} and asserts
|
||||
// the rendered output surfaces the `modelsActiveCount` interpolation ("3/5
|
||||
// active"). It fails against the pre-fix component (props unused → nothing
|
||||
// rendered) and passes once the badge is restored.
|
||||
|
||||
import React, { act } from "react";
|
||||
import { createRoot } from "react-dom/client";
|
||||
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
|
||||
|
||||
import {
|
||||
ModelVisibilityToolbar,
|
||||
type ModelVisibilityToolbarProps,
|
||||
} from "../components/ModelRow";
|
||||
|
||||
// Minimal translator stub: no `has`, so providerText() falls back to
|
||||
// interpolating the values into the fallback string ("{active}/{total} active").
|
||||
const t = ((key: string) => key) as ModelVisibilityToolbarProps["t"];
|
||||
|
||||
function buildProps(
|
||||
overrides: Partial<ModelVisibilityToolbarProps>
|
||||
): ModelVisibilityToolbarProps {
|
||||
return {
|
||||
t,
|
||||
filterValue: "",
|
||||
onFilterChange: vi.fn(),
|
||||
activeCount: 0,
|
||||
totalCount: 0,
|
||||
onSelectAll: vi.fn(),
|
||||
onDeselectAll: vi.fn(),
|
||||
...overrides,
|
||||
};
|
||||
}
|
||||
|
||||
const roots: Array<{ root: ReturnType<typeof createRoot>; el: HTMLDivElement }> = [];
|
||||
|
||||
function render(props: ModelVisibilityToolbarProps): HTMLDivElement {
|
||||
const el = document.createElement("div");
|
||||
document.body.appendChild(el);
|
||||
const root = createRoot(el);
|
||||
act(() => {
|
||||
root.render(<ModelVisibilityToolbar {...props} />);
|
||||
});
|
||||
roots.push({ root, el });
|
||||
return el;
|
||||
}
|
||||
|
||||
beforeEach(() => {
|
||||
(
|
||||
globalThis as typeof globalThis & { IS_REACT_ACT_ENVIRONMENT?: boolean }
|
||||
).IS_REACT_ACT_ENVIRONMENT = true;
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
for (const { root, el } of roots.splice(0)) {
|
||||
act(() => root.unmount());
|
||||
el.remove();
|
||||
}
|
||||
vi.clearAllMocks();
|
||||
});
|
||||
|
||||
describe("ModelVisibilityToolbar active/total count badge (#5264)", () => {
|
||||
it("renders the {active}/{total} active count", () => {
|
||||
const el = render(buildProps({ activeCount: 3, totalCount: 5 }));
|
||||
expect(el.textContent).toContain("3/5 active");
|
||||
});
|
||||
|
||||
it("reflects updated counts", () => {
|
||||
const el = render(buildProps({ activeCount: 7, totalCount: 12 }));
|
||||
expect(el.textContent).toContain("7/12 active");
|
||||
});
|
||||
});
|
||||
@@ -103,8 +103,8 @@ export function ModelVisibilityToolbar({
|
||||
t,
|
||||
filterValue,
|
||||
onFilterChange,
|
||||
activeCount: _activeCount,
|
||||
totalCount: _totalCount,
|
||||
activeCount,
|
||||
totalCount,
|
||||
onSelectAll,
|
||||
onDeselectAll,
|
||||
selectAllDisabled,
|
||||
@@ -245,6 +245,12 @@ export function ModelVisibilityToolbar({
|
||||
<span className="material-symbols-outlined text-[16px]">visibility_off</span>
|
||||
<span>{providerText(t, "hideAllModels", "Hide all")}</span>
|
||||
</button>
|
||||
<span className="whitespace-nowrap text-xs text-text-muted">
|
||||
{providerText(t, "modelsActiveCount", "{active}/{total} active", {
|
||||
active: activeCount,
|
||||
total: totalCount,
|
||||
})}
|
||||
</span>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user