Merge pull request #18617 from BerriAI/litellm_ui_sso_loading_state

[Feature] UI - SSO Settings Loading State + Deprecate Previous SSO Flow
This commit is contained in:
yuneng-jiang 2026-01-03 16:33:51 -08:00 committed by GitHub
commit c07ff5d9ee
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 130 additions and 50 deletions

View file

@ -10,12 +10,13 @@ import DeleteSSOSettingsModal from "./Modals/DeleteSSOSettingsModal";
import EditSSOSettingsModal from "./Modals/EditSSOSettingsModal";
import RedactableField from "./RedactableField";
import SSOSettingsEmptyPlaceholder from "./SSOSettingsEmptyPlaceholder";
import SSOSettingsLoadingSkeleton from "./SSOSettingsLoadingSkeleton";
import { ssoProviderDisplayNames, ssoProviderLogoMap } from "./constants";
const { Title, Text } = Typography;
export default function SSOSettings() {
const { data: ssoSettings, refetch } = useSSOSettings();
const { data: ssoSettings, refetch, isLoading } = useSSOSettings();
const { accessToken } = useAuthorized();
const [isDeleteModalVisible, setIsDeleteModalVisible] = useState(false);
const [isAddModalVisible, setIsAddModalVisible] = useState(false);
@ -26,27 +27,28 @@ export default function SSOSettings() {
Boolean(ssoSettings?.values.generic_client_id);
// Determine the SSO provider based on the configuration
let selectedProvider: string | null = null;
if (ssoSettings?.values.google_client_id) {
selectedProvider = "google";
} else if (ssoSettings?.values.microsoft_client_id) {
selectedProvider = "microsoft";
} else if (ssoSettings?.values.generic_client_id) {
// Check if it looks like Okta based on endpoints
if (
ssoSettings.values.generic_authorization_endpoint?.includes("okta") ||
ssoSettings.values.generic_authorization_endpoint?.includes("auth0")
) {
selectedProvider = "okta";
} else {
selectedProvider = "generic";
const detectSSOProvider = (values: SSOSettingsValues): string | null => {
if (values.google_client_id) return "google";
if (values.microsoft_client_id) return "microsoft";
if (values.generic_client_id) {
// Check if it looks like Okta/Auth0 based on endpoints
if (
values.generic_authorization_endpoint?.includes("okta") ||
values.generic_authorization_endpoint?.includes("auth0")
) {
return "okta";
}
return "generic";
}
}
return null;
};
const selectedProvider = ssoSettings?.values ? detectSSOProvider(ssoSettings.values) : null;
const renderEndpointValue = (value?: string | null) => (
<span className="font-mono text-gray-600 text-sm break-all">
{value || <span className="text-gray-400 italic">Not configured</span>}
</span>
<Text className="font-mono text-gray-600 text-sm" copyable={!!value}>
{value || "-"}
</Text>
);
const renderSimpleValue = (value?: string | null) =>
@ -179,38 +181,44 @@ export default function SSOSettings() {
};
return (
<Card>
<Space direction="vertical" size="large" className="w-full">
{/* Header Section */}
<div className="flex items-center justify-between">
<div className="flex items-center gap-3">
<Shield className="w-6 h-6 text-gray-400" />
<div>
<Title level={3}>SSO Configuration</Title>
<Text type="secondary">Manage Single Sign-On authentication settings</Text>
<>
{isLoading ? (
<SSOSettingsLoadingSkeleton />
) : (
<Card>
<Space direction="vertical" size="large" className="w-full">
{/* Header Section */}
<div className="flex items-center justify-between">
<div className="flex items-center gap-3">
<Shield className="w-6 h-6 text-gray-400" />
<div>
<Title level={3}>SSO Configuration</Title>
<Text type="secondary">Manage Single Sign-On authentication settings</Text>
</div>
</div>
<div className="flex items-center gap-3">
{isSSOConfigured && (
<>
<Button icon={<Edit className="w-4 h-4" />} onClick={() => setIsEditModalVisible(true)}>
Edit SSO Settings
</Button>
<Button danger icon={<Trash2 className="w-4 h-4" />} onClick={() => setIsDeleteModalVisible(true)}>
Delete SSO Settings
</Button>
</>
)}
</div>
</div>
</div>
<div className="flex items-center gap-3">
{isSSOConfigured && (
<>
<Button icon={<Edit className="w-4 h-4" />} onClick={() => setIsEditModalVisible(true)}>
Edit SSO Settings
</Button>
<Button danger icon={<Trash2 className="w-4 h-4" />} onClick={() => setIsDeleteModalVisible(true)}>
Delete SSO Settings
</Button>
</>
{isSSOConfigured ? (
renderSSOSettings()
) : (
<SSOSettingsEmptyPlaceholder onAdd={() => setIsAddModalVisible(true)} />
)}
</div>
</div>
{isSSOConfigured ? (
renderSSOSettings()
) : (
<SSOSettingsEmptyPlaceholder onAdd={() => setIsAddModalVisible(true)} />
)}
</Space>
</Space>
</Card>
)}
<DeleteSSOSettingsModal
isVisible={isDeleteModalVisible}
@ -236,6 +244,6 @@ export default function SSOSettings() {
refetch();
}}
/>
</Card>
</>
);
}

View file

@ -0,0 +1,66 @@
"use client";
import { Card, Descriptions, Skeleton, Space, Typography } from "antd";
import { Shield } from "lucide-react";
const { Title, Text } = Typography;
export default function SSOSettingsLoadingSkeleton() {
const descriptionsConfig = {
column: {
xxl: 1,
xl: 1,
lg: 1,
md: 1,
sm: 1,
xs: 1,
},
};
return (
<Card>
<Space direction="vertical" size="large" className="w-full">
{/* Header Section */}
<div className="flex items-center justify-between">
<div className="flex items-center gap-3">
<Shield className="w-6 h-6 text-gray-400" />
<div>
<Title level={3}>SSO Configuration</Title>
<Text type="secondary">Manage Single Sign-On authentication settings</Text>
</div>
</div>
<div className="flex items-center gap-3">
<Skeleton.Button active size="default" style={{ width: 170, height: 32 }} />
<Skeleton.Button active size="default" style={{ width: 190, height: 32 }} />
</div>
</div>
{/* Descriptions Table Skeleton */}
<Descriptions bordered {...descriptionsConfig}>
{/* Provider Row */}
<Descriptions.Item label={<Skeleton.Node active style={{ width: 80, height: 16 }} />}>
<div style={{ display: "flex", alignItems: "center", gap: "8px" }}>
<Skeleton.Node active style={{ width: 100, height: 16 }} />
</div>
</Descriptions.Item>
<Descriptions.Item label={<Skeleton.Node active style={{ width: 80, height: 16 }} />}>
<Skeleton.Node active style={{ width: 200, height: 16 }} />
</Descriptions.Item>
<Descriptions.Item label={<Skeleton.Node active style={{ width: 80, height: 16 }} />}>
<Skeleton.Node active style={{ width: 250, height: 16 }} />
</Descriptions.Item>
<Descriptions.Item label={<Skeleton.Node active style={{ width: 80, height: 16 }} />}>
<Skeleton.Node active style={{ width: 180, height: 16 }} />
</Descriptions.Item>
<Descriptions.Item label={<Skeleton.Node active style={{ width: 80, height: 16 }} />}>
<Skeleton.Node active style={{ width: 220, height: 16 }} />
</Descriptions.Item>
</Descriptions>
</Space>
</Card>
);
}

View file

@ -3,7 +3,7 @@
* Use this to avoid sharing master key with others
*/
import React, { useState, useEffect } from "react";
import { Typography } from "antd";
import { Alert, Typography } from "antd";
import { useRouter } from "next/navigation";
import { Button as Button2, Modal, Form, Input } from "antd";
import { Select, SelectItem } from "@tremor/react";
@ -509,6 +509,12 @@ const AdminPanel: React.FC<AdminPanelProps> = ({
<TabPanel>
<Card>
<Title level={4}> Security Settings</Title>
<Alert
message="SSO Configuration Deprecated"
description="Editing SSO Settings on this page is deprecated and will be removed in a future version. Please use the SSO Settings tab for SSO configuration."
type="warning"
showIcon
/>
<div
style={{
display: "flex",