mirror of
https://github.com/tiennm99/litellm.git
synced 2026-07-17 10:17:46 +00:00
Merge pull request #18071 from BerriAI/litellm_ui_hide_add_models
[Feature] UI - Add Models Conditional Rendering
This commit is contained in:
+24
-5
@@ -1,7 +1,7 @@
|
||||
/* @vitest-environment jsdom */
|
||||
import { render } from "@testing-library/react";
|
||||
import { describe, it, expect, vi } from "vitest";
|
||||
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
|
||||
import { render } from "@testing-library/react";
|
||||
import { beforeEach, describe, expect, it, vi } from "vitest";
|
||||
import ModelsAndEndpointsView from "./ModelsAndEndpointsView";
|
||||
|
||||
// Minimal stubs to avoid Next.js router and network usage during render
|
||||
@@ -57,21 +57,40 @@ vi.mock("@/app/(dashboard)/hooks/useTeams", () => ({
|
||||
}),
|
||||
}));
|
||||
|
||||
const mockUseModelsInfo = vi.fn();
|
||||
vi.mock("@/app/(dashboard)/hooks/models/useModels", () => ({
|
||||
useModelsInfo: () => mockUseModelsInfo(),
|
||||
}));
|
||||
|
||||
const mockUseUISettings = vi.fn();
|
||||
vi.mock("@/app/(dashboard)/hooks/uiSettings/useUISettings", () => ({
|
||||
useUISettings: () => mockUseUISettings(),
|
||||
}));
|
||||
|
||||
const createQueryClient = () =>
|
||||
new QueryClient({
|
||||
defaultOptions: { queries: { retry: false, gcTime: 0 } },
|
||||
});
|
||||
|
||||
describe("ModelsAndEndpointsView", () => {
|
||||
it("should render the models and endpoints view", async () => {
|
||||
// JSDOM polyfill for libraries expecting ResizeObserver (e.g., recharts)
|
||||
// Note: ResizeObserver is now globally mocked in setupTests.ts, but keeping this for backwards compatibility
|
||||
beforeEach(() => {
|
||||
mockUseModelsInfo.mockReturnValue({
|
||||
data: { data: [] },
|
||||
isLoading: false,
|
||||
refetch: vi.fn(),
|
||||
});
|
||||
mockUseUISettings.mockReturnValue({
|
||||
data: { values: {} },
|
||||
});
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
(global as any).ResizeObserver = class {
|
||||
observe() {}
|
||||
unobserve() {}
|
||||
disconnect() {}
|
||||
};
|
||||
});
|
||||
|
||||
it("should render the models and endpoints view", async () => {
|
||||
const queryClient = createQueryClient();
|
||||
const { findByText } = render(
|
||||
<QueryClientProvider client={queryClient}>
|
||||
|
||||
+7
-2
@@ -36,7 +36,7 @@ import ModelAnalyticsTab from "@/app/(dashboard)/models-and-endpoints/components
|
||||
import ModelRetrySettingsTab from "@/app/(dashboard)/models-and-endpoints/components/ModelRetrySettingsTab";
|
||||
import PriceDataManagementTab from "@/app/(dashboard)/models-and-endpoints/components/PriceDataManagementTab";
|
||||
import { useUISettings } from "@/app/(dashboard)/hooks/uiSettings/useUISettings";
|
||||
import { all_admin_roles, internalUserRoles } from "@/utils/roles";
|
||||
import { all_admin_roles, internalUserRoles, isProxyAdminRole, isUserTeamAdminForAnyTeam } from "@/utils/roles";
|
||||
import HealthCheckComponent from "../../../components/model_dashboard/HealthCheckComponent";
|
||||
import ModelGroupAliasSettings from "../../../components/model_group_alias_settings";
|
||||
import NotificationsManager from "../../../components/molecules/notifications_manager";
|
||||
@@ -161,8 +161,13 @@ const ModelsAndEndpointsView: React.FC<ModelDashboardProps> = ({
|
||||
const credentialsList = credentialsResponse?.credentials || [];
|
||||
const { data: uiSettings } = useUISettings(accessToken || "");
|
||||
|
||||
const isProxyAdmin = userRole && isProxyAdminRole(userRole);
|
||||
const isInternalUser = userRole && internalUserRoles.includes(userRole);
|
||||
const shouldHideAddModelTab = isInternalUser && uiSettings?.values?.disable_model_add_for_internal_users === true;
|
||||
const isUserTeamAdmin = userID && isUserTeamAdminForAnyTeam(teams, userID);
|
||||
const addModelDisabledForInternalUsers =
|
||||
isInternalUser && uiSettings?.values?.disable_model_add_for_internal_users === true;
|
||||
// Hide tab if user is NOT a proxy admin AND (internal user with setting enabled OR not a team admin)
|
||||
const shouldHideAddModelTab = !isProxyAdmin && (addModelDisabledForInternalUsers || !isUserTeamAdmin);
|
||||
|
||||
const setProviderModelsFn = (provider: Providers) => {
|
||||
const _providerModels = getProviderModels(provider, modelMap);
|
||||
|
||||
@@ -0,0 +1,196 @@
|
||||
import { describe, it, expect } from "vitest";
|
||||
import { isAdminRole, isProxyAdminRole, isUserTeamAdminForAnyTeam, isUserTeamAdminForSingleTeam } from "./roles";
|
||||
import { Team } from "@/components/networking";
|
||||
|
||||
describe("roles", () => {
|
||||
describe("isAdminRole", () => {
|
||||
it("should return true for all admin roles", () => {
|
||||
expect(isAdminRole("Admin")).toBe(true);
|
||||
expect(isAdminRole("Admin Viewer")).toBe(true);
|
||||
expect(isAdminRole("proxy_admin")).toBe(true);
|
||||
expect(isAdminRole("proxy_admin_viewer")).toBe(true);
|
||||
expect(isAdminRole("org_admin")).toBe(true);
|
||||
});
|
||||
|
||||
it("should return false for non-admin roles", () => {
|
||||
expect(isAdminRole("Internal User")).toBe(false);
|
||||
expect(isAdminRole("Internal Viewer")).toBe(false);
|
||||
expect(isAdminRole("regular_user")).toBe(false);
|
||||
expect(isAdminRole("")).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe("isProxyAdminRole", () => {
|
||||
it("should return true for proxy_admin and Admin roles", () => {
|
||||
expect(isProxyAdminRole("proxy_admin")).toBe(true);
|
||||
expect(isProxyAdminRole("Admin")).toBe(true);
|
||||
});
|
||||
|
||||
it("should return false for other admin roles", () => {
|
||||
expect(isProxyAdminRole("Admin Viewer")).toBe(false);
|
||||
expect(isProxyAdminRole("proxy_admin_viewer")).toBe(false);
|
||||
expect(isProxyAdminRole("org_admin")).toBe(false);
|
||||
});
|
||||
|
||||
it("should return false for non-admin roles", () => {
|
||||
expect(isProxyAdminRole("Internal User")).toBe(false);
|
||||
expect(isProxyAdminRole("Internal Viewer")).toBe(false);
|
||||
expect(isProxyAdminRole("regular_user")).toBe(false);
|
||||
expect(isProxyAdminRole("")).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe("isUserTeamAdminForSingleTeam", () => {
|
||||
it("should return true when user is team admin", () => {
|
||||
const team: Team = {
|
||||
team_id: "team-1",
|
||||
team_alias: "Test Team",
|
||||
models: [],
|
||||
max_budget: null,
|
||||
budget_duration: null,
|
||||
tpm_limit: null,
|
||||
rpm_limit: null,
|
||||
organization_id: "org-1",
|
||||
created_at: "2024-01-01",
|
||||
keys: [],
|
||||
members_with_roles: [
|
||||
{ user_id: "user-1", user_email: "user1@test.com", role: "admin" },
|
||||
{ user_id: "user-2", user_email: "user2@test.com", role: "user" },
|
||||
],
|
||||
};
|
||||
expect(isUserTeamAdminForSingleTeam(team, "user-1")).toBe(true);
|
||||
});
|
||||
|
||||
it("should return false when user is not team admin", () => {
|
||||
const team: Team = {
|
||||
team_id: "team-1",
|
||||
team_alias: "Test Team",
|
||||
models: [],
|
||||
max_budget: null,
|
||||
budget_duration: null,
|
||||
tpm_limit: null,
|
||||
rpm_limit: null,
|
||||
organization_id: "org-1",
|
||||
created_at: "2024-01-01",
|
||||
keys: [],
|
||||
members_with_roles: [
|
||||
{ user_id: "user-1", user_email: "user1@test.com", role: "user" },
|
||||
{ user_id: "user-2", user_email: "user2@test.com", role: "user" },
|
||||
],
|
||||
};
|
||||
expect(isUserTeamAdminForSingleTeam(team, "user-1")).toBe(false);
|
||||
});
|
||||
|
||||
it("should return false when user is not in team", () => {
|
||||
const team: Team = {
|
||||
team_id: "team-1",
|
||||
team_alias: "Test Team",
|
||||
models: [],
|
||||
max_budget: null,
|
||||
budget_duration: null,
|
||||
tpm_limit: null,
|
||||
rpm_limit: null,
|
||||
organization_id: "org-1",
|
||||
created_at: "2024-01-01",
|
||||
keys: [],
|
||||
members_with_roles: [{ user_id: "user-2", user_email: "user2@test.com", role: "admin" }],
|
||||
};
|
||||
expect(isUserTeamAdminForSingleTeam(team, "user-1")).toBe(false);
|
||||
});
|
||||
|
||||
it("should return false when team is null", () => {
|
||||
expect(isUserTeamAdminForSingleTeam(null, "user-1")).toBe(false);
|
||||
});
|
||||
|
||||
it("should return false when members_with_roles is null", () => {
|
||||
const team = {
|
||||
team_id: "team-1",
|
||||
team_alias: "Test Team",
|
||||
models: [],
|
||||
max_budget: null,
|
||||
budget_duration: null,
|
||||
tpm_limit: null,
|
||||
rpm_limit: null,
|
||||
organization_id: "org-1",
|
||||
created_at: "2024-01-01",
|
||||
keys: [],
|
||||
members_with_roles: [],
|
||||
} as Team;
|
||||
expect(isUserTeamAdminForSingleTeam(team, "user-1")).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe("isUserTeamAdminForAnyTeam", () => {
|
||||
it("should return true when user is admin of at least one team", () => {
|
||||
const teams: Team[] = [
|
||||
{
|
||||
team_id: "team-1",
|
||||
team_alias: "Test Team 1",
|
||||
models: [],
|
||||
max_budget: null,
|
||||
budget_duration: null,
|
||||
tpm_limit: null,
|
||||
rpm_limit: null,
|
||||
organization_id: "org-1",
|
||||
created_at: "2024-01-01",
|
||||
keys: [],
|
||||
members_with_roles: [{ user_id: "user-1", user_email: "user1@test.com", role: "user" }],
|
||||
},
|
||||
{
|
||||
team_id: "team-2",
|
||||
team_alias: "Test Team 2",
|
||||
models: [],
|
||||
max_budget: null,
|
||||
budget_duration: null,
|
||||
tpm_limit: null,
|
||||
rpm_limit: null,
|
||||
organization_id: "org-1",
|
||||
created_at: "2024-01-01",
|
||||
keys: [],
|
||||
members_with_roles: [{ user_id: "user-1", user_email: "user1@test.com", role: "admin" }],
|
||||
},
|
||||
];
|
||||
expect(isUserTeamAdminForAnyTeam(teams, "user-1")).toBe(true);
|
||||
});
|
||||
|
||||
it("should return false when user is not admin of any team", () => {
|
||||
const teams: Team[] = [
|
||||
{
|
||||
team_id: "team-1",
|
||||
team_alias: "Test Team 1",
|
||||
models: [],
|
||||
max_budget: null,
|
||||
budget_duration: null,
|
||||
tpm_limit: null,
|
||||
rpm_limit: null,
|
||||
organization_id: "org-1",
|
||||
created_at: "2024-01-01",
|
||||
keys: [],
|
||||
members_with_roles: [{ user_id: "user-1", user_email: "user1@test.com", role: "user" }],
|
||||
},
|
||||
{
|
||||
team_id: "team-2",
|
||||
team_alias: "Test Team 2",
|
||||
models: [],
|
||||
max_budget: null,
|
||||
budget_duration: null,
|
||||
tpm_limit: null,
|
||||
rpm_limit: null,
|
||||
organization_id: "org-1",
|
||||
created_at: "2024-01-01",
|
||||
keys: [],
|
||||
members_with_roles: [{ user_id: "user-2", user_email: "user2@test.com", role: "admin" }],
|
||||
},
|
||||
];
|
||||
expect(isUserTeamAdminForAnyTeam(teams, "user-1")).toBe(false);
|
||||
});
|
||||
|
||||
it("should return false when teams is null", () => {
|
||||
expect(isUserTeamAdminForAnyTeam(null, "user-1")).toBe(false);
|
||||
});
|
||||
|
||||
it("should return false when teams is empty array", () => {
|
||||
expect(isUserTeamAdminForAnyTeam([], "user-1")).toBe(false);
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -1,41 +0,0 @@
|
||||
import { describe, it, expect } from "vitest";
|
||||
import { isAdminRole, isProxyAdminRole } from "./roles";
|
||||
|
||||
describe("roles", () => {
|
||||
describe("isAdminRole", () => {
|
||||
it("should return true for all admin roles", () => {
|
||||
expect(isAdminRole("Admin")).toBe(true);
|
||||
expect(isAdminRole("Admin Viewer")).toBe(true);
|
||||
expect(isAdminRole("proxy_admin")).toBe(true);
|
||||
expect(isAdminRole("proxy_admin_viewer")).toBe(true);
|
||||
expect(isAdminRole("org_admin")).toBe(true);
|
||||
});
|
||||
|
||||
it("should return false for non-admin roles", () => {
|
||||
expect(isAdminRole("Internal User")).toBe(false);
|
||||
expect(isAdminRole("Internal Viewer")).toBe(false);
|
||||
expect(isAdminRole("regular_user")).toBe(false);
|
||||
expect(isAdminRole("")).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe("isProxyAdminRole", () => {
|
||||
it("should return true for proxy_admin and Admin roles", () => {
|
||||
expect(isProxyAdminRole("proxy_admin")).toBe(true);
|
||||
expect(isProxyAdminRole("Admin")).toBe(true);
|
||||
});
|
||||
|
||||
it("should return false for other admin roles", () => {
|
||||
expect(isProxyAdminRole("Admin Viewer")).toBe(false);
|
||||
expect(isProxyAdminRole("proxy_admin_viewer")).toBe(false);
|
||||
expect(isProxyAdminRole("org_admin")).toBe(false);
|
||||
});
|
||||
|
||||
it("should return false for non-admin roles", () => {
|
||||
expect(isProxyAdminRole("Internal User")).toBe(false);
|
||||
expect(isProxyAdminRole("Internal Viewer")).toBe(false);
|
||||
expect(isProxyAdminRole("regular_user")).toBe(false);
|
||||
expect(isProxyAdminRole("")).toBe(false);
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -1,3 +1,5 @@
|
||||
import { Team } from "@/components/networking";
|
||||
|
||||
// Define admin roles and permissions
|
||||
export const old_admin_roles = ["Admin", "Admin Viewer"];
|
||||
export const v2_admin_role_names = ["proxy_admin", "proxy_admin_viewer", "org_admin"];
|
||||
@@ -15,3 +17,17 @@ export const isAdminRole = (role: string): boolean => {
|
||||
export const isProxyAdminRole = (role: string): boolean => {
|
||||
return role === "proxy_admin" || role === "Admin";
|
||||
};
|
||||
|
||||
export const isUserTeamAdminForAnyTeam = (teams: Team[] | null, userID: string): boolean => {
|
||||
if (teams == null) {
|
||||
return false;
|
||||
}
|
||||
return teams.some((team) => isUserTeamAdminForSingleTeam(team, userID));
|
||||
};
|
||||
|
||||
export const isUserTeamAdminForSingleTeam = (team: Team | null, userID: string): boolean => {
|
||||
if (team == null || team.members_with_roles == null) {
|
||||
return false;
|
||||
}
|
||||
return team.members_with_roles.some((member) => member.user_id === userID && member.role === "admin");
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user