diff --git a/ui/litellm-dashboard/eslint-suppressions.json b/ui/litellm-dashboard/eslint-suppressions.json
index f935af8907d..8e81dc55f31 100644
--- a/ui/litellm-dashboard/eslint-suppressions.json
+++ b/ui/litellm-dashboard/eslint-suppressions.json
@@ -697,11 +697,6 @@
"count": 1
}
},
- "src/app/(dashboard)/organizations/_components/organizations.tsx": {
- "no-restricted-imports": {
- "count": 1
- }
- },
"src/app/(dashboard)/playground/components/chat_ui/AdditionalModelSettings.tsx": {
"no-restricted-imports": {
"count": 1
diff --git a/ui/litellm-dashboard/src/app/(dashboard)/organizations/OrganizationFilters.test.tsx b/ui/litellm-dashboard/src/app/(dashboard)/organizations/OrganizationFilters.test.tsx
index 814625ff6be..37eeaf4c2af 100644
--- a/ui/litellm-dashboard/src/app/(dashboard)/organizations/OrganizationFilters.test.tsx
+++ b/ui/litellm-dashboard/src/app/(dashboard)/organizations/OrganizationFilters.test.tsx
@@ -7,8 +7,6 @@ describe("OrganizationFilters", () => {
const defaultFilters: FilterState = {
org_id: "",
org_alias: "",
- sort_by: "",
- sort_order: "asc",
};
it("should render", () => {
diff --git a/ui/litellm-dashboard/src/app/(dashboard)/organizations/OrganizationFilters.tsx b/ui/litellm-dashboard/src/app/(dashboard)/organizations/OrganizationFilters.tsx
index 5643a4bc51a..6ad2f00fdb0 100644
--- a/ui/litellm-dashboard/src/app/(dashboard)/organizations/OrganizationFilters.tsx
+++ b/ui/litellm-dashboard/src/app/(dashboard)/organizations/OrganizationFilters.tsx
@@ -14,8 +14,6 @@ interface OrganizationFiltersProps {
type FilterState = {
org_id: string;
org_alias: string;
- sort_by: string;
- sort_order: "asc" | "desc";
};
const OrganizationFilters = ({
diff --git a/ui/litellm-dashboard/src/app/(dashboard)/organizations/_components/OrganizationsPanel.test.tsx b/ui/litellm-dashboard/src/app/(dashboard)/organizations/_components/OrganizationsPanel.test.tsx
new file mode 100644
index 00000000000..d381e5e65ca
--- /dev/null
+++ b/ui/litellm-dashboard/src/app/(dashboard)/organizations/_components/OrganizationsPanel.test.tsx
@@ -0,0 +1,57 @@
+import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
+import { render, screen } from "@testing-library/react";
+import React from "react";
+import { describe, expect, it, vi } from "vitest";
+
+vi.mock("@/components/vector_store_management/VectorStoreSelector", () => ({
+ __esModule: true,
+ default: () => null,
+}));
+vi.mock("@/components/mcp_server_management/MCPServerSelector", () => ({
+ __esModule: true,
+ default: () => null,
+}));
+vi.mock("@/app/(dashboard)/hooks/useAuthorized", () => ({
+ default: () => ({
+ accessToken: null,
+ userId: null,
+ userRole: null,
+ }),
+}));
+vi.mock("./OrganizationsTable", () => ({
+ __esModule: true,
+ default: (props: { isLoading: boolean }) => (
+
isLoading:{String(props.isLoading)}
+ ),
+}));
+
+import OrganizationsPanel from "./OrganizationsPanel";
+
+const renderWithQueryClient = (ui: React.ReactElement) => {
+ const queryClient = new QueryClient({
+ defaultOptions: { queries: { retry: false } },
+ });
+ return render({ui} );
+};
+
+describe("OrganizationsPanel", () => {
+ it("gates non-premium users behind the enterprise notice", () => {
+ renderWithQueryClient( );
+
+ expect(screen.getByText(/LiteLLM Enterprise feature/i)).toBeInTheDocument();
+ expect(screen.queryByText("+ Create New Organization")).not.toBeInTheDocument();
+ });
+
+ it("shows the create button for a premium admin", () => {
+ renderWithQueryClient( );
+
+ expect(screen.getByText("+ Create New Organization")).toBeInTheDocument();
+ });
+
+ it("resolves the loading skeleton to false when the query is disabled (no token)", () => {
+ renderWithQueryClient( );
+
+ // A disabled React Query keeps isPending true forever; feeding isLoading avoids a stuck skeleton.
+ expect(screen.getByTestId("organizations-table")).toHaveTextContent("isLoading:false");
+ });
+});
diff --git a/ui/litellm-dashboard/src/app/(dashboard)/organizations/_components/OrganizationsPanel.tsx b/ui/litellm-dashboard/src/app/(dashboard)/organizations/_components/OrganizationsPanel.tsx
new file mode 100644
index 00000000000..9f7e029a1d4
--- /dev/null
+++ b/ui/litellm-dashboard/src/app/(dashboard)/organizations/_components/OrganizationsPanel.tsx
@@ -0,0 +1,299 @@
+import { organizationKeys, useOrganizations } from "@/app/(dashboard)/hooks/organizations/useOrganizations";
+import { useUserModels } from "@/app/(dashboard)/hooks/models/useModels";
+import OrganizationFilters, { FilterState } from "@/app/(dashboard)/organizations/OrganizationFilters";
+import { InfoCircleOutlined } from "@ant-design/icons";
+import { Form, Input, Modal, Select as Select2, Tooltip } from "antd";
+import { useQueryClient } from "@tanstack/react-query";
+import React, { useState } from "react";
+import DeleteResourceModal from "@/components/common_components/DeleteResourceModal";
+import MCPServerSelector from "@/components/mcp_server_management/MCPServerSelector";
+import { ModelSelect } from "@/components/ModelSelect/ModelSelect";
+import NotificationsManager from "@/components/molecules/notifications_manager";
+import { organizationCreateCall, organizationDeleteCall } from "@/components/networking";
+import OrganizationInfoView from "@/components/organization/organization_view";
+import NumericalInput from "@/components/shared/numerical_input";
+import { Button } from "@/components/ui/button";
+import VectorStoreSelector from "@/components/vector_store_management/VectorStoreSelector";
+
+import OrganizationsTable from "./OrganizationsTable";
+
+interface OrganizationsPanelProps {
+ userRole: string;
+ accessToken: string | null;
+ premiumUser: boolean;
+}
+
+const OrganizationsPanel: React.FC = ({ userRole, accessToken, premiumUser }) => {
+ 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 [showFilters, setShowFilters] = useState(false);
+ const [filters, setFilters] = useState({ org_id: "", org_alias: "" });
+
+ const queryClient = useQueryClient();
+ const { data: organizations = [], isLoading } = useOrganizations({
+ org_id: filters.org_id,
+ org_alias: filters.org_alias,
+ });
+ const { data: userModels = [] } = useUserModels();
+
+ const searchActive = Boolean(filters.org_id || filters.org_alias);
+
+ const refetchOrganizations = () => queryClient.invalidateQueries({ queryKey: organizationKeys.lists() });
+
+ const handleFilterChange = (key: keyof FilterState, value: string) => {
+ setFilters((previousFilters) => ({ ...previousFilters, [key]: value }));
+ };
+
+ const handleFilterReset = () => {
+ setFilters({ org_id: "", org_alias: "" });
+ };
+
+ const handleDelete = (orgId: string | null) => {
+ if (!orgId) return;
+
+ setOrgToDelete(orgId);
+ setIsDeleteModalOpen(true);
+ };
+
+ const confirmDelete = async () => {
+ if (!orgToDelete || !accessToken) return;
+
+ try {
+ setIsDeleting(true);
+ await organizationDeleteCall(accessToken, orgToDelete);
+ NotificationsManager.success("Organization deleted successfully");
+
+ setIsDeleteModalOpen(false);
+ setOrgToDelete(null);
+ await refetchOrganizations();
+ } catch (error) {
+ console.error("Error deleting organization:", error);
+ } finally {
+ setIsDeleting(false);
+ }
+ };
+
+ const cancelDelete = () => {
+ setIsDeleteModalOpen(false);
+ setOrgToDelete(null);
+ };
+
+ const handleCreate = async (values: any) => {
+ try {
+ if (!accessToken) return;
+
+ // 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.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;
+ }
+ 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.accessGroups?.length > 0) {
+ values.object_permission.mcp_access_groups = values.allowed_mcp_servers_and_groups.accessGroups;
+ }
+ delete values.allowed_mcp_servers_and_groups;
+ }
+ }
+
+ await organizationCreateCall(accessToken, values);
+ NotificationsManager.success("Organization created successfully");
+ setIsOrgModalVisible(false);
+ form.resetFields();
+ await refetchOrganizations();
+ } catch (error) {
+ console.error("Error creating organization:", error);
+ }
+ };
+
+ const handleCancel = () => {
+ setIsOrgModalVisible(false);
+ form.resetFields();
+ };
+
+ 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
+
+ )}
+
+ {selectedOrgId ? (
+
{
+ setSelectedOrgId(null);
+ setEditOrg(false);
+ }}
+ accessToken={accessToken}
+ is_org_admin={true}
+ is_proxy_admin={userRole === "Admin"}
+ userModels={userModels}
+ editOrg={editOrg}
+ />
+ ) : (
+ <>
+ Click on an organization ID to view its details.
+
+ {
+ setSelectedOrgId(organizationId);
+ setEditOrg(true);
+ }}
+ onDeleteClick={handleDelete}
+ />
+ >
+ )}
+
+
+
+
+
+
+ form.setFieldValue("models", values)}
+ context="organization"
+ />
+
+
+
+
+
+
+
+ daily
+ weekly
+ monthly
+
+
+
+
+
+
+
+
+
+
+ Allowed Vector Stores{" "}
+
+
+
+
+ }
+ name="allowed_vector_store_ids"
+ className="mt-4"
+ help="Select vector stores this organization can access. Leave empty for access to all vector stores"
+ >
+ form.setFieldValue("allowed_vector_store_ids", values)}
+ value={form.getFieldValue("allowed_vector_store_ids")}
+ accessToken={accessToken || ""}
+ placeholder="Select vector stores (optional)"
+ />
+
+
+
+ Allowed MCP Servers{" "}
+
+
+
+
+ }
+ name="allowed_mcp_servers_and_groups"
+ className="mt-4"
+ help="Select MCP servers and access groups this organization can access."
+ >
+ form.setFieldValue("allowed_mcp_servers_and_groups", values)}
+ value={form.getFieldValue("allowed_mcp_servers_and_groups")}
+ accessToken={accessToken || ""}
+ placeholder="Select MCP servers and access groups (optional)"
+ />
+
+
+
+
+
+
+
+ Create Organization
+
+
+
+
+
+
+ );
+};
+
+export default OrganizationsPanel;
diff --git a/ui/litellm-dashboard/src/app/(dashboard)/organizations/_components/OrganizationsTable.test.tsx b/ui/litellm-dashboard/src/app/(dashboard)/organizations/_components/OrganizationsTable.test.tsx
new file mode 100644
index 00000000000..a06c5c885e3
--- /dev/null
+++ b/ui/litellm-dashboard/src/app/(dashboard)/organizations/_components/OrganizationsTable.test.tsx
@@ -0,0 +1,188 @@
+import { render, screen, within } from "@testing-library/react";
+import userEvent from "@testing-library/user-event";
+import React from "react";
+import { describe, expect, it, vi } from "vitest";
+
+import { Organization } from "@/components/networking";
+
+import OrganizationsTable from "./OrganizationsTable";
+
+const makeOrganization = (overrides: Partial = {}): Organization => ({
+ organization_id: "org-alpha",
+ organization_alias: "Alpha",
+ budget_id: "budget-1",
+ metadata: {},
+ models: [],
+ spend: 0,
+ model_spend: {},
+ created_at: "2023-01-01T00:00:00Z",
+ created_by: "someone",
+ updated_at: "2023-01-01T00:00:00Z",
+ updated_by: "someone",
+ litellm_budget_table: null,
+ teams: null,
+ users: null,
+ members: null,
+ ...overrides,
+});
+
+const baseProps = {
+ isLoading: false,
+ userRole: "Admin",
+ searchActive: false,
+ onOrganizationClick: vi.fn(),
+ onEditClick: vi.fn(),
+ onDeleteClick: vi.fn(),
+};
+
+describe("OrganizationsTable", () => {
+ it("renders every column header", () => {
+ render( );
+ for (const header of [
+ "Organization ID",
+ "Organization Name",
+ "Created",
+ "Spend (USD)",
+ "Budget (USD)",
+ "Models",
+ "TPM / RPM Limits",
+ "Members",
+ ]) {
+ expect(screen.getByText(header)).toBeInTheDocument();
+ }
+ });
+
+ it("opens the detail view when the organization ID cell is clicked", async () => {
+ const user = userEvent.setup();
+ const onOrganizationClick = vi.fn();
+ render(
+ ,
+ );
+
+ await user.click(screen.getByText("org-123"));
+
+ expect(onOrganizationClick).toHaveBeenCalledWith("org-123");
+ });
+
+ it("edits and deletes an organization through the ⋯ actions menu (admin)", async () => {
+ const user = userEvent.setup();
+ const onEditClick = vi.fn();
+ const onDeleteClick = vi.fn();
+ render(
+ ,
+ );
+
+ await user.click(screen.getByTestId("organization-actions-org-9"));
+ await user.click(await screen.findByTestId("organization-action-edit"));
+ expect(onEditClick).toHaveBeenCalledWith("org-9");
+
+ await user.click(screen.getByTestId("organization-actions-org-9"));
+ await user.click(await screen.findByTestId("organization-action-delete"));
+ expect(onDeleteClick).toHaveBeenCalledWith("org-9");
+ });
+
+ it("hides the row actions menu from non-admins", () => {
+ render(
+ ,
+ );
+
+ expect(screen.queryByTestId("organization-actions-org-9")).not.toBeInTheDocument();
+ });
+
+ it("sorts by created_at descending by default", () => {
+ render(
+ ,
+ );
+
+ const rows = screen.getAllByRole("row");
+ // rows[0] is the header row; the newest organization must lead the body.
+ expect(within(rows[1]).getByText("Newer")).toBeInTheDocument();
+ expect(within(rows[2]).getByText("Older")).toBeInTheDocument();
+ });
+
+ it("renders budget, limits, members, and models for a fully-populated organization", () => {
+ render(
+ ,
+ );
+
+ expect(screen.getByText("$100.00")).toBeInTheDocument();
+ expect(screen.getByText("TPM: 1000")).toBeInTheDocument();
+ expect(screen.getByText("RPM: 60")).toBeInTheDocument();
+ expect(screen.getByText("3 Members")).toBeInTheDocument();
+ // Five models, three visible -> the shared ModelsCell collapses the rest.
+ expect(screen.getByText("+2 more")).toBeInTheDocument();
+ });
+
+ it("shows Unlimited budget and All Proxy Models when unset", () => {
+ render(
+ ,
+ );
+
+ expect(screen.getByText("All Proxy Models")).toBeInTheDocument();
+ // Budget shows a standalone "Unlimited"; the limits fall back inline.
+ expect(screen.getByText("Unlimited")).toBeInTheDocument();
+ expect(screen.getByText("TPM: Unlimited")).toBeInTheDocument();
+ expect(screen.getByText("RPM: Unlimited")).toBeInTheDocument();
+ });
+
+ it("renders loading skeletons instead of rows while loading", () => {
+ render(
+ ,
+ );
+
+ expect(screen.getAllByTestId("skeleton-row").length).toBeGreaterThan(0);
+ expect(screen.queryByText("ShouldNotShow")).not.toBeInTheDocument();
+ });
+
+ it("uses a search-aware empty state", () => {
+ const { rerender } = render( );
+ expect(screen.getByText("No organizations yet")).toBeInTheDocument();
+
+ rerender( );
+ expect(screen.getByText("No matching organizations")).toBeInTheDocument();
+ });
+});
diff --git a/ui/litellm-dashboard/src/app/(dashboard)/organizations/_components/OrganizationsTable.tsx b/ui/litellm-dashboard/src/app/(dashboard)/organizations/_components/OrganizationsTable.tsx
new file mode 100644
index 00000000000..8e68a57d2f7
--- /dev/null
+++ b/ui/litellm-dashboard/src/app/(dashboard)/organizations/_components/OrganizationsTable.tsx
@@ -0,0 +1,75 @@
+"use client";
+
+import { SortingState } from "@tanstack/react-table";
+import { Building2, SearchX } from "lucide-react";
+import React, { useMemo, useState } from "react";
+
+import { DataTable } from "@/components/shared/DataTable";
+import { Organization } from "@/components/networking";
+
+import { getOrganizationsTableColumns } from "./OrganizationsTableColumns";
+
+interface OrganizationsTableProps {
+ organizations: Organization[];
+ isLoading: boolean;
+ userRole: string;
+ searchActive: boolean;
+ onOrganizationClick: (organizationId: string) => void;
+ onEditClick: (organizationId: string) => void;
+ onDeleteClick: (organizationId: string) => void;
+}
+
+const DEFAULT_SORTING: SortingState = [{ id: "created_at", desc: true }];
+
+function EmptyState({ searchActive }: { searchActive: boolean }) {
+ const Icon = searchActive ? SearchX : Building2;
+ return (
+
+
+
+
+
+ {searchActive ? "No matching organizations" : "No organizations yet"}
+
+
+ {searchActive
+ ? "No organizations match your search. Try a different name or ID."
+ : "Create an organization to group teams, models, and budgets."}
+
+
+ );
+}
+
+const OrganizationsTable: React.FC = ({
+ organizations,
+ isLoading,
+ userRole,
+ searchActive,
+ onOrganizationClick,
+ onEditClick,
+ onDeleteClick,
+}) => {
+ const [sorting, setSorting] = useState(DEFAULT_SORTING);
+
+ const columns = useMemo(() => {
+ const deps = { userRole, onOrganizationClick, onEditClick, onDeleteClick };
+ return getOrganizationsTableColumns(deps);
+ }, [userRole, onOrganizationClick, onEditClick, onDeleteClick]);
+
+ return (
+ organization.organization_id || String(index)}
+ sortingMode="client"
+ sorting={sorting}
+ onSortingChange={setSorting}
+ isLoading={isLoading}
+ loadingMessage="Loading organizations…"
+ noDataMessage={ }
+ size="compact"
+ />
+ );
+};
+
+export default OrganizationsTable;
diff --git a/ui/litellm-dashboard/src/app/(dashboard)/organizations/_components/OrganizationsTableColumns.tsx b/ui/litellm-dashboard/src/app/(dashboard)/organizations/_components/OrganizationsTableColumns.tsx
new file mode 100644
index 00000000000..31f6a00916c
--- /dev/null
+++ b/ui/litellm-dashboard/src/app/(dashboard)/organizations/_components/OrganizationsTableColumns.tsx
@@ -0,0 +1,186 @@
+"use client";
+
+import { ColumnDef } from "@tanstack/react-table";
+import { MoreHorizontal, Pencil, Trash2 } from "lucide-react";
+
+import { DataTableSortHeader } from "@/components/shared/DataTable";
+import { DateCell, IdentityCell, ModelsCell, MoneyCell } from "@/components/shared/table_cells";
+import { Organization } from "@/components/networking";
+import { buttonVariants } from "@/components/ui/button";
+import {
+ DropdownMenu,
+ DropdownMenuContent,
+ DropdownMenuItem,
+ DropdownMenuTrigger,
+} from "@/components/ui/dropdown-menu";
+import { cn } from "@/lib/cva.config";
+
+interface OrganizationBudget {
+ max_budget?: number | null;
+ tpm_limit?: number | null;
+ rpm_limit?: number | null;
+}
+
+const getOrganizationBudget = (organization: Organization): OrganizationBudget =>
+ (organization.litellm_budget_table ?? {}) as OrganizationBudget;
+
+function OrganizationLimitsCell({ organization }: { organization: Organization }) {
+ const { tpm_limit, rpm_limit } = getOrganizationBudget(organization);
+ return (
+
+ TPM: {tpm_limit ? tpm_limit : "Unlimited"}
+ RPM: {rpm_limit ? rpm_limit : "Unlimited"}
+
+ );
+}
+
+interface OrganizationRowActionsProps {
+ organization: Organization;
+ onEditClick: (organizationId: string) => void;
+ onDeleteClick: (organizationId: string) => void;
+}
+
+function OrganizationRowActions({ organization, onEditClick, onDeleteClick }: OrganizationRowActionsProps) {
+ return (
+
+
+
+
+
+ onEditClick(organization.organization_id)}
+ >
+
+ Edit
+
+ onDeleteClick(organization.organization_id)}
+ >
+
+ Delete
+
+
+
+ );
+}
+
+export interface OrganizationsTableColumnsDeps {
+ userRole: string;
+ onOrganizationClick: (organizationId: string) => void;
+ onEditClick: (organizationId: string) => void;
+ onDeleteClick: (organizationId: string) => void;
+}
+
+export const getOrganizationsTableColumns = ({
+ userRole,
+ onOrganizationClick,
+ onEditClick,
+ onDeleteClick,
+}: OrganizationsTableColumnsDeps): ColumnDef[] => [
+ {
+ id: "organization_id",
+ accessorKey: "organization_id",
+ meta: { title: "Organization ID" },
+ header: ({ column }) => ,
+ size: 220,
+ enableSorting: true,
+ cell: ({ row }) => (
+ onOrganizationClick(row.original.organization_id)}
+ />
+ ),
+ },
+ {
+ id: "organization_alias",
+ accessorKey: "organization_alias",
+ meta: { title: "Organization Name" },
+ header: ({ column }) => ,
+ size: 200,
+ enableSorting: true,
+ cell: ({ row }) => {
+ const alias = row.original.organization_alias;
+ return (
+
+ {alias || "-"}
+
+ );
+ },
+ },
+ {
+ id: "created_at",
+ accessorKey: "created_at",
+ sortingFn: "datetime",
+ meta: { title: "Created" },
+ header: ({ column }) => ,
+ size: 130,
+ enableSorting: true,
+ cell: ({ row }) => ,
+ },
+ {
+ id: "spend",
+ accessorKey: "spend",
+ meta: { title: "Spend (USD)" },
+ header: ({ column }) => ,
+ size: 120,
+ enableSorting: true,
+ cell: ({ row }) => ,
+ },
+ {
+ id: "max_budget",
+ meta: { title: "Budget (USD)" },
+ header: "Budget (USD)",
+ size: 120,
+ enableSorting: false,
+ cell: ({ row }) => (
+
+ ),
+ },
+ {
+ id: "models",
+ meta: { title: "Models", skeleton: "chips" },
+ header: "Models",
+ size: 260,
+ enableSorting: false,
+ cell: ({ row }) => ,
+ },
+ {
+ id: "limits",
+ meta: { title: "TPM / RPM Limits" },
+ header: "TPM / RPM Limits",
+ size: 150,
+ enableSorting: false,
+ cell: ({ row }) => ,
+ },
+ {
+ id: "members",
+ meta: { title: "Members" },
+ header: "Members",
+ size: 100,
+ enableSorting: false,
+ cell: ({ row }) => {row.original.members?.length ?? 0} Members ,
+ },
+ {
+ id: "actions",
+ meta: { className: "text-right", headerClassName: "text-right" },
+ header: () => Actions ,
+ size: 64,
+ enableSorting: false,
+ enableHiding: false,
+ cell: ({ row }) =>
+ userRole === "Admin" ? (
+
+
+
+ ) : null,
+ },
+];
diff --git a/ui/litellm-dashboard/src/app/(dashboard)/organizations/_components/organizations.test.tsx b/ui/litellm-dashboard/src/app/(dashboard)/organizations/_components/organizations.test.tsx
deleted file mode 100644
index 75a6d30ac2e..00000000000
--- a/ui/litellm-dashboard/src/app/(dashboard)/organizations/_components/organizations.test.tsx
+++ /dev/null
@@ -1,39 +0,0 @@
-import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
-import { render } from "@testing-library/react";
-import React from "react";
-import { describe, expect, it, vi } from "vitest";
-
-vi.mock("@/components/vector_store_management/VectorStoreSelector", () => ({
- __esModule: true,
- default: () => null,
-}));
-vi.mock("@/components/mcp_server_management/MCPServerSelector", () => ({
- __esModule: true,
- default: () => null,
-}));
-vi.mock("@/app/(dashboard)/hooks/useAuthorized", () => ({
- default: () => ({
- accessToken: null,
- userId: null,
- userRole: null,
- }),
-}));
-
-import OrganizationsTable from "./organizations";
-
-const renderWithQueryClient = (ui: React.ReactElement) => {
- const queryClient = new QueryClient({
- defaultOptions: { queries: { retry: false } },
- });
- return render({ui} );
-};
-
-describe("OrganizationsTable", () => {
- it("should render the OrganizationsTable component", () => {
- const { getByText } = renderWithQueryClient(
- ,
- );
-
- expect(getByText("+ Create New Organization")).toBeInTheDocument();
- });
-});
diff --git a/ui/litellm-dashboard/src/app/(dashboard)/organizations/_components/organizations.tsx b/ui/litellm-dashboard/src/app/(dashboard)/organizations/_components/organizations.tsx
deleted file mode 100644
index 87d8010759d..00000000000
--- a/ui/litellm-dashboard/src/app/(dashboard)/organizations/_components/organizations.tsx
+++ /dev/null
@@ -1,535 +0,0 @@
-import { organizationKeys, useOrganizations } from "@/app/(dashboard)/hooks/organizations/useOrganizations";
-import { useUserModels } from "@/app/(dashboard)/hooks/models/useModels";
-import OrganizationFilters, { FilterState } from "@/app/(dashboard)/organizations/OrganizationFilters";
-import { InfoCircleOutlined } from "@ant-design/icons";
-import { ChevronDownIcon, ChevronRightIcon, RefreshIcon } from "@heroicons/react/outline";
-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 { useQueryClient } from "@tanstack/react-query";
-import React, { useState } from "react";
-import { DateCell, IdCell, MoneyCell } from "@/components/shared/table_cells";
-import DeleteResourceModal from "@/components/common_components/DeleteResourceModal";
-import TableIconActionButton from "@/components/common_components/IconActionButton/TableIconActionButtons/TableIconActionButton";
-import { getModelDisplayName } from "@/components/key_team_helpers/fetch_available_models_team_key";
-import MCPServerSelector from "@/components/mcp_server_management/MCPServerSelector";
-import { ModelSelect } from "@/components/ModelSelect/ModelSelect";
-import NotificationsManager from "@/components/molecules/notifications_manager";
-import {
- Organization,
- organizationCreateCall,
- organizationDeleteCall,
- organizationListCall,
-} from "@/components/networking";
-import OrganizationInfoView from "@/components/organization/organization_view";
-import NumericalInput from "@/components/shared/numerical_input";
-import VectorStoreSelector from "@/components/vector_store_management/VectorStoreSelector";
-
-interface OrganizationsTableProps {
- userRole: string;
- accessToken: string | null;
- lastRefreshed?: string;
- handleRefreshClick?: () => void;
- premiumUser: boolean;
-}
-
-export const fetchOrganizations = async (
- accessToken: string,
- setOrganizations: (organizations: Organization[]) => void,
- org_id: string | null = null,
- org_alias: string | null = null,
-) => {
- const organizations = await organizationListCall(accessToken, org_id, org_alias);
- setOrganizations(organizations);
-};
-
-const OrganizationsTable: React.FC = ({
- userRole,
- accessToken,
- lastRefreshed,
- handleRefreshClick,
- premiumUser,
-}) => {
- 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 [expandedAccordions, setExpandedAccordions] = useState>({});
- const [showFilters, setShowFilters] = useState(false);
- const [filters, setFilters] = useState({
- org_id: "",
- org_alias: "",
- sort_by: "created_at",
- sort_order: "desc",
- });
-
- const queryClient = useQueryClient();
- const { data: organizations = [] } = useOrganizations({ org_id: filters.org_id, org_alias: filters.org_alias });
- const { data: userModels = [] } = useUserModels();
-
- const refetchOrganizations = () => queryClient.invalidateQueries({ queryKey: organizationKeys.lists() });
-
- const handleFilterChange = (key: keyof FilterState, value: string) => {
- setFilters((previousFilters) => ({ ...previousFilters, [key]: value }));
- };
-
- const handleFilterReset = () => {
- setFilters({
- org_id: "",
- org_alias: "",
- sort_by: "created_at",
- sort_order: "desc",
- });
- };
-
- const handleDelete = (orgId: string | null) => {
- if (!orgId) return;
-
- setOrgToDelete(orgId);
- setIsDeleteModalOpen(true);
- };
-
- const confirmDelete = async () => {
- if (!orgToDelete || !accessToken) return;
-
- try {
- setIsDeleting(true);
- await organizationDeleteCall(accessToken, orgToDelete);
- NotificationsManager.success("Organization deleted successfully");
-
- setIsDeleteModalOpen(false);
- setOrgToDelete(null);
- await refetchOrganizations();
- } catch (error) {
- console.error("Error deleting organization:", error);
- } finally {
- setIsDeleting(false);
- }
- };
-
- const cancelDelete = () => {
- setIsDeleteModalOpen(false);
- setOrgToDelete(null);
- };
-
- const handleCreate = async (values: any) => {
- try {
- if (!accessToken) return;
-
- // 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.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;
- }
- 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.accessGroups?.length > 0) {
- values.object_permission.mcp_access_groups = values.allowed_mcp_servers_and_groups.accessGroups;
- }
- delete values.allowed_mcp_servers_and_groups;
- }
- }
-
- await organizationCreateCall(accessToken, values);
- NotificationsManager.success("Organization created successfully");
- setIsOrgModalVisible(false);
- form.resetFields();
- await refetchOrganizations();
- } catch (error) {
- console.error("Error creating organization:", error);
- }
- };
-
- const handleCancel = () => {
- setIsOrgModalVisible(false);
- form.resetFields();
- };
-
- 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
-
- )}
- {selectedOrgId ? (
- {
- setSelectedOrgId(null);
- setEditOrg(false);
- }}
- accessToken={accessToken}
- is_org_admin={true} // You'll need to implement proper org admin check
- is_proxy_admin={userRole === "Admin"}
- userModels={userModels}
- editOrg={editOrg}
- />
- ) : (
-
-
-
- Your Organizations
-
-
- {lastRefreshed && Last Refreshed: {lastRefreshed} }
-
-
-
-
-
- Click on “Organization ID” to view organization details.
-
-
-
-
-
-
-
- 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) => (
-
-
-
-
- {org.organization_alias}
-
-
-
-
-
-
-
-
-
- 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.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(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}
-
-
-
-
-
-
-
-
- )}
-
-
-
-
-
-
-
- form.setFieldValue("models", values)}
- context="organization"
- />
-
-
-
-
-
-
-
- daily
- weekly
- monthly
-
-
-
-
-
-
-
-
-
-
- Allowed Vector Stores{" "}
-
-
-
-
- }
- name="allowed_vector_store_ids"
- className="mt-4"
- help="Select vector stores this organization can access. Leave empty for access to all vector stores"
- >
- form.setFieldValue("allowed_vector_store_ids", values)}
- value={form.getFieldValue("allowed_vector_store_ids")}
- accessToken={accessToken || ""}
- placeholder="Select vector stores (optional)"
- />
-
-
-
- Allowed MCP Servers{" "}
-
-
-
-
- }
- name="allowed_mcp_servers_and_groups"
- className="mt-4"
- help="Select MCP servers and access groups this organization can access."
- >
- form.setFieldValue("allowed_mcp_servers_and_groups", values)}
- value={form.getFieldValue("allowed_mcp_servers_and_groups")}
- accessToken={accessToken || ""}
- placeholder="Select MCP servers and access groups (optional)"
- />
-
-
-
-
-
-
-
- Create Organization
-
-
-
-
-
-
- );
-};
-
-export default OrganizationsTable;
diff --git a/ui/litellm-dashboard/src/app/(dashboard)/organizations/page.tsx b/ui/litellm-dashboard/src/app/(dashboard)/organizations/page.tsx
index 649e54f63eb..a492a572580 100644
--- a/ui/litellm-dashboard/src/app/(dashboard)/organizations/page.tsx
+++ b/ui/litellm-dashboard/src/app/(dashboard)/organizations/page.tsx
@@ -1,9 +1,9 @@
"use client";
-import OrganizationsTable from "./_components/organizations";
+import OrganizationsPanel from "./_components/OrganizationsPanel";
import useAuthorized from "@/app/(dashboard)/hooks/useAuthorized";
export default function OrganizationsPage() {
const { accessToken, userRole, premiumUser } = useAuthorized();
- return ;
+ return ;
}