From e0b96c4265ba4eb4640a947ac844bd0aff3d7f4d Mon Sep 17 00:00:00 2001 From: yuneng-jiang Date: Mon, 29 Dec 2025 17:03:11 -0800 Subject: [PATCH 1/2] refactor keys table --- .../VirtualKeysTable.test.tsx} | 18 +-- .../VirtualKeysTable.tsx} | 102 +++------------ .../key_team_helpers/filter_logic.tsx | 4 +- .../organisms/create_key_button.tsx | 2 +- .../organisms/regenerate_key_modal.tsx | 33 ++--- .../KeyInfoView.handleKeyUpdate.test.tsx | 51 ++++---- .../templates/key_info_view.test.tsx | 119 +++++++++--------- .../components/templates/key_info_view.tsx | 16 +-- .../components/templates/view_key_table.tsx | 113 +---------------- 9 files changed, 122 insertions(+), 336 deletions(-) rename ui/litellm-dashboard/src/components/{all_keys_table.test.tsx => VirtualKeysPage/VirtualKeysTable.test.tsx} (90%) rename ui/litellm-dashboard/src/components/{all_keys_table.tsx => VirtualKeysPage/VirtualKeysTable.tsx} (89%) diff --git a/ui/litellm-dashboard/src/components/all_keys_table.test.tsx b/ui/litellm-dashboard/src/components/VirtualKeysPage/VirtualKeysTable.test.tsx similarity index 90% rename from ui/litellm-dashboard/src/components/all_keys_table.test.tsx rename to ui/litellm-dashboard/src/components/VirtualKeysPage/VirtualKeysTable.test.tsx index 1458143d77e..4f40ed99a84 100644 --- a/ui/litellm-dashboard/src/components/all_keys_table.test.tsx +++ b/ui/litellm-dashboard/src/components/VirtualKeysPage/VirtualKeysTable.test.tsx @@ -1,13 +1,13 @@ import { screen, waitFor } from "@testing-library/react"; import { vi, it, expect } from "vitest"; -import { renderWithProviders } from "../../tests/test-utils"; -import { AllKeysTable } from "./all_keys_table"; -import { KeyResponse, Team } from "./key_team_helpers/key_list"; -import { Organization } from "./networking"; +import { renderWithProviders } from "../../../tests/test-utils"; +import { VirtualKeysTable } from "./VirtualKeysTable"; +import { KeyResponse, Team } from "../key_team_helpers/key_list"; +import { Organization } from "../networking"; // Mock network calls vi.mock("./networking", async (importOriginal) => { - const actual = await importOriginal(); + const actual = await importOriginal(); return { ...actual, userListCall: vi.fn().mockResolvedValue({ @@ -131,7 +131,7 @@ const mockOrganization: Organization = { members: [], }; -it("should render AllKeysTable component", () => { +it("should render VirtualKeysTable component", () => { const mockProps = { keys: [mockKey], setKeys: vi.fn(), @@ -156,7 +156,7 @@ it("should render AllKeysTable component", () => { premiumUser: false, }; - renderWithProviders(); + renderWithProviders(); expect(screen.getByText("Test Key Alias")).toBeInTheDocument(); }); @@ -186,7 +186,7 @@ it("should display key information correctly", async () => { premiumUser: false, }; - renderWithProviders(); + renderWithProviders(); await waitFor(() => { expect(screen.getByText("Test Key Alias")).toBeInTheDocument(); @@ -220,7 +220,7 @@ it("should display user email correctly", async () => { premiumUser: false, }; - renderWithProviders(); + renderWithProviders(); await waitFor(() => { expect(screen.getByText("user@example.com")).toBeInTheDocument(); diff --git a/ui/litellm-dashboard/src/components/all_keys_table.tsx b/ui/litellm-dashboard/src/components/VirtualKeysPage/VirtualKeysTable.tsx similarity index 89% rename from ui/litellm-dashboard/src/components/all_keys_table.tsx rename to ui/litellm-dashboard/src/components/VirtualKeysPage/VirtualKeysTable.tsx index 10f678be03f..128129ce66a 100644 --- a/ui/litellm-dashboard/src/components/all_keys_table.tsx +++ b/ui/litellm-dashboard/src/components/VirtualKeysPage/VirtualKeysTable.tsx @@ -4,8 +4,6 @@ import { formatNumberWithCommas, updateExistingKeys } from "@/utils/dataUtils"; import { ChevronDownIcon, ChevronRightIcon, ChevronUpIcon, SwitchVerticalIcon } from "@heroicons/react/outline"; import { ColumnDef, - ColumnResizeDirection, - ColumnResizeMode, flexRender, getCoreRowModel, getSortedRowModel, @@ -16,8 +14,6 @@ import { Badge, Button, Icon, - Select, - SelectItem, Table, TableBody, TableCell, @@ -28,14 +24,15 @@ import { } from "@tremor/react"; import { Tooltip } from "antd"; import React, { useEffect, useState } from "react"; -import { getModelDisplayName } from "./key_team_helpers/fetch_available_models_team_key"; -import { useFilterLogic } from "./key_team_helpers/filter_logic"; -import { KeyResponse, Team } from "./key_team_helpers/key_list"; -import FilterComponent, { FilterOption } from "./molecules/filter"; -import { Organization } from "./networking"; -import KeyInfoView from "./templates/key_info_view"; +import { getModelDisplayName } from "../key_team_helpers/fetch_available_models_team_key"; +import { useFilterLogic } from "../key_team_helpers/filter_logic"; +import { KeyResponse, Team } from "../key_team_helpers/key_list"; +import FilterComponent, { FilterOption } from "../molecules/filter"; +import { Organization } from "../networking"; +import KeyInfoView from "../templates/key_info_view"; +import useAuthorized from "@/app/(dashboard)/hooks/useAuthorized"; -interface AllKeysTableProps { +interface VirtualKeysTableProps { keys: KeyResponse[]; setKeys: (keys: KeyResponse[] | ((prev: KeyResponse[]) => KeyResponse[])) => void; isLoading?: boolean; @@ -51,9 +48,6 @@ interface AllKeysTableProps { setSelectedTeam: (team: Team | null) => void; selectedKeyAlias: string | null; setSelectedKeyAlias: Setter; - accessToken: string | null; - userID: string | null; - userRole: string | null; organizations: Organization[] | null; setCurrentOrg: React.Dispatch>; refresh?: () => void; @@ -62,61 +56,14 @@ interface AllKeysTableProps { sortBy: string; sortOrder: "asc" | "desc"; }; - premiumUser: boolean; - setAccessToken?: (token: string) => void; } -// Define columns similar to our logs table - -interface UserResponse { - user_id: string; - user_email: string; - user_role: string; -} - -const TeamFilter = ({ - teams, - selectedTeam, - setSelectedTeam, -}: { - teams: Team[] | null; - selectedTeam: Team | null; - setSelectedTeam: (team: Team | null) => void; -}) => { - const handleTeamChange = (value: string) => { - const team = teams?.find((t) => t.team_id === value); - setSelectedTeam(team || null); - }; - - return ( -
-
- Where Team is - -
-
- ); -}; - /** - * AllKeysTable – a new table for keys that mimics the table styling used in view_logs. + * VirtualKeysTable – a new table for keys that mimics the table styling used in view_logs. * The team selector and filtering have been removed so that all keys are shown. */ -export function AllKeysTable({ +export function VirtualKeysTable({ keys, setKeys, isLoading = false, @@ -124,24 +71,13 @@ export function AllKeysTable({ onPageChange, pageSize = 50, teams, - selectedTeam, - setSelectedTeam, - selectedKeyAlias, - setSelectedKeyAlias, - accessToken, - userID, - userRole, organizations, - setCurrentOrg, refresh, onSortChange, currentSort, - premiumUser, - setAccessToken, -}: AllKeysTableProps) { +}: VirtualKeysTableProps) { + const { accessToken, userId: userID, userRole, premiumUser } = useAuthorized(); const [selectedKeyId, setSelectedKeyId] = useState(null); - const [columnResizeMode, setColumnResizeMode] = React.useState("onChange"); - const [columnResizeDirection, setColumnResizeDirection] = React.useState("ltr"); const [sorting, setSorting] = React.useState(() => { if (currentSort) { return [ @@ -167,7 +103,6 @@ export function AllKeysTable({ keys, teams, organizations, - accessToken, }); // Add a useEffect to call refresh when a key is created @@ -557,8 +492,8 @@ export function AllKeysTable({ const table = useReactTable({ data: filteredKeys, columns: columns.filter((col) => col.id !== "expander"), - columnResizeMode, - columnResizeDirection, + columnResizeMode: "onChange", + columnResizeDirection: "ltr", state: { sorting, }, @@ -619,12 +554,7 @@ export function AllKeysTable({ setKeys((keys) => keys.filter((key) => key.token !== selectedKeyId)); if (refresh) refresh(); // Minimal fix: refresh the full key list after a delete }} - accessToken={accessToken} - userID={userID} - userRole={userRole} teams={allTeams} - premiumUser={premiumUser} - setAccessToken={setAccessToken} /> ) : (
@@ -736,10 +666,6 @@ export function AllKeysTable({ userSelect: "none", touchAction: "none", opacity: header.column.getIsResizing() ? 1 : 0, - transform: - columnResizeMode === "onEnd" && header.column.getIsResizing() - ? `translateX(${(table.options.columnResizeDirection === "rtl" ? -1 : 1) * (table.getState().columnSizingInfo.deltaOffset ?? 0)}px)` - : "", }} />
diff --git a/ui/litellm-dashboard/src/components/key_team_helpers/filter_logic.tsx b/ui/litellm-dashboard/src/components/key_team_helpers/filter_logic.tsx index a074a5484f6..5428efb28aa 100644 --- a/ui/litellm-dashboard/src/components/key_team_helpers/filter_logic.tsx +++ b/ui/litellm-dashboard/src/components/key_team_helpers/filter_logic.tsx @@ -6,6 +6,7 @@ import { useQuery } from "@tanstack/react-query"; import { fetchAllKeyAliases, fetchAllOrganizations, fetchAllTeams } from "./filter_helpers"; import { debounce } from "lodash"; import { defaultPageSize } from "../constants"; +import useAuthorized from "@/app/(dashboard)/hooks/useAuthorized"; export interface FilterState { "Team ID": string; @@ -21,12 +22,10 @@ export function useFilterLogic({ keys, teams, organizations, - accessToken, }: { keys: KeyResponse[]; teams: Team[] | null; organizations: Organization[] | null; - accessToken: string | null; }) { const defaultFilters: FilterState = { "Team ID": "", @@ -36,6 +35,7 @@ export function useFilterLogic({ "Sort By": "created_at", "Sort Order": "desc", }; + const { accessToken } = useAuthorized(); const [filters, setFilters] = useState(defaultFilters); const [allTeams, setAllTeams] = useState(teams || []); const [allOrganizations, setAllOrganizations] = useState(organizations || []); diff --git a/ui/litellm-dashboard/src/components/organisms/create_key_button.tsx b/ui/litellm-dashboard/src/components/organisms/create_key_button.tsx index f3ad803e27c..83ef444d59e 100644 --- a/ui/litellm-dashboard/src/components/organisms/create_key_button.tsx +++ b/ui/litellm-dashboard/src/components/organisms/create_key_button.tsx @@ -401,7 +401,7 @@ const CreateKey: React.FC = ({ console.log("key create Response:", response); // Add the data to the state in the parent component - // Also directly update the keys list in AllKeysTable without an API call + // Also directly update the keys list in VirtualKeysTable without an API call addKey(response); setApiKey(response["key"]); diff --git a/ui/litellm-dashboard/src/components/organisms/regenerate_key_modal.tsx b/ui/litellm-dashboard/src/components/organisms/regenerate_key_modal.tsx index 583a097449f..a4339e11920 100644 --- a/ui/litellm-dashboard/src/components/organisms/regenerate_key_modal.tsx +++ b/ui/litellm-dashboard/src/components/organisms/regenerate_key_modal.tsx @@ -1,31 +1,22 @@ -import React, { useEffect, useState } from "react"; -import { Button, Text, TextInput, Title, Grid, Col } from "@tremor/react"; -import { Modal, Form, InputNumber } from "antd"; +import useAuthorized from "@/app/(dashboard)/hooks/useAuthorized"; +import { Button, Col, Grid, Text, TextInput, Title } from "@tremor/react"; +import { Form, InputNumber, Modal } from "antd"; import { add } from "date-fns"; -import { regenerateKeyCall } from "../networking"; -import { KeyResponse } from "../key_team_helpers/key_list"; +import { useEffect, useState } from "react"; import { CopyToClipboard } from "react-copy-to-clipboard"; +import { KeyResponse } from "../key_team_helpers/key_list"; import NotificationManager from "../molecules/notifications_manager"; +import { regenerateKeyCall } from "../networking"; interface RegenerateKeyModalProps { selectedToken: KeyResponse | null; visible: boolean; onClose: () => void; - accessToken: string | null; - premiumUser: boolean; - setAccessToken?: (token: string) => void; onKeyUpdate?: (updatedKeyData: Partial) => void; } -export function RegenerateKeyModal({ - selectedToken, - visible, - onClose, - accessToken, - premiumUser, - setAccessToken, - onKeyUpdate, -}: RegenerateKeyModalProps) { +export function RegenerateKeyModal({ selectedToken, visible, onClose, onKeyUpdate }: RegenerateKeyModalProps) { + const { accessToken } = useAuthorized(); const [form] = Form.useForm(); const [regeneratedKey, setRegeneratedKey] = useState(null); const [regenerateFormData, setRegenerateFormData] = useState(null); @@ -132,14 +123,6 @@ export function RegenerateKeyModal({ console.log("Updated key data with new token:", updatedKeyData); // Debug log - // If user regenerated their own auth key, update both local and global access tokens - if (isOwnKey) { - setCurrentAccessToken(response.key); // Update local token immediately - if (setAccessToken) { - setAccessToken(response.key); // Update global token - } - } - // Update the parent component with new key data if (onKeyUpdate) { onKeyUpdate(updatedKeyData); diff --git a/ui/litellm-dashboard/src/components/templates/KeyInfoView.handleKeyUpdate.test.tsx b/ui/litellm-dashboard/src/components/templates/KeyInfoView.handleKeyUpdate.test.tsx index a4680d7992e..c2c32390cd8 100644 --- a/ui/litellm-dashboard/src/components/templates/KeyInfoView.handleKeyUpdate.test.tsx +++ b/ui/litellm-dashboard/src/components/templates/KeyInfoView.handleKeyUpdate.test.tsx @@ -2,15 +2,21 @@ import { fireEvent, render, screen, waitFor } from "@testing-library/react"; import { beforeEach, describe, expect, it, vi } from "vitest"; // ---- Hoisted shared mocks (safe to use inside vi.mock factories) ---- -const { keyUpdateCallMock, keyDeleteCallMock } = vi.hoisted(() => { +const { keyUpdateCallMock, keyDeleteCallMock, mockUseAuthorized } = vi.hoisted(() => { return { keyUpdateCallMock: vi.fn().mockResolvedValue({}), keyDeleteCallMock: vi.fn().mockResolvedValue({}), + mockUseAuthorized: vi.fn(), }; }); // ---- Module mocks ---- +// Mock useAuthorized hook FIRST (before component imports it) +vi.mock("@/app/(dashboard)/hooks/useAuthorized", () => ({ + default: mockUseAuthorized, +})); + // Networking: wire the hoisted fns so we can assert calls later vi.mock("../networking", () => { return { @@ -29,12 +35,12 @@ vi.mock("../molecules/notifications_manager", () => { return { default: Notifications }; }); -// Roles: ensure 'admin' has write access and include all role helper functions +// Roles: ensure 'Admin' has write access and include all role helper functions vi.mock("../../utils/roles", async (importOriginal) => { const actual = await importOriginal(); return { ...actual, - rolesWithWriteAccess: ["admin"], + rolesWithWriteAccess: ["Admin"], }; }); @@ -243,20 +249,6 @@ vi.mock("@/app/(dashboard)/hooks/useTeams", () => ({ })), })); -// Mock useAuthorized hook to avoid Next.js router dependency -vi.mock("@/app/(dashboard)/hooks/useAuthorized", () => ({ - default: vi.fn(() => ({ - accessToken: "access_abc", - userId: "user_1", - userRole: "admin", - premiumUser: true, - token: "token_123", - userEmail: "test@example.com", - disabledPersonalKeyCreation: false, - showSSOBanner: false, - })), -})); - // KeyEditView mock: triggers onSubmit with our injected form values vi.mock("./key_edit_view", async () => { const React = await import("react"); @@ -302,21 +294,29 @@ const baseKeyData = { next_rotation_at: null as any, }; -const renderView = (premiumUser: boolean) => - render( +const renderView = (premiumUser: boolean) => { + // Configure the mock for this test + mockUseAuthorized.mockReturnValue({ + accessToken: "access_abc", + userId: "user_1", + userRole: "Admin", + premiumUser, + token: "token_123", + userEmail: "test@example.com", + disabledPersonalKeyCreation: false, + showSSOBanner: false, + }); + + return render( {}} keyData={baseKeyData as any} onKeyDataUpdate={() => {}} - accessToken="access_abc" - userID="user_1" - userRole="admin" teams={[]} - premiumUser={premiumUser} - setAccessToken={() => {}} />, ); +}; beforeEach(() => { vi.clearAllMocks(); @@ -328,6 +328,7 @@ describe("KeyInfoView handleKeyUpdate premium guard", () => { it("removes guardrails & prompts for non-premium users and prevents metadata.guardrails", async () => { renderView(false); // premiumUser = false + fireEvent.click(screen.getByText("Settings")); fireEvent.click(screen.getByText("Edit Settings")); (globalThis as any).__TEST_FORM_VALUES = { token: "tok_123", @@ -352,6 +353,7 @@ describe("KeyInfoView handleKeyUpdate premium guard", () => { it("preserves guardrails & prompts for premium users and includes metadata.guardrails", async () => { renderView(true); // premiumUser = true + fireEvent.click(screen.getByText("Settings")); fireEvent.click(screen.getByText("Edit Settings")); (globalThis as any).__TEST_FORM_VALUES = { token: "tok_123", @@ -378,6 +380,7 @@ describe("KeyInfoView handleKeyUpdate empty strings", () => { it(`maps empty strings to null for ${limit}`, async () => { renderView(true); // premiumUser = true + fireEvent.click(screen.getByText("Settings")); fireEvent.click(screen.getByText("Edit Settings")); (globalThis as any).__TEST_FORM_VALUES = { token: "tok_123", diff --git a/ui/litellm-dashboard/src/components/templates/key_info_view.test.tsx b/ui/litellm-dashboard/src/components/templates/key_info_view.test.tsx index 008b4cbe6ca..96d4b574a18 100644 --- a/ui/litellm-dashboard/src/components/templates/key_info_view.test.tsx +++ b/ui/litellm-dashboard/src/components/templates/key_info_view.test.tsx @@ -1,4 +1,5 @@ import useTeams from "@/app/(dashboard)/hooks/useTeams"; +import useAuthorized from "@/app/(dashboard)/hooks/useAuthorized"; import { render, screen, waitFor } from "@testing-library/react"; import { beforeEach, describe, expect, it, vi } from "vitest"; import { KeyResponse, Team } from "../key_team_helpers/key_list"; @@ -8,6 +9,10 @@ vi.mock("@/app/(dashboard)/hooks/useTeams", () => ({ default: vi.fn(), })); +vi.mock("@/app/(dashboard)/hooks/useAuthorized", () => ({ + default: vi.fn(), +})); + describe("KeyInfoView", () => { beforeEach(() => { vi.mocked(useTeams).mockReturnValue({ @@ -85,17 +90,27 @@ describe("KeyInfoView", () => { key_rotation_at: undefined, }; + // Base mock for useAuthorized hook + const baseUseAuthorizedMock = { + accessToken: "test-token", + userId: "test-user", + userRole: "admin", + premiumUser: true, + token: "test-token", + userEmail: null, + disabledPersonalKeyCreation: null, + showSSOBanner: false, + }; + it("should render tags", async () => { + vi.mocked(useAuthorized).mockReturnValue(baseUseAuthorizedMock); + const { getByText } = render( {}} keyId={"test-key-id"} onKeyDataUpdate={() => {}} - accessToken={"test-token"} - userID={"test-user"} - userRole={"admin"} - premiumUser={true} teams={[]} />, ); @@ -105,16 +120,14 @@ describe("KeyInfoView", () => { }); it("should not render tags in metadata textarea", async () => { + vi.mocked(useAuthorized).mockReturnValue(baseUseAuthorizedMock); + const { container, getByText } = render( {}} keyId={"test-key-id"} onKeyDataUpdate={() => {}} - accessToken={"test-token"} - userID={"test-user"} - userRole={"admin"} - premiumUser={true} teams={[]} />, ); @@ -132,19 +145,15 @@ describe("KeyInfoView", () => { setTeams: vi.fn(), }); + vi.mocked(useAuthorized).mockReturnValue({ + ...baseUseAuthorizedMock, + userId: "proxy-admin-user", + userRole: "proxy_admin", + }); + const keyData = { ...MOCK_KEY_DATA, user_id: "other-user-id" }; render( - {}} - keyId={"test-key-id"} - onKeyDataUpdate={() => {}} - accessToken={"test-token"} - userID={"proxy-admin-user"} - userRole={"proxy_admin"} - premiumUser={true} - teams={[]} - />, + {}} keyId={"test-key-id"} onKeyDataUpdate={() => {}} teams={[]} />, ); await waitFor(() => { @@ -180,19 +189,15 @@ describe("KeyInfoView", () => { setTeams: vi.fn(), }); + vi.mocked(useAuthorized).mockReturnValue({ + ...baseUseAuthorizedMock, + userId: teamAdminUserId, + userRole: "user", + }); + const keyData = { ...MOCK_KEY_DATA, team_id: teamId, user_id: "other-user-id" }; render( - {}} - keyId={"test-key-id"} - onKeyDataUpdate={() => {}} - accessToken={"test-token"} - userID={teamAdminUserId} - userRole={"user"} - premiumUser={true} - teams={[]} - />, + {}} keyId={"test-key-id"} onKeyDataUpdate={() => {}} teams={[]} />, ); await waitFor(() => { @@ -207,20 +212,16 @@ describe("KeyInfoView", () => { setTeams: vi.fn(), }); + vi.mocked(useAuthorized).mockReturnValue({ + ...baseUseAuthorizedMock, + userId: "owner-user-id", + userRole: "user", + }); + const ownerUserId = "owner-user-id"; const keyData = { ...MOCK_KEY_DATA, user_id: ownerUserId }; render( - {}} - keyId={"test-key-id"} - onKeyDataUpdate={() => {}} - accessToken={"test-token"} - userID={ownerUserId} - userRole={"user"} - premiumUser={true} - teams={[]} - />, + {}} keyId={"test-key-id"} onKeyDataUpdate={() => {}} teams={[]} />, ); await waitFor(() => { @@ -235,19 +236,15 @@ describe("KeyInfoView", () => { setTeams: vi.fn(), }); + vi.mocked(useAuthorized).mockReturnValue({ + ...baseUseAuthorizedMock, + userId: "other-user-id", + userRole: "user", + }); + const keyData = { ...MOCK_KEY_DATA, user_id: "owner-user-id" }; render( - {}} - keyId={"test-key-id"} - onKeyDataUpdate={() => {}} - accessToken={"test-token"} - userID={"other-user-id"} - userRole={"user"} - premiumUser={true} - teams={[]} - />, + {}} keyId={"test-key-id"} onKeyDataUpdate={() => {}} teams={[]} />, ); await waitFor(() => { @@ -262,20 +259,16 @@ describe("KeyInfoView", () => { setTeams: vi.fn(), }); + vi.mocked(useAuthorized).mockReturnValue({ + ...baseUseAuthorizedMock, + userId: "internal-viewer-user-id", + userRole: "Internal Viewer", + }); + const ownerUserId = "internal-viewer-user-id"; const keyData = { ...MOCK_KEY_DATA, user_id: ownerUserId }; render( - {}} - keyId={"test-key-id"} - onKeyDataUpdate={() => {}} - accessToken={"test-token"} - userID={ownerUserId} - userRole={"Internal Viewer"} - premiumUser={true} - teams={[]} - />, + {}} keyId={"test-key-id"} onKeyDataUpdate={() => {}} teams={[]} />, ); await waitFor(() => { diff --git a/ui/litellm-dashboard/src/components/templates/key_info_view.tsx b/ui/litellm-dashboard/src/components/templates/key_info_view.tsx index 47abda58c29..76361cca2fd 100644 --- a/ui/litellm-dashboard/src/components/templates/key_info_view.tsx +++ b/ui/litellm-dashboard/src/components/templates/key_info_view.tsx @@ -19,6 +19,7 @@ import ObjectPermissionsView from "../object_permissions_view"; import { RegenerateKeyModal } from "../organisms/regenerate_key_modal"; import { parseErrorMessage } from "../shared/errorUtils"; import { KeyEditView } from "./key_edit_view"; +import useAuthorized from "@/app/(dashboard)/hooks/useAuthorized"; interface KeyInfoViewProps { keyId: string; @@ -26,12 +27,7 @@ interface KeyInfoViewProps { keyData: KeyResponse | undefined; onKeyDataUpdate?: (data: Partial) => void; onDelete?: () => void; - accessToken: string | null; - userID: string | null; - userRole: string | null; teams: any[] | null; - premiumUser: boolean; - setAccessToken?: (token: string) => void; backButtonText?: string; } @@ -43,19 +39,14 @@ interface KeyInfoViewProps { * ───────────────────────────────────────────────────────────────────────── */ export default function KeyInfoView({ - keyId, onClose, keyData, - accessToken, - userID, - userRole, teams, onKeyDataUpdate, onDelete, - premiumUser, - setAccessToken, backButtonText = "Back to Keys", }: KeyInfoViewProps) { + const { accessToken, userId: userID, userRole, premiumUser } = useAuthorized(); const { teams: teamsData } = useTeams(); const [isEditing, setIsEditing] = useState(false); const [form] = Form.useForm(); @@ -400,9 +391,6 @@ export default function KeyInfoView({ selectedToken={currentKeyData} visible={isRegenerateModalOpen} onClose={() => setIsRegenerateModalOpen(false)} - accessToken={accessToken} - premiumUser={premiumUser} - setAccessToken={setAccessToken} onKeyUpdate={handleRegenerateKeyUpdate} /> diff --git a/ui/litellm-dashboard/src/components/templates/view_key_table.tsx b/ui/litellm-dashboard/src/components/templates/view_key_table.tsx index f8a86f0fad4..de347fd3305 100644 --- a/ui/litellm-dashboard/src/components/templates/view_key_table.tsx +++ b/ui/litellm-dashboard/src/components/templates/view_key_table.tsx @@ -5,27 +5,10 @@ import { Form, InputNumber, Modal } from "antd"; import { add } from "date-fns"; import React, { useEffect, useState } from "react"; import { CopyToClipboard } from "react-copy-to-clipboard"; -import { AllKeysTable } from "../all_keys_table"; -import { fetchAvailableModelsForTeamOrKey } from "../key_team_helpers/fetch_available_models_team_key"; +import { VirtualKeysTable } from "../VirtualKeysPage/VirtualKeysTable"; import useKeyList, { KeyResponse, Team } from "../key_team_helpers/key_list"; -import { keyDeleteCall, Organization, regenerateKeyCall } from "../networking"; - import NotificationManager from "../molecules/notifications_manager"; - -interface EditKeyModalProps { - visible: boolean; - onCancel: () => void; - token: any; // Assuming TeamType is a type representing your team object - onSubmit: (data: FormData) => void; // Assuming FormData is the type of data to be submitted -} - -interface ModelLimitModalProps { - visible: boolean; - onCancel: () => void; - token: KeyResponse; - onSubmit: (updatedMetadata: any) => void; - accessToken: string; -} +import { keyDeleteCall, Organization, regenerateKeyCall } from "../networking"; // Define the props type interface ViewKeyTableProps { @@ -47,39 +30,6 @@ interface ViewKeyTableProps { setAccessToken?: (token: string) => void; } -interface ItemData { - key_alias: string | null; - key_name: string; - spend: string; - max_budget: string | null; - models: string[]; - tpm_limit: string | null; - rpm_limit: string | null; - token: string; - token_id: string | null; - id: number; - team_id: string; - metadata: any; - user_id: string | null; - expires: any; - budget_duration: string | null; - budget_reset_at: string | null; - // Add any other properties that exist in the item data -} - -interface ModelLimits { - [key: string]: number; // Index signature allowing string keys -} - -interface CombinedLimit { - tpm: number; - rpm: number; -} - -interface CombinedLimits { - [key: string]: CombinedLimit; // Index signature allowing string keys -} - const ViewKeyTable: React.FC = ({ userID, userRole, @@ -98,24 +48,10 @@ const ViewKeyTable: React.FC = ({ createClicked, setAccessToken, }) => { - const [isButtonClicked, setIsButtonClicked] = useState(false); const [isDeleteModalOpen, setIsDeleteModalOpen] = useState(false); const [keyToDelete, setKeyToDelete] = useState(null); const [deleteConfirmInput, setDeleteConfirmInput] = useState(""); - const [selectedItem, setSelectedItem] = useState(null); - const [spendData, setSpendData] = useState<{ day: string; spend: number }[] | null>(null); - // NEW: Declare filter states for team and key alias. - const [teamFilter, setTeamFilter] = useState(selectedTeam?.team_id || ""); - - // Keep the team filter in sync with the incoming prop. - useEffect(() => { - setTeamFilter(selectedTeam?.team_id || ""); - }, [selectedTeam]); - - // Build a memoized filters object for the backend call. - - // Pass filters into the hook so the API call includes these query parameters. const { keys, isLoading, error, pagination, refresh, setKeys } = useKeyList({ selectedTeam: selectedTeam || undefined, currentOrg, @@ -129,21 +65,13 @@ const ViewKeyTable: React.FC = ({ refresh({ page: newPage }); }; - const [editModalVisible, setEditModalVisible] = useState(false); - const [infoDialogVisible, setInfoDialogVisible] = useState(false); const [selectedToken, setSelectedToken] = useState(null); - const [userModels, setUserModels] = useState([]); - const initialKnownTeamIDs: Set = new Set(); - const [modelLimitModalVisible, setModelLimitModalVisible] = useState(false); const [regenerateDialogVisible, setRegenerateDialogVisible] = useState(false); const [regeneratedKey, setRegeneratedKey] = useState(null); const [regenerateFormData, setRegenerateFormData] = useState(null); const [regenerateForm] = Form.useForm(); const [newExpiryTime, setNewExpiryTime] = useState(null); - const [knownTeamIDs, setKnownTeamIDs] = useState(initialKnownTeamIDs); - const [guardrailsList, setGuardrailsList] = useState([]); - useEffect(() => { const calculateNewExpiryTime = (duration: string | undefined) => { if (!duration) { @@ -190,36 +118,6 @@ const ViewKeyTable: React.FC = ({ console.log("calculateNewExpiryTime:", newExpiryTime); }, [selectedToken, regenerateFormData?.duration]); - useEffect(() => { - const fetchUserModels = async () => { - try { - if (userID === null || userRole === null || accessToken === null) { - return; - } - - const models = await fetchAvailableModelsForTeamOrKey(userID, userRole, accessToken); - if (models) { - setUserModels(models); - } - } catch (error) { - NotificationManager.error({ description: "Error fetching user models" }); - } - }; - - fetchUserModels(); - }, [accessToken, userID, userRole]); - - useEffect(() => { - if (teams) { - const teamIDSet: Set = new Set(); - teams.forEach((team: any, index: number) => { - const team_obj: string = team.team_id; - teamIDSet.add(team_obj); - }); - setKnownTeamIDs(teamIDSet); - } - }, [teams]); - const confirmDelete = async () => { if (keyToDelete == null || keys == null) { return; @@ -305,7 +203,7 @@ const ViewKeyTable: React.FC = ({ return (
- = ({ teams={teams} selectedTeam={selectedTeam} setSelectedTeam={setSelectedTeam} - accessToken={accessToken} - userID={userID} - userRole={userRole} organizations={organizations} setCurrentOrg={setCurrentOrg} refresh={refresh} selectedKeyAlias={selectedKeyAlias} setSelectedKeyAlias={setSelectedKeyAlias} - premiumUser={premiumUser} - setAccessToken={setAccessToken} /> {isDeleteModalOpen && From 5cdd88bc5c716a95f4b2d9ed600afd6cad85a64a Mon Sep 17 00:00:00 2001 From: yuneng-jiang Date: Mon, 29 Dec 2025 17:06:16 -0800 Subject: [PATCH 2/2] fixing build --- .../UsagePage/components/EntityUsage/TopKeyView.tsx | 11 +---------- .../src/components/view_logs/index.tsx | 9 +-------- 2 files changed, 2 insertions(+), 18 deletions(-) diff --git a/ui/litellm-dashboard/src/components/UsagePage/components/EntityUsage/TopKeyView.tsx b/ui/litellm-dashboard/src/components/UsagePage/components/EntityUsage/TopKeyView.tsx index 8f6bc411630..db268286007 100644 --- a/ui/litellm-dashboard/src/components/UsagePage/components/EntityUsage/TopKeyView.tsx +++ b/ui/litellm-dashboard/src/components/UsagePage/components/EntityUsage/TopKeyView.tsx @@ -268,16 +268,7 @@ const TopKeyView: React.FC = ({ topKeys, teams, showTags = fals {/* Content */}
- +
diff --git a/ui/litellm-dashboard/src/components/view_logs/index.tsx b/ui/litellm-dashboard/src/components/view_logs/index.tsx index 34aa0ef3be8..2a94284fca5 100644 --- a/ui/litellm-dashboard/src/components/view_logs/index.tsx +++ b/ui/litellm-dashboard/src/components/view_logs/index.tsx @@ -526,12 +526,8 @@ export default function SpendLogsTable({ setSelectedKeyIdInfoView(null)} - premiumUser={premiumUser} backButtonText="Back to Logs" /> ) : selectedSessionId ? ( @@ -968,10 +964,7 @@ export function RequestViewer({ row }: { row: Row }) { {/* Cost Breakdown - Show if cost breakdown data is available */} - + {/* Configuration Info Message - Show when data is missing */}