diff --git a/ui/litellm-dashboard/src/app/(dashboard)/access-groups/_components/AccessGroupsPage.test.tsx b/ui/litellm-dashboard/src/app/(dashboard)/access-groups/_components/AccessGroupsPage.test.tsx
index 55ce5061af7..63ff0f4100f 100644
--- a/ui/litellm-dashboard/src/app/(dashboard)/access-groups/_components/AccessGroupsPage.test.tsx
+++ b/ui/litellm-dashboard/src/app/(dashboard)/access-groups/_components/AccessGroupsPage.test.tsx
@@ -99,6 +99,7 @@ describe("AccessGroupsPage", () => {
renderWithProviders( );
expect(screen.getByRole("heading", { name: "Access Groups" })).toBeInTheDocument();
expect(screen.getByText("Manage resource permissions for your organization")).toBeInTheDocument();
+ expect(document.querySelector(".lucide-boxes")).not.toBeNull();
});
it("shows the Create Access Group button for an admin", () => {
diff --git a/ui/litellm-dashboard/src/app/(dashboard)/access-groups/_components/AccessGroupsPage.tsx b/ui/litellm-dashboard/src/app/(dashboard)/access-groups/_components/AccessGroupsPage.tsx
index 4fc51910161..2e82fe3c418 100644
--- a/ui/litellm-dashboard/src/app/(dashboard)/access-groups/_components/AccessGroupsPage.tsx
+++ b/ui/litellm-dashboard/src/app/(dashboard)/access-groups/_components/AccessGroupsPage.tsx
@@ -1,9 +1,9 @@
import { AccessGroupResponse, useAccessGroups } from "@/app/(dashboard)/hooks/accessGroups/useAccessGroups";
import { useDeleteAccessGroup } from "@/app/(dashboard)/hooks/accessGroups/useDeleteAccessGroup";
-import { Plus, SearchIcon, X } from "lucide-react";
+import { Boxes, Plus, SearchIcon, X } from "lucide-react";
import { useMemo, useState } from "react";
import DeleteResourceModal from "@/components/common_components/DeleteResourceModal";
-import { LegacyPageHeader } from "@/components/shared/LegacyPageHeader";
+import { PageHeader } from "@/components/shared/PageHeader";
import { Button } from "@/components/ui/button";
import { InputGroup, InputGroupAddon, InputGroupButton, InputGroupInput } from "@/components/ui/input-group";
import { AccessGroupDetail } from "./AccessGroupsDetailsPage";
@@ -59,23 +59,22 @@ export function AccessGroupsPage() {
}
return (
-
-
-
setIsCreateModalVisible(true)}>
-
- Create Access Group
-
- ) : undefined
- }
- />
-
+
+
}
+ title="Access Groups"
+ subtitle="Manage resource permissions for your organization"
+ primaryAction={
+ canModify ? (
+
setIsCreateModalVisible(true)}>
+
+ Create Access Group
+
+ ) : undefined
+ }
+ />
-
+
diff --git a/ui/litellm-dashboard/src/app/(dashboard)/budgets/_components/budget_panel.test.tsx b/ui/litellm-dashboard/src/app/(dashboard)/budgets/_components/budget_panel.test.tsx
index 60e886754ce..f2608c1221f 100644
--- a/ui/litellm-dashboard/src/app/(dashboard)/budgets/_components/budget_panel.test.tsx
+++ b/ui/litellm-dashboard/src/app/(dashboard)/budgets/_components/budget_panel.test.tsx
@@ -85,6 +85,14 @@ describe("Budget Panel", () => {
respondWith(DEFAULT_ROWS, 1);
});
+ it("renders the standard page header with the sidebar's Budgets icon", async () => {
+ const { container } = renderPanel();
+
+ expect(await screen.findByRole("heading", { level: 1, name: "Budgets" })).toBeInTheDocument();
+ expect(screen.getByText("Spend, TPM and RPM limits you can assign to customers.")).toBeInTheDocument();
+ expect(container.querySelector(".lucide-wallet")).not.toBeNull();
+ });
+
it("loads the first page of budgets, newest first", async () => {
renderPanel();
await waitFor(() => expect(getMock).toHaveBeenCalled());
diff --git a/ui/litellm-dashboard/src/app/(dashboard)/budgets/_components/budget_panel.tsx b/ui/litellm-dashboard/src/app/(dashboard)/budgets/_components/budget_panel.tsx
index 18b8e774aae..25344c52847 100644
--- a/ui/litellm-dashboard/src/app/(dashboard)/budgets/_components/budget_panel.tsx
+++ b/ui/litellm-dashboard/src/app/(dashboard)/budgets/_components/budget_panel.tsx
@@ -9,8 +9,7 @@ import { Prism as SyntaxHighlighter } from "react-syntax-highlighter";
import { prism } from "react-syntax-highlighter/dist/esm/styles/prism";
import { useSyntaxTheme } from "@/hooks/useSyntaxTheme";
-import { LegacyPageHeader } from "@/components/shared/LegacyPageHeader";
-import { ToolbarSeparator } from "@/components/shared/ToolbarSeparator";
+import { PageHeader } from "@/components/shared/PageHeader";
import { Button } from "@/components/ui/button";
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs";
import DeleteResourceModal from "@/components/common_components/DeleteResourceModal";
@@ -79,34 +78,37 @@ const BudgetPanel: React.FC = ({ accessToken }) => {
};
return (
-
-
}
- title="Budgets"
- subtitle="Spend, TPM and RPM limits you can assign to customers."
- />
-
-
- {canModify && (
- <>
+
+
+ }
+ title="Budgets"
+ subtitle="Spend, TPM and RPM limits you can assign to customers."
+ primaryAction={
+ canModify ? (
setIsCreateModelVisible(true)}>
Create Budget
-
- >
+ ) : undefined
+ }
+ tabs={({ leadingControls }) => (
+
+ {leadingControls}
+
+ Budgets
+
+
+ Examples
+
+
)}
-
-
- Budgets
-
-
- Examples
-
-
-
+ />
-
+
{selectedBudget && (
= ({ accessToken }) => {
-
+
);
};
diff --git a/ui/litellm-dashboard/src/app/(dashboard)/cost-optimization/_components/CostOptimizationView.test.tsx b/ui/litellm-dashboard/src/app/(dashboard)/cost-optimization/_components/CostOptimizationView.test.tsx
index 384c6cdbc8f..d5df5aa75da 100644
--- a/ui/litellm-dashboard/src/app/(dashboard)/cost-optimization/_components/CostOptimizationView.test.tsx
+++ b/ui/litellm-dashboard/src/app/(dashboard)/cost-optimization/_components/CostOptimizationView.test.tsx
@@ -44,6 +44,14 @@ describe("CostOptimizationView", () => {
useAuthorizedMock.mockReturnValue({ accessToken: "test-token", userId: "u1", userRole: "Admin" });
});
+ it("renders the standard page header with the sidebar's Cost Optimization icon", () => {
+ const { container, getByRole, getByText } = renderView();
+
+ expect(getByRole("heading", { level: 1, name: "Cost Optimization" })).toBeInTheDocument();
+ expect(getByText(/Track and configure the mechanisms that save you money/)).toBeInTheDocument();
+ expect(container.querySelector(".lucide-piggy-bank")).not.toBeNull();
+ });
+
it("renders the four cost-optimization tabs", () => {
const { getByText } = renderView();
diff --git a/ui/litellm-dashboard/src/app/(dashboard)/cost-optimization/_components/CostOptimizationView.tsx b/ui/litellm-dashboard/src/app/(dashboard)/cost-optimization/_components/CostOptimizationView.tsx
index 165c63ea969..8094fa2e8b6 100644
--- a/ui/litellm-dashboard/src/app/(dashboard)/cost-optimization/_components/CostOptimizationView.tsx
+++ b/ui/litellm-dashboard/src/app/(dashboard)/cost-optimization/_components/CostOptimizationView.tsx
@@ -6,6 +6,7 @@ import { Info, PiggyBank } from "lucide-react";
import useCan from "@/app/(dashboard)/hooks/useCan";
import PaginationStatusAlerts from "@/components/shared/PaginationStatusAlerts";
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs";
+import { PageHeader } from "@/components/shared/PageHeader";
import UsageTab from "./UsageTab";
import PromptCompressionTab from "./PromptCompressionTab";
import PromptCachingTab from "./PromptCachingTab";
@@ -32,63 +33,63 @@ const CostOptimizationView: React.FC = ({ accessToken
};
return (
-
-
-
-
- Track and configure the mechanisms that save you money: prompt compression and prompt caching. Auto routers
- live under Models + Endpoints, on the Auto-Routers tab
-
-
-
-
-
-
This is an experimental dashboard
-
- Have feedback? Join the discussion{" "}
-
- here
-
-
-
-
-
-
-
-
- Overall
-
- {canViewProxyWideCostData && (
- <>
-
- Prompt Compression
+
+
+ }
+ title="Cost Optimization"
+ subtitle="Track and configure the mechanisms that save you money: prompt compression and prompt caching. Auto routers live under Models + Endpoints, on the Auto-Routers tab"
+ tabs={({ leadingControls }) => (
+
+ {leadingControls}
+
+ Overall
-
- Prompt Caching
-
-
- Auto-Router
-
- >
+ {canViewProxyWideCostData && (
+ <>
+
+ Prompt Compression
+
+
+ Prompt Caching
+
+
+ Auto-Router
+
+ >
+ )}
+
)}
-
+ />
+
+
+
This is an experimental dashboard
+
+ Have feedback? Join the discussion{" "}
+
+ here
+
+
+
+
+
@@ -106,7 +107,7 @@ const CostOptimizationView: React.FC = ({ accessToken
>
)}
-
+
);
};
diff --git a/ui/litellm-dashboard/src/app/(dashboard)/guardrails-monitor/_components/GuardrailsMonitorView.tsx b/ui/litellm-dashboard/src/app/(dashboard)/guardrails-monitor/_components/GuardrailsMonitorView.tsx
index 3b5cc156242..a9acf3e6377 100644
--- a/ui/litellm-dashboard/src/app/(dashboard)/guardrails-monitor/_components/GuardrailsMonitorView.tsx
+++ b/ui/litellm-dashboard/src/app/(dashboard)/guardrails-monitor/_components/GuardrailsMonitorView.tsx
@@ -41,27 +41,32 @@ export default function GuardrailsMonitorView({ accessToken = null }: Guardrails
setView({ type: "overview" });
};
+ const dateRangeControl = (
+
+ );
+
return (
-
-
+
{view.type === "overview" ? (
) : (
-
+ <>
+ {dateRangeControl}
+
+ >
)}
-
+
);
}
diff --git a/ui/litellm-dashboard/src/app/(dashboard)/guardrails-monitor/_components/GuardrailsOverview.test.tsx b/ui/litellm-dashboard/src/app/(dashboard)/guardrails-monitor/_components/GuardrailsOverview.test.tsx
index b616982d69b..c62505cc74f 100644
--- a/ui/litellm-dashboard/src/app/(dashboard)/guardrails-monitor/_components/GuardrailsOverview.test.tsx
+++ b/ui/litellm-dashboard/src/app/(dashboard)/guardrails-monitor/_components/GuardrailsOverview.test.tsx
@@ -110,6 +110,7 @@ describe("GuardrailsOverview", () => {
expect(await screen.findByRole("heading", { name: "Guardrails Monitor", level: 1 })).toBeInTheDocument();
expect(screen.getByText("Monitor guardrail performance across all requests")).toBeInTheDocument();
+ expect(document.querySelector(".lucide-heart-pulse")).not.toBeNull();
expect(screen.getByRole("button", { name: /Export Data/i })).toBeInTheDocument();
});
diff --git a/ui/litellm-dashboard/src/app/(dashboard)/guardrails-monitor/_components/GuardrailsOverview.tsx b/ui/litellm-dashboard/src/app/(dashboard)/guardrails-monitor/_components/GuardrailsOverview.tsx
index 42de8e2707b..5bc9eb16cee 100644
--- a/ui/litellm-dashboard/src/app/(dashboard)/guardrails-monitor/_components/GuardrailsOverview.tsx
+++ b/ui/litellm-dashboard/src/app/(dashboard)/guardrails-monitor/_components/GuardrailsOverview.tsx
@@ -1,11 +1,12 @@
import { useQuery } from "@tanstack/react-query";
import type { ColumnDef, OnChangeFn, SortingState } from "@tanstack/react-table";
-import { Download, Settings, Shield, TrendingUp, TriangleAlert } from "lucide-react";
+import { Download, HeartPulse, Settings, TrendingUp, TriangleAlert } from "lucide-react";
import React, { useMemo, useState } from "react";
import { DataTable, DataTableSortHeader } from "@/components/shared/DataTable";
import { getGuardrailsUsageOverview } from "@/components/networking";
import { type PerformanceRow } from "@/components/GuardrailsMonitor/mockData";
import { Button } from "@/components/ui/button";
+import { PageHeader } from "@/components/shared/PageHeader";
import { UiLoadingSpinner } from "@/components/ui/ui-loading-spinner";
import { EvaluationSettingsModal } from "./EvaluationSettingsModal";
import { MetricCard } from "@/components/GuardrailsMonitor/MetricCard";
@@ -16,6 +17,7 @@ interface GuardrailsOverviewProps {
startDate: string;
endDate: string;
onSelectGuardrail: (id: string) => void;
+ dateRangeControl?: React.ReactNode;
}
type SortKey = "failRate" | "requestsEvaluated" | "avgLatency" | "falsePositiveRate" | "falseNegativeRate";
@@ -43,6 +45,7 @@ export function GuardrailsOverview({
startDate,
endDate,
onSelectGuardrail,
+ dateRangeControl,
}: GuardrailsOverviewProps) {
const [sortBy, setSortBy] = useState("failRate");
const [sortDir, setSortDir] = useState<"asc" | "desc">("desc");
@@ -197,23 +200,22 @@ export function GuardrailsOverview({
return (
-
-
-
-
-
Guardrails Monitor
-
-
Monitor guardrail performance across all requests
-
-
-
-
- Export Data
-
-
-
+
}
+ title="Guardrails Monitor"
+ subtitle="Monitor guardrail performance across all requests"
+ utilities={
+ <>
+ {dateRangeControl}
+
+
+ Export Data
+
+ >
+ }
+ />
-
+
{
mockUseProjects.mockReturnValue({ data: [], isLoading: false });
renderWithProviders( );
expect(screen.getByRole("heading", { name: /projects/i })).toBeInTheDocument();
+ expect(screen.getByText("Manage projects within your teams")).toBeInTheDocument();
+ expect(document.querySelector(".lucide-folder")).not.toBeNull();
});
it("should show a 'Create Project' button", () => {
diff --git a/ui/litellm-dashboard/src/app/(dashboard)/projects/_components/ProjectsPage.tsx b/ui/litellm-dashboard/src/app/(dashboard)/projects/_components/ProjectsPage.tsx
index dc18a05edca..4aba2bb627d 100644
--- a/ui/litellm-dashboard/src/app/(dashboard)/projects/_components/ProjectsPage.tsx
+++ b/ui/litellm-dashboard/src/app/(dashboard)/projects/_components/ProjectsPage.tsx
@@ -1,9 +1,9 @@
import { useProjects } from "@/app/(dashboard)/hooks/projects/useProjects";
import { useTeams } from "@/app/(dashboard)/hooks/teams/useTeams";
-import { Plus, SearchIcon, X } from "lucide-react";
+import { Folder, Plus, SearchIcon, X } from "lucide-react";
import { parseAsString, useQueryState } from "nuqs";
import { useMemo, useState } from "react";
-import { LegacyPageHeader } from "@/components/shared/LegacyPageHeader";
+import { PageHeader } from "@/components/shared/PageHeader";
import { Button } from "@/components/ui/button";
import { InputGroup, InputGroupAddon, InputGroupButton, InputGroupInput } from "@/components/ui/input-group";
import { CreateProjectModal } from "./ProjectModals/CreateProjectModal";
@@ -54,21 +54,20 @@ export function ProjectsPage() {
}
return (
-
-
-
setIsCreateModalVisible(true)}>
-
- Create Project
-
- }
- />
-
+
+
}
+ title="Projects"
+ subtitle="Manage projects within your teams"
+ primaryAction={
+
setIsCreateModalVisible(true)}>
+
+ Create Project
+
+ }
+ />
-
+
diff --git a/ui/litellm-dashboard/src/components/VirtualKeysPage/VirtualKeysTable.test.tsx b/ui/litellm-dashboard/src/components/VirtualKeysPage/VirtualKeysTable.test.tsx
index 643da71e8b1..d09ccbf8826 100644
--- a/ui/litellm-dashboard/src/components/VirtualKeysPage/VirtualKeysTable.test.tsx
+++ b/ui/litellm-dashboard/src/components/VirtualKeysPage/VirtualKeysTable.test.tsx
@@ -207,6 +207,8 @@ it("left-anchors the create-key CTA below the title, between the header and the
renderWithProviders(Create New Key} />);
const heading = screen.getByRole("heading", { name: "Virtual Keys" });
+ expect(screen.getByText("Every key that authenticates requests to the gateway.")).toBeInTheDocument();
+ expect(document.querySelector(".lucide-key-round")).not.toBeNull();
const ctas = screen.getAllByRole("button", { name: "Create New Key" });
expect(ctas).toHaveLength(1);
const cta = ctas[0];
diff --git a/ui/litellm-dashboard/src/components/VirtualKeysPage/VirtualKeysTable.tsx b/ui/litellm-dashboard/src/components/VirtualKeysPage/VirtualKeysTable.tsx
index fbdcf2d8d83..7dd25ca1fd0 100644
--- a/ui/litellm-dashboard/src/components/VirtualKeysPage/VirtualKeysTable.tsx
+++ b/ui/litellm-dashboard/src/components/VirtualKeysPage/VirtualKeysTable.tsx
@@ -12,7 +12,7 @@ import {
DataTableToolbar,
} from "@/components/shared/DataTable";
import { SearchSelect } from "@/components/shared/SearchSelect";
-import { LegacyPageHeader } from "@/components/shared/LegacyPageHeader";
+import { PageHeader } from "@/components/shared/PageHeader";
import { Input } from "@/components/ui/input";
import { useDebouncedValue } from "@tanstack/react-pacer/debouncer";
import { ColumnFiltersState, OnChangeFn, PaginationState, SortingState } from "@tanstack/react-table";
@@ -182,13 +182,13 @@ export function VirtualKeysTable({ headerActions }: VirtualKeysTableProps) {
}
return (
-
-
}
+
+
}
title="Virtual Keys"
subtitle="Every key that authenticates requests to the gateway."
+ primaryAction={headerActions}
/>
- {headerActions}
{
- it("should render the title as a heading", () => {
- renderWithProviders( );
-
- expect(screen.getByRole("heading", { name: "Virtual Keys" })).toBeInTheDocument();
- });
-
- it("should render the optional identity and actions", () => {
- renderWithProviders(
- Key icon}
- actions={Create New Key }
- />,
- );
-
- expect(screen.getByText("Every key that authenticates requests")).toBeInTheDocument();
- expect(screen.getByText("Key icon")).toBeInTheDocument();
- expect(screen.getByRole("button", { name: "Create New Key" })).toBeInTheDocument();
- });
-
- it("should omit optional actions when none are provided", () => {
- renderWithProviders( );
-
- expect(screen.queryByRole("button")).not.toBeInTheDocument();
- });
-});
diff --git a/ui/litellm-dashboard/src/components/shared/LegacyPageHeader.tsx b/ui/litellm-dashboard/src/components/shared/LegacyPageHeader.tsx
deleted file mode 100644
index 43979ad00b4..00000000000
--- a/ui/litellm-dashboard/src/components/shared/LegacyPageHeader.tsx
+++ /dev/null
@@ -1,25 +0,0 @@
-"use client";
-
-import * as React from "react";
-
-interface LegacyPageHeaderProps {
- title: React.ReactNode;
- subtitle?: React.ReactNode;
- icon?: React.ReactNode;
- actions?: React.ReactNode;
-}
-
-export function LegacyPageHeader({ title, subtitle, icon, actions }: LegacyPageHeaderProps) {
- return (
-
-
- {icon != null &&
{icon} }
-
-
{title}
- {subtitle != null &&
{subtitle}
}
-
-
- {actions != null &&
{actions}
}
-
- );
-}
diff --git a/ui/litellm-dashboard/src/components/user_dashboard.tsx b/ui/litellm-dashboard/src/components/user_dashboard.tsx
index ce5337aa7a7..614b3b7af62 100644
--- a/ui/litellm-dashboard/src/components/user_dashboard.tsx
+++ b/ui/litellm-dashboard/src/components/user_dashboard.tsx
@@ -216,27 +216,25 @@ const UserDashboard: React.FC = ({
const canCreateKey = userRole !== "Admin Viewer" && userRole !== "proxy_admin_viewer";
return (
-
-
-
-
- ) : undefined
- }
- />
-
+
+
+
+ ) : undefined
+ }
+ />
-
+
);
};