From 73e7105e606edf8b6770892f2ef026417a76ab79 Mon Sep 17 00:00:00 2001 From: ryan-crabbe-berri Date: Wed, 19 Aug 2026 13:28:20 -0700 Subject: [PATCH] fix(ui): restore tab strip styling and panel persistence lost in the shadcn migration (#37403) Tremor's TabList defaulted to the underlined `line` variant and its TabPanel rendered every panel, hiding the inactive ones with a class. The shadcn TabsList defaults to a segmented pill and Base UI's TabsPanel unmounts a hidden panel unless it carries `keepMounted`, so the conversion waves quietly changed both on the pages that took tremor's defaults. Restores the underline on the nine strips whose tremor markup carried no `variant`, leaving the ones that were explicitly `variant="solid"` as pills, and puts `keepMounted` back on the thirteen files whose panels used to stay mounted, so filters, scroll position and in-progress input survive a tab switch again. Seeding the old usage page's activity state properly comes with it: it was cast from `{}`, so the panel crashed on `data.length` the moment it mounted before its fetch resolved, which only stayed hidden while the panel was unmounted until first opened. --- .../_components/APIReferenceView.tsx | 6 ++-- .../budgets/_components/budget_panel.tsx | 10 +++--- .../caching/_components/cache_dashboard.tsx | 20 +++++------ .../caching/_components/cache_health.tsx | 4 +-- .../_components/cost_tracking_settings.tsx | 14 +++++--- .../_components/mcp_server_view.tsx | 14 ++++---- .../mcp-servers/_components/mcp_servers.tsx | 12 +++---- .../old-usage/_components/usage.tsx | 16 +++++---- .../src/app/(dashboard)/playground/page.tsx | 12 ++++--- .../policies/_components/index.tsx | 18 +++++----- .../_components/general_settings.tsx | 10 +++--- .../components/AIHub/ModelHubTable.test.tsx | 36 +++++++++++++++++++ .../src/components/AIHub/ModelHubTable.tsx | 26 +++++++++----- .../src/components/per_user_usage.tsx | 6 ++-- .../src/components/settings.tsx | 10 +++--- .../components/templates/key_info_view.tsx | 10 ++++-- .../src/components/user_agent_activity.tsx | 14 ++++---- 17 files changed, 148 insertions(+), 90 deletions(-) diff --git a/ui/litellm-dashboard/src/app/(dashboard)/api-reference/_components/APIReferenceView.tsx b/ui/litellm-dashboard/src/app/(dashboard)/api-reference/_components/APIReferenceView.tsx index 9342017ed3f..847bd34da3e 100644 --- a/ui/litellm-dashboard/src/app/(dashboard)/api-reference/_components/APIReferenceView.tsx +++ b/ui/litellm-dashboard/src/app/(dashboard)/api-reference/_components/APIReferenceView.tsx @@ -46,7 +46,7 @@ const APIReferenceView: React.FC = ({ proxySettings }) => { Langchain Py - + - + - + = ({ accessToken }) => { - +
{selectedBudget && ( @@ -134,7 +134,7 @@ const BudgetPanel: React.FC = ({ accessToken }) => { />
- +

How to use budget id

@@ -149,13 +149,13 @@ const BudgetPanel: React.FC = ({ accessToken }) => { Test it (OpenAI SDK) - + {CREATE_END_USER_CURL_COMMAND} - + {CHAT_COMPLETIONS_CURL_COMMAND} - + {OPENAI_SDK_PYTHON_CODE} diff --git a/ui/litellm-dashboard/src/app/(dashboard)/caching/_components/cache_dashboard.tsx b/ui/litellm-dashboard/src/app/(dashboard)/caching/_components/cache_dashboard.tsx index 62c456b1b38..cafa7a2ce37 100644 --- a/ui/litellm-dashboard/src/app/(dashboard)/caching/_components/cache_dashboard.tsx +++ b/ui/litellm-dashboard/src/app/(dashboard)/caching/_components/cache_dashboard.tsx @@ -140,18 +140,18 @@ const CacheDashboard: React.FC = ({ accessToken, token, userRole return ( -
- - +
+ + Cache Analytics - + Cache Health - + Cache Settings - + Coordination Redis @@ -164,7 +164,7 @@ const CacheDashboard: React.FC = ({ accessToken, token, userRole
- +

@@ -311,7 +311,7 @@ const CacheDashboard: React.FC = ({ accessToken, token, userRole - + = ({ accessToken, token, userRole /> - + - + diff --git a/ui/litellm-dashboard/src/app/(dashboard)/caching/_components/cache_health.tsx b/ui/litellm-dashboard/src/app/(dashboard)/caching/_components/cache_health.tsx index 0f52bed874d..221a17f911d 100644 --- a/ui/litellm-dashboard/src/app/(dashboard)/caching/_components/cache_health.tsx +++ b/ui/litellm-dashboard/src/app/(dashboard)/caching/_components/cache_health.tsx @@ -164,7 +164,7 @@ const HealthCheckDetails: React.FC<{ response: any }> = ({ response }) => { - +

{response?.status === "healthy" ? ( @@ -228,7 +228,7 @@ const HealthCheckDetails: React.FC<{ response: any }> = ({ response }) => {
- +
               {(() => {
diff --git a/ui/litellm-dashboard/src/app/(dashboard)/cost-tracking/_components/cost_tracking_settings.tsx b/ui/litellm-dashboard/src/app/(dashboard)/cost-tracking/_components/cost_tracking_settings.tsx
index 029f8aa424c..85350a539c2 100644
--- a/ui/litellm-dashboard/src/app/(dashboard)/cost-tracking/_components/cost_tracking_settings.tsx
+++ b/ui/litellm-dashboard/src/app/(dashboard)/cost-tracking/_components/cost_tracking_settings.tsx
@@ -197,11 +197,15 @@ const CostTrackingSettings: React.FC = ({ userID, use
             />
             
               
-                
-                  Discounts
-                  Test It
+                
+                  
+                    Discounts
+                  
+                  
+                    Test It
+                  
                 
-                
+                
                   
@@ -237,7 +241,7 @@ const CostTrackingSettings: React.FC = ({ userID, use )}
- +
diff --git a/ui/litellm-dashboard/src/app/(dashboard)/mcp-servers/_components/mcp_server_view.tsx b/ui/litellm-dashboard/src/app/(dashboard)/mcp-servers/_components/mcp_server_view.tsx index 736327409d6..9c5396d1e9a 100644 --- a/ui/litellm-dashboard/src/app/(dashboard)/mcp-servers/_components/mcp_server_view.tsx +++ b/ui/litellm-dashboard/src/app/(dashboard)/mcp-servers/_components/mcp_server_view.tsx @@ -130,22 +130,22 @@ export const MCPServerView: React.FC = ({
setSelectedTabIndex(Number(v))}> - - + + Overview - + MCP Tools {isProxyAdmin && ( - + Settings )} {/* Overview Panel */} - +

Transport

@@ -192,7 +192,7 @@ export const MCPServerView: React.FC = ({ {/* Tool Panel */} - + = ({ {/* Settings Panel */} - +

MCP Server Settings

diff --git a/ui/litellm-dashboard/src/app/(dashboard)/mcp-servers/_components/mcp_servers.tsx b/ui/litellm-dashboard/src/app/(dashboard)/mcp-servers/_components/mcp_servers.tsx index 970fb56799d..ed0c77064b9 100644 --- a/ui/litellm-dashboard/src/app/(dashboard)/mcp-servers/_components/mcp_servers.tsx +++ b/ui/litellm-dashboard/src/app/(dashboard)/mcp-servers/_components/mcp_servers.tsx @@ -543,7 +543,7 @@ const MCPServers: React.FC = ({ accessToken, userRole, userID }) )} - + {selectedServerId ? ( = ({ accessToken, userRole, userID })
)}
- + - + {isAdminRole(userRole) && ( - + )} {isAdminRole(userRole) && ( - + )} {isAdminRole(userRole) && ( - + )} diff --git a/ui/litellm-dashboard/src/app/(dashboard)/old-usage/_components/usage.tsx b/ui/litellm-dashboard/src/app/(dashboard)/old-usage/_components/usage.tsx index 7fe67fb9a41..aca1db0fbae 100644 --- a/ui/litellm-dashboard/src/app/(dashboard)/old-usage/_components/usage.tsx +++ b/ui/litellm-dashboard/src/app/(dashboard)/old-usage/_components/usage.tsx @@ -56,6 +56,8 @@ interface GlobalActivityData { daily_data: { date: string; api_requests: number; total_tokens: number }[]; } +const EMPTY_GLOBAL_ACTIVITY: GlobalActivityData = { sum_api_requests: 0, sum_total_tokens: 0, daily_data: [] }; + type UsageDateRange = { from?: Date; to?: Date }; type TeamSpendTotal = { name: string; value: number }; @@ -105,7 +107,7 @@ const UsagePage: React.FC = ({ accessToken, token, userRole, use const [uniqueTeamIds, setUniqueTeamIds] = useState([]); const [totalSpendPerTeam, setTotalSpendPerTeam] = useState([]); const [spendByProvider, setSpendByProvider] = useState([]); - const [globalActivity, setGlobalActivity] = useState({} as GlobalActivityData); + const [globalActivity, setGlobalActivity] = useState(EMPTY_GLOBAL_ACTIVITY); const [globalActivityPerModel, setGlobalActivityPerModel] = useState([]); const [selectedKeyToken, setSelectedKeyToken] = useState(null); const [selectedTags, setSelectedTags] = useState([ALL_TAGS]); @@ -560,14 +562,14 @@ const UsagePage: React.FC = ({ accessToken, token, userRole, use )} - + Cost Activity - +

@@ -671,7 +673,7 @@ const UsagePage: React.FC = ({ accessToken, token, userRole, use

- +
@@ -751,7 +753,7 @@ const UsagePage: React.FC = ({ accessToken, token, userRole, use - +
@@ -782,7 +784,7 @@ const UsagePage: React.FC = ({ accessToken, token, userRole, use
- +

Customers of your LLM API calls. Tracked when a `user` param is passed in your LLM calls{" "} = ({ accessToken, token, userRole, use - +

- + - + - + - + = ({ accessToken, userRole }) return (
- - + + Templates - + Policies - + Attachments - + Policy Simulator - + = ({ accessToken, userRole }) /> - +
@@ -503,7 +503,7 @@ const PoliciesPanel: React.FC = ({ accessToken, userRole }) /> - + }>

Policy attachments control where your policies apply. Policies don't do anything until you attach @@ -571,7 +571,7 @@ const PoliciesPanel: React.FC = ({ accessToken, userRole }) /> - + diff --git a/ui/litellm-dashboard/src/app/(dashboard)/router-settings/_components/general_settings.tsx b/ui/litellm-dashboard/src/app/(dashboard)/router-settings/_components/general_settings.tsx index 2a5b94b2fd7..1e58e81f4e3 100644 --- a/ui/litellm-dashboard/src/app/(dashboard)/router-settings/_components/general_settings.tsx +++ b/ui/litellm-dashboard/src/app/(dashboard)/router-settings/_components/general_settings.tsx @@ -261,19 +261,19 @@ const GeneralSettings: React.FC = ({ accessToken, user Prompt Caching General - + - + - + - + - + diff --git a/ui/litellm-dashboard/src/components/AIHub/ModelHubTable.test.tsx b/ui/litellm-dashboard/src/components/AIHub/ModelHubTable.test.tsx index 5c4fa4eed43..397e2ab10fa 100644 --- a/ui/litellm-dashboard/src/components/AIHub/ModelHubTable.test.tsx +++ b/ui/litellm-dashboard/src/components/AIHub/ModelHubTable.test.tsx @@ -1,4 +1,5 @@ import * as networking from "@/components/networking"; +import userEvent from "@testing-library/user-event"; import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"; import { renderWithProviders, screen, waitFor } from "../../../tests/test-utils"; import ModelHubTable from "./ModelHubTable"; @@ -201,6 +202,41 @@ describe("ModelHubTable", () => { expect(getUiConfigCallOrder).toBeLessThan(modelHubPublicModelsCallOrder); }); + describe("hub tabs", () => { + const renderHub = async () => { + vi.mocked(networking.modelHubCall).mockResolvedValue({ + data: [{ model_group: "claude-opus-4-8", providers: ["anthropic"], mode: "chat" }], + }); + vi.mocked(networking.getConfigFieldSetting).mockResolvedValue({ field_value: false }); + vi.mocked(networking.getAgentsList).mockResolvedValue({ agents: [] }); + vi.mocked(networking.fetchMCPServers).mockResolvedValue([]); + vi.mocked(networking.getUiSettings).mockResolvedValue({ values: {} }); + mockUseUISettings.mockReturnValue({ data: { values: {} }, isLoading: false }); + + const user = userEvent.setup(); + renderWithProviders( + , + ); + return { user, search: await screen.findByPlaceholderText("Search model names...") }; + }; + + it("keeps the model filter typed on the Model Hub tab after visiting another hub", async () => { + const { user, search } = await renderHub(); + + await user.type(search, "opus"); + await user.click(screen.getByRole("tab", { name: "Agent Hub" })); + await user.click(screen.getByRole("tab", { name: "Model Hub" })); + + expect(await screen.findByPlaceholderText("Search model names...")).toHaveValue("opus"); + }); + + it("renders the hub strip as underlined tabs rather than a segmented pill", async () => { + await renderHub(); + + expect(screen.getByRole("tablist")).toHaveAttribute("data-variant", "line"); + }); + }); + describe("authentication redirect behavior", () => { // Test cases where requireAuth is true - should redirect on invalid tokens testAuthRedirect( diff --git a/ui/litellm-dashboard/src/components/AIHub/ModelHubTable.tsx b/ui/litellm-dashboard/src/components/AIHub/ModelHubTable.tsx index 28dac9badcd..abddb9577e0 100644 --- a/ui/litellm-dashboard/src/components/AIHub/ModelHubTable.tsx +++ b/ui/litellm-dashboard/src/components/AIHub/ModelHubTable.tsx @@ -428,16 +428,24 @@ const ModelHubTable: React.FC = ({ accessToken, publicPage, {/* Tab System for Model Hub, Agent Hub, MCP Hub, and Plugin Marketplace */} - - Model Hub - Agent Hub - MCP Hub - Skill Hub + + + Model Hub + + + Agent Hub + + + MCP Hub + + + Skill Hub +
{/* Model Hub Tab */} - + {/* Model Filters and Table */} {/* Header with Make Public Button */} @@ -482,7 +490,7 @@ const ModelHubTable: React.FC = ({ accessToken, publicPage, {/* Agent Hub Tab */} - + {/* Header with Make Public Button */} {publicPage == false && canModify && ( @@ -516,7 +524,7 @@ const ModelHubTable: React.FC = ({ accessToken, publicPage, {/* MCP Hub Tab */} - + {/* Header with Make Public Button */} {publicPage == false && canModify && ( @@ -553,7 +561,7 @@ const ModelHubTable: React.FC = ({ accessToken, publicPage, {/* Skill Hub Tab */} - + {publicPage == false && canModify && (
diff --git a/ui/litellm-dashboard/src/components/per_user_usage.tsx b/ui/litellm-dashboard/src/components/per_user_usage.tsx index b239da83766..af45facb9a8 100644 --- a/ui/litellm-dashboard/src/components/per_user_usage.tsx +++ b/ui/litellm-dashboard/src/components/per_user_usage.tsx @@ -124,11 +124,11 @@ const PerUserUsage: React.FC = ({ accessToken, selectedTags,

Individual developer usage metrics

- - + + User Details - + Usage Distribution diff --git a/ui/litellm-dashboard/src/components/settings.tsx b/ui/litellm-dashboard/src/components/settings.tsx index 2b9d8dad90b..a0bf3afdb20 100644 --- a/ui/litellm-dashboard/src/components/settings.tsx +++ b/ui/litellm-dashboard/src/components/settings.tsx @@ -491,7 +491,7 @@ const Settings: React.FC = ({ accessToken, userRole, userID, Alerting Settings Email Alerts - + = ({ accessToken, userRole, userID, }} /> - +
- +

Alerts are only supported for Slack Webhook URLs. Get your webhook urls from{" "} @@ -601,10 +601,10 @@ const Settings: React.FC = ({ accessToken, userRole, userID, - + - + 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 c57a76a878a..e33ae8323f5 100644 --- a/ui/litellm-dashboard/src/components/templates/key_info_view.tsx +++ b/ui/litellm-dashboard/src/components/templates/key_info_view.tsx @@ -599,9 +599,13 @@ export default function KeyInfoView({ - - Overview - Settings + + + Overview + + + Settings +

diff --git a/ui/litellm-dashboard/src/components/user_agent_activity.tsx b/ui/litellm-dashboard/src/components/user_agent_activity.tsx index 74716fd866e..4a8b1d11204 100644 --- a/ui/litellm-dashboard/src/components/user_agent_activity.tsx +++ b/ui/litellm-dashboard/src/components/user_agent_activity.tsx @@ -485,11 +485,11 @@ const UserAgentActivity: React.FC = ({ accessToken, user - - + + DAU/WAU/MAU - + Per User Usage (Last 30 Days) @@ -502,14 +502,14 @@ const UserAgentActivity: React.FC = ({ accessToken, user
- - + + DAU - + WAU - + MAU