mirror of
https://github.com/BerriAI/litellm.git
synced 2026-09-08 22:21:35 +00:00
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:
commit
c07ff5d9ee
3 changed files with 130 additions and 50 deletions
|
|
@ -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>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
);
|
||||
}
|
||||
|
|
@ -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",
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue