diff --git a/ui/litellm-dashboard/eslint.config.mjs b/ui/litellm-dashboard/eslint.config.mjs index df10ca1befa2..6d7ca2ad0717 100644 --- a/ui/litellm-dashboard/eslint.config.mjs +++ b/ui/litellm-dashboard/eslint.config.mjs @@ -22,6 +22,7 @@ const eslintConfig = [ "local/no-large-inline-object-arg": "warn", "local/no-long-condition-chain": "warn", "local/no-complex-jsx-arrow": ["error", { maxStatements: 2 }], + "local/no-noop-hover-variant": "error", "@typescript-eslint/no-explicit-any": "warn", "no-console": ["warn", { allow: ["warn", "error"] }], "@typescript-eslint/no-unused-vars": "off", @@ -81,6 +82,10 @@ const eslintConfig = [ "no-restricted-syntax": "off", }, }, + { + files: ["tests/eslint-rules/**/*.{ts,tsx}"], + rules: { "local/no-noop-hover-variant": "off" }, + }, { files: ["src/**/*.test.{ts,tsx}", "tests/**/*.{ts,tsx}"], plugins: { "testing-library": testingLibrary, "jest-dom": jestDom }, diff --git a/ui/litellm-dashboard/scripts/eslint-rules/index.mjs b/ui/litellm-dashboard/scripts/eslint-rules/index.mjs index 9e9f901a6dfa..983399ae4a3e 100644 --- a/ui/litellm-dashboard/scripts/eslint-rules/index.mjs +++ b/ui/litellm-dashboard/scripts/eslint-rules/index.mjs @@ -2,6 +2,7 @@ import noLargeInlineObjectArg from "./no-large-inline-object-arg.mjs"; import noLongConditionChain from "./no-long-condition-chain.mjs"; import noComplexJsxArrow from "./no-complex-jsx-arrow.mjs"; import filenamePascalCase from "./filename-pascal-case.mjs"; +import noNoopHoverVariant from "./no-noop-hover-variant.mjs"; const plugin = { rules: { @@ -9,6 +10,7 @@ const plugin = { "no-long-condition-chain": noLongConditionChain, "no-complex-jsx-arrow": noComplexJsxArrow, "filename-pascal-case": filenamePascalCase, + "no-noop-hover-variant": noNoopHoverVariant, }, }; diff --git a/ui/litellm-dashboard/scripts/eslint-rules/no-noop-hover-variant.mjs b/ui/litellm-dashboard/scripts/eslint-rules/no-noop-hover-variant.mjs new file mode 100644 index 000000000000..75a5b09596ca --- /dev/null +++ b/ui/litellm-dashboard/scripts/eslint-rules/no-noop-hover-variant.mjs @@ -0,0 +1,40 @@ +const noopHovers = (value) => { + const tokens = value.split(/\s+/).filter(Boolean); + const bare = new Set(tokens.filter((t) => !t.includes(":"))); + return tokens + .filter((t) => t.startsWith("hover:")) + .map((t) => [t, t.slice("hover:".length)]) + .filter(([, base]) => bare.has(base)); +}; + +const rule = { + meta: { + type: "problem", + docs: { + description: + "Disallow a hover: utility whose value is identical to the base utility in the same class string, which renders no hover feedback.", + }, + schema: [], + messages: { + noop: "`{{hover}}` is identical to the base `{{base}}`, so hovering changes nothing. Give it a distinct value (e.g. `{{hover}}/80`) or drop it.", + }, + }, + create(context) { + const check = (node, value) => { + if (typeof value !== "string" || !value.includes("hover:")) return; + for (const [hover, base] of noopHovers(value)) { + context.report({ node, messageId: "noop", data: { hover, base } }); + } + }; + return { + Literal(node) { + check(node, node.value); + }, + TemplateElement(node) { + check(node, node.value.cooked); + }, + }; + }, +}; + +export default rule; diff --git a/ui/litellm-dashboard/src/app/(dashboard)/agents/_components/add_agent_form.tsx b/ui/litellm-dashboard/src/app/(dashboard)/agents/_components/add_agent_form.tsx index ed0d6193e5a0..b5c04029d698 100644 --- a/ui/litellm-dashboard/src/app/(dashboard)/agents/_components/add_agent_form.tsx +++ b/ui/litellm-dashboard/src/app/(dashboard)/agents/_components/add_agent_form.tsx @@ -758,7 +758,7 @@ const AddAgentForm: React.FC = ({ visible, onClose, accessTok
Not listed?
- + @@ -877,7 +877,7 @@ const AddAgentForm: React.FC = ({ visible, onClose, accessTok
{/* Agent name chip */}
- + {agentName} @@ -906,7 +906,7 @@ const AddAgentForm: React.FC = ({ visible, onClose, accessTok
setKeyAssignOption("create_new")} @@ -916,7 +916,7 @@ const AddAgentForm: React.FC = ({ visible, onClose, accessTok
- + Create a new key for this agent

A dedicated key scoped to this agent.

@@ -943,7 +943,7 @@ const AddAgentForm: React.FC = ({ visible, onClose, accessTok
setKeyAssignOption("existing_key")} @@ -993,7 +993,7 @@ const AddAgentForm: React.FC = ({ visible, onClose, accessTok

Agent Created!

- + {createdAgentName} diff --git a/ui/litellm-dashboard/src/app/(dashboard)/agents/_components/agent_form_fields.tsx b/ui/litellm-dashboard/src/app/(dashboard)/agents/_components/agent_form_fields.tsx index 243da085cb7a..622e5ea7c5cb 100644 --- a/ui/litellm-dashboard/src/app/(dashboard)/agents/_components/agent_form_fields.tsx +++ b/ui/litellm-dashboard/src/app/(dashboard)/agents/_components/agent_form_fields.tsx @@ -131,7 +131,7 @@ const SkillsFieldArray = () => { @@ -122,7 +122,7 @@ const ProviderDiscountTable: React.FC = ({ size="icon-sm" aria-label={`Edit discount for ${displayName}`} onClick={() => handleStartEdit(row.provider, row.discount)} - className="cursor-pointer text-info hover:text-info" + className="cursor-pointer text-info hover:text-info/80" > diff --git a/ui/litellm-dashboard/src/app/(dashboard)/cost-tracking/_components/provider_margin_table.tsx b/ui/litellm-dashboard/src/app/(dashboard)/cost-tracking/_components/provider_margin_table.tsx index 37db4afc5047..fa322d1b4d1a 100644 --- a/ui/litellm-dashboard/src/app/(dashboard)/cost-tracking/_components/provider_margin_table.tsx +++ b/ui/litellm-dashboard/src/app/(dashboard)/cost-tracking/_components/provider_margin_table.tsx @@ -152,7 +152,7 @@ const ProviderMarginTable: React.FC = ({ size="icon-sm" aria-label={`Save margin for ${displayName}`} onClick={() => handleSaveEdit(row.provider)} - className="cursor-pointer text-success hover:text-success" + className="cursor-pointer text-success hover:text-success/80" > @@ -174,7 +174,7 @@ const ProviderMarginTable: React.FC = ({ size="icon-sm" aria-label={`Edit margin for ${displayName}`} onClick={() => handleStartEdit(row.provider, row.margin)} - className="cursor-pointer text-info hover:text-info" + className="cursor-pointer text-info hover:text-info/80" > diff --git a/ui/litellm-dashboard/src/app/(dashboard)/guardrails/_components/TeamGuardrailsTab.tsx b/ui/litellm-dashboard/src/app/(dashboard)/guardrails/_components/TeamGuardrailsTab.tsx index ea8355bfed00..cc2d2051a240 100644 --- a/ui/litellm-dashboard/src/app/(dashboard)/guardrails/_components/TeamGuardrailsTab.tsx +++ b/ui/litellm-dashboard/src/app/(dashboard)/guardrails/_components/TeamGuardrailsTab.tsx @@ -199,7 +199,7 @@ const STATUS_CONFIG: Record = { - "ML Platform": "bg-purple-100 text-purple-700", + "ML Platform": "bg-purple-100 text-purple-700 dark:bg-purple-900 dark:text-purple-300", "Data Science": "bg-info/15 text-info", Security: "bg-destructive/15 text-destructive", "Customer Success": "bg-warning/15 text-warning", @@ -311,7 +311,7 @@ function GuardrailCard({ return (
@@ -358,7 +358,7 @@ function GuardrailCard({ @@ -600,7 +600,7 @@ function DetailPanel({ setNewStaticHeaderValue(""); } }} - className="text-xs font-medium text-info hover:text-info border border-info/20 bg-info/10 hover:bg-info/15 px-2 py-1.5 rounded-sm transition-colors shrink-0" + className="text-xs font-medium text-info border border-info/20 bg-info/10 hover:bg-info/15 px-2 py-1.5 rounded-sm transition-colors shrink-0" > Add @@ -671,7 +671,7 @@ function DetailPanel({ setNewExtraHeader(""); } }} - className="text-xs font-medium text-info hover:text-info border border-info/20 bg-info/10 hover:bg-info/15 px-2 py-1.5 rounded-sm transition-colors" + className="text-xs font-medium text-info border border-info/20 bg-info/10 hover:bg-info/15 px-2 py-1.5 rounded-sm transition-colors" > Add @@ -682,7 +682,7 @@ function DetailPanel({
- {!isLast &&
} + {!isLast &&
}
{/* Step content */} @@ -1142,7 +1142,7 @@ const AddGuardrailForm: React.FC = ({ visible, onClose, a {step.optional && !isCurrent && ( optional )} - {isDone && Edit} + {isDone && Edit}
{/* Expanded form content for current step */} diff --git a/ui/litellm-dashboard/src/app/(dashboard)/guardrails/_components/guardrail_optional_params.tsx b/ui/litellm-dashboard/src/app/(dashboard)/guardrails/_components/guardrail_optional_params.tsx index 2dcc0c69ed1f..78077b0e768b 100644 --- a/ui/litellm-dashboard/src/app/(dashboard)/guardrails/_components/guardrail_optional_params.tsx +++ b/ui/litellm-dashboard/src/app/(dashboard)/guardrails/_components/guardrail_optional_params.tsx @@ -167,7 +167,7 @@ const DictField: React.FC = ({ field, fullFieldKey, control, val
diff --git a/ui/litellm-dashboard/src/app/(dashboard)/mcp-servers/_components/IdJagFormFields.tsx b/ui/litellm-dashboard/src/app/(dashboard)/mcp-servers/_components/IdJagFormFields.tsx index e5c26921043b..df1d8d3436ad 100644 --- a/ui/litellm-dashboard/src/app/(dashboard)/mcp-servers/_components/IdJagFormFields.tsx +++ b/ui/litellm-dashboard/src/app/(dashboard)/mcp-servers/_components/IdJagFormFields.tsx @@ -20,7 +20,7 @@ const FieldLabel: React.FC<{ label: string; tooltip: string }> = ({ label, toolt {label} - + ); diff --git a/ui/litellm-dashboard/src/app/(dashboard)/mcp-servers/_components/MCPPermissionManagement.tsx b/ui/litellm-dashboard/src/app/(dashboard)/mcp-servers/_components/MCPPermissionManagement.tsx index 7cdb5fc1d37b..da264063839b 100644 --- a/ui/litellm-dashboard/src/app/(dashboard)/mcp-servers/_components/MCPPermissionManagement.tsx +++ b/ui/litellm-dashboard/src/app/(dashboard)/mcp-servers/_components/MCPPermissionManagement.tsx @@ -201,7 +201,7 @@ const MCPPermissionManagement: React.FC = ({ Allow All LiteLLM Keys - +

@@ -218,7 +218,7 @@ const MCPPermissionManagement: React.FC = ({ Internal network only - +

@@ -236,7 +236,7 @@ const MCPPermissionManagement: React.FC = ({ Delegate auth to upstream (PKCE passthrough) - +

@@ -261,7 +261,7 @@ const MCPPermissionManagement: React.FC = ({ OAuth pass-through - +

@@ -296,7 +296,7 @@ const MCPPermissionManagement: React.FC = ({ MCP Access Groups - + } @@ -318,7 +318,7 @@ const MCPPermissionManagement: React.FC = ({ Extra Headers - + {mcpServer?.extra_headers && mcpServer.extra_headers.length > 0 && ( @@ -347,7 +347,7 @@ const MCPPermissionManagement: React.FC = ({ Static Headers - + diff --git a/ui/litellm-dashboard/src/app/(dashboard)/mcp-servers/_components/MCPSubmissionsTab.tsx b/ui/litellm-dashboard/src/app/(dashboard)/mcp-servers/_components/MCPSubmissionsTab.tsx index 6afa14adbe5c..c6e47d4fb282 100644 --- a/ui/litellm-dashboard/src/app/(dashboard)/mcp-servers/_components/MCPSubmissionsTab.tsx +++ b/ui/litellm-dashboard/src/app/(dashboard)/mcp-servers/_components/MCPSubmissionsTab.tsx @@ -123,7 +123,7 @@ function ConfirmDialog({ action, serverName, isCurrentlyActive, onConfirm, onCan type="button" onClick={() => onConfirm(isApprove ? undefined : reviewNotes || undefined)} className={`flex-1 text-white text-sm font-medium py-2 rounded-md transition-colors ${ - isApprove ? "bg-success hover:bg-success" : "bg-destructive hover:bg-destructive" + isApprove ? "bg-success hover:bg-success/80" : "bg-destructive hover:bg-destructive/80" }`} > {isApprove ? "Approve" : "Reject"} @@ -235,7 +235,7 @@ function SubmissionRulesPanel({ requiredFields, onChange, onSave, isSaving }: Su await onSave(); setExpanded(false); }} - className="px-4 py-1.5 text-sm font-medium text-white bg-info hover:bg-info disabled:opacity-50 rounded-md transition-colors" + className="px-4 py-1.5 text-sm font-medium text-white bg-info hover:bg-info/80 disabled:opacity-50 rounded-md transition-colors" > {isSaving ? "Saving…" : "Save Rules"} @@ -322,7 +322,7 @@ function MCPServerCard({ server, onApprove, onReject, requiredFields }: MCPServe @@ -341,7 +341,7 @@ function MCPServerCard({ server, onApprove, onReject, requiredFields }: MCPServe @@ -383,7 +383,7 @@ function MCPServerCard({ server, onApprove, onReject, requiredFields }: MCPServe @@ -392,7 +392,7 @@ function MCPServerCard({ server, onApprove, onReject, requiredFields }: MCPServe diff --git a/ui/litellm-dashboard/src/app/(dashboard)/mcp-servers/_components/MCPToolsetsTab.tsx b/ui/litellm-dashboard/src/app/(dashboard)/mcp-servers/_components/MCPToolsetsTab.tsx index c8407664e6e4..c077a5137571 100644 --- a/ui/litellm-dashboard/src/app/(dashboard)/mcp-servers/_components/MCPToolsetsTab.tsx +++ b/ui/litellm-dashboard/src/app/(dashboard)/mcp-servers/_components/MCPToolsetsTab.tsx @@ -87,7 +87,9 @@ function MCPToolList({ serverId, serverName, accessToken, selectedTools, onToggl {serverName} {selectedSet.size > 0 && ( - {selectedSet.size} selected + + {selectedSet.size} selected + )} {expanded ? "▲" : "▼"} @@ -110,12 +112,14 @@ function MCPToolList({ serverId, serverName, accessToken, selectedTools, onToggl type="button" onClick={() => onToggle({ server_id: serverId, tool_name: tool.name })} className={`flex items-start justify-between px-3 py-2 rounded-lg text-left transition-colors ${ - selected ? "bg-purple-50 border border-purple-300" : "bg-card border border-border hover:bg-muted" + selected + ? "bg-purple-50 border border-purple-300 dark:bg-purple-950 dark:border-purple-700" + : "bg-card border border-border hover:bg-muted" }`} >

{tool.name}

@@ -125,7 +129,11 @@ function MCPToolList({ serverId, serverName, accessToken, selectedTools, onToggl

)}
- {selected && } + {selected && ( + + ✓ + + )} ); })} @@ -271,15 +279,19 @@ function CreateToolsetModal({ open, onClose, onSave, accessToken, initialToolset key={idx} type="button" onClick={() => handleToggleTool(tool)} - className="w-full flex items-center justify-between px-3 py-1.5 rounded-lg border border-purple-200 bg-purple-50 hover:bg-destructive/10 hover:border-destructive/20 group transition-colors" + className="w-full flex items-center justify-between px-3 py-1.5 rounded-lg border border-purple-200 bg-purple-50 hover:bg-destructive/10 hover:border-destructive/20 group transition-colors dark:border-purple-800 dark:bg-purple-950" >
- + {displayToolName(serverPrefixById.get(tool.server_id), tool.tool_name)} - {tool.server_id.slice(0, 8)}… + + {tool.server_id.slice(0, 8)}… +
- + + ✕ + )) )} diff --git a/ui/litellm-dashboard/src/app/(dashboard)/mcp-servers/_components/OAuthFormFields.tsx b/ui/litellm-dashboard/src/app/(dashboard)/mcp-servers/_components/OAuthFormFields.tsx index 3b960a3fcc06..76d667039d42 100644 --- a/ui/litellm-dashboard/src/app/(dashboard)/mcp-servers/_components/OAuthFormFields.tsx +++ b/ui/litellm-dashboard/src/app/(dashboard)/mcp-servers/_components/OAuthFormFields.tsx @@ -54,7 +54,7 @@ const FieldLabel: React.FC<{ label: string; tooltip: string }> = ({ label, toolt {label} - + ); @@ -190,7 +190,7 @@ const OAuthFormFields: React.FC = ({ href={docsUrl} target="_blank" rel="noopener noreferrer" - className="text-xs text-info hover:text-info ml-2 font-normal" + className="text-xs text-info hover:text-info/80 ml-2 font-normal" onClick={(e) => e.stopPropagation()} > Create OAuth App → diff --git a/ui/litellm-dashboard/src/app/(dashboard)/mcp-servers/_components/OpenAPIFormSection.tsx b/ui/litellm-dashboard/src/app/(dashboard)/mcp-servers/_components/OpenAPIFormSection.tsx index 3340c180fd4b..248776802cf0 100644 --- a/ui/litellm-dashboard/src/app/(dashboard)/mcp-servers/_components/OpenAPIFormSection.tsx +++ b/ui/litellm-dashboard/src/app/(dashboard)/mcp-servers/_components/OpenAPIFormSection.tsx @@ -70,7 +70,7 @@ const OpenAPIFormSection: React.FC = ({ OpenAPI Spec URL - + } diff --git a/ui/litellm-dashboard/src/app/(dashboard)/mcp-servers/_components/OpenApiByokFields.tsx b/ui/litellm-dashboard/src/app/(dashboard)/mcp-servers/_components/OpenApiByokFields.tsx index 78a806b8553a..a63821cd04cb 100644 --- a/ui/litellm-dashboard/src/app/(dashboard)/mcp-servers/_components/OpenApiByokFields.tsx +++ b/ui/litellm-dashboard/src/app/(dashboard)/mcp-servers/_components/OpenApiByokFields.tsx @@ -29,7 +29,7 @@ const OpenApiByokFields: React.FC = () => { BYOK (Bring Your Own Key) - + } @@ -65,7 +65,7 @@ const OpenApiByokFields: React.FC = () => { Access Description - + } @@ -85,7 +85,7 @@ const OpenApiByokFields: React.FC = () => { API Key Help URL - + } diff --git a/ui/litellm-dashboard/src/app/(dashboard)/mcp-servers/_components/StdioConfiguration.tsx b/ui/litellm-dashboard/src/app/(dashboard)/mcp-servers/_components/StdioConfiguration.tsx index 223957ed446a..854e67940a19 100644 --- a/ui/litellm-dashboard/src/app/(dashboard)/mcp-servers/_components/StdioConfiguration.tsx +++ b/ui/litellm-dashboard/src/app/(dashboard)/mcp-servers/_components/StdioConfiguration.tsx @@ -38,7 +38,7 @@ const StdioConfiguration: React.FC = ({ isVisible, requ Stdio Configuration (JSON) - + } diff --git a/ui/litellm-dashboard/src/app/(dashboard)/mcp-servers/_components/TokenEndpointAuthMethodField.tsx b/ui/litellm-dashboard/src/app/(dashboard)/mcp-servers/_components/TokenEndpointAuthMethodField.tsx index 6e2f81ef8f5b..3dc2d62539de 100644 --- a/ui/litellm-dashboard/src/app/(dashboard)/mcp-servers/_components/TokenEndpointAuthMethodField.tsx +++ b/ui/litellm-dashboard/src/app/(dashboard)/mcp-servers/_components/TokenEndpointAuthMethodField.tsx @@ -21,7 +21,7 @@ const TokenEndpointAuthMethodField: React.FC Token Endpoint Auth Method (optional) - + } diff --git a/ui/litellm-dashboard/src/app/(dashboard)/mcp-servers/_components/TokenExchangeFormFields.tsx b/ui/litellm-dashboard/src/app/(dashboard)/mcp-servers/_components/TokenExchangeFormFields.tsx index ed36337cc6be..f081bae83f5c 100644 --- a/ui/litellm-dashboard/src/app/(dashboard)/mcp-servers/_components/TokenExchangeFormFields.tsx +++ b/ui/litellm-dashboard/src/app/(dashboard)/mcp-servers/_components/TokenExchangeFormFields.tsx @@ -26,7 +26,7 @@ const FieldLabel: React.FC<{ label: string; tooltip: string }> = ({ label, toolt {label} - + ); diff --git a/ui/litellm-dashboard/src/app/(dashboard)/mcp-servers/_components/ToolTestPanel.tsx b/ui/litellm-dashboard/src/app/(dashboard)/mcp-servers/_components/ToolTestPanel.tsx index dd4f623de918..d51fc5da5de1 100644 --- a/ui/litellm-dashboard/src/app/(dashboard)/mcp-servers/_components/ToolTestPanel.tsx +++ b/ui/litellm-dashboard/src/app/(dashboard)/mcp-servers/_components/ToolTestPanel.tsx @@ -275,7 +275,7 @@ export function ToolTestPanel({ className={`px-2 py-1 text-xs font-medium rounded transition-colors ${ viewMode === "formatted" ? "bg-success/15 text-success" - : "text-success hover:text-success" + : "text-success hover:text-success/80" }`} > Formatted @@ -283,7 +283,7 @@ export function ToolTestPanel({
))} -