mirror of
https://github.com/BerriAI/litellm.git
synced 2026-09-10 22:41:41 +00:00
refactor admin page
This commit is contained in:
parent
02ed25c886
commit
539ff7a850
4 changed files with 524 additions and 520 deletions
|
|
@ -1,26 +1,11 @@
|
|||
"use client";
|
||||
|
||||
import AdminPanel from "@/components/admins";
|
||||
import useAuthorized from "@/app/(dashboard)/hooks/useAuthorized";
|
||||
import { useState } from "react";
|
||||
import useTeams from "@/app/(dashboard)/hooks/useTeams";
|
||||
import AdminPanel from "@/components/Admins";
|
||||
|
||||
const AdminSettings = () => {
|
||||
const { teams, setTeams } = useTeams();
|
||||
|
||||
const [searchParams, setSearchParams] = useState<URLSearchParams>(() =>
|
||||
typeof window === "undefined" ? new URLSearchParams() : new URLSearchParams(window.location.search),
|
||||
);
|
||||
const { accessToken, userId, premiumUser, showSSOBanner } = useAuthorized();
|
||||
|
||||
return (
|
||||
<AdminPanel
|
||||
searchParams={searchParams}
|
||||
accessToken={accessToken}
|
||||
userID={userId}
|
||||
setTeams={setTeams}
|
||||
showSSOBanner={showSSOBanner}
|
||||
premiumUser={premiumUser}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ import APIReferenceView from "@/app/(dashboard)/api-reference/APIReferenceView";
|
|||
import SidebarProvider from "@/app/(dashboard)/components/SidebarProvider";
|
||||
import OldModelDashboard from "@/app/(dashboard)/models-and-endpoints/ModelsAndEndpointsView";
|
||||
import PlaygroundPage from "@/app/(dashboard)/playground/page";
|
||||
import AdminPanel from "@/components/admins";
|
||||
import AdminPanel from "@/components/Admins";
|
||||
import AgentsPanel from "@/components/agents";
|
||||
import BudgetPanel from "@/components/budgets/budget_panel";
|
||||
import CacheDashboard from "@/components/cache_dashboard";
|
||||
|
|
@ -469,12 +469,6 @@ function CreateKeyPageContent() {
|
|||
/>
|
||||
) : page == "admin-panel" ? (
|
||||
<AdminPanel
|
||||
setTeams={setTeams}
|
||||
searchParams={searchParams}
|
||||
accessToken={accessToken}
|
||||
userID={userID}
|
||||
showSSOBanner={showSSOBanner}
|
||||
premiumUser={premiumUser}
|
||||
proxySettings={proxySettings}
|
||||
/>
|
||||
) : page == "api_ref" ? (
|
||||
|
|
|
|||
325
ui/litellm-dashboard/src/components/Admins.test.tsx
Normal file
325
ui/litellm-dashboard/src/components/Admins.test.tsx
Normal file
|
|
@ -0,0 +1,325 @@
|
|||
import { render, screen, waitFor } from "@testing-library/react";
|
||||
import userEvent from "@testing-library/user-event";
|
||||
import { beforeEach, describe, expect, it, vi } from "vitest";
|
||||
import AdminPanel from "./Admins";
|
||||
|
||||
const mockGetSSOSettings = vi.fn();
|
||||
const mockGetAllowedIPs = vi.fn();
|
||||
const mockAddAllowedIP = vi.fn();
|
||||
const mockDeleteAllowedIP = vi.fn();
|
||||
|
||||
vi.mock("./networking", () => ({
|
||||
getSSOSettings: (...args: unknown[]) => mockGetSSOSettings(...args),
|
||||
getAllowedIPs: (...args: unknown[]) => mockGetAllowedIPs(...args),
|
||||
addAllowedIP: (...args: unknown[]) => mockAddAllowedIP(...args),
|
||||
deleteAllowedIP: (...args: unknown[]) => mockDeleteAllowedIP(...args),
|
||||
}));
|
||||
|
||||
vi.mock("./constants", () => ({
|
||||
useBaseUrl: () => "http://localhost:4000",
|
||||
}));
|
||||
|
||||
vi.mock("./Settings/AdminSettings/SSOSettings/SSOSettings", () => ({
|
||||
default: () => <div>SSO Settings</div>,
|
||||
}));
|
||||
|
||||
vi.mock("./Settings/AdminSettings/UISettings/UISettings", () => ({
|
||||
default: () => <div>UI Settings</div>,
|
||||
}));
|
||||
|
||||
vi.mock("./SCIM", () => ({
|
||||
default: () => <div>SCIM Config</div>,
|
||||
}));
|
||||
|
||||
vi.mock("./SSOModals", () => ({
|
||||
default: () => <div>SSO Modals</div>,
|
||||
}));
|
||||
|
||||
vi.mock("./UIAccessControlForm", () => ({
|
||||
default: () => <div>UI Access Control Form</div>,
|
||||
}));
|
||||
|
||||
const mockUseAuthorized = vi.fn();
|
||||
vi.mock("@/app/(dashboard)/hooks/useAuthorized", () => ({
|
||||
default: () => mockUseAuthorized(),
|
||||
}));
|
||||
|
||||
describe("AdminPanel", () => {
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks();
|
||||
mockUseAuthorized.mockReturnValue({
|
||||
premiumUser: false,
|
||||
accessToken: "test-token",
|
||||
userId: "user-1",
|
||||
});
|
||||
mockGetSSOSettings.mockResolvedValue({
|
||||
values: {},
|
||||
});
|
||||
mockGetAllowedIPs.mockResolvedValue([]);
|
||||
mockAddAllowedIP.mockResolvedValue({});
|
||||
mockDeleteAllowedIP.mockResolvedValue({});
|
||||
});
|
||||
|
||||
it("should render the admin panel", () => {
|
||||
render(<AdminPanel />);
|
||||
expect(screen.getByRole("heading", { name: /admin access/i })).toBeInTheDocument();
|
||||
expect(screen.getByText(/go to 'internal users' page to add other admins/i)).toBeInTheDocument();
|
||||
});
|
||||
|
||||
describe("Tabs", () => {
|
||||
it("should render all tabs", () => {
|
||||
render(<AdminPanel />);
|
||||
expect(screen.getByRole("tab", { name: /sso settings/i })).toBeInTheDocument();
|
||||
expect(screen.getByRole("tab", { name: /security settings/i })).toBeInTheDocument();
|
||||
expect(screen.getByRole("tab", { name: /scim/i })).toBeInTheDocument();
|
||||
expect(screen.getByRole("tab", { name: /ui settings/i })).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("should display Security Settings content when Security Settings tab is clicked", async () => {
|
||||
const user = userEvent.setup();
|
||||
render(<AdminPanel />);
|
||||
const securityTab = screen.getByRole("tab", { name: /security settings/i });
|
||||
await user.click(securityTab);
|
||||
expect(screen.getByRole("heading", { name: /security settings/i })).toBeInTheDocument();
|
||||
expect(screen.getByRole("button", { name: /add sso/i })).toBeInTheDocument();
|
||||
expect(screen.getByRole("button", { name: /allowed ips/i })).toBeInTheDocument();
|
||||
expect(screen.getByRole("button", { name: /ui access control/i })).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("should display SCIM content when SCIM tab is clicked", async () => {
|
||||
const user = userEvent.setup();
|
||||
render(<AdminPanel />);
|
||||
const scimTab = screen.getByRole("tab", { name: /scim/i });
|
||||
await user.click(scimTab);
|
||||
expect(screen.getByText("SCIM Config")).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
describe("SSO Configuration", () => {
|
||||
it("should check SSO configuration on mount when accessToken is available", async () => {
|
||||
render(<AdminPanel />);
|
||||
await waitFor(() => {
|
||||
expect(mockGetSSOSettings).toHaveBeenCalledWith("test-token");
|
||||
});
|
||||
});
|
||||
|
||||
it("should display 'Add SSO' button when SSO is not configured", async () => {
|
||||
const user = userEvent.setup();
|
||||
mockGetSSOSettings.mockResolvedValue({
|
||||
values: {},
|
||||
});
|
||||
render(<AdminPanel />);
|
||||
const securityTab = screen.getByRole("tab", { name: /security settings/i });
|
||||
await user.click(securityTab);
|
||||
await waitFor(() => {
|
||||
expect(screen.getByRole("button", { name: /add sso/i })).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
it("should display 'Edit SSO Settings' button when SSO is configured", async () => {
|
||||
const user = userEvent.setup();
|
||||
mockGetSSOSettings.mockResolvedValue({
|
||||
values: {
|
||||
google_client_id: "test-id",
|
||||
google_client_secret: "test-secret",
|
||||
},
|
||||
});
|
||||
render(<AdminPanel />);
|
||||
const securityTab = screen.getByRole("tab", { name: /security settings/i });
|
||||
await user.click(securityTab);
|
||||
await waitFor(() => {
|
||||
expect(screen.getByRole("button", { name: /edit sso settings/i })).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
it("should detect Google SSO configuration", async () => {
|
||||
mockGetSSOSettings.mockResolvedValue({
|
||||
values: {
|
||||
google_client_id: "test-id",
|
||||
google_client_secret: "test-secret",
|
||||
},
|
||||
});
|
||||
render(<AdminPanel />);
|
||||
await waitFor(() => {
|
||||
expect(mockGetSSOSettings).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
it("should detect Microsoft SSO configuration", async () => {
|
||||
mockGetSSOSettings.mockResolvedValue({
|
||||
values: {
|
||||
microsoft_client_id: "test-id",
|
||||
microsoft_client_secret: "test-secret",
|
||||
},
|
||||
});
|
||||
render(<AdminPanel />);
|
||||
await waitFor(() => {
|
||||
expect(mockGetSSOSettings).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
it("should detect Generic SSO configuration", async () => {
|
||||
mockGetSSOSettings.mockResolvedValue({
|
||||
values: {
|
||||
generic_client_id: "test-id",
|
||||
generic_client_secret: "test-secret",
|
||||
},
|
||||
});
|
||||
render(<AdminPanel />);
|
||||
await waitFor(() => {
|
||||
expect(mockGetSSOSettings).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
it("should handle SSO configuration check error gracefully", async () => {
|
||||
mockGetSSOSettings.mockRejectedValue(new Error("Network error"));
|
||||
render(<AdminPanel />);
|
||||
await waitFor(() => {
|
||||
expect(mockGetSSOSettings).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("Allowed IPs", () => {
|
||||
beforeEach(async () => {
|
||||
const user = userEvent.setup();
|
||||
mockUseAuthorized.mockReturnValue({
|
||||
premiumUser: true,
|
||||
accessToken: "test-token",
|
||||
userId: "user-1",
|
||||
});
|
||||
render(<AdminPanel />);
|
||||
const securityTab = screen.getByRole("tab", { name: /security settings/i });
|
||||
await user.click(securityTab);
|
||||
});
|
||||
|
||||
it("should open allowed IPs modal when premium user clicks Allowed IPs button", async () => {
|
||||
const user = userEvent.setup();
|
||||
mockGetAllowedIPs.mockResolvedValue(["192.168.1.1", "10.0.0.1"]);
|
||||
const allowedIPsButton = screen.getByRole("button", { name: /allowed ips/i });
|
||||
await user.click(allowedIPsButton);
|
||||
await waitFor(() => {
|
||||
expect(screen.getByRole("dialog", { name: /manage allowed ip addresses/i })).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
it("should display 'All IP Addresses Allowed' when no IPs are configured", async () => {
|
||||
const user = userEvent.setup();
|
||||
mockGetAllowedIPs.mockResolvedValue([]);
|
||||
const allowedIPsButton = screen.getByRole("button", { name: /allowed ips/i });
|
||||
await user.click(allowedIPsButton);
|
||||
await waitFor(() => {
|
||||
expect(screen.getByText("All IP Addresses Allowed")).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
it("should display list of allowed IPs", async () => {
|
||||
const user = userEvent.setup();
|
||||
mockGetAllowedIPs.mockResolvedValue(["192.168.1.1", "10.0.0.1"]);
|
||||
const allowedIPsButton = screen.getByRole("button", { name: /allowed ips/i });
|
||||
await user.click(allowedIPsButton);
|
||||
await waitFor(() => {
|
||||
expect(screen.getByText("192.168.1.1")).toBeInTheDocument();
|
||||
expect(screen.getByText("10.0.0.1")).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
it("should show delete button for IP addresses except 'All IP Addresses Allowed'", async () => {
|
||||
const user = userEvent.setup();
|
||||
mockGetAllowedIPs.mockResolvedValue(["192.168.1.1", "All IP Addresses Allowed"]);
|
||||
const allowedIPsButton = screen.getByRole("button", { name: /allowed ips/i });
|
||||
await user.click(allowedIPsButton);
|
||||
await waitFor(() => {
|
||||
const deleteButtons = screen.queryAllByRole("button", { name: /delete/i });
|
||||
expect(deleteButtons.length).toBeGreaterThan(0);
|
||||
});
|
||||
});
|
||||
|
||||
it("should not show delete button for 'All IP Addresses Allowed'", async () => {
|
||||
const user = userEvent.setup();
|
||||
mockGetAllowedIPs.mockResolvedValue(["All IP Addresses Allowed"]);
|
||||
const allowedIPsButton = screen.getByRole("button", { name: /allowed ips/i });
|
||||
await user.click(allowedIPsButton);
|
||||
await waitFor(() => {
|
||||
expect(screen.getByText("All IP Addresses Allowed")).toBeInTheDocument();
|
||||
});
|
||||
const deleteButtons = screen.queryAllByRole("button", { name: /delete/i });
|
||||
expect(deleteButtons.length).toBe(0);
|
||||
});
|
||||
|
||||
it("should handle error when fetching allowed IPs fails", async () => {
|
||||
const user = userEvent.setup();
|
||||
mockGetAllowedIPs.mockRejectedValue(new Error("Network error"));
|
||||
const allowedIPsButton = screen.getByRole("button", { name: /allowed ips/i });
|
||||
await user.click(allowedIPsButton);
|
||||
await waitFor(() => {
|
||||
expect(mockGetAllowedIPs).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("UI Access Control", () => {
|
||||
it("should show premium user message when non-premium user tries to access UI Access Control", async () => {
|
||||
const user = userEvent.setup();
|
||||
mockUseAuthorized.mockReturnValue({
|
||||
premiumUser: false,
|
||||
accessToken: "test-token",
|
||||
userId: "user-1",
|
||||
});
|
||||
render(<AdminPanel />);
|
||||
const securityTab = screen.getByRole("tab", { name: /security settings/i });
|
||||
await user.click(securityTab);
|
||||
const uiAccessControlButton = screen.getByRole("button", { name: /ui access control/i });
|
||||
await user.click(uiAccessControlButton);
|
||||
await waitFor(() => {
|
||||
expect(screen.queryByRole("dialog", { name: /ui access control settings/i })).not.toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
it("should open UI Access Control modal when premium user clicks button", async () => {
|
||||
const user = userEvent.setup();
|
||||
mockUseAuthorized.mockReturnValue({
|
||||
premiumUser: true,
|
||||
accessToken: "test-token",
|
||||
userId: "user-1",
|
||||
});
|
||||
render(<AdminPanel />);
|
||||
const securityTab = screen.getByRole("tab", { name: /security settings/i });
|
||||
await user.click(securityTab);
|
||||
const uiAccessControlButton = screen.getByRole("button", { name: /ui access control/i });
|
||||
await user.click(uiAccessControlButton);
|
||||
await waitFor(() => {
|
||||
expect(screen.getByRole("dialog", { name: /ui access control settings/i })).toBeInTheDocument();
|
||||
expect(screen.getByText("UI Access Control Form")).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("Login without SSO", () => {
|
||||
it("should display fallback login URL", async () => {
|
||||
const user = userEvent.setup();
|
||||
render(<AdminPanel />);
|
||||
const securityTab = screen.getByRole("tab", { name: /security settings/i });
|
||||
await user.click(securityTab);
|
||||
const link = screen.getByRole("link", { name: /http:\/\/localhost:4000\/fallback\/login/i });
|
||||
expect(link).toBeInTheDocument();
|
||||
expect(link).toHaveAttribute("href", "http://localhost:4000/fallback/login");
|
||||
expect(link).toHaveAttribute("target", "_blank");
|
||||
});
|
||||
});
|
||||
|
||||
describe("SSO Configuration Deprecation Warning", () => {
|
||||
it("should display deprecation warning in Security Settings tab", async () => {
|
||||
const user = userEvent.setup();
|
||||
render(<AdminPanel />);
|
||||
const securityTab = screen.getByRole("tab", { name: /security settings/i });
|
||||
await user.click(securityTab);
|
||||
await waitFor(() => {
|
||||
expect(screen.getByText(/sso configuration deprecated/i)).toBeInTheDocument();
|
||||
expect(
|
||||
screen.getByText(/editing sso settings on this page is deprecated and will be removed/i),
|
||||
).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
@ -2,80 +2,43 @@
|
|||
* Allow proxy admin to add other people to view global spend
|
||||
* Use this to avoid sharing master key with others
|
||||
*/
|
||||
import React, { useState, useEffect } from "react";
|
||||
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";
|
||||
import { Team } from "./key_team_helpers/key_list";
|
||||
import useAuthorized from "@/app/(dashboard)/hooks/useAuthorized";
|
||||
import {
|
||||
Button,
|
||||
Callout,
|
||||
Card,
|
||||
Table,
|
||||
TableBody,
|
||||
TableCell,
|
||||
TableHead,
|
||||
TableHeaderCell,
|
||||
TableRow,
|
||||
Card,
|
||||
Button,
|
||||
Callout,
|
||||
TabGroup,
|
||||
TabList,
|
||||
Tab,
|
||||
TabPanel,
|
||||
TabPanels,
|
||||
} from "@tremor/react";
|
||||
import { InvitationLink } from "./onboarding_link";
|
||||
import SSOModals from "./SSOModals";
|
||||
import SCIMConfig from "./SCIM";
|
||||
import UIAccessControlForm from "./UIAccessControlForm";
|
||||
import NotificationsManager from "./molecules/notifications_manager";
|
||||
|
||||
interface AdminPanelProps {
|
||||
searchParams: any;
|
||||
accessToken: string | null;
|
||||
userID: string | null;
|
||||
setTeams: React.Dispatch<React.SetStateAction<Team[] | null>>;
|
||||
showSSOBanner: boolean;
|
||||
premiumUser: boolean;
|
||||
proxySettings?: any;
|
||||
userRole?: string | null;
|
||||
}
|
||||
import { Alert, Button as Button2, Form, Input, Modal, Tabs, Typography } from "antd";
|
||||
import React, { useEffect, useState } from "react";
|
||||
import { useBaseUrl } from "./constants";
|
||||
|
||||
import NotificationsManager from "./molecules/notifications_manager";
|
||||
import {
|
||||
userUpdateUserCall,
|
||||
Member,
|
||||
userGetAllUsersCall,
|
||||
User,
|
||||
invitationCreateCall,
|
||||
getPossibleUserRoles,
|
||||
addAllowedIP,
|
||||
getAllowedIPs,
|
||||
deleteAllowedIP,
|
||||
getAllowedIPs,
|
||||
getSSOSettings,
|
||||
} from "./networking";
|
||||
import UISettings from "./Settings/AdminSettings/UISettings/UISettings";
|
||||
import SCIMConfig from "./SCIM";
|
||||
import SSOSettings from "./Settings/AdminSettings/SSOSettings/SSOSettings";
|
||||
import UISettings from "./Settings/AdminSettings/UISettings/UISettings";
|
||||
import SSOModals from "./SSOModals";
|
||||
import UIAccessControlForm from "./UIAccessControlForm";
|
||||
|
||||
const AdminPanel: React.FC<AdminPanelProps> = ({
|
||||
searchParams,
|
||||
accessToken,
|
||||
userID,
|
||||
showSSOBanner,
|
||||
premiumUser,
|
||||
proxySettings,
|
||||
userRole,
|
||||
}) => {
|
||||
const { Title, Paragraph, Text } = Typography;
|
||||
|
||||
interface AdminPanelProps {
|
||||
proxySettings?: any;
|
||||
}
|
||||
|
||||
const AdminPanel: React.FC<AdminPanelProps> = ({ proxySettings }) => {
|
||||
const { premiumUser, accessToken, userId: userID } = useAuthorized();
|
||||
const [form] = Form.useForm();
|
||||
const [memberForm] = Form.useForm();
|
||||
const { Title, Paragraph } = Typography;
|
||||
const [value, setValue] = useState("");
|
||||
const [admins, setAdmins] = useState<null | any[]>(null);
|
||||
const [invitationLinkData, setInvitationLinkData] = useState<InvitationLink | null>(null);
|
||||
const [isInvitationLinkModalVisible, setIsInvitationLinkModalVisible] = useState(false);
|
||||
const [isAddMemberModalVisible, setIsAddMemberModalVisible] = useState(false);
|
||||
const [isAddAdminModalVisible, setIsAddAdminModalVisible] = useState(false);
|
||||
const [isUpdateMemberModalVisible, setIsUpdateModalModalVisible] = useState(false);
|
||||
const [isAddSSOModalVisible, setIsAddSSOModalVisible] = useState(false);
|
||||
const [isInstructionsModalVisible, setIsInstructionsModalVisible] = useState(false);
|
||||
const [isAllowedIPModalVisible, setIsAllowedIPModalVisible] = useState(false);
|
||||
|
|
@ -85,14 +48,6 @@ const AdminPanel: React.FC<AdminPanelProps> = ({
|
|||
const [allowedIPs, setAllowedIPs] = useState<string[]>([]);
|
||||
const [ipToDelete, setIPToDelete] = useState<string | null>(null);
|
||||
const [ssoConfigured, setSsoConfigured] = useState<boolean>(false);
|
||||
const router = useRouter();
|
||||
|
||||
const [possibleUIRoles, setPossibleUIRoles] = useState<null | Record<string, Record<string, string>>>(null);
|
||||
|
||||
const isLocal = process.env.NODE_ENV === "development";
|
||||
if (isLocal != true) {
|
||||
console.log = function () { };
|
||||
}
|
||||
|
||||
const baseUrl = useBaseUrl();
|
||||
const all_ip_address_allowed = "All IP Addresses Allowed";
|
||||
|
|
@ -100,14 +55,11 @@ const AdminPanel: React.FC<AdminPanelProps> = ({
|
|||
let nonSssoUrl = baseUrl;
|
||||
nonSssoUrl += "/fallback/login";
|
||||
|
||||
// Extract the SSO configuration check logic into a separate function for reuse
|
||||
const checkSSOConfiguration = async () => {
|
||||
if (accessToken) {
|
||||
try {
|
||||
const ssoData = await getSSOSettings(accessToken);
|
||||
console.log("SSO data:", ssoData);
|
||||
|
||||
// Check if any SSO provider is configured
|
||||
if (ssoData && ssoData.values) {
|
||||
const hasGoogleSSO = ssoData.values.google_client_id && ssoData.values.google_client_secret;
|
||||
const hasMicrosoftSSO = ssoData.values.microsoft_client_id && ssoData.values.microsoft_client_secret;
|
||||
|
|
@ -192,7 +144,6 @@ const AdminPanel: React.FC<AdminPanelProps> = ({
|
|||
const handleAddSSOOk = () => {
|
||||
setIsAddSSOModalVisible(false);
|
||||
form.resetFields();
|
||||
// Refresh SSO configuration status
|
||||
if (accessToken && premiumUser) {
|
||||
checkSSOConfiguration();
|
||||
}
|
||||
|
|
@ -210,7 +161,6 @@ const AdminPanel: React.FC<AdminPanelProps> = ({
|
|||
|
||||
const handleInstructionsOk = () => {
|
||||
setIsInstructionsModalVisible(false);
|
||||
// Refresh SSO configuration status after instructions are closed
|
||||
if (accessToken && premiumUser) {
|
||||
checkSSOConfiguration();
|
||||
}
|
||||
|
|
@ -218,270 +168,15 @@ const AdminPanel: React.FC<AdminPanelProps> = ({
|
|||
|
||||
const handleInstructionsCancel = () => {
|
||||
setIsInstructionsModalVisible(false);
|
||||
// Refresh SSO configuration status after instructions are closed
|
||||
if (accessToken && premiumUser) {
|
||||
checkSSOConfiguration();
|
||||
}
|
||||
};
|
||||
|
||||
const roles = ["proxy_admin", "proxy_admin_viewer"];
|
||||
|
||||
// useEffect(() => {
|
||||
// if (router) {
|
||||
// const { protocol, host } = window.location;
|
||||
// const baseUrl = `${protocol}//${host}`;
|
||||
// setBaseUrl(baseUrl);
|
||||
// }
|
||||
// }, [router]);
|
||||
|
||||
useEffect(() => {
|
||||
// Fetch model info and set the default selected model
|
||||
const fetchProxyAdminInfo = async () => {
|
||||
if (accessToken != null) {
|
||||
const combinedList: any[] = [];
|
||||
const response = await userGetAllUsersCall(accessToken, "proxy_admin_viewer");
|
||||
console.log("proxy admin viewer response: ", response);
|
||||
const proxyViewers: User[] = response["users"];
|
||||
console.log(`proxy viewers response: ${proxyViewers}`);
|
||||
proxyViewers.forEach((viewer: User) => {
|
||||
combinedList.push({
|
||||
user_role: viewer.user_role,
|
||||
user_id: viewer.user_id,
|
||||
user_email: viewer.user_email,
|
||||
});
|
||||
});
|
||||
|
||||
console.log(`proxy viewers: ${proxyViewers}`);
|
||||
|
||||
const response2 = await userGetAllUsersCall(accessToken, "proxy_admin");
|
||||
|
||||
const proxyAdmins: User[] = response2["users"];
|
||||
|
||||
proxyAdmins.forEach((admins: User) => {
|
||||
combinedList.push({
|
||||
user_role: admins.user_role,
|
||||
user_id: admins.user_id,
|
||||
user_email: admins.user_email,
|
||||
});
|
||||
});
|
||||
|
||||
console.log(`proxy admins: ${proxyAdmins}`);
|
||||
console.log(`combinedList: ${combinedList}`);
|
||||
setAdmins(combinedList);
|
||||
|
||||
const availableUserRoles = await getPossibleUserRoles(accessToken);
|
||||
setPossibleUIRoles(availableUserRoles);
|
||||
}
|
||||
};
|
||||
|
||||
fetchProxyAdminInfo();
|
||||
}, [accessToken]);
|
||||
|
||||
// Add new useEffect to check SSO configuration
|
||||
useEffect(() => {
|
||||
checkSSOConfiguration();
|
||||
}, [accessToken, premiumUser]);
|
||||
|
||||
const handleMemberUpdateOk = () => {
|
||||
setIsUpdateModalModalVisible(false);
|
||||
memberForm.resetFields();
|
||||
form.resetFields();
|
||||
};
|
||||
|
||||
const handleMemberOk = () => {
|
||||
setIsAddMemberModalVisible(false);
|
||||
memberForm.resetFields();
|
||||
form.resetFields();
|
||||
};
|
||||
|
||||
const handleAdminOk = () => {
|
||||
setIsAddAdminModalVisible(false);
|
||||
memberForm.resetFields();
|
||||
form.resetFields();
|
||||
};
|
||||
|
||||
const handleMemberCancel = () => {
|
||||
setIsAddMemberModalVisible(false);
|
||||
memberForm.resetFields();
|
||||
form.resetFields();
|
||||
};
|
||||
|
||||
const handleAdminCancel = () => {
|
||||
setIsAddAdminModalVisible(false);
|
||||
setIsInvitationLinkModalVisible(false);
|
||||
memberForm.resetFields();
|
||||
form.resetFields();
|
||||
};
|
||||
|
||||
const handleMemberUpdateCancel = () => {
|
||||
setIsUpdateModalModalVisible(false);
|
||||
memberForm.resetFields();
|
||||
form.resetFields();
|
||||
};
|
||||
// Define the type for the handleMemberCreate function
|
||||
type HandleMemberCreate = (formValues: Record<string, any>) => Promise<void>;
|
||||
|
||||
const addMemberForm = (handleMemberCreate: HandleMemberCreate) => {
|
||||
return (
|
||||
<Form
|
||||
form={form}
|
||||
onFinish={handleMemberCreate}
|
||||
labelCol={{ span: 8 }}
|
||||
wrapperCol={{ span: 16 }}
|
||||
labelAlign="left"
|
||||
>
|
||||
<>
|
||||
<Form.Item label="Email" name="user_email" className="mb-8 mt-4">
|
||||
<Input name="user_email" className="px-3 py-2 border rounded-md w-full" />
|
||||
</Form.Item>
|
||||
</>
|
||||
<div style={{ textAlign: "right", marginTop: "10px" }} className="mt-4">
|
||||
<Button2 htmlType="submit">Add member</Button2>
|
||||
</div>
|
||||
</Form>
|
||||
);
|
||||
};
|
||||
|
||||
const modifyMemberForm = (handleMemberUpdate: HandleMemberCreate, currentRole: string, userID: string) => {
|
||||
return (
|
||||
<Form
|
||||
form={form}
|
||||
onFinish={handleMemberUpdate}
|
||||
labelCol={{ span: 8 }}
|
||||
wrapperCol={{ span: 16 }}
|
||||
labelAlign="left"
|
||||
>
|
||||
<>
|
||||
<Form.Item
|
||||
rules={[{ required: true, message: "Required" }]}
|
||||
label="User Role"
|
||||
name="user_role"
|
||||
labelCol={{ span: 10 }}
|
||||
labelAlign="left"
|
||||
>
|
||||
<Select value={currentRole}>
|
||||
{roles.map((role, index) => (
|
||||
<SelectItem key={index} value={role}>
|
||||
{role}
|
||||
</SelectItem>
|
||||
))}
|
||||
</Select>
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
label="Team ID"
|
||||
name="user_id"
|
||||
hidden={true}
|
||||
initialValue={userID}
|
||||
valuePropName="user_id"
|
||||
className="mt-8"
|
||||
>
|
||||
<Input value={userID} disabled />
|
||||
</Form.Item>
|
||||
</>
|
||||
<div style={{ textAlign: "right", marginTop: "10px" }}>
|
||||
<Button2 htmlType="submit">Update role</Button2>
|
||||
</div>
|
||||
</Form>
|
||||
);
|
||||
};
|
||||
|
||||
const handleMemberUpdate = async (formValues: Record<string, any>) => {
|
||||
try {
|
||||
if (accessToken != null && admins != null) {
|
||||
NotificationsManager.info("Making API Call");
|
||||
const response: any = await userUpdateUserCall(accessToken, formValues, null);
|
||||
console.log(`response for team create call: ${response}`);
|
||||
// Checking if the team exists in the list and updating or adding accordingly
|
||||
const foundIndex = admins.findIndex((user) => {
|
||||
console.log(`user.user_id=${user.user_id}; response.user_id=${response.user_id}`);
|
||||
return user.user_id === response.user_id;
|
||||
});
|
||||
console.log(`foundIndex: ${foundIndex}`);
|
||||
if (foundIndex == -1) {
|
||||
console.log(`updates admin with new user`);
|
||||
admins.push(response);
|
||||
// If new user is found, update it
|
||||
setAdmins(admins); // Set the new state
|
||||
}
|
||||
NotificationsManager.success("Refresh tab to see updated user role");
|
||||
setIsUpdateModalModalVisible(false);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Error creating the key:", error);
|
||||
}
|
||||
};
|
||||
|
||||
const handleMemberCreate = async (formValues: Record<string, any>) => {
|
||||
try {
|
||||
if (accessToken != null && admins != null) {
|
||||
NotificationsManager.info("Making API Call");
|
||||
const response: any = await userUpdateUserCall(accessToken, formValues, "proxy_admin_viewer");
|
||||
console.log(`response for team create call: ${response}`);
|
||||
// Checking if the team exists in the list and updating or adding accordingly
|
||||
|
||||
// Give admin an invite link for inviting user to proxy
|
||||
const user_id = response.data?.user_id || response.user_id;
|
||||
invitationCreateCall(accessToken, user_id).then((data) => {
|
||||
setInvitationLinkData(data);
|
||||
setIsInvitationLinkModalVisible(true);
|
||||
});
|
||||
|
||||
const foundIndex = admins.findIndex((user) => {
|
||||
console.log(`user.user_id=${user.user_id}; response.user_id=${response.user_id}`);
|
||||
return user.user_id === response.user_id;
|
||||
});
|
||||
console.log(`foundIndex: ${foundIndex}`);
|
||||
if (foundIndex == -1) {
|
||||
console.log(`updates admin with new user`);
|
||||
admins.push(response);
|
||||
// If new user is found, update it
|
||||
setAdmins(admins); // Set the new state
|
||||
}
|
||||
form.resetFields();
|
||||
setIsAddMemberModalVisible(false);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Error creating the key:", error);
|
||||
}
|
||||
};
|
||||
const handleAdminCreate = async (formValues: Record<string, any>) => {
|
||||
try {
|
||||
if (accessToken != null && admins != null) {
|
||||
NotificationsManager.info("Making API Call");
|
||||
const user_role: Member = {
|
||||
role: "user",
|
||||
user_email: formValues.user_email,
|
||||
user_id: formValues.user_id,
|
||||
};
|
||||
const response: any = await userUpdateUserCall(accessToken, formValues, "proxy_admin");
|
||||
|
||||
// Give admin an invite link for inviting user to proxy
|
||||
const user_id = response.data?.user_id || response.user_id;
|
||||
invitationCreateCall(accessToken, user_id).then((data) => {
|
||||
setInvitationLinkData(data);
|
||||
setIsInvitationLinkModalVisible(true);
|
||||
});
|
||||
console.log(`response for team create call: ${response}`);
|
||||
// Checking if the team exists in the list and updating or adding accordingly
|
||||
const foundIndex = admins.findIndex((user) => {
|
||||
console.log(`user.user_id=${user.user_id}; response.user_id=${user_id}`);
|
||||
return user.user_id === response.user_id;
|
||||
});
|
||||
console.log(`foundIndex: ${foundIndex}`);
|
||||
if (foundIndex == -1) {
|
||||
console.log(`updates admin with new user`);
|
||||
admins.push(response);
|
||||
// If new user is found, update it
|
||||
setAdmins(admins); // Set the new state
|
||||
}
|
||||
form.resetFields();
|
||||
setIsAddAdminModalVisible(false);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Error creating the key:", error);
|
||||
}
|
||||
};
|
||||
|
||||
const handleUIAccessControlOk = () => {
|
||||
setIsUIAccessControlModalVisible(false);
|
||||
};
|
||||
|
|
@ -490,182 +185,187 @@ const AdminPanel: React.FC<AdminPanelProps> = ({
|
|||
setIsUIAccessControlModalVisible(false);
|
||||
};
|
||||
|
||||
console.log(`admins: ${admins?.length}`);
|
||||
const tabItems = [
|
||||
{
|
||||
key: "sso-settings",
|
||||
label: "SSO Settings",
|
||||
children: <SSOSettings />,
|
||||
},
|
||||
{
|
||||
key: "security-settings",
|
||||
label: "Security Settings",
|
||||
children: (
|
||||
<>
|
||||
<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",
|
||||
flexDirection: "column",
|
||||
gap: "1rem",
|
||||
marginTop: "1rem",
|
||||
marginLeft: "0.5rem",
|
||||
}}
|
||||
>
|
||||
<div>
|
||||
<Button style={{ width: "150px" }} onClick={() => setIsAddSSOModalVisible(true)}>
|
||||
{ssoConfigured ? "Edit SSO Settings" : "Add SSO"}
|
||||
</Button>
|
||||
</div>
|
||||
<div>
|
||||
<Button style={{ width: "150px" }} onClick={handleShowAllowedIPs}>
|
||||
Allowed IPs
|
||||
</Button>
|
||||
</div>
|
||||
<div>
|
||||
<Button
|
||||
style={{ width: "150px" }}
|
||||
onClick={() =>
|
||||
premiumUser === true
|
||||
? setIsUIAccessControlModalVisible(true)
|
||||
: NotificationsManager.fromBackend("Only premium users can configure UI access control")
|
||||
}
|
||||
>
|
||||
UI Access Control
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</Card>
|
||||
|
||||
<div className="flex justify-start mb-4">
|
||||
<SSOModals
|
||||
isAddSSOModalVisible={isAddSSOModalVisible}
|
||||
isInstructionsModalVisible={isInstructionsModalVisible}
|
||||
handleAddSSOOk={handleAddSSOOk}
|
||||
handleAddSSOCancel={handleAddSSOCancel}
|
||||
handleShowInstructions={handleShowInstructions}
|
||||
handleInstructionsOk={handleInstructionsOk}
|
||||
handleInstructionsCancel={handleInstructionsCancel}
|
||||
form={form}
|
||||
accessToken={accessToken}
|
||||
ssoConfigured={ssoConfigured}
|
||||
/>
|
||||
<Modal
|
||||
title="Manage Allowed IP Addresses"
|
||||
width={800}
|
||||
open={isAllowedIPModalVisible}
|
||||
onCancel={() => setIsAllowedIPModalVisible(false)}
|
||||
footer={[
|
||||
<Button className="mx-1" key="add" onClick={() => setIsAddIPModalVisible(true)}>
|
||||
Add IP Address
|
||||
</Button>,
|
||||
<Button key="close" onClick={() => setIsAllowedIPModalVisible(false)}>
|
||||
Close
|
||||
</Button>,
|
||||
]}
|
||||
>
|
||||
<Table>
|
||||
<TableHead>
|
||||
<TableRow>
|
||||
<TableHeaderCell>IP Address</TableHeaderCell>
|
||||
<TableHeaderCell className="text-right">Action</TableHeaderCell>
|
||||
</TableRow>
|
||||
</TableHead>
|
||||
<TableBody>
|
||||
{allowedIPs.map((ip, index) => (
|
||||
<TableRow key={index}>
|
||||
<TableCell>{ip}</TableCell>
|
||||
<TableCell className="text-right">
|
||||
{ip !== all_ip_address_allowed && (
|
||||
<Button onClick={() => handleDeleteIP(ip)} color="red" size="xs">
|
||||
Delete
|
||||
</Button>
|
||||
)}
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
))}
|
||||
</TableBody>
|
||||
</Table>
|
||||
</Modal>
|
||||
|
||||
<Modal
|
||||
title="Add Allowed IP Address"
|
||||
open={isAddIPModalVisible}
|
||||
onCancel={() => setIsAddIPModalVisible(false)}
|
||||
footer={null}
|
||||
>
|
||||
<Form onFinish={handleAddIP}>
|
||||
<Form.Item name="ip" rules={[{ required: true, message: "Please enter an IP address" }]}>
|
||||
<Input placeholder="Enter IP address" />
|
||||
</Form.Item>
|
||||
<Form.Item>
|
||||
<Button2 htmlType="submit">Add IP Address</Button2>
|
||||
</Form.Item>
|
||||
</Form>
|
||||
</Modal>
|
||||
|
||||
<Modal
|
||||
title="Confirm Delete"
|
||||
open={isDeleteIPModalVisible}
|
||||
onCancel={() => setIsDeleteIPModalVisible(false)}
|
||||
onOk={confirmDeleteIP}
|
||||
footer={[
|
||||
<Button className="mx-1" key="delete" onClick={() => confirmDeleteIP()}>
|
||||
Yes
|
||||
</Button>,
|
||||
<Button key="close" onClick={() => setIsDeleteIPModalVisible(false)}>
|
||||
Close
|
||||
</Button>,
|
||||
]}
|
||||
>
|
||||
<Text>Are you sure you want to delete the IP address: {ipToDelete}?</Text>
|
||||
</Modal>
|
||||
|
||||
{/* UI Access Control Modal */}
|
||||
<Modal
|
||||
title="UI Access Control Settings"
|
||||
open={isUIAccessControlModalVisible}
|
||||
width={600}
|
||||
footer={null}
|
||||
onOk={handleUIAccessControlOk}
|
||||
onCancel={handleUIAccessControlCancel}
|
||||
>
|
||||
<UIAccessControlForm
|
||||
accessToken={accessToken}
|
||||
onSuccess={() => {
|
||||
handleUIAccessControlOk();
|
||||
NotificationsManager.success("UI Access Control settings updated successfully");
|
||||
}}
|
||||
/>
|
||||
</Modal>
|
||||
</div>
|
||||
<Callout title="Login without SSO" color="teal">
|
||||
If you need to login without sso, you can access{" "}
|
||||
<a href={nonSssoUrl} target="_blank">
|
||||
<b>{nonSssoUrl}</b>{" "}
|
||||
</a>
|
||||
</Callout>
|
||||
</>
|
||||
),
|
||||
},
|
||||
{
|
||||
key: "scim",
|
||||
label: "SCIM",
|
||||
children: <SCIMConfig accessToken={accessToken} userID={userID} proxySettings={proxySettings} />,
|
||||
},
|
||||
{
|
||||
key: "ui-settings",
|
||||
label: "UI Settings",
|
||||
children: <UISettings />,
|
||||
},
|
||||
];
|
||||
|
||||
return (
|
||||
<div className="w-full m-2 mt-2 p-8">
|
||||
<Title level={4}>Admin Access </Title>
|
||||
<Paragraph>Go to 'Internal Users' page to add other admins.</Paragraph>
|
||||
<TabGroup>
|
||||
<TabList>
|
||||
<Tab>SSO Settings</Tab>
|
||||
<Tab>Security Settings</Tab>
|
||||
<Tab>SCIM</Tab>
|
||||
<Tab>UI Settings</Tab>
|
||||
</TabList>
|
||||
<TabPanels>
|
||||
<TabPanel>
|
||||
<SSOSettings />
|
||||
</TabPanel>
|
||||
<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",
|
||||
flexDirection: "column",
|
||||
gap: "1rem",
|
||||
marginTop: "1rem",
|
||||
marginLeft: "0.5rem",
|
||||
}}
|
||||
>
|
||||
<div>
|
||||
<Button style={{ width: "150px" }} onClick={() => setIsAddSSOModalVisible(true)}>
|
||||
{ssoConfigured ? "Edit SSO Settings" : "Add SSO"}
|
||||
</Button>
|
||||
</div>
|
||||
<div>
|
||||
<Button style={{ width: "150px" }} onClick={handleShowAllowedIPs}>
|
||||
Allowed IPs
|
||||
</Button>
|
||||
</div>
|
||||
<div>
|
||||
<Button
|
||||
style={{ width: "150px" }}
|
||||
onClick={() =>
|
||||
premiumUser === true
|
||||
? setIsUIAccessControlModalVisible(true)
|
||||
: NotificationsManager.fromBackend("Only premium users can configure UI access control")
|
||||
}
|
||||
>
|
||||
UI Access Control
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</Card>
|
||||
|
||||
<div className="flex justify-start mb-4">
|
||||
<SSOModals
|
||||
isAddSSOModalVisible={isAddSSOModalVisible}
|
||||
isInstructionsModalVisible={isInstructionsModalVisible}
|
||||
handleAddSSOOk={handleAddSSOOk}
|
||||
handleAddSSOCancel={handleAddSSOCancel}
|
||||
handleShowInstructions={handleShowInstructions}
|
||||
handleInstructionsOk={handleInstructionsOk}
|
||||
handleInstructionsCancel={handleInstructionsCancel}
|
||||
form={form}
|
||||
accessToken={accessToken}
|
||||
ssoConfigured={ssoConfigured}
|
||||
/>
|
||||
<Modal
|
||||
title="Manage Allowed IP Addresses"
|
||||
width={800}
|
||||
open={isAllowedIPModalVisible}
|
||||
onCancel={() => setIsAllowedIPModalVisible(false)}
|
||||
footer={[
|
||||
<Button className="mx-1" key="add" onClick={() => setIsAddIPModalVisible(true)}>
|
||||
Add IP Address
|
||||
</Button>,
|
||||
<Button key="close" onClick={() => setIsAllowedIPModalVisible(false)}>
|
||||
Close
|
||||
</Button>,
|
||||
]}
|
||||
>
|
||||
<Table>
|
||||
<TableHead>
|
||||
<TableRow>
|
||||
<TableHeaderCell>IP Address</TableHeaderCell>
|
||||
<TableHeaderCell className="text-right">Action</TableHeaderCell>
|
||||
</TableRow>
|
||||
</TableHead>
|
||||
<TableBody>
|
||||
{allowedIPs.map((ip, index) => (
|
||||
<TableRow key={index}>
|
||||
<TableCell>{ip}</TableCell>
|
||||
<TableCell className="text-right">
|
||||
{ip !== all_ip_address_allowed && (
|
||||
<Button onClick={() => handleDeleteIP(ip)} color="red" size="xs">
|
||||
Delete
|
||||
</Button>
|
||||
)}
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
))}
|
||||
</TableBody>
|
||||
</Table>
|
||||
</Modal>
|
||||
|
||||
<Modal
|
||||
title="Add Allowed IP Address"
|
||||
open={isAddIPModalVisible}
|
||||
onCancel={() => setIsAddIPModalVisible(false)}
|
||||
footer={null}
|
||||
>
|
||||
<Form onFinish={handleAddIP}>
|
||||
<Form.Item name="ip" rules={[{ required: true, message: "Please enter an IP address" }]}>
|
||||
<Input placeholder="Enter IP address" />
|
||||
</Form.Item>
|
||||
<Form.Item>
|
||||
<Button2 htmlType="submit">Add IP Address</Button2>
|
||||
</Form.Item>
|
||||
</Form>
|
||||
</Modal>
|
||||
|
||||
<Modal
|
||||
title="Confirm Delete"
|
||||
open={isDeleteIPModalVisible}
|
||||
onCancel={() => setIsDeleteIPModalVisible(false)}
|
||||
onOk={confirmDeleteIP}
|
||||
footer={[
|
||||
<Button className="mx-1" key="delete" onClick={() => confirmDeleteIP()}>
|
||||
Yes
|
||||
</Button>,
|
||||
<Button key="close" onClick={() => setIsDeleteIPModalVisible(false)}>
|
||||
Close
|
||||
</Button>,
|
||||
]}
|
||||
>
|
||||
<p>Are you sure you want to delete the IP address: {ipToDelete}?</p>
|
||||
</Modal>
|
||||
|
||||
{/* UI Access Control Modal */}
|
||||
<Modal
|
||||
title="UI Access Control Settings"
|
||||
open={isUIAccessControlModalVisible}
|
||||
width={600}
|
||||
footer={null}
|
||||
onOk={handleUIAccessControlOk}
|
||||
onCancel={handleUIAccessControlCancel}
|
||||
>
|
||||
<UIAccessControlForm
|
||||
accessToken={accessToken}
|
||||
onSuccess={() => {
|
||||
handleUIAccessControlOk();
|
||||
NotificationsManager.success("UI Access Control settings updated successfully");
|
||||
}}
|
||||
/>
|
||||
</Modal>
|
||||
</div>
|
||||
<Callout title="Login without SSO" color="teal">
|
||||
If you need to login without sso, you can access{" "}
|
||||
<a href={nonSssoUrl} target="_blank">
|
||||
<b>{nonSssoUrl}</b>{" "}
|
||||
</a>
|
||||
</Callout>
|
||||
</TabPanel>
|
||||
<TabPanel>
|
||||
<SCIMConfig accessToken={accessToken} userID={userID} proxySettings={proxySettings} />
|
||||
</TabPanel>
|
||||
<TabPanel>
|
||||
<UISettings />
|
||||
</TabPanel>
|
||||
</TabPanels>
|
||||
</TabGroup>
|
||||
<Tabs items={tabItems} />
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue