From 617ad8194c0a7642b0c21ad5acb9020ce2d4ac00 Mon Sep 17 00:00:00 2001 From: Yuneng Jiang Date: Fri, 14 Aug 2026 03:30:31 -0700 Subject: [PATCH] refactor(ui): migrate key info and permissions views off antd and tremor Replaces Ant Design and Tremor in the key info header and detail view, the agent and vector store permission panels, and the team member permissions table. - antd Popover, Dropdown and Modal become HoverCard, DropdownMenu and Dialog, and Tremor TabGroup becomes Tabs with keepMounted so panel state survives a tab switch the way Tremor's did - the key id copy control moves to the shared CopyButton, which also fixes an icon that rendered at 24px because it inherited the heading font size - antd Checkbox onChange becomes onCheckedChange - every public prop signature is unchanged, since these are shared views - three member permission tests were passing vacuously: they searched for an unchecked box by reading .checked, which is undefined on a Base UI checkbox, so the assertions sat inside an if that never ran. They now scope the checkbox to its own row and assert the toggle, the save and the revert - drops the eslint suppressions these files no longer need --- ui/litellm-dashboard/eslint-suppressions.json | 21 -- .../permissions/AgentPermissions.tsx | 31 +- .../permissions/VectorStorePermissions.tsx | 12 +- .../team/member_permissions.test.tsx | 91 +++-- .../components/team/member_permissions.tsx | 41 ++- .../components/templates/KeyInfoHeader.tsx | 252 +++++++------ .../KeyInfoView.handleKeyUpdate.test.tsx | 13 - .../components/templates/key_info_view.tsx | 338 ++++++++++-------- 8 files changed, 416 insertions(+), 383 deletions(-) diff --git a/ui/litellm-dashboard/eslint-suppressions.json b/ui/litellm-dashboard/eslint-suppressions.json index 5e322598a10..d0a5e44e424 100644 --- a/ui/litellm-dashboard/eslint-suppressions.json +++ b/ui/litellm-dashboard/eslint-suppressions.json @@ -2940,11 +2940,6 @@ "count": 1 } }, - "src/components/permissions/AgentPermissions.tsx": { - "no-restricted-imports": { - "count": 2 - } - }, "src/components/permissions/MCPServerPermissions.tsx": { "no-nested-ternary": { "count": 3 @@ -2953,11 +2948,6 @@ "count": 2 } }, - "src/components/permissions/VectorStorePermissions.tsx": { - "no-restricted-imports": { - "count": 1 - } - }, "src/components/policies/PolicySelector.tsx": { "no-nested-ternary": { "count": 1 @@ -3242,9 +3232,6 @@ "local/filename-pascal-case": { "count": 1 }, - "no-restricted-imports": { - "count": 2 - }, "react-hooks/set-state-in-effect": { "count": 1 } @@ -3259,11 +3246,6 @@ "count": 1 } }, - "src/components/templates/KeyInfoHeader.tsx": { - "no-restricted-imports": { - "count": 2 - } - }, "src/components/templates/key_edit_view.tsx": { "local/filename-pascal-case": { "count": 1 @@ -3293,9 +3275,6 @@ "no-nested-ternary": { "count": 1 }, - "no-restricted-imports": { - "count": 2 - }, "react-hooks/set-state-in-effect": { "count": 1 } diff --git a/ui/litellm-dashboard/src/components/permissions/AgentPermissions.tsx b/ui/litellm-dashboard/src/components/permissions/AgentPermissions.tsx index 11951b2decb..ee6fbbd89f5 100644 --- a/ui/litellm-dashboard/src/components/permissions/AgentPermissions.tsx +++ b/ui/litellm-dashboard/src/components/permissions/AgentPermissions.tsx @@ -1,7 +1,7 @@ import React, { useState, useEffect } from "react"; -import { Text, Badge } from "@tremor/react"; import { UserGroupIcon } from "@heroicons/react/outline"; -import { Tooltip } from "antd"; +import { Badge } from "@/components/ui/badge"; +import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from "@/components/ui/tooltip"; import { getAgentsList } from "../networking"; interface Agent { @@ -58,10 +58,8 @@ export function AgentPermissions({ agents, agentAccessGroups = [], accessToken }
- Agents - - {totalCount} - +

Agents

+ {totalCount}
{totalCount > 0 ? ( @@ -71,14 +69,17 @@ export function AgentPermissions({ agents, agentAccessGroups = [], accessToken }
{item.type === "agent" ? ( - -
- - - {getAgentDisplayName(item.value)} - -
-
+ + + }> + + + {getAgentDisplayName(item.value)} + + + {`Full ID: ${item.value}`} + + ) : (
@@ -96,7 +97,7 @@ export function AgentPermissions({ agents, agentAccessGroups = [], accessToken } ) : (
- No agents or access groups configured +

No agents or access groups configured

)}
diff --git a/ui/litellm-dashboard/src/components/permissions/VectorStorePermissions.tsx b/ui/litellm-dashboard/src/components/permissions/VectorStorePermissions.tsx index 8541d65e11f..6bf79d8a632 100644 --- a/ui/litellm-dashboard/src/components/permissions/VectorStorePermissions.tsx +++ b/ui/litellm-dashboard/src/components/permissions/VectorStorePermissions.tsx @@ -1,6 +1,6 @@ import React, { useState, useEffect } from "react"; -import { Text, Badge } from "@tremor/react"; import { DatabaseIcon } from "@heroicons/react/outline"; +import { Badge } from "@/components/ui/badge"; import { vectorStoreListCall } from "../networking"; interface VectorStoreDetails { @@ -52,10 +52,8 @@ export function VectorStorePermissions({ vectorStores, accessToken }: VectorStor
- Vector Stores - - {vectorStores.length} - +

Vector Stores

+ {vectorStores.length}
{vectorStores.length > 0 ? ( @@ -63,7 +61,7 @@ export function VectorStorePermissions({ vectorStores, accessToken }: VectorStor {vectorStores.map((store, index) => (
{getVectorStoreDisplayName(store)}
@@ -72,7 +70,7 @@ export function VectorStorePermissions({ vectorStores, accessToken }: VectorStor ) : (
- No vector stores configured +

No vector stores configured

)}
diff --git a/ui/litellm-dashboard/src/components/team/member_permissions.test.tsx b/ui/litellm-dashboard/src/components/team/member_permissions.test.tsx index 10c78331c68..652d4f8e685 100644 --- a/ui/litellm-dashboard/src/components/team/member_permissions.test.tsx +++ b/ui/litellm-dashboard/src/components/team/member_permissions.test.tsx @@ -1,5 +1,5 @@ import * as networking from "@/components/networking"; -import { act, fireEvent, screen, waitFor } from "@testing-library/react"; +import { act, fireEvent, screen, waitFor, within } from "@testing-library/react"; import { renderWithProviders } from "../../../tests/test-utils"; import { afterEach, describe, expect, it, vi } from "vitest"; import MemberPermissions from "./member_permissions"; @@ -9,6 +9,9 @@ vi.mock("@/components/networking", () => ({ teamPermissionsUpdateCall: vi.fn(), })); +const checkboxFor = (endpoint: string) => + within(screen.getByText(endpoint).closest("tr") as HTMLElement).getByRole("checkbox"); + describe("MemberPermissions", () => { afterEach(() => { vi.clearAllMocks(); @@ -69,32 +72,27 @@ describe("MemberPermissions", () => { expect(screen.getByText("Member Permissions")).toBeInTheDocument(); }); - const checkboxes = screen.getAllByRole("checkbox"); - const unselectedCheckbox = checkboxes.find((cb) => !(cb as HTMLInputElement).checked); + expect(checkboxFor("/key/generate")).toBeChecked(); + expect(checkboxFor("/key/list")).not.toBeChecked(); - if (unselectedCheckbox) { - await act(async () => { - fireEvent.click(unselectedCheckbox); - }); + await act(async () => { + fireEvent.click(checkboxFor("/key/list")); + }); - await waitFor(() => { - const saveButton = screen.getByRole("button", { name: /save changes/i }); - expect(saveButton).toBeInTheDocument(); - }); + expect(checkboxFor("/key/list")).toBeChecked(); - const saveButton = screen.getByRole("button", { name: /save changes/i }); - await act(async () => { - fireEvent.click(saveButton); - }); + const saveButton = await screen.findByRole("button", { name: /save changes/i }); + await act(async () => { + fireEvent.click(saveButton); + }); - await waitFor(() => { - expect(networking.teamPermissionsUpdateCall).toHaveBeenCalledWith( - "token-123", - "team-123", - expect.arrayContaining(["/key/generate", "/key/list"]), - ); - }); - } + await waitFor(() => { + expect(networking.teamPermissionsUpdateCall).toHaveBeenCalledWith( + "token-123", + "team-123", + expect.arrayContaining(["/key/generate", "/key/list"]), + ); + }); }); it("should render team daily activity permission with correct method and description", async () => { @@ -123,11 +121,13 @@ describe("MemberPermissions", () => { expect(screen.getByText("Member Permissions")).toBeInTheDocument(); }); - const checkboxes = screen.getAllByRole("checkbox"); - checkboxes.forEach((checkbox) => { - expect(checkbox).toBeDisabled(); + expect(checkboxFor("/key/list")).not.toBeChecked(); + + await act(async () => { + fireEvent.click(checkboxFor("/key/list")); }); + expect(checkboxFor("/key/list")).not.toBeChecked(); expect(screen.queryByRole("button", { name: /save changes/i })).not.toBeInTheDocument(); }); @@ -143,32 +143,27 @@ describe("MemberPermissions", () => { expect(screen.getByText("Member Permissions")).toBeInTheDocument(); }); - const checkboxes = screen.getAllByRole("checkbox"); - const unselectedCheckbox = checkboxes.find((cb) => !(cb as HTMLInputElement).checked); + await act(async () => { + fireEvent.click(checkboxFor("/key/list")); + }); - if (unselectedCheckbox) { - await act(async () => { - fireEvent.click(unselectedCheckbox); - }); + expect(checkboxFor("/key/list")).toBeChecked(); - await waitFor(() => { - const resetButton = screen.getByRole("button", { name: /reset/i }); - expect(resetButton).toBeInTheDocument(); - }); + vi.mocked(networking.getTeamPermissionsCall).mockResolvedValueOnce({ + all_available_permissions: ["/key/generate", "/key/list"], + team_member_permissions: ["/key/generate"], + }); - vi.mocked(networking.getTeamPermissionsCall).mockResolvedValueOnce({ - all_available_permissions: ["/key/generate", "/key/list"], - team_member_permissions: ["/key/generate"], - }); + const resetButton = await screen.findByRole("button", { name: /reset/i }); + await act(async () => { + fireEvent.click(resetButton); + }); - const resetButton = screen.getByRole("button", { name: /reset/i }); - await act(async () => { - fireEvent.click(resetButton); - }); + await waitFor(() => { + expect(networking.getTeamPermissionsCall).toHaveBeenCalledTimes(2); + }); - await waitFor(() => { - expect(networking.getTeamPermissionsCall).toHaveBeenCalledTimes(2); - }); - } + expect(checkboxFor("/key/list")).not.toBeChecked(); + expect(screen.queryByRole("button", { name: /save changes/i })).not.toBeInTheDocument(); }); }); diff --git a/ui/litellm-dashboard/src/components/team/member_permissions.tsx b/ui/litellm-dashboard/src/components/team/member_permissions.tsx index 5bbd82f4a5d..62c7d1f96da 100644 --- a/ui/litellm-dashboard/src/components/team/member_permissions.tsx +++ b/ui/litellm-dashboard/src/components/team/member_permissions.tsx @@ -1,7 +1,9 @@ import { getTeamPermissionsCall, teamPermissionsUpdateCall } from "@/components/networking"; -import { ReloadOutlined, SaveOutlined } from "@ant-design/icons"; -import { Card, Table, TableBody, TableCell, TableHead, TableHeaderCell, TableRow, Text, Title } from "@tremor/react"; -import { Button, Checkbox, Empty } from "antd"; +import { Button } from "@/components/ui/button"; +import { Card } from "@/components/ui/card"; +import { Checkbox } from "@/components/ui/checkbox"; +import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "@/components/ui/table"; +import { RotateCw, Save } from "lucide-react"; import React, { useEffect, useState } from "react"; import NotificationsManager from "../molecules/notifications_manager"; import { getPermissionInfo } from "./permission_definitions"; @@ -75,36 +77,38 @@ const MemberPermissions: React.FC = ({ teamId, accessTok const hasPermissions = permissions.length > 0; return ( - +
- Member Permissions +

Member Permissions

{canEditTeam && hasChanges && (
- -
)}
- Control what team members can do when they are not team admins. +

Control what team members can do when they are not team admins.

{hasPermissions ? (
- - +
+ - Method - Endpoint - Description - + Method + Endpoint + Description + Allow Access - + - + {permissions.map((permission) => { const permInfo = getPermissionInfo(permission); @@ -125,8 +129,9 @@ const MemberPermissions: React.FC = ({ teamId, accessTok {permInfo.description} handlePermissionChange(permission, e.target.checked)} + onCheckedChange={(checked) => handlePermissionChange(permission, checked)} disabled={!canEditTeam} /> @@ -138,7 +143,7 @@ const MemberPermissions: React.FC = ({ teamId, accessTok ) : (
- +

No permissions available

)} diff --git a/ui/litellm-dashboard/src/components/templates/KeyInfoHeader.tsx b/ui/litellm-dashboard/src/components/templates/KeyInfoHeader.tsx index d0dd782a697..f31a265da87 100644 --- a/ui/litellm-dashboard/src/components/templates/KeyInfoHeader.tsx +++ b/ui/litellm-dashboard/src/components/templates/KeyInfoHeader.tsx @@ -1,27 +1,35 @@ import React from "react"; -import { Button, Typography, Tooltip, Space, Divider, Flex, Popover, Dropdown, Tag } from "antd"; -import type { MenuProps } from "antd"; import { - ArrowLeftOutlined, - SyncOutlined, - DeleteOutlined, - PlusOutlined, - UserOutlined, - CalendarOutlined, - ClockCircleOutlined, - ThunderboltOutlined, - SafetyCertificateOutlined, - TransactionOutlined, - FieldTimeOutlined, - MoreOutlined, - StopOutlined, - CheckCircleOutlined, -} from "@ant-design/icons"; + ArrowLeft, + ArrowLeftRight, + Ban, + Calendar, + CircleCheck, + Clock, + MoreVertical, + Plus, + RefreshCw, + ShieldCheck, + Timer, + Trash2, + User, + Zap, +} from "lucide-react"; +import { Badge } from "@/components/ui/badge"; +import { Button } from "@/components/ui/button"; +import { + DropdownMenu, + DropdownMenuContent, + DropdownMenuItem, + DropdownMenuTrigger, +} from "@/components/ui/dropdown-menu"; +import { HoverCard, HoverCardContent, HoverCardTrigger } from "@/components/ui/hover-card"; +import { Separator } from "@/components/ui/separator"; +import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from "@/components/ui/tooltip"; +import CopyButton from "@/components/shared/CopyButton"; import LabeledField from "../common_components/LabeledField"; import DefaultProxyAdminTag from "../common_components/DefaultProxyAdminTag"; -const { Title, Text } = Typography; - export interface KeyInfoData { keyName: string; keyId: string; @@ -52,14 +60,12 @@ interface KeyInfoHeaderProps { function UserField({ userAlias, userEmail, userId }: { userAlias?: string | null; userEmail: string; userId: string }) { const labelEl = ( - - - - - - User - - +
+ + + + User +
); const isEmpty = !userAlias && !userEmail && !userId; @@ -68,7 +74,7 @@ function UserField({ userAlias, userEmail, userId }: { userAlias?: string | null
{labelEl}
- - + -
); @@ -87,14 +93,12 @@ function UserField({ userAlias, userEmail, userId }: { userAlias?: string | null
{label} {value ? ( - - {value} - +
+ + {value} + + +
) : ( - )} @@ -108,11 +112,18 @@ function UserField({ userAlias, userEmail, userId }: { userAlias?: string | null
{labelEl}
- - - - - + + + + + } + /> + + {popoverContent} + +
); @@ -122,11 +133,14 @@ function UserField({ userAlias, userEmail, userId }: { userAlias?: string | null
{labelEl}
- - - {displayValue} - - + + {displayValue}} + /> + + {popoverContent} + +
); @@ -146,104 +160,124 @@ export function KeyInfoHeader({ regenerateDisabled = false, regenerateTooltip, }: KeyInfoHeaderProps) { - const destructiveActionItems: MenuProps["items"] = [ - ...(onToggleBlocked - ? [ - isBlocked - ? { key: "unblock", label: "Unblock Key", icon: } - : { key: "block", label: "Block Key", icon: , danger: true }, - ] - : []), - ...(onResetSpend - ? [{ key: "reset-spend", label: "Reset Spend", icon: , danger: true }] - : []), - { key: "delete", label: "Delete Key", icon: , danger: true }, - ]; - - const handleDestructiveActionClick: MenuProps["onClick"] = ({ key }) => { - if (key === "block" || key === "unblock") onToggleBlocked?.(); - if (key === "reset-spend") onResetSpend?.(); - if (key === "delete") onDelete?.(); - }; + const regenerateButton = ( + + + + ); return (
{onCreateNew && (
-
)}
-
- -
- - + <div className="flex items-start justify-between" style={{ marginBottom: 20 }}> + <div className="min-w-0"> + <div className="flex items-center gap-2"> + <h3 className="m-0 flex items-center gap-1 text-2xl font-semibold"> {data.keyName} - + + {isBlocked && ( - }> + + Blocked - + )} - - - Key ID: {data.keyId} - +
+
+ Key ID: {data.keyId} + +
{canModifyKey && ( - - - - - - - -
- - +
+
- } /> - + } /> +
- + - - } /> +
+ } /> } + icon={} truncate copyable defaultUserIdCheck /> - +
- + - - } /> - } /> - - +
+ } /> + } /> +
+
); } 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 374e36029a0..42d1884e563 100644 --- a/ui/litellm-dashboard/src/components/templates/KeyInfoView.handleKeyUpdate.test.tsx +++ b/ui/litellm-dashboard/src/components/templates/KeyInfoView.handleKeyUpdate.test.tsx @@ -182,19 +182,6 @@ vi.mock("@heroicons/react/outline", async () => { return { ArrowLeftIcon, TrashIcon, RefreshIcon }; }); -vi.mock("lucide-react", async () => { - const React = await import("react"); - function CopyIcon() { - return React.createElement("span"); - } - (CopyIcon as any).displayName = "CopyIcon"; - function CheckIcon() { - return React.createElement("span"); - } - (CheckIcon as any).displayName = "CheckIcon"; - return { CopyIcon, CheckIcon }; -}); - // Heavy children -> async factories & local React vi.mock("../organisms/RegenerateKeyModal", () => { function RegenerateKeyModal() { 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 15d14d5abf5..a2d926dff8a 100644 --- a/ui/litellm-dashboard/src/components/templates/key_info_view.tsx +++ b/ui/litellm-dashboard/src/components/templates/key_info_view.tsx @@ -4,9 +4,12 @@ import { useUISettings } from "@/app/(dashboard)/hooks/uiSettings/useUISettings" import useTeams from "@/app/(dashboard)/hooks/useTeams"; import { formatNumberWithCommas } from "@/utils/dataUtils"; import { mapEmptyStringToNull } from "@/utils/keyUpdateUtils"; -import { ArrowLeftIcon } from "@heroicons/react/outline"; -import { Badge, Button, Card, Grid, Tab, TabGroup, TabList, TabPanel, TabPanels, Text, Title } from "@tremor/react"; -import { Modal, Tag } from "antd"; +import { ArrowLeft } from "lucide-react"; +import { Badge } from "@/components/ui/badge"; +import { Button } from "@/components/ui/button"; +import { Card } from "@/components/ui/card"; +import { Dialog, DialogContent, DialogFooter, DialogHeader, DialogTitle } from "@/components/ui/dialog"; +import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs"; import { KeyInfoHeader } from "./KeyInfoHeader"; import { useEffect, useState } from "react"; import { isProxyAdminRole, isUserTeamAdminForSingleTeam, rolesWithWriteAccess } from "../../utils/roles"; @@ -150,10 +153,11 @@ export default function KeyInfoView({ if (!currentKeyData) { return (
- - Key not found +

Key not found

); } @@ -534,93 +538,111 @@ export default function KeyInfoView({ /> {/* Reset Spend Confirmation Modal */} - setIsResetSpendModalOpen(false)} - okText="Reset" - okButtonProps={{ danger: true }} - confirmLoading={resetSpendLoading} - > -

- Reset spend for {currentKeyData?.key_alias || currentKeyData?.token_id || "this key"} to{" "} - $0? -

-

- Current spend: ${formatNumberWithCommas(currentKeyData.spend, 4)}. Spend history is preserved - in logs. This resets the current period spend counter, the same as an automatic budget reset. -

-
+ setIsResetSpendModalOpen(open)}> + + + Reset Key Spend + +

+ Reset spend for {currentKeyData?.key_alias || currentKeyData?.token_id || "this key"} to{" "} + $0? +

+

+ Current spend: ${formatNumberWithCommas(currentKeyData.spend, 4)}. Spend history is + preserved in logs. This resets the current period spend counter, the same as an automatic budget reset. +

+ + + + +
+
- setIsBlockModalOpen(false)} - okText={isBlocked ? "Unblock" : "Block"} - okButtonProps={isBlocked ? undefined : { danger: true }} - confirmLoading={blockLoading} - > -

- {isBlocked ? "Unblock" : "Block"}{" "} - {currentKeyData?.key_alias || currentKeyData?.token_id || "this key"}? -

-

- {isBlocked - ? "Requests using this key will be accepted again." - : "Requests using this key will be rejected with a 401 error until it is unblocked. The key is not deleted and can be unblocked at any time."} -

-
+ setIsBlockModalOpen(open)}> + + + {isBlocked ? "Unblock Key" : "Block Key"} + +

+ {isBlocked ? "Unblock" : "Block"}{" "} + {currentKeyData?.key_alias || currentKeyData?.token_id || "this key"}? +

+

+ {isBlocked + ? "Requests using this key will be accepted again." + : "Requests using this key will be rejected with a 401 error until it is unblocked. The key is not deleted and can be unblocked at any time."} +

+ + + + +
+
- - - Overview - Settings - + + + Overview + Settings + - +
{/* Overview Panel */} - - - - Spend + +
+ +

Spend

- ${formatNumberWithCommas(currentKeyData.spend, 4)} - of {budgetDisplay} +

${formatNumberWithCommas(currentKeyData.spend, 4)}

+

of {budgetDisplay}

{currentKeyData.budget_reset_at && ( - Resets {formatTimestamp(currentKeyData.budget_reset_at)} +

Resets {formatTimestamp(currentKeyData.budget_reset_at)}

)}
- - Rate Limits + +

Rate Limits

- TPM: {currentKeyData.tpm_limit !== null ? currentKeyData.tpm_limit : "Unlimited"} - RPM: {currentKeyData.rpm_limit !== null ? currentKeyData.rpm_limit : "Unlimited"} +

+ TPM: {currentKeyData.tpm_limit !== null ? currentKeyData.tpm_limit : "Unlimited"} +

+

+ RPM: {currentKeyData.rpm_limit !== null ? currentKeyData.rpm_limit : "Unlimited"} +

{Boolean(currentKeyData.metadata?.throttle_on_budget_exceeded) && ( - Throttle on budget exceeded: Yes +

Throttle on budget exceeded: Yes

)}
- - Models + +

Models

{currentKeyData.models && currentKeyData.models.length > 0 ? ( currentKeyData.models.map((model, index) => ( - + {model} )) ) : ( - No models specified +

No models specified

)}
- + - - Guardrails + +

Guardrails

{Array.isArray(currentKeyData.metadata?.guardrails) && currentKeyData.metadata.guardrails.length > 0 ? (
{currentKeyData.metadata.guardrails.map((guardrail: string, index: number) => ( - + {guardrail} ))}
) : ( - No guardrails configured +

No guardrails configured

)} {typeof currentKeyData.metadata?.disable_global_guardrails === "boolean" && currentKeyData.metadata.disable_global_guardrails === true && (
- Global Guardrails Disabled + Global Guardrails Disabled
)}
- - Policies + +

Policies

{Array.isArray(currentKeyData.metadata?.policies) && currentKeyData.metadata.policies.length > 0 ? (
{currentKeyData.metadata.policies.map((policy: string, index: number) => (
- {policy} - {loadingPolicies && Loading guardrails...} + + {policy} + + {loadingPolicies &&

Loading guardrails...

}
{!loadingPolicies && policyGuardrails[policy] && policyGuardrails[policy].length > 0 && (
- Resolved Guardrails: +

Resolved Guardrails:

{policyGuardrails[policy].map((guardrail: string, gIndex: number) => ( - + {guardrail} ))} @@ -675,7 +699,7 @@ export default function KeyInfoView({ ))}
) : ( - No policies configured +

No policies configured

)} @@ -697,15 +721,19 @@ export default function KeyInfoView({ nextRotationAt={currentKeyData.next_rotation_at} variant="card" /> - - +
+ {/* Settings Panel */} - - + +
- Key Settings - {!isEditing && canModifyKey && } +

Key Settings

+ {!isEditing && canModifyKey && ( + + )}
{isEditing ? ( @@ -722,29 +750,29 @@ export default function KeyInfoView({ ) : (
- Key ID - {currentKeyData.token_id || currentKeyData.token} +

Key ID

+

{currentKeyData.token_id || currentKeyData.token}

- Key Alias - {currentKeyData.key_alias || "Not Set"} +

Key Alias

+

{currentKeyData.key_alias || "Not Set"}

- Secret Key - {currentKeyData.key_name} +

Secret Key

+

{currentKeyData.key_name}

- Team ID - {currentKeyData.team_id || "Not Set"} +

Team ID

+

{currentKeyData.team_id || "Not Set"}

{enableProjectsUI && (
- Project - +

Project

+

{currentKeyData.project_id ? (() => { const project = projects?.find((p) => p.project_id === currentKeyData.project_id); @@ -753,41 +781,43 @@ export default function KeyInfoView({ : currentKeyData.project_id; })() : "Not Set"} - +

)}
- Organization - {(currentKeyData.organization_id ?? currentKeyData.org_id) || "Not Set"} +

Organization

+

{(currentKeyData.organization_id ?? currentKeyData.org_id) || "Not Set"}

- Created - {formatTimestamp(currentKeyData.created_at)} +

Created

+

{formatTimestamp(currentKeyData.created_at)}

{lastRegeneratedAt && (
- Last Regenerated +

Last Regenerated

- {formatTimestamp(lastRegeneratedAt)} - - Recent - +

{formatTimestamp(lastRegeneratedAt)}

+ Recent
)}
- Expires - {currentKeyData.expires ? formatTimestamp(currentKeyData.expires) : "Never"} +

Expires

+

+ {currentKeyData.expires ? formatTimestamp(currentKeyData.expires) : "Never"} +

{Boolean(currentKeyData.metadata?.enable_prompt_caching) && (
- Prompt Caching - Enabled (auto-injects cache_control markers on Anthropic and Bedrock Claude requests) +

Prompt Caching

+

+ Enabled (auto-injects cache_control markers on Anthropic and Bedrock Claude requests) +

)} @@ -802,31 +832,31 @@ export default function KeyInfoView({ />
- Spend - ${formatNumberWithCommas(currentKeyData.spend, 4)} USD +

Spend

+

${formatNumberWithCommas(currentKeyData.spend, 4)} USD

- Budget - +

Budget

+

{currentKeyData.max_budget !== null ? `$${formatNumberWithCommas(currentKeyData.max_budget, 2)}` : "Unlimited"} - +

- Budget Reset - +

Budget Reset

+

{currentKeyData.budget_reset_at ? `${currentKeyData.budget_duration ? `Every ${currentKeyData.budget_duration}, next ` : ""}${formatTimestamp(currentKeyData.budget_reset_at)}` : "Never"} - +

{currentKeyData.budget_fallbacks && Object.keys(currentKeyData.budget_fallbacks).length > 0 && (
- Budget Fallbacks +

Budget Fallbacks

{Object.entries(currentKeyData.budget_fallbacks).map(([model, fallbacks]) => (
@@ -841,7 +871,7 @@ export default function KeyInfoView({ {hasRouterSettings(currentKeyData.router_settings) && (
- Router Settings +

Router Settings

@@ -849,7 +879,7 @@ export default function KeyInfoView({ )}
- Tags +

Tags

{Array.isArray(currentKeyData.metadata?.tags) && currentKeyData.metadata.tags.length > 0 ? currentKeyData.metadata.tags.map((tag, index) => ( @@ -862,8 +892,8 @@ export default function KeyInfoView({
- Prompts - +

Prompts

+

{Array.isArray(currentKeyData.metadata?.prompts) && currentKeyData.metadata.prompts.length > 0 ? currentKeyData.metadata.prompts.map((prompt, index) => ( @@ -871,11 +901,11 @@ export default function KeyInfoView({ )) : "No prompts specified"} - +

- Allowed Routes +

Allowed Routes

{Array.isArray(currentKeyData.allowed_routes) && currentKeyData.allowed_routes.length > 0 ? ( currentKeyData.allowed_routes.map((route, index) => ( @@ -884,14 +914,14 @@ export default function KeyInfoView({ )) ) : ( - All routes allowed + All routes allowed )}
- Allowed Pass Through Routes - +

Allowed Pass Through Routes

+

{Array.isArray(currentKeyData.metadata?.allowed_passthrough_routes) && currentKeyData.metadata.allowed_passthrough_routes.length > 0 ? currentKeyData.metadata.allowed_passthrough_routes.map((route, index) => ( @@ -900,22 +930,22 @@ export default function KeyInfoView({ )) : "No pass through routes specified"} - +

- Disable Global Guardrails - +

Disable Global Guardrails

+

{currentKeyData.metadata?.disable_global_guardrails === true ? ( - Enabled - Global guardrails bypassed + Enabled - Global guardrails bypassed ) : ( - Disabled - Global guardrails active + Disabled - Global guardrails active )} - +

- Models +

Models

{currentKeyData.models && currentKeyData.models.length > 0 ? ( currentKeyData.models.map((model, index) => ( @@ -924,56 +954,60 @@ export default function KeyInfoView({ )) ) : ( - No models specified +

No models specified

)}
- Rate Limits - TPM: {currentKeyData.tpm_limit !== null ? currentKeyData.tpm_limit : "Unlimited"} - RPM: {currentKeyData.rpm_limit !== null ? currentKeyData.rpm_limit : "Unlimited"} - +

Rate Limits

+

+ TPM: {currentKeyData.tpm_limit !== null ? currentKeyData.tpm_limit : "Unlimited"} +

+

+ RPM: {currentKeyData.rpm_limit !== null ? currentKeyData.rpm_limit : "Unlimited"} +

+

Max Parallel Requests:{" "} {currentKeyData.max_parallel_requests !== null ? currentKeyData.max_parallel_requests : "Unlimited"} - - +

+

Model TPM Limits:{" "} {currentKeyData.metadata?.model_tpm_limit ? JSON.stringify(currentKeyData.metadata.model_tpm_limit) : "Unlimited"} - - +

+

Model RPM Limits:{" "} {currentKeyData.metadata?.model_rpm_limit ? JSON.stringify(currentKeyData.metadata.model_rpm_limit) : "Unlimited"} - - +

+

Tag RPM Limits:{" "} {currentKeyData.metadata?.tag_rpm_limit && Object.keys(currentKeyData.metadata.tag_rpm_limit).length > 0 ? JSON.stringify(currentKeyData.metadata.tag_rpm_limit) : "Unlimited"} - - +

+

Estimated Output Tokens:{" "} {currentKeyData.metadata?.default_estimated_output_tokens != null ? String(currentKeyData.metadata.default_estimated_output_tokens) : "Default"} - - +

+

Estimated Output Tokens Per Model:{" "} {currentKeyData.metadata?.default_estimated_output_tokens_per_model ? JSON.stringify(currentKeyData.metadata.default_estimated_output_tokens_per_model) : "Default"} - +

- Metadata +

Metadata

                       {formatMetadataForDisplay(stripTagsFromMetadata(currentKeyData.metadata))}
                     
@@ -999,9 +1033,9 @@ export default function KeyInfoView({
)} - - - + +
+
); }