revert(ui): return Models + Endpoints tabs to in-memory, keep the ?model drill-in (#34629)

Per-tab path routing for Models made each tab a separate route parsed out
of the pathname, which is fragile under a static export mounted at a
runtime-variable server-root prefix. Revert the tabs to in-memory state:
a single /models-and-endpoints route renders an antd Tabs whose active
tab is React state, and each tab body moves from its own page.tsx into a
non-routed panel component under panels/. Role-gating (which tabs show),
the refresh control and the header are unchanged.

The ?model= / ?team= query drill-in stays: it is query-param based (read
via useSearchParams, written via history.pushState), so it is unaffected
by the server-root prefix and remains shareable. The shared tab-routing
helpers (createTabRoutes / useTabRouting) are untouched; the other four
pages still use them.

Removes the per-tab route dirs, layout.tsx and tabRoutes.ts (+ their
path-routing tests) and replaces the layout's coverage with a page test
for in-memory tab switching, the drill-in overlays and role-gating.
This commit is contained in:
ryan-crabbe-berri 2026-07-25 09:53:13 -07:00 • committed by GitHub
parent 21de59b32e
commit 57894b5b5e
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
16 changed files with 322 additions and 364 deletions

View file

@ -1061,7 +1061,17 @@
"count": 1
}
},
"src/app/(dashboard)/models-and-endpoints/layout.tsx": {
"src/app/(dashboard)/models-and-endpoints/page.tsx": {
"no-restricted-imports": {
"count": 1
}
},
"src/app/(dashboard)/models-and-endpoints/panels/AddModelPanel.tsx": {
"no-restricted-imports": {
"count": 1
}
},
"src/app/(dashboard)/models-and-endpoints/panels/LlmCredentialsPanel.tsx": {
"no-restricted-imports": {
"count": 1
}
@ -4340,4 +4350,4 @@
"count": 1
}
}
}
}

View file

