mirror of
https://github.com/tiennm99/litellm.git
synced 2026-08-21 00:24:55 +00:00
@@ -1,15 +0,0 @@
|
||||
import { getUiSettings } from "@/components/networking";
|
||||
import { useQuery } from "@tanstack/react-query";
|
||||
import { createQueryKeys } from "../common/queryKeysFactory";
|
||||
|
||||
const uiSettingsKeys = createQueryKeys("uiSettings");
|
||||
|
||||
export const useUISettings = (accessToken: string) => {
|
||||
return useQuery<Record<string, any>>({
|
||||
queryKey: uiSettingsKeys.list({}),
|
||||
queryFn: async () => await getUiSettings(accessToken),
|
||||
enabled: !!accessToken,
|
||||
staleTime: 60 * 60 * 1000, // 1 hour - data rarely changes
|
||||
gcTime: 60 * 60 * 1000, // 1 hour - keep in cache for 1 hour
|
||||
});
|
||||
};
|
||||
@@ -1,21 +0,0 @@
|
||||
import { updateUiSettings } from "@/components/networking";
|
||||
import { useMutation, useQueryClient } from "@tanstack/react-query";
|
||||
import { createQueryKeys } from "../common/queryKeysFactory";
|
||||
|
||||
const uiSettingsKeys = createQueryKeys("uiSettings");
|
||||
|
||||
export const useUpdateUISettings = (accessToken: string) => {
|
||||
const queryClient = useQueryClient();
|
||||
|
||||
return useMutation({
|
||||
mutationFn: async (settings: Record<string, any>) => {
|
||||
if (!accessToken) {
|
||||
throw new Error("Access token is required");
|
||||
}
|
||||
return updateUiSettings(accessToken, settings);
|
||||
},
|
||||
onSuccess: () => {
|
||||
queryClient.invalidateQueries({ queryKey: uiSettingsKeys.all });
|
||||
},
|
||||
});
|
||||
};
|
||||
-90
@@ -1,90 +0,0 @@
|
||||
import { render, screen, fireEvent, act } from "@testing-library/react";
|
||||
import { beforeEach, describe, expect, it, vi } from "vitest";
|
||||
import UISettings from "./UISettings";
|
||||
import NotificationManager from "@/components/molecules/notifications_manager";
|
||||
|
||||
const mockUseAuthorized = vi.hoisted(() => vi.fn());
|
||||
const mockUseUISettings = vi.hoisted(() => vi.fn());
|
||||
const mockUseUpdateUISettings = vi.hoisted(() => vi.fn());
|
||||
|
||||
vi.mock("@/app/(dashboard)/hooks/useAuthorized", () => ({
|
||||
__esModule: true,
|
||||
default: mockUseAuthorized,
|
||||
}));
|
||||
|
||||
vi.mock("@/app/(dashboard)/hooks/uiSettings/useUISettings", () => ({
|
||||
useUISettings: mockUseUISettings,
|
||||
}));
|
||||
|
||||
vi.mock("@/app/(dashboard)/hooks/uiSettings/useUpdateUISettings", () => ({
|
||||
useUpdateUISettings: mockUseUpdateUISettings,
|
||||
}));
|
||||
|
||||
const buildSettingsResponse = (overrides?: Partial<Record<string, unknown>>) => ({
|
||||
data: {
|
||||
field_schema: {
|
||||
description: "UI settings description",
|
||||
properties: {
|
||||
disable_model_add_for_internal_users: {
|
||||
description: "Disable model add for internal users",
|
||||
},
|
||||
},
|
||||
},
|
||||
values: {
|
||||
disable_model_add_for_internal_users: false,
|
||||
},
|
||||
},
|
||||
isLoading: false,
|
||||
isError: false,
|
||||
error: null,
|
||||
...overrides,
|
||||
});
|
||||
|
||||
describe("UISettings", () => {
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks();
|
||||
mockUseAuthorized.mockReturnValue({ accessToken: "test-token" });
|
||||
mockUseUISettings.mockReturnValue(buildSettingsResponse());
|
||||
mockUseUpdateUISettings.mockReturnValue({
|
||||
mutate: vi.fn(),
|
||||
isPending: false,
|
||||
error: null,
|
||||
});
|
||||
});
|
||||
|
||||
it("should render", () => {
|
||||
render(<UISettings />);
|
||||
|
||||
expect(screen.getByText("UI Settings")).toBeInTheDocument();
|
||||
expect(screen.getByRole("switch", { name: "Disable model add for internal users" })).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("should toggle setting and call update", () => {
|
||||
const mutateMock = vi.fn((_settings, options) => {
|
||||
options?.onSuccess?.();
|
||||
});
|
||||
|
||||
mockUseUpdateUISettings.mockReturnValue({
|
||||
mutate: mutateMock,
|
||||
isPending: false,
|
||||
error: null,
|
||||
});
|
||||
|
||||
render(<UISettings />);
|
||||
|
||||
const toggle = screen.getByRole("switch", { name: "Disable model add for internal users" });
|
||||
|
||||
act(() => {
|
||||
fireEvent.click(toggle);
|
||||
});
|
||||
|
||||
expect(mutateMock).toHaveBeenCalledWith(
|
||||
{ disable_model_add_for_internal_users: true },
|
||||
expect.objectContaining({
|
||||
onSuccess: expect.any(Function),
|
||||
onError: expect.any(Function),
|
||||
}),
|
||||
);
|
||||
expect(NotificationManager.success).toHaveBeenCalledWith("UI settings updated successfully");
|
||||
});
|
||||
});
|
||||
@@ -1,74 +0,0 @@
|
||||
"use client";
|
||||
|
||||
import { useUISettings } from "@/app/(dashboard)/hooks/uiSettings/useUISettings";
|
||||
import { useUpdateUISettings } from "@/app/(dashboard)/hooks/uiSettings/useUpdateUISettings";
|
||||
import useAuthorized from "@/app/(dashboard)/hooks/useAuthorized";
|
||||
import NotificationManager from "@/components/molecules/notifications_manager";
|
||||
import { Alert, Card, Skeleton, Space, Switch, Typography } from "antd";
|
||||
|
||||
export default function UISettings() {
|
||||
const { accessToken } = useAuthorized();
|
||||
const { data, isLoading, isError, error } = useUISettings(accessToken);
|
||||
const { mutate: updateSettings, isPending: isUpdating, error: updateError } = useUpdateUISettings(accessToken);
|
||||
|
||||
const schema = data?.field_schema;
|
||||
const property = schema?.properties?.disable_model_add_for_internal_users;
|
||||
const values = data?.values ?? {};
|
||||
const isDisabledForInternalUsers = Boolean(values.disable_model_add_for_internal_users);
|
||||
|
||||
const handleToggle = (checked: boolean) => {
|
||||
updateSettings(
|
||||
{ disable_model_add_for_internal_users: checked },
|
||||
{
|
||||
onSuccess: () => {
|
||||
NotificationManager.success("UI settings updated successfully");
|
||||
},
|
||||
onError: (error) => {
|
||||
NotificationManager.fromBackend(error);
|
||||
},
|
||||
},
|
||||
);
|
||||
};
|
||||
|
||||
return (
|
||||
<Card title="UI Settings">
|
||||
{isLoading ? (
|
||||
<Skeleton active />
|
||||
) : isError ? (
|
||||
<Alert
|
||||
type="error"
|
||||
message="Could not load UI settings"
|
||||
description={error instanceof Error ? error.message : undefined}
|
||||
/>
|
||||
) : (
|
||||
<Space direction="vertical" size="large" style={{ width: "100%" }}>
|
||||
{schema?.description && (
|
||||
<Typography.Paragraph style={{ marginBottom: 0 }}>{schema.description}</Typography.Paragraph>
|
||||
)}
|
||||
|
||||
{updateError && (
|
||||
<Alert
|
||||
type="error"
|
||||
message="Could not update UI settings"
|
||||
description={updateError instanceof Error ? updateError.message : undefined}
|
||||
/>
|
||||
)}
|
||||
|
||||
<Space align="start" size="middle">
|
||||
<Switch
|
||||
checked={isDisabledForInternalUsers}
|
||||
disabled={isUpdating}
|
||||
loading={isUpdating}
|
||||
onChange={handleToggle}
|
||||
aria-label={property?.description ?? "Disable model add for internal users"}
|
||||
/>
|
||||
<Space direction="vertical" size={4}>
|
||||
<Typography.Text strong>Disable model add for internal users</Typography.Text>
|
||||
{property?.description && <Typography.Text type="secondary">{property.description}</Typography.Text>}
|
||||
</Space>
|
||||
</Space>
|
||||
</Space>
|
||||
)}
|
||||
</Card>
|
||||
);
|
||||
}
|
||||
@@ -54,7 +54,6 @@ import {
|
||||
deleteAllowedIP,
|
||||
getSSOSettings,
|
||||
} from "./networking";
|
||||
import UISettings from "./Settings/AdminSettings/UISettings/UISettings";
|
||||
|
||||
const AdminPanel: React.FC<AdminPanelProps> = ({
|
||||
searchParams,
|
||||
@@ -498,7 +497,6 @@ const AdminPanel: React.FC<AdminPanelProps> = ({
|
||||
<TabList>
|
||||
<Tab>Security Settings</Tab>
|
||||
<Tab>SCIM</Tab>
|
||||
<Tab>UI Settings</Tab>
|
||||
</TabList>
|
||||
<TabPanels>
|
||||
<TabPanel>
|
||||
@@ -650,9 +648,6 @@ const AdminPanel: React.FC<AdminPanelProps> = ({
|
||||
<TabPanel>
|
||||
<SCIMConfig accessToken={accessToken} userID={userID} proxySettings={proxySettings} />
|
||||
</TabPanel>
|
||||
<TabPanel>
|
||||
<UISettings />
|
||||
</TabPanel>
|
||||
</TabPanels>
|
||||
</TabGroup>
|
||||
</div>
|
||||
|
||||
@@ -8030,41 +8030,3 @@ export const loginCall = async (username: string, password: string): Promise<Log
|
||||
const data = await response.json();
|
||||
return data;
|
||||
};
|
||||
|
||||
export const getUiSettings = async (accessToken: string) => {
|
||||
const proxyBaseUrl = getProxyBaseUrl();
|
||||
const url = proxyBaseUrl ? `${proxyBaseUrl}/get/ui_settings` : `/get/ui_settings`;
|
||||
const response = await fetch(url, {
|
||||
method: "GET",
|
||||
headers: {
|
||||
[globalLitellmHeaderName]: `Bearer ${accessToken}`,
|
||||
},
|
||||
});
|
||||
if (!response.ok) {
|
||||
const errorData = await response.json();
|
||||
const errorMessage = deriveErrorMessage(errorData);
|
||||
throw new Error(errorMessage);
|
||||
}
|
||||
const data = await response.json();
|
||||
return data;
|
||||
};
|
||||
|
||||
export const updateUiSettings = async (accessToken: string, settings: Record<string, any>) => {
|
||||
const proxyBaseUrl = getProxyBaseUrl();
|
||||
const url = proxyBaseUrl ? `${proxyBaseUrl}/update/ui_settings` : `/update/ui_settings`;
|
||||
const response = await fetch(url, {
|
||||
method: "PATCH",
|
||||
headers: {
|
||||
[globalLitellmHeaderName]: `Bearer ${accessToken}`,
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
body: JSON.stringify(settings),
|
||||
});
|
||||
if (!response.ok) {
|
||||
const errorData = await response.json();
|
||||
const errorMessage = deriveErrorMessage(errorData);
|
||||
throw new Error(errorMessage);
|
||||
}
|
||||
const data = await response.json();
|
||||
return data;
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user