mirror of
https://github.com/BerriAI/litellm.git
synced 2026-09-22 00:31:44 +00:00
init ui for internal user controsl
This commit is contained in:
parent
5baca5592d
commit
fed0f3748e
5 changed files with 226 additions and 26 deletions
|
|
@ -1,4 +1,9 @@
|
|||
"use client";
|
||||
|
||||
import Sidebar from "@/components/leftnav";
|
||||
import { getUISettings } from "@/components/networking";
|
||||
import useAuthorized from "@/app/(dashboard)/hooks/useAuthorized";
|
||||
import { useEffect, useState } from "react";
|
||||
|
||||
interface SidebarProviderProps {
|
||||
setPage: (page: string) => void;
|
||||
|
|
@ -7,7 +12,34 @@ interface SidebarProviderProps {
|
|||
}
|
||||
|
||||
const SidebarProvider = ({ setPage, defaultSelectedKey, sidebarCollapsed }: SidebarProviderProps) => {
|
||||
return <Sidebar setPage={setPage} defaultSelectedKey={defaultSelectedKey} collapsed={sidebarCollapsed} />;
|
||||
const { accessToken } = useAuthorized();
|
||||
const [enabledPagesInternalUsers, setEnabledPagesInternalUsers] = useState<string[] | null>(null);
|
||||
|
||||
useEffect(() => {
|
||||
const fetchUISettings = async () => {
|
||||
if (!accessToken) return;
|
||||
|
||||
try {
|
||||
const settings = await getUISettings(accessToken);
|
||||
if (settings?.settings?.enabled_ui_pages_internal_users !== undefined) {
|
||||
setEnabledPagesInternalUsers(settings.settings.enabled_ui_pages_internal_users);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Failed to fetch UI settings:", error);
|
||||
}
|
||||
};
|
||||
|
||||
fetchUISettings();
|
||||
}, [accessToken]);
|
||||
|
||||
return (
|
||||
<Sidebar
|
||||
setPage={setPage}
|
||||
defaultSelectedKey={defaultSelectedKey}
|
||||
collapsed={sidebarCollapsed}
|
||||
enabledPagesInternalUsers={enabledPagesInternalUsers}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
export default SidebarProvider;
|
||||
|
|
|
|||
|
|
@ -39,6 +39,7 @@ interface SidebarProps {
|
|||
setPage: (page: string) => void;
|
||||
defaultSelectedKey: string;
|
||||
collapsed?: boolean;
|
||||
enabledPagesInternalUsers?: string[] | null;
|
||||
}
|
||||
|
||||
// Menu item configuration
|
||||
|
|
@ -59,28 +60,8 @@ interface MenuGroup {
|
|||
roles?: string[];
|
||||
}
|
||||
|
||||
const Sidebar: React.FC<SidebarProps> = ({ setPage, defaultSelectedKey, collapsed = false }) => {
|
||||
const { userId, accessToken, userRole } = useAuthorized();
|
||||
const { data: organizations } = useOrganizations();
|
||||
|
||||
// Check if user is an org_admin
|
||||
const isOrgAdmin = useMemo(() => {
|
||||
if (!userId || !organizations) return false;
|
||||
return organizations.some((org: Organization) =>
|
||||
org.members?.some((member) => member.user_id === userId && member.user_role === "org_admin"),
|
||||
);
|
||||
}, [userId, organizations]);
|
||||
|
||||
// Navigate to page helper
|
||||
const navigateToPage = (page: string) => {
|
||||
const newSearchParams = new URLSearchParams(window.location.search);
|
||||
newSearchParams.set("page", page);
|
||||
window.history.pushState(null, "", `?${newSearchParams.toString()}`);
|
||||
setPage(page);
|
||||
};
|
||||
|
||||
// Menu groups organized by category
|
||||
const menuGroups: MenuGroup[] = [
|
||||
// Menu groups organized by category - defined outside component for export
|
||||
const menuGroups: MenuGroup[] = [
|
||||
{
|
||||
groupLabel: "AI GATEWAY",
|
||||
items: [
|
||||
|
|
@ -337,15 +318,53 @@ const Sidebar: React.FC<SidebarProps> = ({ setPage, defaultSelectedKey, collapse
|
|||
},
|
||||
];
|
||||
|
||||
// Filter items based on user role
|
||||
const Sidebar: React.FC<SidebarProps> = ({ setPage, defaultSelectedKey, collapsed = false, enabledPagesInternalUsers }) => {
|
||||
const { userId, accessToken, userRole } = useAuthorized();
|
||||
const { data: organizations } = useOrganizations();
|
||||
|
||||
// Check if user is an org_admin
|
||||
const isOrgAdmin = useMemo(() => {
|
||||
if (!userId || !organizations) return false;
|
||||
return organizations.some((org: Organization) =>
|
||||
org.members?.some((member) => member.user_id === userId && member.user_role === "org_admin"),
|
||||
);
|
||||
}, [userId, organizations]);
|
||||
|
||||
// Navigate to page helper
|
||||
const navigateToPage = (page: string) => {
|
||||
const newSearchParams = new URLSearchParams(window.location.search);
|
||||
newSearchParams.set("page", page);
|
||||
window.history.pushState(null, "", `?${newSearchParams.toString()}`);
|
||||
setPage(page);
|
||||
};
|
||||
|
||||
// Filter items based on user role and enabled pages for internal users
|
||||
const filterItemsByRole = (items: MenuItem[]): MenuItem[] => {
|
||||
const isAdmin = isAdminRole(userRole);
|
||||
|
||||
return items
|
||||
.filter((item) => {
|
||||
// Special handling for organizations menu item - allow org_admins
|
||||
if (item.key === "organizations") {
|
||||
return !item.roles || item.roles.includes(userRole) || isOrgAdmin;
|
||||
const hasRoleAccess = !item.roles || item.roles.includes(userRole) || isOrgAdmin;
|
||||
if (!hasRoleAccess) return false;
|
||||
|
||||
// Check enabled pages for internal users (non-admins)
|
||||
if (!isAdmin && enabledPagesInternalUsers !== null && enabledPagesInternalUsers !== undefined) {
|
||||
return enabledPagesInternalUsers.includes(item.page);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
return !item.roles || item.roles.includes(userRole);
|
||||
|
||||
// Existing role check
|
||||
if (item.roles && !item.roles.includes(userRole)) return false;
|
||||
|
||||
// Check enabled pages for internal users (non-admins)
|
||||
if (!isAdmin && enabledPagesInternalUsers !== null && enabledPagesInternalUsers !== undefined) {
|
||||
return enabledPagesInternalUsers.includes(item.page);
|
||||
}
|
||||
|
||||
return true;
|
||||
})
|
||||
.map((item) => ({
|
||||
...item,
|
||||
|
|
@ -485,3 +504,6 @@ const Sidebar: React.FC<SidebarProps> = ({ setPage, defaultSelectedKey, collapse
|
|||
};
|
||||
|
||||
export default Sidebar;
|
||||
|
||||
// Also export menuGroups for advanced use cases
|
||||
export { menuGroups };
|
||||
|
|
|
|||
|
|
@ -5330,6 +5330,66 @@ export const getProxyUISettings = async (accessToken: string) => {
|
|||
}
|
||||
};
|
||||
|
||||
export const getUISettings = async (accessToken: string) => {
|
||||
/**
|
||||
* Get UI-specific configuration flags from the database
|
||||
*/
|
||||
try {
|
||||
const url = proxyBaseUrl ? `${proxyBaseUrl}/get/ui_settings` : `/get/ui_settings`;
|
||||
const response = await fetch(url, {
|
||||
method: "GET",
|
||||
headers: {
|
||||
[globalLitellmHeaderName]: `Bearer ${accessToken}`,
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
const errorData = await response.json();
|
||||
const errorMessage = deriveErrorMessage(errorData);
|
||||
console.error("Failed to get UI settings:", errorMessage);
|
||||
return null;
|
||||
}
|
||||
|
||||
const data = await response.json();
|
||||
return data;
|
||||
} catch (error) {
|
||||
console.error("Failed to get UI settings:", error);
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
||||
export const updateUISettings = async (accessToken: string, settings: any) => {
|
||||
/**
|
||||
* Update UI-specific configuration flags in the database
|
||||
* Only proxy admins can update these settings
|
||||
*/
|
||||
try {
|
||||
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);
|
||||
handleError(errorMessage);
|
||||
throw new Error(errorMessage);
|
||||
}
|
||||
|
||||
const data = await response.json();
|
||||
return data;
|
||||
} catch (error) {
|
||||
console.error("Failed to update UI settings:", error);
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
|
||||
export const getGuardrailsList = async (accessToken: string) => {
|
||||
try {
|
||||
const url = proxyBaseUrl ? `${proxyBaseUrl}/v2/guardrails/list` : `/v2/guardrails/list`;
|
||||
|
|
|
|||
41
ui/litellm-dashboard/src/components/page_metadata.ts
Normal file
41
ui/litellm-dashboard/src/components/page_metadata.ts
Normal file
|
|
@ -0,0 +1,41 @@
|
|||
/**
|
||||
* Page metadata for UI Settings configuration
|
||||
* This file contains descriptions and metadata for all navigation pages
|
||||
*/
|
||||
|
||||
// Page descriptions for UI Settings configuration
|
||||
export const pageDescriptions: Record<string, string> = {
|
||||
"api-keys": "Manage virtual keys for API access and authentication",
|
||||
playground: "Interactive playground for testing LLM requests",
|
||||
"models-and-endpoints": "Configure and manage LLM models and endpoints",
|
||||
agents: "Create and manage AI agents",
|
||||
"mcp-servers": "Configure Model Context Protocol servers",
|
||||
guardrails: "Set up content moderation and safety guardrails",
|
||||
policies: "Define access control and usage policies",
|
||||
"search-tools": "Configure RAG search and retrieval tools",
|
||||
"vector-stores": "Manage vector databases for embeddings",
|
||||
usage: "View usage analytics and metrics",
|
||||
logs: "Access request and response logs",
|
||||
"internal-users": "Manage internal user accounts and permissions",
|
||||
teams: "Create and manage teams for access control",
|
||||
organizations: "Manage organizations and their members",
|
||||
budgets: "Set and monitor spending budgets",
|
||||
"api-reference": "Browse API documentation and endpoints",
|
||||
"ai-hub": "Explore available AI models and providers",
|
||||
"learning-resources": "Access tutorials and documentation",
|
||||
caching: "Configure response caching settings",
|
||||
"transform-request": "Set up request transformation rules",
|
||||
"pass-through-endpoints": "Configure pass-through API endpoints",
|
||||
"cost-tracking": "Track and analyze API costs",
|
||||
"ui-themes": "Customize dashboard appearance",
|
||||
"tag-management": "Organize resources with tags",
|
||||
prompts: "Manage and version prompt templates",
|
||||
"claude-code-plugins": "Configure Claude Code plugins",
|
||||
};
|
||||
|
||||
export interface PageMetadata {
|
||||
page: string;
|
||||
label: string;
|
||||
group: string;
|
||||
description: string;
|
||||
}
|
||||
45
ui/litellm-dashboard/src/components/page_utils.ts
Normal file
45
ui/litellm-dashboard/src/components/page_utils.ts
Normal file
|
|
@ -0,0 +1,45 @@
|
|||
/**
|
||||
* Utility functions for working with navigation pages
|
||||
*/
|
||||
|
||||
import { menuGroups } from "./leftnav";
|
||||
import { pageDescriptions, PageMetadata } from "./page_metadata";
|
||||
|
||||
/**
|
||||
* Get all available pages from the navigation menu configuration
|
||||
* Used by UI Settings to display available pages for visibility control
|
||||
*/
|
||||
export const getAvailablePages = (): PageMetadata[] => {
|
||||
const pages: PageMetadata[] = [];
|
||||
|
||||
menuGroups.forEach((group) => {
|
||||
group.items.forEach((item) => {
|
||||
// Add top-level items (skip parent containers like 'tools', 'experimental', 'settings')
|
||||
if (item.page && item.page !== "tools" && item.page !== "experimental" && item.page !== "settings") {
|
||||
const label = typeof item.label === "string" ? item.label : item.key;
|
||||
pages.push({
|
||||
page: item.page,
|
||||
label: label,
|
||||
group: group.groupLabel,
|
||||
description: pageDescriptions[item.page] || "No description available",
|
||||
});
|
||||
}
|
||||
|
||||
// Add children items
|
||||
if (item.children) {
|
||||
const parentLabel = typeof item.label === "string" ? item.label : item.key;
|
||||
item.children.forEach((child) => {
|
||||
const childLabel = typeof child.label === "string" ? child.label : child.key;
|
||||
pages.push({
|
||||
page: child.page,
|
||||
label: childLabel,
|
||||
group: `${group.groupLabel} > ${parentLabel}`,
|
||||
description: pageDescriptions[child.page] || "No description available",
|
||||
});
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
return pages;
|
||||
};
|
||||
Loading…
Add table
Reference in a new issue