@ -1,126 +0,0 @@
/* @vitest-environment jsdom */
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
import { act, render } from "@testing-library/react";
import { beforeEach, describe, expect, it, vi } from "vitest";
import ModelsAndEndpointsLayout from "./layout";
const { mockPush, mockReplace, navState } = vi.hoisted(() => ({
mockPush: vi.fn(),
mockReplace: vi.fn(),
navState: { pathname: "/models-and-endpoints", search: "" },
}));
vi.mock("next/navigation", () => ({
usePathname: () => navState.pathname,
useRouter: () => ({ push: mockPush, replace: mockReplace }),
useSearchParams: () => new URLSearchParams(navState.search),
}));
vi.mock("@/components/networking", () => ({ serverRootPath: "" }));
vi.mock("@/components/molecules/cost_optimization_feedback_banner", () => ({ default: () => null }));
vi.mock("@/components/model_info_view", () => ({
default: ({ modelId }: { modelId: string }) => <div data-testid="model-info">model:{modelId}</div>,
}));
vi.mock("@/components/team/TeamInfo", () => ({
default: ({ teamId }: { teamId: string }) => <div data-testid="team-info">team:{teamId}</div>,
}));
const mockUseAuthorized = vi.fn();
vi.mock("@/app/(dashboard)/hooks/useAuthorized", () => ({ default: () => mockUseAuthorized() }));
vi.mock("@/app/(dashboard)/hooks/teams/useTeams", () => ({ useTeams: () => ({ data: [] }) }));
vi.mock("@/app/(dashboard)/hooks/uiSettings/useUISettings", () => ({
useUISettings: () => ({ data: { values: {} } }),
}));
vi.mock("@/app/(dashboard)/models-and-endpoints/useModelDashboardData", () => ({
useModelDashboardData: () => ({
availableModelGroups: [],
availableModelAccessGroups: [],
allModelsOnProxy: [],
isLoading: false,
}),
}));
const renderLayout = () => {
const queryClient = new QueryClient({ defaultOptions: { queries: { retry: false, gcTime: 0 } } });
return render(
<QueryClientProvider client={queryClient}>
<ModelsAndEndpointsLayout>
<div data-testid="tab-content">CHILD</div>
</ModelsAndEndpointsLayout>
</QueryClientProvider>,
);
};
describe("ModelsAndEndpointsLayout", () => {
beforeEach(() => {
navState.pathname = "/models-and-endpoints";
navState.search = "";
mockPush.mockClear();
mockReplace.mockClear();
mockUseAuthorized.mockReturnValue({
accessToken: "123",
token: "123",
userRole: "Admin",
userId: "123",
premiumUser: false,
});
// eslint-disable-next-line @typescript-eslint/no-explicit-any
(global as any).ResizeObserver = class {
observe() {}
unobserve() {}
disconnect() {}
};
});
it("renders the admin tab bar and the active tab's page content", () => {
const { getByRole, getByTestId } = renderLayout();
expect(getByRole("tab", { name: "LLM Credentials" })).toBeInTheDocument();
expect(getByRole("tab", { name: "Health Status" })).toBeInTheDocument();
expect(getByTestId("tab-content")).toHaveTextContent("CHILD");
});
it("navigates to a tab's path when its tab is clicked", async () => {
const { getByRole } = renderLayout();
await act(async () => {
getByRole("tab", { name: "Health Status" }).click();
});
expect(mockPush).toHaveBeenCalledWith(expect.stringMatching(/\/models-and-endpoints\/health\/$/));
});
it("redirects to the base models path when the tab path is not permitted for the role", async () => {
const replaceMock = vi.fn();
const originalLocation = window.location;
Object.defineProperty(window, "location", {
configurable: true,
value: { replace: replaceMock, assign: vi.fn(), href: "http://localhost/", pathname: "/", search: "" },
});
mockUseAuthorized.mockReturnValue({
accessToken: "123",
token: "123",
userRole: "Internal User",
userId: "123",
premiumUser: false,
});
navState.pathname = "/models-and-endpoints/llm-credentials";
await act(async () => {
renderLayout();
});
expect(replaceMock).toHaveBeenCalledWith(expect.stringMatching(/\/models-and-endpoints\/$/));
Object.defineProperty(window, "location", { configurable: true, value: originalLocation });
});
it("renders the model detail overlay from ?model and hides the tabs and page content", () => {
navState.search = "model=abc-123";
const { getByTestId, queryByTestId, queryByRole } = renderLayout();
expect(getByTestId("model-info")).toHaveTextContent("model:abc-123");
expect(queryByTestId("tab-content")).toBeNull();
expect(queryByRole("tab", { name: "Health Status" })).toBeNull();
});
it("renders the team detail overlay from ?team", () => {
navState.search = "team=team-9";
const { getByTestId, queryByTestId } = renderLayout();
expect(getByTestId("team-info")).toHaveTextContent("team:team-9");
expect(queryByTestId("tab-content")).toBeNull();
});
});

View file

