diff --git a/ui/litellm-dashboard/.eslintrc.json b/ui/litellm-dashboard/.eslintrc.json
index 5e6965516fa..98a8ff7fe7a 100644
--- a/ui/litellm-dashboard/.eslintrc.json
+++ b/ui/litellm-dashboard/.eslintrc.json
@@ -24,6 +24,8 @@
"src/components/molecules/notifications_manager.tsx",
"src/components/molecules/message_manager.tsx",
"src/components/ToolPolicies/PolicySelect.tsx",
+ "src/components/ToolDetail.tsx",
+ "src/components/ToolPolicies.tsx",
"src/components/GuardrailsMonitor/GuardrailConfig.tsx",
"src/components/GuardrailsMonitor/GuardrailDetail.tsx",
"src/components/GuardrailsMonitor/LogViewer.tsx",
diff --git a/ui/litellm-dashboard/src/components/ToolDetail.tsx b/ui/litellm-dashboard/src/components/ToolDetail.tsx
index 55adb593fff..bd7071fa19f 100644
--- a/ui/litellm-dashboard/src/components/ToolDetail.tsx
+++ b/ui/litellm-dashboard/src/components/ToolDetail.tsx
@@ -2,8 +2,16 @@
import { ArrowLeft as ArrowLeftOutlined, History as HistoryOutlined, Wrench as ToolOutlined } from "lucide-react";
import { useQuery, useQueryClient } from "@tanstack/react-query";
-import { Button, Select, Spin } from "antd";
-import React, { useCallback, useEffect, useMemo, useState } from "react";
+import { Button } from "@/components/ui/button";
+import {
+ Select,
+ SelectContent,
+ SelectItem,
+ SelectTrigger,
+ SelectValue,
+} from "@/components/ui/select";
+import { Skeleton } from "@/components/ui/skeleton";
+import React, { useCallback, useMemo, useState } from "react";
import TeamDropdown from "@/components/common_components/team_dropdown";
import { LogViewer } from "@/components/GuardrailsMonitor/LogViewer";
import type { LogEntry } from "@/components/GuardrailsMonitor/mockData";
@@ -16,7 +24,6 @@ import {
keyListCall,
teamListCall,
updateToolPolicy,
- type ToolPolicyOption,
type ToolPolicyOverrideRow,
} from "@/components/networking";
import type { Team } from "@/components/key_team_helpers/key_list";
@@ -210,7 +217,7 @@ export function ToolDetail({ toolName, onBack, accessToken }: ToolDetailProps) {
if (detailLoading && !detail) {
return (
-
+
);
}
@@ -218,9 +225,11 @@ export function ToolDetail({ toolName, onBack, accessToken }: ToolDetailProps) {
if (detailError && !detail) {
return (
-
} onClick={onBack} className="pl-0 mb-4">
+
+ {/* eslint-disable-next-line litellm-ui/no-raw-tailwind-colors */}
Failed to load tool details.
);
@@ -243,11 +252,11 @@ export function ToolDetail({ toolName, onBack, accessToken }: ToolDetailProps) {
}
+ variant="link"
onClick={onBack}
className="pl-0 mb-4"
>
+
Back to Tool Policies
@@ -341,9 +350,9 @@ export function ToolDetail({ toolName, onBack, accessToken }: ToolDetailProps) {
{!ov.team_id && !ov.key_hash ? "—" : ""}
+
);
};
diff --git a/ui/litellm-dashboard/src/components/organizations.tsx b/ui/litellm-dashboard/src/components/organizations.tsx
index 8038893be58..ddf6fe15e44 100644
--- a/ui/litellm-dashboard/src/components/organizations.tsx
+++ b/ui/litellm-dashboard/src/components/organizations.tsx
@@ -1,30 +1,52 @@
import OrganizationFilters, { FilterState } from "@/app/(dashboard)/organizations/OrganizationFilters";
import { Info as InfoCircleOutlined } from "lucide-react";
-import { ChevronDown as ChevronDownIcon, ChevronRight as ChevronRightIcon, RefreshCcw as RefreshIcon } from "lucide-react";
+import {
+ ChevronDown as ChevronDownIcon,
+ ChevronRight as ChevronRightIcon,
+ RefreshCcw as RefreshIcon,
+} from "lucide-react";
// eslint-disable-next-line litellm-ui/no-banned-ui-imports
import {
- Badge,
- Button,
- Card,
- Col,
- Grid,
- Icon,
- Tab,
- TabGroup,
Table,
TableBody,
TableCell,
TableHead,
TableHeaderCell,
TableRow,
- TabList,
- TabPanel,
- TabPanels,
- Text,
- TextInput,
} from "@tremor/react";
-import { Form, Input, Modal, Select as Select2, Tooltip } from "antd";
+import { Badge } from "@/components/ui/badge";
+import { Button } from "@/components/ui/button";
+import { Card } from "@/components/ui/card";
+import {
+ Dialog,
+ DialogContent,
+ DialogHeader,
+ DialogTitle,
+} from "@/components/ui/dialog";
+import { Input } from "@/components/ui/input";
+import { Label } from "@/components/ui/label";
+import {
+ Select,
+ SelectContent,
+ SelectItem,
+ SelectTrigger,
+ SelectValue,
+} from "@/components/ui/select";
+import {
+ Tabs,
+ TabsContent,
+ TabsList,
+ TabsTrigger,
+} from "@/components/ui/tabs";
+import { Textarea } from "@/components/ui/textarea";
+import {
+ Tooltip,
+ TooltipContent,
+ TooltipProvider,
+ TooltipTrigger,
+} from "@/components/ui/tooltip";
import React, { useState } from "react";
+import { Controller, FormProvider, useForm } from "react-hook-form";
import { formatNumberWithCommas } from "../utils/dataUtils";
import DeleteResourceModal from "./common_components/DeleteResourceModal";
import TableIconActionButton from "./common_components/IconActionButton/TableIconActionButtons/TableIconActionButton";
@@ -50,6 +72,18 @@ interface OrganizationsTableProps {
premiumUser: boolean;
}
+interface OrgFormValues {
+ organization_alias: string;
+ models: string[];
+ max_budget: number | null;
+ budget_duration: string;
+ tpm_limit: number | null;
+ rpm_limit: number | null;
+ allowed_vector_store_ids: string[];
+ allowed_mcp_servers_and_groups: { servers?: string[]; accessGroups?: string[] } | null;
+ metadata: string;
+}
+
export const fetchOrganizations = async (
accessToken: string,
setOrganizations: (organizations: Organization[]) => void,
@@ -72,13 +106,28 @@ const OrganizationsTable: React.FC = ({
setOrganizations,
premiumUser,
}) => {
+ void userModels;
+ void currentOrg;
+ void guardrailsList;
const [selectedOrgId, setSelectedOrgId] = useState(null);
const [editOrg, setEditOrg] = useState(false);
const [isDeleteModalOpen, setIsDeleteModalOpen] = useState(false);
const [orgToDelete, setOrgToDelete] = useState(null);
const [isDeleting, setIsDeleting] = useState(false);
const [isOrgModalVisible, setIsOrgModalVisible] = useState(false);
- const [form] = Form.useForm();
+ const form = useForm({
+ defaultValues: {
+ organization_alias: "",
+ models: [],
+ max_budget: null,
+ budget_duration: "",
+ tpm_limit: null,
+ rpm_limit: null,
+ allowed_vector_store_ids: [],
+ allowed_mcp_servers_and_groups: null,
+ metadata: "",
+ },
+ });
const [expandedAccordions, setExpandedAccordions] = useState>({});
const [showFilters, setShowFilters] = useState(false);
const [filters, setFilters] = useState({
@@ -91,7 +140,6 @@ const OrganizationsTable: React.FC = ({
const handleFilterChange = (key: keyof FilterState, value: string) => {
const newFilters = { ...filters, [key]: value };
setFilters(newFilters);
- // Call organizationListCall with the new filters
if (accessToken) {
organizationListCall(accessToken, newFilters.org_id || null, newFilters.org_alias || null)
.then((response) => {
@@ -112,7 +160,6 @@ const OrganizationsTable: React.FC = ({
sort_by: "created_at",
sort_order: "desc",
});
- // Reset organizations list
if (accessToken) {
organizationListCall(accessToken, null, null)
.then((response) => {
@@ -128,7 +175,6 @@ const OrganizationsTable: React.FC = ({
const handleDelete = (orgId: string | null) => {
if (!orgId) return;
-
setOrgToDelete(orgId);
setIsDeleteModalOpen(true);
};
@@ -143,7 +189,6 @@ const OrganizationsTable: React.FC = ({
setIsDeleteModalOpen(false);
setOrgToDelete(null);
- // Refresh organizations list
await fetchOrganizations(accessToken, setOrganizations, filters.org_id || null, filters.org_alias || null);
} catch (error) {
console.error("Error deleting organization:", error);
@@ -157,69 +202,68 @@ const OrganizationsTable: React.FC = ({
setOrgToDelete(null);
};
- const handleCreate = async (values: any) => {
+ const handleCreate = form.handleSubmit(async (values) => {
try {
if (!accessToken) return;
- console.log(`values in organizations new create call: ${JSON.stringify(values)}`);
+ const payload: Record = { ...values };
- // Transform allowed_vector_store_ids and allowed_mcp_servers_and_groups into object_permission
if (
(values.allowed_vector_store_ids && values.allowed_vector_store_ids.length > 0) ||
(values.allowed_mcp_servers_and_groups &&
- (values.allowed_mcp_servers_and_groups.servers?.length > 0 ||
- values.allowed_mcp_servers_and_groups.accessGroups?.length > 0))
+ ((values.allowed_mcp_servers_and_groups.servers?.length ?? 0) > 0 ||
+ (values.allowed_mcp_servers_and_groups.accessGroups?.length ?? 0) > 0))
) {
- values.object_permission = {};
+ payload.object_permission = {};
if (values.allowed_vector_store_ids && values.allowed_vector_store_ids.length > 0) {
- values.object_permission.vector_stores = values.allowed_vector_store_ids;
- delete values.allowed_vector_store_ids;
+ payload.object_permission.vector_stores = values.allowed_vector_store_ids;
+ delete payload.allowed_vector_store_ids;
}
if (values.allowed_mcp_servers_and_groups) {
- if (values.allowed_mcp_servers_and_groups.servers?.length > 0) {
- values.object_permission.mcp_servers = values.allowed_mcp_servers_and_groups.servers;
+ if ((values.allowed_mcp_servers_and_groups.servers?.length ?? 0) > 0) {
+ payload.object_permission.mcp_servers = values.allowed_mcp_servers_and_groups.servers;
}
- if (values.allowed_mcp_servers_and_groups.accessGroups?.length > 0) {
- values.object_permission.mcp_access_groups = values.allowed_mcp_servers_and_groups.accessGroups;
+ if ((values.allowed_mcp_servers_and_groups.accessGroups?.length ?? 0) > 0) {
+ payload.object_permission.mcp_access_groups = values.allowed_mcp_servers_and_groups.accessGroups;
}
- delete values.allowed_mcp_servers_and_groups;
+ delete payload.allowed_mcp_servers_and_groups;
}
}
- await organizationCreateCall(accessToken, values);
+ await organizationCreateCall(accessToken, payload);
NotificationsManager.success("Organization created successfully");
setIsOrgModalVisible(false);
- form.resetFields();
- // Refresh organizations list
+ form.reset();
fetchOrganizations(accessToken, setOrganizations, filters.org_id || null, filters.org_alias || null);
} catch (error) {
console.error("Error creating organization:", error);
}
- };
+ });
const handleCancel = () => {
setIsOrgModalVisible(false);
- form.resetFields();
+ form.reset();
};
if (!premiumUser) {
return (
-
+
This is a LiteLLM Enterprise feature, and requires a valid key to use. Get a trial key{" "}
here
.
-
+
);
}
return (
+
-
-
+
+
{(userRole === "Admin" || userRole === "Org Admin") && (
setIsOrgModalVisible(true)}>
+ Create New Organization
@@ -233,324 +277,429 @@ const OrganizationsTable: React.FC = ({
setEditOrg(false);
}}
accessToken={accessToken}
- is_org_admin={true} // You'll need to implement proper org admin check
+ is_org_admin={true}
is_proxy_admin={userRole === "Admin"}
userModels={userModels}
editOrg={editOrg}
/>
) : (
-
-
+
+
- Your Organizations
+ Your Organizations
- {lastRefreshed && Last Refreshed: {lastRefreshed}}
-
+ Last Refreshed: {lastRefreshed}
+
+ )}
+
+ aria-label="Refresh"
+ >
+
+
-
-
-
- Click on “Organization ID” to view organization details.
-
-
-
-
-
-
-
+
+
+
+ Click on “Organization ID” to view organization details.
+
+
+
+
+
+
+
-
-
-
- Organization ID
- Organization Name
- Created
- Spend (USD)
- Budget (USD)
- Models
- TPM / RPM Limits
- Info
- Actions
-
-
+
+
+
+
+ Organization ID
+ Organization Name
+ Created
+ Spend (USD)
+ Budget (USD)
+ Models
+ TPM / RPM Limits
+ Info
+ Actions
+
+
-
- {organizations && organizations.length > 0
- ? organizations
- .sort((a, b) => new Date(b.created_at).getTime() - new Date(a.created_at).getTime())
- .map((org: Organization) => (
-
-
-
-
+
+ {organizations && organizations.length > 0
+ ? organizations
+ .sort((a, b) => new Date(b.created_at).getTime() - new Date(a.created_at).getTime())
+ .map((org: Organization) => (
+
+
+
+
+
setSelectedOrgId(org.organization_id)}
>
{org.organization_id?.slice(0, 7)}
...
-
-
-
- {org.organization_alias}
-
- {org.created_at ? new Date(org.created_at).toLocaleDateString() : "N/A"}
-
- {formatNumberWithCommas(org.spend, 4)}
-
- {org.litellm_budget_table?.max_budget !== null &&
- org.litellm_budget_table?.max_budget !== undefined
- ? org.litellm_budget_table?.max_budget
- : "No limit"}
-
- 3 ? "px-0" : ""}
- >
-
- {Array.isArray(org.models) ? (
-
- {org.models.length === 0 ? (
-
- All Proxy Models
-
- ) : (
- <>
-
- {org.models.length > 3 && (
-
- {
- setExpandedAccordions((prev) => ({
- ...prev,
- [org.organization_id || ""]:
- !prev[org.organization_id || ""],
- }));
- }}
- />
-
+
+
{org.organization_id}
+
+
+
+
{org.organization_alias}
+
+ {org.created_at ? new Date(org.created_at).toLocaleDateString() : "N/A"}
+
+
{formatNumberWithCommas(org.spend, 4)}
+
+ {org.litellm_budget_table?.max_budget !== null &&
+ org.litellm_budget_table?.max_budget !== undefined
+ ? org.litellm_budget_table?.max_budget
+ : "No limit"}
+
+
3 ? "px-0" : ""}
+ >
+
+ {Array.isArray(org.models) ? (
+
+ {org.models.length === 0 ? (
+
+ All Proxy Models
+
+ ) : (
+
+ {org.models.length > 3 && (
+
{
+ setExpandedAccordions((prev) => ({
+ ...prev,
+ [org.organization_id || ""]:
+ !prev[org.organization_id || ""],
+ }));
+ }}
+ aria-label="Toggle models"
+ >
+ {expandedAccordions[org.organization_id || ""] ? (
+
+ ) : (
+
)}
+
+ )}
+
+ {org.models.slice(0, 3).map((model, index) =>
+ model === "all-proxy-models" ? (
+
+ All Proxy Models
+
+ ) : (
+
+ {model.length > 30
+ ? `${getModelDisplayName(model).slice(0, 30)}...`
+ : getModelDisplayName(model)}
+
+ ),
+ )}
+ {org.models.length > 3 &&
+ !expandedAccordions[org.organization_id || ""] && (
+
+ +{org.models.length - 3}{" "}
+ {org.models.length - 3 === 1 ? "more model" : "more models"}
+
+ )}
+ {expandedAccordions[org.organization_id || ""] && (
- {org.models.slice(0, 3).map((model, index) =>
+ {org.models.slice(3).map((model, index) =>
model === "all-proxy-models" ? (
-
- All Proxy Models
+
+ All Proxy Models
) : (
-
-
- {model.length > 30
- ? `${getModelDisplayName(model).slice(0, 30)}...`
- : getModelDisplayName(model)}
-
+
+ {model.length > 30
+ ? `${getModelDisplayName(model).slice(0, 30)}...`
+ : getModelDisplayName(model)}
),
)}
- {org.models.length > 3 &&
- !expandedAccordions[org.organization_id || ""] && (
-
-
- +{org.models.length - 3}{" "}
- {org.models.length - 3 === 1
- ? "more model"
- : "more models"}
-
-
- )}
- {expandedAccordions[org.organization_id || ""] && (
-
- {org.models.slice(3).map((model, index) =>
- model === "all-proxy-models" ? (
-
- All Proxy Models
-
- ) : (
-
-
- {model.length > 30
- ? `${getModelDisplayName(model).slice(0, 30)}...`
- : getModelDisplayName(model)}
-
-
- ),
- )}
-
- )}
-
- >
- )}
-
- ) : null}
-
-
-
-
- TPM:{" "}
- {org.litellm_budget_table?.tpm_limit
- ? org.litellm_budget_table?.tpm_limit
- : "Unlimited"}
-
- RPM:{" "}
- {org.litellm_budget_table?.rpm_limit
- ? org.litellm_budget_table?.rpm_limit
- : "Unlimited"}
-
-
-
- {org.members?.length || 0} Members
-
-
- {userRole === "Admin" && (
- <>
- {
- setSelectedOrgId(org.organization_id);
- setEditOrg(true);
- }}
- />
- handleDelete(org.organization_id)}
- />
- >
- )}
-
-
- ))
- : null}
-
-
-
-
-
-
-
-
+ )}
+
+
+ )}
+
+ ) : null}
+
+
+
+
+ TPM:{" "}
+ {org.litellm_budget_table?.tpm_limit
+ ? org.litellm_budget_table?.tpm_limit
+ : "Unlimited"}
+
+ RPM:{" "}
+ {org.litellm_budget_table?.rpm_limit
+ ? org.litellm_budget_table?.rpm_limit
+ : "Unlimited"}
+
+
+
+ {org.members?.length || 0} Members
+
+
+ {userRole === "Admin" && (
+ <>
+ {
+ setSelectedOrgId(org.organization_id);
+ setEditOrg(true);
+ }}
+ />
+ handleDelete(org.organization_id)}
+ />
+ >
+ )}
+
+
+ ))
+ : null}
+
+
+
+
+
+
+
)}
-
-
-
-
-
-
-
- form.setFieldValue("models", values)}
- context="organization"
- />
-
+
+
-
-
-
-
-
- daily
- weekly
- monthly
-
-
-
-
-
-
-
-
+
= ({
confirmLoading={isDeleting}
/>
+
);
};