feat(ui): migrate onboarding_link + logging_settings_view to shadcn

- onboarding_link: antd Modal/Typography/Button + @tremor Text \u2192 shadcn
  Dialog/Button. Preserves CopyToClipboard wrapper.
- logging_settings_view: antd Tag + @heroicons CogIcon/BanIcon \u2192 shadcn
  Badge + lucide Settings/Ban. Per-event-type categorical badge palette
  (success/failure/success_and_failure) preserved as inline className
  strings; logging_settings_view.tsx added to .eslintrc.json overrides.

Gates: TS \u2713 | Build \u2713.

Co-authored-by: yuneng-jiang <yuneng-berri@users.noreply.github.com>
This commit is contained in:
Cursor Agent 2026-04-23 09:01:42 +00:00
parent 5bda5b58d0
commit 869ef8d137
No known key found for this signature in database
3 changed files with 142 additions and 100 deletions

View file

@ -31,7 +31,8 @@
"src/components/common_components/IconActionButton/TableIconActionButtons/TableIconActionButton.tsx",
"src/components/GuardrailSettingsView.tsx",
"src/components/DebugWarningBanner.tsx",
"src/components/SCIM.tsx"
"src/components/SCIM.tsx",
"src/components/logging_settings_view.tsx"
],
"rules": {
"litellm-ui/no-banned-ui-imports": "off",

View file

@ -1,7 +1,12 @@
import React from "react";
import { Tag } from "antd";
import { CogIcon, BanIcon } from "@heroicons/react/outline";
import { callbackInfo, callback_map, reverse_callback_map } from "./callback_info_helpers";
import { Badge } from "@/components/ui/badge";
import { Ban, Settings as Cog } from "lucide-react";
import {
callbackInfo,
callback_map,
reverse_callback_map,
} from "./callback_info_helpers";
import { cn } from "@/lib/utils";
interface LoggingConfig {
callback_name: string;
@ -23,21 +28,22 @@ export function LoggingSettingsView({
className = "",
}: LoggingSettingsViewProps) {
const getLoggingDisplayName = (callbackName: string) => {
// Find the display name for the callback
const callbackDisplayName = Object.entries(callback_map).find(([_, value]) => value === callbackName)?.[0];
const callbackDisplayName = Object.entries(callback_map).find(
([, value]) => value === callbackName,
)?.[0];
return callbackDisplayName || callbackName;
};
const getEventTypeColor = (eventType: string): string | undefined => {
const getEventTypeBadgeClass = (eventType: string): string => {
switch (eventType) {
case "success":
return "green";
return "bg-emerald-100 text-emerald-800 dark:bg-emerald-950/40 dark:text-emerald-300";
case "failure":
return "red";
return "bg-red-100 text-red-800 dark:bg-red-950/40 dark:text-red-300";
case "success_and_failure":
return "blue";
return "bg-blue-100 text-blue-800 dark:bg-blue-950/40 dark:text-blue-300";
default:
return undefined;
return "";
}
};
@ -56,14 +62,15 @@ export function LoggingSettingsView({
const content = (
<div className="space-y-6">
{/* Logging Integrations Section */}
<div className="space-y-3">
<div className="flex items-center gap-2">
<CogIcon className="h-4 w-4 text-blue-600" />
<span className="font-semibold text-gray-900">Logging Integrations</span>
<Tag color="blue">
<Cog className="h-4 w-4 text-blue-600" />
<span className="font-semibold text-foreground">
Logging Integrations
</span>
<Badge className="bg-blue-100 text-blue-800 dark:bg-blue-950/40 dark:text-blue-300">
{loggingConfigs.length}
</Tag>
</Badge>
</div>
{loggingConfigs.length > 0 ? (
@ -71,84 +78,98 @@ export function LoggingSettingsView({
{loggingConfigs.map((config, index) => {
const displayName = getLoggingDisplayName(config.callback_name);
const logoUrl = callbackInfo[displayName]?.logo;
return (
<div
key={index}
className="flex items-center justify-between p-3 rounded-lg bg-blue-50 border border-blue-200"
className="flex items-center justify-between p-3 rounded-lg bg-blue-50 dark:bg-blue-950/30 border border-blue-200 dark:border-blue-800"
>
<div className="flex items-center gap-3">
{logoUrl ? (
<img src={logoUrl} alt={displayName} className="w-5 h-5 object-contain" />
<img
src={logoUrl}
alt={displayName}
className="w-5 h-5 object-contain"
/>
) : (
<CogIcon className="h-5 w-5 text-gray-400" />
<Cog className="h-5 w-5 text-muted-foreground" />
)}
<div>
<span className="block font-medium text-blue-800">{displayName}</span>
<span className="block text-xs text-blue-600">
{Object.keys(config.callback_vars).length} parameters configured
<span className="block font-medium text-blue-800 dark:text-blue-300">
{displayName}
</span>
<span className="block text-xs text-blue-600 dark:text-blue-400">
{Object.keys(config.callback_vars).length} parameters
configured
</span>
</div>
</div>
<Tag color={getEventTypeColor(config.callback_type)}>
<Badge className={getEventTypeBadgeClass(config.callback_type)}>
{getEventTypeLabel(config.callback_type)}
</Tag>
</Badge>
</div>
);
})}
</div>
) : (
<div className="flex items-center gap-2 px-3 py-2 rounded-lg bg-gray-50 border border-gray-200">
<CogIcon className="h-4 w-4 text-gray-400" />
<span className="text-gray-500 text-sm">No logging integrations configured</span>
<div className="flex items-center gap-2 px-3 py-2 rounded-lg bg-muted border border-border">
<Cog className="h-4 w-4 text-muted-foreground" />
<span className="text-muted-foreground text-sm">
No logging integrations configured
</span>
</div>
)}
</div>
{/* Disabled Callbacks Section */}
<div className="space-y-3">
<div className="flex items-center gap-2">
<BanIcon className="h-4 w-4 text-red-600" />
<span className="font-semibold text-gray-900">Disabled Callbacks</span>
<Tag color="red">
{disabledCallbacks.length}
</Tag>
<Ban className="h-4 w-4 text-red-600" />
<span className="font-semibold text-foreground">
Disabled Callbacks
</span>
<Badge variant="destructive">{disabledCallbacks.length}</Badge>
</div>
{disabledCallbacks.length > 0 ? (
<div className="space-y-3">
{disabledCallbacks.map((callbackName, index) => {
// Handle both display names and internal values
const displayName = reverse_callback_map[callbackName] || callbackName;
const displayName =
reverse_callback_map[callbackName] || callbackName;
const logoUrl = callbackInfo[displayName]?.logo;
return (
<div
key={index}
className="flex items-center justify-between p-3 rounded-lg bg-red-50 border border-red-200"
className="flex items-center justify-between p-3 rounded-lg bg-red-50 dark:bg-red-950/30 border border-red-200 dark:border-red-800"
>
<div className="flex items-center gap-3">
{logoUrl ? (
<img src={logoUrl} alt={displayName} className="w-5 h-5 object-contain" />
<img
src={logoUrl}
alt={displayName}
className="w-5 h-5 object-contain"
/>
) : (
<BanIcon className="h-5 w-5 text-gray-400" />
<Ban className="h-5 w-5 text-muted-foreground" />
)}
<div>
<span className="block font-medium text-red-800">{displayName}</span>
<span className="block text-xs text-red-600">Disabled for this key</span>
<span className="block font-medium text-red-800 dark:text-red-300">
{displayName}
</span>
<span className="block text-xs text-red-600 dark:text-red-400">
Disabled for this key
</span>
</div>
</div>
<Tag color="red">
Disabled
</Tag>
<Badge variant="destructive">Disabled</Badge>
</div>
);
})}
</div>
) : (
<div className="flex items-center gap-2 px-3 py-2 rounded-lg bg-gray-50 border border-gray-200">
<BanIcon className="h-4 w-4 text-gray-400" />
<span className="text-gray-500 text-sm">No callbacks disabled</span>
<div className="flex items-center gap-2 px-3 py-2 rounded-lg bg-muted border border-border">
<Ban className="h-4 w-4 text-muted-foreground" />
<span className="text-muted-foreground text-sm">
No callbacks disabled
</span>
</div>
)}
</div>
@ -157,11 +178,18 @@ export function LoggingSettingsView({
if (variant === "card") {
return (
<div className={`bg-white border border-gray-200 rounded-lg p-6 ${className}`}>
<div
className={cn(
"bg-background border border-border rounded-lg p-6",
className,
)}
>
<div className="flex items-center gap-2 mb-6">
<div>
<span className="block font-semibold text-gray-900">Logging Settings</span>
<span className="block text-xs text-gray-500">
<span className="block font-semibold text-foreground">
Logging Settings
</span>
<span className="block text-xs text-muted-foreground">
Active logging integrations and disabled callbacks for this key
</span>
</div>
@ -172,8 +200,10 @@ export function LoggingSettingsView({
}
return (
<div className={`${className}`}>
<span className="block font-medium text-gray-900 mb-3">Logging Settings</span>
<div className={className}>
<span className="block font-medium text-foreground mb-3">
Logging Settings
</span>
{content}
</div>
);

View file

@ -1,7 +1,13 @@
import React from "react";
import { Button, Modal, Typography } from "antd";
import { Button } from "@/components/ui/button";
import {
Dialog,
DialogContent,
DialogDescription,
DialogHeader,
DialogTitle,
} from "@/components/ui/dialog";
import { CopyToClipboard } from "react-copy-to-clipboard";
import { Text } from "@tremor/react";
import NotificationsManager from "./molecules/notifications_manager";
export interface InvitationLink {
@ -19,7 +25,9 @@ export interface InvitationLink {
interface OnboardingProps {
isInvitationLinkModalVisible: boolean;
setIsInvitationLinkModalVisible: React.Dispatch<React.SetStateAction<boolean>>;
setIsInvitationLinkModalVisible: React.Dispatch<
React.SetStateAction<boolean>
>;
baseUrl: string;
invitationLinkData: InvitationLink | null;
modalType?: "invitation" | "resetPassword";
@ -32,23 +40,13 @@ export default function OnboardingModal({
invitationLinkData,
modalType = "invitation",
}: OnboardingProps) {
const { Title, Paragraph } = Typography;
const handleInvitationOk = () => {
setIsInvitationLinkModalVisible(false);
};
const handleInvitationCancel = () => {
setIsInvitationLinkModalVisible(false);
};
const handleClose = () => setIsInvitationLinkModalVisible(false);
const getInvitationUrl = () => {
if (!baseUrl) {
return "";
}
if (!baseUrl) return "";
const baseUrlObj = new URL(baseUrl);
const basePath = baseUrlObj.pathname; // This will be "/litellm" or ""
const basePath = baseUrlObj.pathname;
const path = basePath && basePath !== "/" ? `${basePath}/ui` : "ui";
// Get the path from the base URL
if (invitationLinkData?.has_user_setup_sso) {
return new URL(path, baseUrl).toString();
}
@ -56,41 +54,54 @@ export default function OnboardingModal({
if (modalType === "resetPassword") {
urlPath += "&action=reset_password";
}
const url = new URL(urlPath, baseUrl).toString();
return url;
return new URL(urlPath, baseUrl).toString();
};
return (
<Modal
title={modalType === "invitation" ? "Invitation Link" : "Reset Password Link"}
<Dialog
open={isInvitationLinkModalVisible}
width={800}
footer={null}
onOk={handleInvitationOk}
onCancel={handleInvitationCancel}
onOpenChange={(o) => (!o ? handleClose() : undefined)}
>
<Paragraph>
{modalType === "invitation"
? "Copy and send the generated link to onboard this user to the proxy."
: "Copy and send the generated link to the user to reset their password."}
</Paragraph>
<div className="flex justify-between pt-5 pb-2">
<Text className="text-base">User ID</Text>
<Text>{invitationLinkData?.user_id}</Text>
</div>
<div className="flex justify-between pt-5 pb-2">
<Text>{modalType === "invitation" ? "Invitation Link" : "Reset Password Link"}</Text>
<Text>
<Text>{getInvitationUrl()}</Text>
</Text>
</div>
<div className="flex justify-end mt-5">
<CopyToClipboard text={getInvitationUrl()} onCopy={() => NotificationsManager.success("Copied!")}>
<Button type="primary">
{modalType === "invitation" ? "Copy invitation link" : "Copy password reset link"}
</Button>
</CopyToClipboard>
</div>
</Modal>
<DialogContent className="max-w-3xl">
<DialogHeader>
<DialogTitle>
{modalType === "invitation"
? "Invitation Link"
: "Reset Password Link"}
</DialogTitle>
<DialogDescription>
{modalType === "invitation"
? "Copy and send the generated link to onboard this user to the proxy."
: "Copy and send the generated link to the user to reset their password."}
</DialogDescription>
</DialogHeader>
<div className="flex justify-between pt-2 pb-2">
<span className="text-base">User ID</span>
<span>{invitationLinkData?.user_id}</span>
</div>
<div className="flex justify-between pt-2 pb-2 gap-4">
<span>
{modalType === "invitation"
? "Invitation Link"
: "Reset Password Link"}
</span>
<span className="text-right break-all font-mono text-sm">
{getInvitationUrl()}
</span>
</div>
<div className="flex justify-end mt-3">
<CopyToClipboard
text={getInvitationUrl()}
onCopy={() => NotificationsManager.success("Copied!")}
>
<Button>
{modalType === "invitation"
? "Copy invitation link"
: "Copy password reset link"}
</Button>
</CopyToClipboard>
</div>
</DialogContent>
</Dialog>
);
}