mirror of
https://github.com/BerriAI/litellm.git
synced 2026-10-04 02:31:27 +00:00
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:
parent
5bda5b58d0
commit
869ef8d137
3 changed files with 142 additions and 100 deletions
|
|
@ -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",
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
);
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue