Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions ui/litellm-dashboard/eslint.config.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down Expand Up @@ -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 },
Expand Down
2 changes: 2 additions & 0 deletions ui/litellm-dashboard/scripts/eslint-rules/index.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,15 @@ 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: {
"no-large-inline-object-arg": noLargeInlineObjectArg,
"no-long-condition-chain": noLongConditionChain,
"no-complex-jsx-arrow": noComplexJsxArrow,
"filename-pascal-case": filenamePascalCase,
"no-noop-hover-variant": noNoopHoverVariant,
},
};

Expand Down
Original file line number Diff line number Diff line change
@@ -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;
Original file line number Diff line number Diff line change
Expand Up @@ -190,11 +190,11 @@
};
fetchKeys();
}
}, [currentStep, accessToken]);

Check warning on line 193 in ui/litellm-dashboard/src/app/(dashboard)/agents/_components/add_agent_form.tsx

View workflow job for this annotation

GitHub Actions / frontend-lint

React Hook useEffect has a missing dependency: 'existingKeys.length'. Either include it or remove the dependency array

// Fetch available models when Agent Management step is active (same list as key generation)
useEffect(() => {
if ((currentStep !== 1 && currentStep !== 3) || !accessToken || !userId || !userRole) return;

Check warning on line 197 in ui/litellm-dashboard/src/app/(dashboard)/agents/_components/add_agent_form.tsx

View workflow job for this annotation

GitHub Actions / frontend-lint

Boolean expression combines 5 conditions; extract it into a named variable
let cancelled = false;
setLoadingModels(true);
modelAvailableCall(accessToken, userId, userRole)
Expand Down Expand Up @@ -331,7 +331,7 @@
return overlayDiscoveredCardParams(agentData, appliedDiscoveredSelection?.selected_card);
};

const handleCreateAgent = async () => {

Check warning on line 334 in ui/litellm-dashboard/src/app/(dashboard)/agents/_components/add_agent_form.tsx

View workflow job for this annotation

GitHub Actions / frontend-lint

Async arrow function has a complexity of 40. Maximum allowed is 20
if (!accessToken) {
toast.error("No access token available");
return;
Expand Down Expand Up @@ -696,7 +696,7 @@
}));

const currentAgentName = form.getValues("agent_name");
const seededAgentName = currentAgentName || selected_card.name || selected_card.provider?.organization || "";

Check warning on line 699 in ui/litellm-dashboard/src/app/(dashboard)/agents/_components/add_agent_form.tsx

View workflow job for this annotation

GitHub Actions / frontend-lint

Boolean expression combines 4 conditions; extract it into a named variable

