From 74c36c920e9049683823257bd02b70454b95ccdb Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Thu, 23 Apr 2026 08:23:21 +0000 Subject: [PATCH] feat(ui): migrate cache_health to shadcn - @tremor TabGroup/TabList/Tab/TabPanel/TabPanels/Text/Button \u2192 shadcn Tabs/TabsList/TabsTrigger/TabsContent + Button. - @heroicons CheckCircleIcon/XCircleIcon/ClipboardCopyIcon \u2192 lucide CheckCircle/XCircle/ClipboardCopy. - Raw text-gray-* / bg-gray-* / text-green-500 / text-red-500 \u2192 semantic tokens (text-muted-foreground/bg-muted/text-foreground/text-emerald-500/ text-destructive). Status badge uses non-semantic emerald/red because health/error is a categorical signal. - Replace hardcoded indigo primary button gradient with default Button (now driven by shadcn primary token). Gates: TS \u2713 | Build \u2713. Co-authored-by: yuneng-jiang --- .../src/components/cache_health.tsx | 333 ++++++++++-------- 1 file changed, 191 insertions(+), 142 deletions(-) diff --git a/ui/litellm-dashboard/src/components/cache_health.tsx b/ui/litellm-dashboard/src/components/cache_health.tsx index 92bbc14846f..936cee1f71d 100644 --- a/ui/litellm-dashboard/src/components/cache_health.tsx +++ b/ui/litellm-dashboard/src/components/cache_health.tsx @@ -1,9 +1,10 @@ import React from "react"; -import { Text, Button, TabGroup, TabList, Tab, TabPanel, TabPanels } from "@tremor/react"; -import { CheckCircleIcon, XCircleIcon, ClipboardCopyIcon } from "@heroicons/react/outline"; +import { Button } from "@/components/ui/button"; +import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs"; +import { CheckCircle, ClipboardCopy, XCircle } from "lucide-react"; import { ResponseTimeIndicator } from "./response_time_indicator"; -// Helper function to deep-parse a JSON string if possible +// eslint-disable-next-line @typescript-eslint/no-explicit-any const deepParse = (input: any) => { let parsed = input; if (typeof parsed === "string") { @@ -16,12 +17,15 @@ const deepParse = (input: any) => { return parsed; }; -// TableClickableErrorField component with copy-to-clipboard functionality -const TableClickableErrorField: React.FC<{ label: string; value: string | null | undefined }> = ({ label, value }) => { +const TableClickableErrorField: React.FC<{ + label: string; + value: string | null | undefined; +}> = ({ label, value }) => { const [isExpanded, setIsExpanded] = React.useState(false); - const [copied, setCopied] = React.useState(false); + const [, setCopied] = React.useState(false); const safeValue = value?.toString() || "N/A"; - const truncated = safeValue.length > 50 ? safeValue.substring(0, 50) + "..." : safeValue; + const truncated = + safeValue.length > 50 ? safeValue.substring(0, 50) + "..." : safeValue; const handleCopy = () => { navigator.clipboard.writeText(safeValue); @@ -30,22 +34,29 @@ const TableClickableErrorField: React.FC<{ label: string; value: string | null | }; return ( - +
-
-
{label}
-
+              
{label}
+
                 {isExpanded ? safeValue : truncated}
               
