refactor admin page

This commit is contained in:
yuneng-jiang 2026-02-04 20:46:31 -08:00
parent 02ed25c886
commit 539ff7a850
4 changed files with 524 additions and 520 deletions

View file

@ -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}
/>
);
};

View file

@ -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" ? (

View 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();
});
});
});
});

View file

@ -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 &apos;Internal Users&apos; 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>
);
};