const urlCredentialKeys = (selectedAgentTypeInfo?.credential_fields ?? [])
.map((f) => f.key)
Expand Down Expand Up @@ -758,7 +758,7 @@
<div className="mb-1 px-2 text-xs font-medium tracking-wide text-muted-foreground uppercase">
Not listed?
</div>
<SelectItem value={CUSTOM_AGENT_TYPE} className="focus:bg-warning/10 dark:focus:**:text-amber-400">
<SelectItem value={CUSTOM_AGENT_TYPE} className="focus:bg-warning/10">
<span className="flex items-center gap-3">
<LayoutGrid className="size-4.5 shrink-0 text-warning" />
<span className="block">
Expand Down Expand Up @@ -877,7 +877,7 @@
<div>
{/* Agent name chip */}
<div className="mb-6 flex justify-center">
<Badge className="h-auto gap-1.5 bg-purple-100 px-3 py-1 text-sm text-purple-700">
<Badge className="h-auto gap-1.5 bg-purple-100 px-3 py-1 text-sm text-purple-700 dark:bg-purple-950 dark:text-purple-300">
<Bot className="size-3.5" />
{agentName}
</Badge>
Expand Down Expand Up @@ -906,7 +906,7 @@
<div
className={`cursor-pointer rounded-lg border-2 p-4 transition-colors ${
keyAssignOption === "create_new"
? "border-indigo-600 bg-indigo-50"
? "border-info bg-info/10"
: "border-border bg-background hover:border-muted-foreground/40"
}`}
onClick={() => setKeyAssignOption("create_new")}
Expand All @@ -916,7 +916,7 @@
<RadioGroupItem value="create_new" aria-label="Create a new key for this agent" />
<div className="flex-1">
<div className="flex items-center gap-2">
<Key className="size-4 text-indigo-600" />
<Key className="size-4 text-info" />
<span className="font-medium text-foreground">Create a new key for this agent</span>
</div>
<p className="mt-1 text-sm text-muted-foreground">A dedicated key scoped to this agent.</p>
Expand All @@ -943,7 +943,7 @@
<div
className={`cursor-pointer rounded-lg border-2 p-4 transition-colors ${
keyAssignOption === "existing_key"
? "border-indigo-600 bg-indigo-50"
? "border-info bg-info/10"
: "border-border bg-background hover:border-muted-foreground/40"
}`}
onClick={() => setKeyAssignOption("existing_key")}
Expand Down Expand Up @@ -993,7 +993,7 @@
<CircleCheck className="mb-4 size-12 text-success" />
<h3 className="mb-2 text-xl font-semibold text-foreground">Agent Created!</h3>
<div className="mb-4 flex justify-center">
<Badge className="h-auto gap-1.5 bg-purple-100 px-3 py-1 text-sm text-purple-700">
<Badge className="h-auto gap-1.5 bg-purple-100 px-3 py-1 text-sm text-purple-700 dark:bg-purple-950 dark:text-purple-300">
<Bot className="size-3.5" />
{createdAgentName}
</Badge>
Expand All @@ -1008,7 +1008,7 @@
Key <span className="font-medium">{assignedKeyAlias}</span> has been assigned to this agent.
</p>
)}
{!createdKeyValue && !assignedKeyAlias && keyAssignOption === "skip" && (

Check warning on line 1011 in ui/litellm-dashboard/src/app/(dashboard)/agents/_components/add_agent_form.tsx

View workflow job for this annotation

GitHub Actions / frontend-lint

Boolean expression combines 4 conditions; extract it into a named variable
<p className="mt-2 text-sm text-muted-foreground">
No key assigned. You can create one from the Virtual Keys page.
</p>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -131,7 +131,7 @@ const SkillsFieldArray = () => {
<Button
type="button"
variant="ghost"
className="mt-4 text-destructive hover:text-destructive"
className="mt-4 text-destructive hover:text-destructive/80"
onClick={() => remove(index)}
>
<Trash2 />
Expand Down Expand Up @@ -184,7 +184,7 @@ const StaticHeadersFieldArray = () => {
variant="ghost"
size="icon"
aria-label="Remove static header"
className="text-destructive hover:text-destructive"
className="text-destructive hover:text-destructive/80"
onClick={() => remove(index)}
>
<Trash2 />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -100,7 +100,7 @@ const ProviderDiscountTable: React.FC<ProviderDiscountTableProps> = ({
size="icon-sm"
aria-label={`Save discount for ${displayName}`}
onClick={() => handleSaveEdit(row.provider)}
className="cursor-pointer text-success hover:text-success"
className="cursor-pointer text-success hover:text-success/80"
>
<Check className="size-5" />
</Button>
Expand All @@ -122,7 +122,7 @@ const ProviderDiscountTable: React.FC<ProviderDiscountTableProps> = ({
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"
>
<SquarePen className="size-5" />
</Button>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@
const percentValue = editPercentage ? parseFloat(editPercentage) : undefined;
const fixedValue = editFixedAmount ? parseFloat(editFixedAmount) : undefined;

if (percentValue !== undefined && !isNaN(percentValue) && percentValue >= 0 && percentValue <= 1000) {

Check warning on line 53 in ui/litellm-dashboard/src/app/(dashboard)/cost-tracking/_components/provider_margin_table.tsx

View workflow job for this annotation

GitHub Actions / frontend-lint

Boolean expression combines 4 conditions; extract it into a named variable
if (fixedValue !== undefined && !isNaN(fixedValue) && fixedValue >= 0) {
// Both percentage and fixed amount
onMarginChange(provider, { percentage: percentValue / 100, fixed_amount: fixedValue });
Expand Down Expand Up @@ -152,7 +152,7 @@
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"
>
<Check className="size-5" />
</Button>
Expand All @@ -174,7 +174,7 @@
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"
>
<SquarePen className="size-5" />
</Button>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@
{ value: "during_call", label: "During Call" },
] as const;

const submitGuardrailSchema = z.object({

Check warning on line 50 in ui/litellm-dashboard/src/app/(dashboard)/guardrails/_components/TeamGuardrailsTab.tsx

View workflow job for this annotation

GitHub Actions / frontend-lint

Object literal with 6 properties passed inline as an argument; assign it to a named variable first
team_id: z.string().min(1, "Select a team"),
guardrail_name: z.string().min(1, "Enter a guardrail name"),
mode: z.string().min(1, "Select a mode"),
Expand Down Expand Up @@ -199,7 +199,7 @@
};

const TEAM_COLORS: Record<string, string> = {
"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",
Expand Down Expand Up @@ -311,7 +311,7 @@
return (
<div
className={`bg-card border rounded-lg p-4 transition-all ${
isSelected ? "border-info ring-1 ring-blue-200" : "border-border"
isSelected ? "border-info ring-1 ring-info/30" : "border-border"
}`}
>
<div className="flex items-start justify-between gap-4">
Expand Down Expand Up @@ -358,7 +358,7 @@
<button
type="button"
onClick={onApprove}
className="text-xs bg-success hover:bg-success text-white px-3 py-1.5 rounded-md transition-colors font-medium"
className="text-xs bg-success hover:bg-success/80 text-white px-3 py-1.5 rounded-md transition-colors font-medium"
>
Approve
</button>
Expand Down Expand Up @@ -600,7 +600,7 @@
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
</button>
Expand Down Expand Up @@ -671,7 +671,7 @@
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
</button>
Expand All @@ -682,7 +682,7 @@
<button
type="button"
onClick={() => setConfigExpanded(!configExpanded)}
className="w-full flex items-center justify-between px-3 py-2 text-left text-xs font-semibold text-foreground bg-muted hover:bg-muted transition-colors"
className="w-full flex items-center justify-between px-3 py-2 text-left text-xs font-semibold text-foreground bg-muted hover:bg-border transition-colors"
>
<span>Equivalent config</span>
{configExpanded ? (
Expand Down Expand Up @@ -727,7 +727,7 @@
<button
type="button"
onClick={onApprove}
className="flex-1 flex items-center justify-center gap-1.5 bg-success hover:bg-success text-white text-sm font-medium py-2 rounded-md transition-colors"
className="flex-1 flex items-center justify-center gap-1.5 bg-success hover:bg-success/80 text-white text-sm font-medium py-2 rounded-md transition-colors"
>
<CheckIcon className="h-4 w-4" />
Approve
Expand Down Expand Up @@ -793,7 +793,7 @@
type="button"
onClick={onConfirm}
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"}
Expand All @@ -812,7 +812,7 @@
const { userRole } = useAuthorized();
const isAdmin = userRole ? isProxyAdminRole(userRole) : false;
const [guardrails, setGuardrails] = useState<TeamGuardrail[]>([]);
const [summary, setSummary] = useState({

Check warning on line 815 in ui/litellm-dashboard/src/app/(dashboard)/guardrails/_components/TeamGuardrailsTab.tsx

View workflow job for this annotation

GitHub Actions / frontend-lint

Object literal with 4 properties passed inline as an argument; assign it to a named variable first
total: 0,
pending_review: 0,
active: 0,
Expand Down Expand Up @@ -869,7 +869,7 @@
api_base: values.api_base,
};
try {
await registerGuardrail.mutateAsync({

Check warning on line 872 in ui/litellm-dashboard/src/app/(dashboard)/guardrails/_components/TeamGuardrailsTab.tsx

View workflow job for this annotation

GitHub Actions / frontend-lint

Object literal with 4 properties passed inline as an argument; assign it to a named variable first
team_id: values.team_id,
guardrail_name: values.guardrail_name,
litellm_params,
Expand Down Expand Up @@ -1015,7 +1015,7 @@
<button
type="button"
onClick={() => setIsSubmitModalOpen(true)}
className="ml-auto flex items-center gap-2 bg-info hover:bg-info text-white text-sm font-medium px-4 py-2 rounded-md transition-colors"
className="ml-auto flex items-center gap-2 bg-info hover:bg-info/80 text-white text-sm font-medium px-4 py-2 rounded-md transition-colors"
>
<PlusIcon className="h-4 w-4" />
Add Guardrail
Expand All @@ -1024,7 +1024,7 @@
<div className="space-y-3">
{isLoading && <div className="text-center py-12 text-muted-foreground text-sm">Loading submissions…</div>}
{error && <div className="text-center py-12 text-destructive text-sm">{error}</div>}
{!isLoading && !error && filtered.length === 0 && (

Check warning on line 1027 in ui/litellm-dashboard/src/app/(dashboard)/guardrails/_components/TeamGuardrailsTab.tsx

View workflow job for this annotation

GitHub Actions / frontend-lint

Boolean expression combines 4 conditions; extract it into a named variable
<div className="text-center py-12 text-muted-foreground text-sm">No guardrails match your filters.</div>
)}
{!isLoading &&
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -144,14 +144,14 @@ const createEmptyToolPermissionConfig = (): ToolPermissionConfig => ({
});

const getStepIndicatorClass = (isDone: boolean, isCurrent: boolean): string => {
if (isDone) return "bg-indigo-600 text-white";
if (isCurrent) return "bg-background text-indigo-600 border-2 border-indigo-600";
if (isDone) return "bg-info text-white";
if (isCurrent) return "bg-background text-info border-2 border-info";
return "bg-muted text-muted-foreground border border-border";
};

const getStepTitleClass = (isDone: boolean, isCurrent: boolean): string => {
if (isCurrent) return "font-semibold text-foreground";
if (isDone) return "font-medium text-indigo-600";
if (isDone) return "font-medium text-info";
return "font-medium text-muted-foreground";
};

Expand Down Expand Up @@ -1126,7 +1126,7 @@ const AddGuardrailForm: React.FC<AddGuardrailFormProps> = ({ visible, onClose, a
>
{isDone ? "\u2713" : index + 1}
</div>
{!isLast && <div className={`min-h-4 w-px flex-1 ${isDone ? "bg-indigo-600" : "bg-border"}`} />}
{!isLast && <div className={`min-h-4 w-px flex-1 ${isDone ? "bg-info" : "bg-border"}`} />}
</div>

{/* Step content */}
Expand All @@ -1142,7 +1142,7 @@ const AddGuardrailForm: React.FC<AddGuardrailFormProps> = ({ visible, onClose, a
{step.optional && !isCurrent && (
<span className="text-[11px] text-muted-foreground">optional</span>
)}
{isDone && <span className="text-[11px] text-indigo-600 hover:underline">Edit</span>}
{isDone && <span className="text-[11px] text-info hover:underline">Edit</span>}
</div>

{/* Expanded form content for current step */}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -167,7 +167,7 @@ const DictField: React.FC<DictFieldProps> = ({ field, fullFieldKey, control, val
<Button
variant="ghost"
size="sm"
className="text-destructive hover:text-destructive"
className="text-destructive hover:text-destructive/80"
onClick={() => removeEntry(entry.id, entry.key)}
>
Remove
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -204,7 +204,7 @@ const LLMJudgeFields: React.FC<LLMJudgeFieldsProps> = ({ availableModels, contro
variant="ghost"
size="sm"
aria-label="Remove criterion"
className="mb-1 text-destructive hover:text-destructive"
className="mb-1 text-destructive hover:text-destructive/80"
onClick={() => setCriteria(criteria.filter((_, position) => position !== index))}
>
<X className="size-4" />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ const FieldLabel: React.FC<{ label: string; tooltip: string }> = ({ label, toolt
<span className="text-sm font-medium text-foreground flex items-center">
{label}
<SimpleTooltip content={tooltip}>
<Info className="ml-2 size-4 text-info hover:text-info cursor-help" />
<Info className="ml-2 size-4 text-info hover:text-info/80 cursor-help" />
</SimpleTooltip>
</span>
);
Expand All @@ -30,7 +30,7 @@ const AwsSigV4Fields: React.FC = () => (
href="https://docs.litellm.ai/docs/mcp_aws_sigv4"
target="_blank"
rel="noopener noreferrer"
className="text-info hover:text-info"
className="text-info hover:text-info/80"
>
View docs &rarr;
</a>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -652,7 +652,7 @@ const CreateMCPServer: React.FC<CreateMCPServerProps> = ({
<span className="text-sm font-medium text-foreground flex items-center">
MCP Server Name
<SimpleTooltip content="Best practice: Use a descriptive name that indicates the server's purpose (e.g., 'GitHub_MCP', 'Email_Service'). Cannot contain spaces or hyphens; use underscores instead. Names must comply with SEP-986 and will be rejected if invalid (https://modelcontextprotocol.io/specification/2025-11-25/server/tools#tool-names).">
<Info className="ml-2 size-4 text-info hover:text-info cursor-help" />
<Info className="ml-2 size-4 text-info hover:text-info/80 cursor-help" />
</SimpleTooltip>
</span>
}
Expand All @@ -673,7 +673,7 @@ const CreateMCPServer: React.FC<CreateMCPServerProps> = ({
<span className="text-sm font-medium text-foreground flex items-center">
Alias
<SimpleTooltip content="A short, unique identifier for this server. Defaults to the server name if not provided. Cannot contain spaces or hyphens; use underscores instead.">
<Info className="ml-2 size-4 text-info hover:text-info cursor-help" />
<Info className="ml-2 size-4 text-info hover:text-info/80 cursor-help" />
</SimpleTooltip>
</span>
}
Expand Down Expand Up @@ -792,7 +792,7 @@ const CreateMCPServer: React.FC<CreateMCPServerProps> = ({
<span className="text-sm font-medium text-foreground flex items-center">
Max Concurrent Requests (optional)
<SimpleTooltip content="Maximum number of tool calls LiteLLM will run against this server at the same time. Additional calls wait for a free slot. Leave blank for no limit.">
<Info className="ml-2 size-4 text-info hover:text-info cursor-help" />
<Info className="ml-2 size-4 text-info hover:text-info/80 cursor-help" />
</SimpleTooltip>
</span>
}
Expand Down Expand Up @@ -859,7 +859,7 @@ const CreateMCPServer: React.FC<CreateMCPServerProps> = ({
<span className="text-sm font-medium text-foreground flex items-center">
Authentication Value
<SimpleTooltip content="Token, password, or header value to send with each request for the selected auth type.">
<Info className="ml-2 size-4 text-info hover:text-info cursor-help" />
<Info className="ml-2 size-4 text-info hover:text-info/80 cursor-help" />
</SimpleTooltip>
</span>
}
Expand Down
Loading
Loading