@ -1,155 +0,0 @@
"use client";
import type { ReactNode } from "react";
import { useMemo, useState } from "react";
import { Tabs } from "antd";
import { RefreshIcon } from "@heroicons/react/outline";
import { useQueryClient } from "@tanstack/react-query";
import useAuthorized from "@/app/(dashboard)/hooks/useAuthorized";
import { useTeams } from "@/app/(dashboard)/hooks/teams/useTeams";
import { useUISettings } from "@/app/(dashboard)/hooks/uiSettings/useUISettings";
import { useTabRouting } from "@/app/(dashboard)/hooks/useTabRouting";
import { all_admin_roles, internalUserRoles, isProxyAdminRole, isUserTeamAdminForAnyTeam } from "@/utils/roles";
import CostOptimizationFeedbackBanner from "@/components/molecules/cost_optimization_feedback_banner";
import ModelInfoView from "@/components/model_info_view";
import TeamInfoView from "@/components/team/TeamInfo";
import { modelsRoutes, type ModelTabSlug } from "@/app/(dashboard)/models-and-endpoints/tabRoutes";
import { useModelDetailRouting } from "@/app/(dashboard)/models-and-endpoints/detailNavigation";
import { useModelDashboardData } from "@/app/(dashboard)/models-and-endpoints/useModelDashboardData";
const BASE_TAB_KEY = "all-models";
const TAB_LABELS: Record<ModelTabSlug, string> = {
add: "Add Model",
"llm-credentials": "LLM Credentials",
"pass-through": "Pass-Through Endpoints",
health: "Health Status",
"retry-settings": "Model Retry Settings",
"model-group-alias": "Model Group Alias",
"price-data": "Price Data Reload",
};
export default function ModelsAndEndpointsLayout({ children }: { children: ReactNode }) {
const { accessToken, userRole, userId: userID, premiumUser } = useAuthorized();
const { data: teams, isLoading: teamsLoading } = useTeams();
const { data: uiSettings, isLoading: uiSettingsLoading } = useUISettings();
const queryClient = useQueryClient();
const { modelId, teamId, close } = useModelDetailRouting();
const { availableModelAccessGroups, allModelsOnProxy } = useModelDashboardData();
const [lastRefreshed, setLastRefreshed] = useState("");
const isProxyAdmin = userRole && isProxyAdminRole(userRole);
const isInternalUser = userRole && internalUserRoles.includes(userRole);
const isUserTeamAdmin = userID && isUserTeamAdminForAnyTeam(teams ?? null, userID);
const addModelDisabledForInternalUsers =
isInternalUser && uiSettings?.values?.disable_model_add_for_internal_users === true;
const shouldHideAddModelTab = !isProxyAdmin && (addModelDisabledForInternalUsers || !isUserTeamAdmin);
const isAdmin = all_admin_roles.includes(userRole);
const visibleSlugs = useMemo<Array<"" | ModelTabSlug>>(
() => [
"",
...(shouldHideAddModelTab ? [] : (["add"] as const)),
...(isAdmin
? (["llm-credentials", "pass-through", "health", "retry-settings", "model-group-alias", "price-data"] as const)
: []),
],
[shouldHideAddModelTab, isAdmin],
);
const tabRoutingConfig = {
routes: modelsRoutes,
baseTabKey: BASE_TAB_KEY,
visibleKeys: visibleSlugs.filter(Boolean),
ready: !teamsLoading && !uiSettingsLoading,
};
const { activeKey, onTabChange } = useTabRouting(tabRoutingConfig);
const allModelsLabel = isAdmin ? "All Models" : "Your Models";
const tabItems = visibleSlugs.map((slug) => {
const key = slug || BASE_TAB_KEY;
return {
key,
label: slug ? TAB_LABELS[slug] : allModelsLabel,
children: key === activeKey ? children : null,
};
});
const handleRefreshClick = () => {
setLastRefreshed(new Date().toLocaleTimeString([], { hour: "2-digit", minute: "2-digit" }));
queryClient.invalidateQueries({ queryKey: ["models", "list"] });
};
const invalidateModels = () => queryClient.invalidateQueries({ queryKey: ["models", "list"] });
if (teamId) {
return (
<div className="w-full h-full">
<TeamInfoView
teamId={teamId}
onClose={close}
accessToken={accessToken}
is_team_admin={userRole === "Admin"}
is_proxy_admin={userRole === "Proxy Admin"}
userModels={allModelsOnProxy}
editTeam={false}
onUpdate={invalidateModels}
premiumUser={premiumUser}
/>
</div>
);
}
return (
<div className="mx-4 h-[75vh]">
<div className="flex flex-col gap-2 p-8 w-full mt-2">
<div className="flex justify-between items-center mb-4">
<div>
<h2 className="text-lg font-semibold">Model Management</h2>
{isAdmin ? (
<p className="text-sm text-gray-600">Add and manage models for the proxy</p>
) : (
<p className="text-sm text-gray-600">Add models for teams you are an admin for.</p>
)}
</div>
</div>
<CostOptimizationFeedbackBanner />
{modelId ? (
<ModelInfoView
modelId={modelId}
onClose={close}
accessToken={accessToken}
userID={userID}
userRole={userRole}
onModelUpdate={invalidateModels}
modelAccessGroups={availableModelAccessGroups}
/>
) : (
<Tabs
activeKey={activeKey}
onChange={onTabChange}
items={tabItems}
tabBarExtraContent={{
right: (
<div className="flex items-center space-x-2 self-center">
{lastRefreshed && <span className="text-xs text-gray-500">Last Refreshed: {lastRefreshed}</span>}
<button
type="button"
onClick={handleRefreshClick}
aria-label="Refresh models"
className="cursor-pointer"
>
<RefreshIcon className="h-4 w-4 text-gray-500" />
</button>
</div>
),
}}
/>
)}
</div>
</div>
);
}