-
@@ -53,7 +64,6 @@ const TableClickableErrorField: React.FC<{ label: string; value: string | null | ); }; -// Add new interface for Redis details interface RedisDetails { redis_host?: string; redis_port?: string; @@ -62,36 +72,42 @@ interface RedisDetails { namespace?: string; } -// Add new interface for Error Details interface ErrorDetails { message: string; traceback: string; + // eslint-disable-next-line @typescript-eslint/no-explicit-any litellm_params?: any; + // eslint-disable-next-line @typescript-eslint/no-explicit-any health_check_cache_params?: any; } -// Update HealthCheckDetails component to handle errors +// eslint-disable-next-line @typescript-eslint/no-explicit-any const HealthCheckDetails: React.FC<{ response: any }> = ({ response }) => { - // Initialize with safe default values let errorDetails: ErrorDetails | null = null; + // eslint-disable-next-line @typescript-eslint/no-explicit-any let parsedLitellmParams: any = {}; + // eslint-disable-next-line @typescript-eslint/no-explicit-any let parsedRedisParams: any = {}; try { if (response?.error) { try { const errorMessage = - typeof response.error.message === "string" ? JSON.parse(response.error.message) : response.error.message; + typeof response.error.message === "string" + ? JSON.parse(response.error.message) + : response.error.message; errorDetails = { message: errorMessage?.message || "Unknown error", traceback: errorMessage?.traceback || "No traceback available", litellm_params: errorMessage?.litellm_cache_params || {}, - health_check_cache_params: errorMessage?.health_check_cache_params || {}, + health_check_cache_params: + errorMessage?.health_check_cache_params || {}, }; parsedLitellmParams = deepParse(errorDetails.litellm_params) || {}; - parsedRedisParams = deepParse(errorDetails.health_check_cache_params) || {}; + parsedRedisParams = + deepParse(errorDetails.health_check_cache_params) || {}; } catch (e) { console.warn("Error parsing error details:", e); errorDetails = { @@ -107,167 +123,204 @@ const HealthCheckDetails: React.FC<{ response: any }> = ({ response }) => { } } catch (e) { console.warn("Error in response parsing:", e); - // Provide safe fallback values parsedLitellmParams = {}; parsedRedisParams = {}; } - // Safely extract Redis details with fallbacks const redisDetails: RedisDetails = { redis_host: - parsedRedisParams?.redis_client?.connection_pool?.connection_kwargs?.host || - parsedRedisParams?.redis_async_client?.connection_pool?.connection_kwargs?.host || + parsedRedisParams?.redis_client?.connection_pool?.connection_kwargs + ?.host || + parsedRedisParams?.redis_async_client?.connection_pool?.connection_kwargs + ?.host || parsedRedisParams?.connection_kwargs?.host || parsedRedisParams?.host || "N/A", - redis_port: - parsedRedisParams?.redis_client?.connection_pool?.connection_kwargs?.port || - parsedRedisParams?.redis_async_client?.connection_pool?.connection_kwargs?.port || + parsedRedisParams?.redis_client?.connection_pool?.connection_kwargs + ?.port || + parsedRedisParams?.redis_async_client?.connection_pool?.connection_kwargs + ?.port || parsedRedisParams?.connection_kwargs?.port || parsedRedisParams?.port || "N/A", - redis_version: parsedRedisParams?.redis_version || "N/A", - startup_nodes: (() => { try { if (parsedRedisParams?.redis_kwargs?.startup_nodes) { return JSON.stringify(parsedRedisParams.redis_kwargs.startup_nodes); } const host = - parsedRedisParams?.redis_client?.connection_pool?.connection_kwargs?.host || - parsedRedisParams?.redis_async_client?.connection_pool?.connection_kwargs?.host; + parsedRedisParams?.redis_client?.connection_pool?.connection_kwargs + ?.host || + parsedRedisParams?.redis_async_client?.connection_pool + ?.connection_kwargs?.host; const port = - parsedRedisParams?.redis_client?.connection_pool?.connection_kwargs?.port || - parsedRedisParams?.redis_async_client?.connection_pool?.connection_kwargs?.port; + parsedRedisParams?.redis_client?.connection_pool?.connection_kwargs + ?.port || + parsedRedisParams?.redis_async_client?.connection_pool + ?.connection_kwargs?.port; return host && port ? JSON.stringify([{ host, port }]) : "N/A"; - } catch (e) { + } catch { return "N/A"; } })(), - namespace: parsedRedisParams?.namespace || "N/A", }; return ( -
- - - Summary - Raw Response - +
+ +
+ + Summary + Raw Response + +
- - -
-
- {response?.status === "healthy" ? ( - - ) : ( - + +
+
+ {response?.status === "healthy" ? ( + + ) : ( + + )} + + Cache Status: {response?.status || "unhealthy"} + +
+ + + + {errorDetails && ( + <> + + + + + + )} - - Cache Status: {response?.status || "unhealthy"} - - -
+ Error Details +
- - {/* Show error message if present */} - {errorDetails && ( - <> - - - - - - - )} + + + + + + + - {/* Always show cache details, regardless of error state */} - - - - - - - + {parsedLitellmParams?.type === "redis" && ( + <> + + + + + + + + + + )} + +
- Error Details -
+ Cache Details +
- Cache Details -
+ Redis Details +
+
+
- {/* Redis Details Section */} - {parsedLitellmParams?.type === "redis" && ( - <> - - - Redis Details - - - - - - - - - )} - - -
- - - -
-
-                {(() => {
-                  try {
-                    const data = {
-                      ...response,
-                      litellm_cache_params: parsedLitellmParams,
-                      health_check_cache_params: parsedRedisParams,
-                    };
-                    // First parse any string JSON values
-                    const prettyData = JSON.parse(
-                      JSON.stringify(data, (key, value) => {
-                        if (typeof value === "string") {
-                          try {
-                            return JSON.parse(value);
-                          } catch {
-                            return value;
-                          }
+        
+          
+
+              {(() => {
+                try {
+                  const data = {
+                    ...response,
+                    litellm_cache_params: parsedLitellmParams,
+                    health_check_cache_params: parsedRedisParams,
+                  };
+                  const prettyData = JSON.parse(
+                    JSON.stringify(data, (_key, value) => {
+                      if (typeof value === "string") {
+                        try {
+                          return JSON.parse(value);
+                        } catch {
+                          return value;
                         }
-                        return value;
-                      }),
-                    );
-                    // Then stringify with proper formatting
-                    return JSON.stringify(prettyData, null, 2);
-                  } catch (e) {
-                    return "Error formatting JSON: " + (e as Error).message;
-                  }
-                })()}
-              
-
- - - + } + return value; + }), + ); + return JSON.stringify(prettyData, null, 2); + } catch (e) { + return "Error formatting JSON: " + (e as Error).message; + } + })()} +
+
+ +
); }; export const CacheHealthTab: React.FC<{ accessToken: string | null; + // eslint-disable-next-line @typescript-eslint/no-explicit-any healthCheckResponse: any; runCachingHealthCheck: () => void; responseTimeMs?: number | null; -}> = ({ accessToken, healthCheckResponse, runCachingHealthCheck, responseTimeMs }) => { - const [localResponseTimeMs, setLocalResponseTimeMs] = React.useState(null); +}> = ({ healthCheckResponse, runCachingHealthCheck }) => { + const [localResponseTimeMs, setLocalResponseTimeMs] = React.useState< + number | null + >(null); const [isLoading, setIsLoading] = React.useState(false); const handleHealthCheck = async () => { @@ -282,11 +335,7 @@ export const CacheHealthTab: React.FC<{ return (
-