View file

@ -0,0 +1,100 @@
/* @vitest-environment jsdom */
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
import { render } from "@testing-library/react";
import userEvent from "@testing-library/user-event";
import { beforeEach, describe, expect, it, vi } from "vitest";
import ModelsAndEndpointsPage from "./page";
vi.mock("./panels/AllModelsPanel", () => ({ default: () => <div data-testid="panel-all-models" /> }));
vi.mock("./panels/AddModelPanel", () => ({ default: () => <div data-testid="panel-add" /> }));
vi.mock("./panels/LlmCredentialsPanel", () => ({ default: () => <div data-testid="panel-credentials" /> }));
vi.mock("./panels/PassThroughPanel", () => ({ default: () => <div data-testid="panel-pass-through" /> }));
vi.mock("./panels/HealthStatusPanel", () => ({ default: () => <div data-testid="panel-health" /> }));
vi.mock("./panels/ModelRetrySettingsPanel", () => ({ default: () => <div data-testid="panel-retry" /> }));
vi.mock("./panels/ModelGroupAliasPanel", () => ({ default: () => <div data-testid="panel-alias" /> }));
vi.mock("./panels/PriceDataPanel", () => ({ default: () => <div data-testid="panel-price" /> }));
const detailState = { modelId: null as string | null, teamId: null as string | null };
vi.mock("./detailNavigation", () => ({
useModelDetailRouting: () => ({ ...detailState, close: vi.fn(), openModel: vi.fn(), openTeam: vi.fn() }),
}));
vi.mock("@/components/molecules/cost_optimization_feedback_banner", () => ({ default: () => null }));
vi.mock("@/components/model_info_view", () => ({
default: ({ modelId }: { modelId: string }) => <div data-testid="model-info">model:{modelId}</div>,
}));
vi.mock("@/components/team/TeamInfo", () => ({
default: ({ teamId }: { teamId: string }) => <div data-testid="team-info">team:{teamId}</div>,
}));
const mockUseAuthorized = vi.fn();
vi.mock("@/app/(dashboard)/hooks/useAuthorized", () => ({ default: () => mockUseAuthorized() }));
vi.mock("@/app/(dashboard)/hooks/teams/useTeams", () => ({ useTeams: () => ({ data: [] }) }));
vi.mock("@/app/(dashboard)/hooks/uiSettings/useUISettings", () => ({
useUISettings: () => ({ data: { values: {} } }),
}));
vi.mock("./useModelDashboardData", () => ({
useModelDashboardData: () => ({ availableModelAccessGroups: [], allModelsOnProxy: [], availableModelGroups: [] }),
}));
const ADMIN = { accessToken: "at", token: "t", userRole: "Admin", userId: "u1", premiumUser: false };
const NON_ADMIN = { accessToken: "at", token: "t", userRole: "Internal User", userId: "u1", premiumUser: false };
const renderPage = () => {
const queryClient = new QueryClient({ defaultOptions: { queries: { retry: false, gcTime: 0 } } });
return render(
<QueryClientProvider client={queryClient}>
<ModelsAndEndpointsPage />
</QueryClientProvider>,
);
};
describe("ModelsAndEndpointsPage", () => {
beforeEach(() => {
detailState.modelId = null;
detailState.teamId = null;
mockUseAuthorized.mockReturnValue(ADMIN);
// eslint-disable-next-line @typescript-eslint/no-explicit-any
(global as any).ResizeObserver = class {
observe() {}
unobserve() {}
disconnect() {}
};
});
it("renders the admin tab bar and the All Models panel by default", () => {
const { getByRole, getByTestId } = renderPage();
expect(getByRole("tab", { name: "All Models" })).toBeInTheDocument();
expect(getByRole("tab", { name: "LLM Credentials" })).toBeInTheDocument();
expect(getByRole("tab", { name: "Health Status" })).toBeInTheDocument();
expect(getByTestId("panel-all-models")).toBeInTheDocument();
});
it("switches tabs in-memory, mounting only the active panel", async () => {
const user = userEvent.setup();
const { getByRole, getByTestId, queryByTestId } = renderPage();
await user.click(getByRole("tab", { name: "Health Status" }));
expect(getByTestId("panel-health")).toBeInTheDocument();
expect(queryByTestId("panel-all-models")).toBeNull();
});
it("renders the model detail overlay from the ?model drill-in and hides the tabs", () => {
detailState.modelId = "abc-123";
const { getByTestId, queryByRole } = renderPage();
expect(getByTestId("model-info")).toHaveTextContent("model:abc-123");
expect(queryByRole("tab", { name: "All Models" })).toBeNull();
});
it("renders the team detail overlay from the ?team drill-in", () => {
detailState.teamId = "team-9";
const { getByTestId } = renderPage();
expect(getByTestId("team-info")).toHaveTextContent("team:team-9");
});
it("hides admin-only tabs for a non-admin user", () => {
mockUseAuthorized.mockReturnValue(NON_ADMIN);
const { queryByRole } = renderPage();
expect(queryByRole("tab", { name: "LLM Credentials" })).toBeNull();
expect(queryByRole("tab", { name: "Health Status" })).toBeNull();
});
});

View file

@ -1,23 +1,185 @@
"use client";
import { useState } from "react";
import AllModelsTab from "@/app/(dashboard)/models-and-endpoints/components/AllModelsTab";
import { useModelDashboardData } from "@/app/(dashboard)/models-and-endpoints/useModelDashboardData";
import { useMemo, useState } from "react";
import { Tabs } from "antd";
import { RefreshIcon } from "@heroicons/react/outline";
import { useQueryClient } from "@tanstack/react-query";
import useAuthorized from "@/app/(dashboard)/hooks/useAuthorized";
import { useTeams } from "@/app/(dashboard)/hooks/teams/useTeams";
import { useUISettings } from "@/app/(dashboard)/hooks/uiSettings/useUISettings";
import { all_admin_roles, internalUserRoles, isProxyAdminRole, isUserTeamAdminForAnyTeam } from "@/utils/roles";
import CostOptimizationFeedbackBanner from "@/components/molecules/cost_optimization_feedback_banner";
import ModelInfoView from "@/components/model_info_view";
import TeamInfoView from "@/components/team/TeamInfo";
import { useModelDetailRouting } from "@/app/(dashboard)/models-and-endpoints/detailNavigation";
import { useModelDashboardData } from "@/app/(dashboard)/models-and-endpoints/useModelDashboardData";
import AllModelsPanel from "@/app/(dashboard)/models-and-endpoints/panels/AllModelsPanel";
import AddModelPanel from "@/app/(dashboard)/models-and-endpoints/panels/AddModelPanel";
import LlmCredentialsPanel from "@/app/(dashboard)/models-and-endpoints/panels/LlmCredentialsPanel";
import PassThroughPanel from "@/app/(dashboard)/models-and-endpoints/panels/PassThroughPanel";
import HealthStatusPanel from "@/app/(dashboard)/models-and-endpoints/panels/HealthStatusPanel";
import ModelRetrySettingsPanel from "@/app/(dashboard)/models-and-endpoints/panels/ModelRetrySettingsPanel";
import ModelGroupAliasPanel from "@/app/(dashboard)/models-and-endpoints/panels/ModelGroupAliasPanel";
import PriceDataPanel from "@/app/(dashboard)/models-and-endpoints/panels/PriceDataPanel";
export default function AllModelsPage() {
const [selectedModelGroup, setSelectedModelGroup] = useState<string | null>(null);
const { availableModelGroups, availableModelAccessGroups } = useModelDashboardData();
const { openModel, openTeam } = useModelDetailRouting();
type ModelTabSlug =
| "add"
| "llm-credentials"
| "pass-through"
| "health"
| "retry-settings"
| "model-group-alias"
| "price-data";
const BASE_TAB_KEY = "all-models";
const TAB_LABELS: Record<ModelTabSlug, string> = {
add: "Add Model",
"llm-credentials": "LLM Credentials",
"pass-through": "Pass-Through Endpoints",
health: "Health Status",
"retry-settings": "Model Retry Settings",
"model-group-alias": "Model Group Alias",
"price-data": "Price Data Reload",
};
const renderPanel = (key: string) => {
switch (key) {
case BASE_TAB_KEY:
return <AllModelsPanel />;
case "add":
return <AddModelPanel />;
case "llm-credentials":
return <LlmCredentialsPanel />;
case "pass-through":
return <PassThroughPanel />;
case "health":
return <HealthStatusPanel />;
case "retry-settings":
return <ModelRetrySettingsPanel />;
case "model-group-alias":
return <ModelGroupAliasPanel />;
case "price-data":
return <PriceDataPanel />;
default:
return null;
}
};
export default function ModelsAndEndpointsPage() {
const { accessToken, userRole, userId: userID, premiumUser } = useAuthorized();
const { data: teams } = useTeams();
const { data: uiSettings } = useUISettings();
const queryClient = useQueryClient();
const { modelId, teamId, close } = useModelDetailRouting();
const { availableModelAccessGroups, allModelsOnProxy } = useModelDashboardData();
const [activeKey, setActiveKey] = useState<string>(BASE_TAB_KEY);
const [lastRefreshed, setLastRefreshed] = useState("");
const isProxyAdmin = userRole && isProxyAdminRole(userRole);
const isInternalUser = userRole && internalUserRoles.includes(userRole);
const isUserTeamAdmin = userID && isUserTeamAdminForAnyTeam(teams ?? null, userID);
const addModelDisabledForInternalUsers =
isInternalUser && uiSettings?.values?.disable_model_add_for_internal_users === true;
const shouldHideAddModelTab = !isProxyAdmin && (addModelDisabledForInternalUsers || !isUserTeamAdmin);
const isAdmin = all_admin_roles.includes(userRole);
const visibleSlugs = useMemo<Array<"" | ModelTabSlug>>(
() => [
"",
...(shouldHideAddModelTab ? [] : (["add"] as const)),
...(isAdmin
? (["llm-credentials", "pass-through", "health", "retry-settings", "model-group-alias", "price-data"] as const)
: []),
],
[shouldHideAddModelTab, isAdmin],
);
const allModelsLabel = isAdmin ? "All Models" : "Your Models";
const tabItems = visibleSlugs.map((slug) => {
const key = slug || BASE_TAB_KEY;
return {
key,
label: slug ? TAB_LABELS[slug] : allModelsLabel,
children: key === activeKey ? renderPanel(key) : null,
};
});
const handleRefreshClick = () => {
setLastRefreshed(new Date().toLocaleTimeString([], { hour: "2-digit", minute: "2-digit" }));
queryClient.invalidateQueries({ queryKey: ["models", "list"] });
};
const invalidateModels = () => queryClient.invalidateQueries({ queryKey: ["models", "list"] });
if (teamId) {
return (
<div className="w-full h-full">
<TeamInfoView
teamId={teamId}
onClose={close}
accessToken={accessToken}
is_team_admin={userRole === "Admin"}
is_proxy_admin={userRole === "Proxy Admin"}
userModels={allModelsOnProxy}
editTeam={false}
onUpdate={invalidateModels}
premiumUser={premiumUser}
/>
</div>
);
}
return (
<AllModelsTab
selectedModelGroup={selectedModelGroup}
setSelectedModelGroup={setSelectedModelGroup}
availableModelGroups={availableModelGroups}
availableModelAccessGroups={availableModelAccessGroups}
setSelectedModelId={openModel}
setSelectedTeamId={openTeam}
/>
<div className="mx-4 h-[75vh]">
<div className="flex flex-col gap-2 p-8 w-full mt-2">
<div className="flex justify-between items-center mb-4">
<div>
<h2 className="text-lg font-semibold">Model Management</h2>
{isAdmin ? (
<p className="text-sm text-gray-600">Add and manage models for the proxy</p>
) : (
<p className="text-sm text-gray-600">Add models for teams you are an admin for.</p>
)}
</div>
</div>
<CostOptimizationFeedbackBanner />
{modelId ? (
<ModelInfoView
modelId={modelId}
onClose={close}
accessToken={accessToken}
userID={userID}
userRole={userRole}
onModelUpdate={invalidateModels}
modelAccessGroups={availableModelAccessGroups}
/>
) : (
<Tabs
activeKey={activeKey}
onChange={setActiveKey}
items={tabItems}
tabBarExtraContent={{
right: (
<div className="flex items-center space-x-2 self-center">
{lastRefreshed && <span className="text-xs text-gray-500">Last Refreshed: {lastRefreshed}</span>}
<button
type="button"
onClick={handleRefreshClick}
aria-label="Refresh models"
className="cursor-pointer"
>
<RefreshIcon className="h-4 w-4 text-gray-500" />
</button>
</div>
),
}}
/>
)}
</div>
</div>
);
}

View file

@ -13,7 +13,7 @@ import { useTeams } from "@/app/(dashboard)/hooks/teams/useTeams";
import useAuthorized from "@/app/(dashboard)/hooks/useAuthorized";
import { vertexCredentialsUploadProps } from "@/app/(dashboard)/models-and-endpoints/vertexCredentialsUpload";
export default function AddModelPage() {
export default function AddModelPanel() {
const { accessToken, userRole } = useAuthorized();
const [form] = Form.useForm();
const queryClient = useQueryClient();

View file

@ -0,0 +1,23 @@
"use client";
import { useState } from "react";
import AllModelsTab from "@/app/(dashboard)/models-and-endpoints/components/AllModelsTab";
import { useModelDashboardData } from "@/app/(dashboard)/models-and-endpoints/useModelDashboardData";
import { useModelDetailRouting } from "@/app/(dashboard)/models-and-endpoints/detailNavigation";
export default function AllModelsPanel() {
const [selectedModelGroup, setSelectedModelGroup] = useState<string | null>(null);
const { availableModelGroups, availableModelAccessGroups } = useModelDashboardData();
const { openModel, openTeam } = useModelDetailRouting();
return (
<AllModelsTab
selectedModelGroup={selectedModelGroup}
setSelectedModelGroup={setSelectedModelGroup}
availableModelGroups={availableModelGroups}
availableModelAccessGroups={availableModelAccessGroups}
setSelectedModelId={openModel}
setSelectedTeamId={openTeam}
/>
);
}

View file

@ -1,7 +1,7 @@
/* @vitest-environment jsdom */
import { render } from "@testing-library/react";
import { beforeEach, describe, expect, it, vi } from "vitest";
import HealthStatusPage from "./page";
import HealthStatusPanel from "./HealthStatusPanel";
vi.mock("next/navigation", () => ({
usePathname: () => "/models-and-endpoints/health",
@ -27,7 +27,7 @@ vi.mock("@/app/(dashboard)/hooks/models/useModelCostMap", () => ({ useModelCostM
vi.mock("@/app/(dashboard)/hooks/teams/useTeams", () => ({ useTeams: () => ({ data: [] }) }));
vi.mock("@/app/(dashboard)/hooks/useAuthorized", () => ({ default: () => ({ accessToken: "123" }) }));
describe("HealthStatusPage", () => {
describe("HealthStatusPanel", () => {
beforeEach(() => {
mockHealthCheckComponent.mockClear();
});
@ -44,7 +44,7 @@ describe("HealthStatusPage", () => {
isLoading: false,
});
render(<HealthStatusPage />);
render(<HealthStatusPanel />);
expect(mockHealthCheckComponent).toHaveBeenCalled();
const props = mockHealthCheckComponent.mock.calls[0][0];

View file

@ -13,7 +13,7 @@ import { useModelDetailRouting } from "@/app/(dashboard)/models-and-endpoints/de
const HEALTH_PAGE_SIZE = 50;
export default function HealthStatusPage() {
export default function HealthStatusPanel() {
const { accessToken } = useAuthorized();
const { data: teams } = useTeams();
const { data: modelCostMapData } = useModelCostMap();

View file

@ -4,7 +4,7 @@ import { Form } from "antd";
import CredentialsPanel from "@/components/model_add/CredentialsPanel";
import { vertexCredentialsUploadProps } from "@/app/(dashboard)/models-and-endpoints/vertexCredentialsUpload";
export default function LlmCredentialsPage() {
export default function LlmCredentialsPanel() {
const [form] = Form.useForm();
return <CredentialsPanel uploadProps={vertexCredentialsUploadProps(form)} />;
}

View file

@ -5,7 +5,7 @@ import ModelGroupAliasSettings from "@/components/model_group_alias_settings";
import { getCallbacksCall } from "@/components/networking";
import useAuthorized from "@/app/(dashboard)/hooks/useAuthorized";
export default function ModelGroupAliasPage() {
export default function ModelGroupAliasPanel() {
const { accessToken, userId: userID, userRole } = useAuthorized();
const [modelGroupAlias, setModelGroupAlias] = useState<{ [key: string]: string }>({});

View file

@ -22,7 +22,7 @@ interface RouterSettings {
num_retries?: number | null;
}
export default function ModelRetrySettingsPage() {
export default function ModelRetrySettingsPanel() {
const { accessToken, userId: userID, userRole } = useAuthorized();
const { availableModelGroups } = useModelDashboardData();
const updateRetryPolicy = useUpdateRetryPolicy(accessToken);

View file

@ -3,7 +3,7 @@
import PassThroughSettings from "@/components/PassThroughSettings/PassThroughSettings";
import useAuthorized from "@/app/(dashboard)/hooks/useAuthorized";
export default function PassThroughPage() {
export default function PassThroughPanel() {
const { accessToken, userRole, userId: userID, premiumUser } = useAuthorized();
return (
<PassThroughSettings accessToken={accessToken} userRole={userRole} userID={userID} premiumUser={premiumUser} />

View file

@ -2,6 +2,6 @@
import PriceDataManagementTab from "@/app/(dashboard)/models-and-endpoints/components/PriceDataManagementTab";
export default function PriceDataPage() {
export default function PriceDataPanel() {
return <PriceDataManagementTab />;
}

View file

@ -1,38 +0,0 @@
/* @vitest-environment jsdom */
import { describe, expect, it, vi } from "vitest";
vi.mock("@/components/networking", () => ({ serverRootPath: "" }));
import { MODEL_TAB_SLUGS, modelTabHref, slugFromPathname } from "./tabRoutes";
describe("slugFromPathname", () => {
it("returns empty string for the base path with or without a trailing slash", () => {
expect(slugFromPathname("/models-and-endpoints")).toBe("");
expect(slugFromPathname("/models-and-endpoints/")).toBe("");
});
it("extracts the tab slug from dev and proxy-mounted (/ui) paths", () => {
expect(slugFromPathname("/models-and-endpoints/add")).toBe("add");
expect(slugFromPathname("/ui/models-and-endpoints/llm-credentials/")).toBe("llm-credentials");
});
it("returns the raw segment for an unknown tab so the view can redirect to base", () => {
expect(slugFromPathname("/ui/models-and-endpoints/bogus")).toBe("bogus");
});
it("returns empty string when the models base segment is not in the path", () => {
expect(slugFromPathname("/teams")).toBe("");
});
});
describe("modelTabHref", () => {
it("builds the trailing-slash base href for the empty slug", () => {
expect(modelTabHref("")).toBe("/ui/models-and-endpoints/");
});
it("builds a trailing-slash href for every tab slug (required by static export)", () => {
for (const slug of MODEL_TAB_SLUGS) {
expect(modelTabHref(slug)).toBe(`/ui/models-and-endpoints/${slug}/`);
}
});
});

View file

@ -1,18 +0,0 @@
import { createTabRoutes } from "@/utils/tabRoutes";
export const modelsRoutes = createTabRoutes("models-and-endpoints", [
"add",
"llm-credentials",
"pass-through",
"health",
"retry-settings",
"model-group-alias",
"price-data",
] as const);
export type ModelTabSlug = (typeof modelsRoutes.slugs)[number];
export const MODELS_BASE_SEGMENT = modelsRoutes.baseSegment;
export const MODEL_TAB_SLUGS = modelsRoutes.slugs;
export const modelTabHref = modelsRoutes.tabHref;
export const slugFromPathname = modelsRoutes.slugFromPathname;