|
|
|
|
|
|
|
|
|
import React, { useState, useEffect } from "react"; |
|
import { Typography } from "antd"; |
|
import { useRouter } from "next/navigation"; |
|
import { |
|
Button as Button2, |
|
Modal, |
|
Form, |
|
Input, |
|
Select as Select2, |
|
InputNumber, |
|
message, |
|
} from "antd"; |
|
import { CopyToClipboard } from "react-copy-to-clipboard"; |
|
import { Select, SelectItem, Subtitle } from "@tremor/react"; |
|
import { |
|
Table, |
|
TableBody, |
|
TableCell, |
|
TableHead, |
|
TableHeaderCell, |
|
TableRow, |
|
Card, |
|
Icon, |
|
Button, |
|
Col, |
|
Text, |
|
Grid, |
|
Callout, |
|
Divider, |
|
} from "@tremor/react"; |
|
import { PencilAltIcon } from "@heroicons/react/outline"; |
|
import OnboardingModal from "./onboarding_link"; |
|
import { InvitationLink } from "./onboarding_link"; |
|
interface AdminPanelProps { |
|
searchParams: any; |
|
accessToken: string | null; |
|
setTeams: React.Dispatch<React.SetStateAction<Object[] | null>>; |
|
showSSOBanner: boolean; |
|
premiumUser: boolean; |
|
} |
|
import { useBaseUrl } from "./constants"; |
|
|
|
|
|
import { |
|
userUpdateUserCall, |
|
Member, |
|
userGetAllUsersCall, |
|
User, |
|
setCallbacksCall, |
|
invitationCreateCall, |
|
getPossibleUserRoles, |
|
addAllowedIP, |
|
getAllowedIPs, |
|
deleteAllowedIP, |
|
} from "./networking"; |
|
|
|
const AdminPanel: React.FC<AdminPanelProps> = ({ |
|
searchParams, |
|
accessToken, |
|
showSSOBanner, |
|
premiumUser, |
|
}) => { |
|
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); |
|
const [isAddIPModalVisible, setIsAddIPModalVisible] = useState(false); |
|
const [isDeleteIPModalVisible, setIsDeleteIPModalVisible] = useState(false); |
|
const [allowedIPs, setAllowedIPs] = useState<string[]>([]); |
|
const [ipToDelete, setIPToDelete] = useState<string | null>(null); |
|
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"; |
|
|
|
let nonSssoUrl = baseUrl; |
|
nonSssoUrl += "/fallback/login"; |
|
|
|
const handleShowAllowedIPs = async () => { |
|
try { |
|
if (premiumUser !== true) { |
|
message.error( |
|
"This feature is only available for premium users. Please upgrade your account." |
|
) |
|
return |
|
} |
|
if (accessToken) { |
|
const data = await getAllowedIPs(accessToken); |
|
setAllowedIPs(data && data.length > 0 ? data : [all_ip_address_allowed]); |
|
} else { |
|
setAllowedIPs([all_ip_address_allowed]); |
|
} |
|
} catch (error) { |
|
console.error("Error fetching allowed IPs:", error); |
|
message.error(`Failed to fetch allowed IPs ${error}`); |
|
setAllowedIPs([all_ip_address_allowed]); |
|
} finally { |
|
if (premiumUser === true) { |
|
setIsAllowedIPModalVisible(true); |
|
} |
|
} |
|
}; |
|
|
|
const handleAddIP = async (values: { ip: string }) => { |
|
try { |
|
if (accessToken) { |
|
await addAllowedIP(accessToken, values.ip); |
|
|
|
const updatedIPs = await getAllowedIPs(accessToken); |
|
setAllowedIPs(updatedIPs); |
|
message.success('IP address added successfully'); |
|
} |
|
} catch (error) { |
|
console.error("Error adding IP:", error); |
|
message.error(`Failed to add IP address ${error}`); |
|
} finally { |
|
setIsAddIPModalVisible(false); |
|
} |
|
}; |
|
|
|
const handleDeleteIP = async (ip: string) => { |
|
setIPToDelete(ip); |
|
setIsDeleteIPModalVisible(true); |
|
}; |
|
|
|
const confirmDeleteIP = async () => { |
|
if (ipToDelete && accessToken) { |
|
try { |
|
await deleteAllowedIP(accessToken, ipToDelete); |
|
|
|
const updatedIPs = await getAllowedIPs(accessToken); |
|
setAllowedIPs(updatedIPs.length > 0 ? updatedIPs : [all_ip_address_allowed]); |
|
message.success('IP address deleted successfully'); |
|
} catch (error) { |
|
console.error("Error deleting IP:", error); |
|
message.error(`Failed to delete IP address ${error}`); |
|
} finally { |
|
setIsDeleteIPModalVisible(false); |
|
setIPToDelete(null); |
|
} |
|
} |
|
}; |
|
|
|
|
|
const handleAddSSOOk = () => { |
|
setIsAddSSOModalVisible(false); |
|
form.resetFields(); |
|
}; |
|
|
|
const handleAddSSOCancel = () => { |
|
setIsAddSSOModalVisible(false); |
|
form.resetFields(); |
|
}; |
|
|
|
const handleShowInstructions = (formValues: Record<string, any>) => { |
|
handleAdminCreate(formValues); |
|
handleSSOUpdate(formValues); |
|
setIsAddSSOModalVisible(false); |
|
setIsInstructionsModalVisible(true); |
|
|
|
}; |
|
|
|
const handleInstructionsOk = () => { |
|
setIsInstructionsModalVisible(false); |
|
}; |
|
|
|
const handleInstructionsCancel = () => { |
|
setIsInstructionsModalVisible(false); |
|
}; |
|
|
|
const roles = ["proxy_admin", "proxy_admin_viewer"]; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
useEffect(() => { |
|
|
|
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]); |
|
|
|
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(); |
|
}; |
|
|
|
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) { |
|
message.info("Making API Call"); |
|
const response: any = await userUpdateUserCall( |
|
accessToken, |
|
formValues, |
|
null |
|
); |
|
console.log(`response for team create call: ${response}`); |
|
|
|
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); |
|
|
|
setAdmins(admins); |
|
} |
|
message.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) { |
|
message.info("Making API Call"); |
|
const response: any = await userUpdateUserCall( |
|
accessToken, |
|
formValues, |
|
"proxy_admin_viewer" |
|
); |
|
console.log(`response for team create call: ${response}`); |
|
|
|
|
|
|
|
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); |
|
|
|
setAdmins(admins); |
|
} |
|
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) { |
|
message.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" |
|
); |
|
|
|
|
|
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}`); |
|
|
|
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); |
|
|
|
setAdmins(admins); |
|
} |
|
form.resetFields(); |
|
setIsAddAdminModalVisible(false); |
|
} |
|
} catch (error) { |
|
console.error("Error creating the key:", error); |
|
} |
|
}; |
|
|
|
const handleSSOUpdate = async (formValues: Record<string, any>) => { |
|
if (accessToken == null) { |
|
return; |
|
} |
|
let payload = { |
|
environment_variables: { |
|
PROXY_BASE_URL: formValues.proxy_base_url, |
|
GOOGLE_CLIENT_ID: formValues.google_client_id, |
|
GOOGLE_CLIENT_SECRET: formValues.google_client_secret, |
|
}, |
|
}; |
|
setCallbacksCall(accessToken, payload); |
|
}; |
|
console.log(`admins: ${admins?.length}`); |
|
return ( |
|
<div className="w-full m-2 mt-2 p-8"> |
|
<Title level={4}>Admin Access </Title> |
|
<Paragraph> |
|
{showSSOBanner && ( |
|
<a href="https://docs.litellm.ai/docs/proxy/ui#restrict-ui-access"> |
|
Requires SSO Setup |
|
</a> |
|
)} |
|
<br /> |
|
<b>Proxy Admin: </b> Can create keys, teams, users, add models, etc.{" "} |
|
<br /> |
|
<b>Proxy Admin Viewer: </b>Can just view spend. They cannot create keys, |
|
teams or grant users access to new models.{" "} |
|
</Paragraph> |
|
<Grid numItems={1} className="gap-2 p-2 w-full"> |
|
<Col numColSpan={1}> |
|
<Card className="w-full mx-auto flex-auto overflow-y-auto max-h-[50vh]"> |
|
<Table> |
|
<TableHead> |
|
<TableRow> |
|
<TableHeaderCell>Member Name</TableHeaderCell> |
|
<TableHeaderCell>Role</TableHeaderCell> |
|
</TableRow> |
|
</TableHead> |
|
|
|
<TableBody> |
|
{admins |
|
? admins.map((member: any, index: number) => ( |
|
<TableRow key={index}> |
|
<TableCell> |
|
{member["user_email"] |
|
? member["user_email"] |
|
: member["user_id"] |
|
? member["user_id"] |
|
: null} |
|
</TableCell> |
|
<TableCell> |
|
{" "} |
|
{possibleUIRoles?.[member?.user_role]?.ui_label || |
|
"-"} |
|
</TableCell> |
|
<TableCell> |
|
<Icon |
|
icon={PencilAltIcon} |
|
size="sm" |
|
onClick={() => setIsUpdateModalModalVisible(true)} |
|
/> |
|
<Modal |
|
title="Update role" |
|
visible={isUpdateMemberModalVisible} |
|
width={800} |
|
footer={null} |
|
onOk={handleMemberUpdateOk} |
|
onCancel={handleMemberUpdateCancel} |
|
> |
|
{modifyMemberForm( |
|
handleMemberUpdate, |
|
member["user_role"], |
|
member["user_id"] |
|
)} |
|
</Modal> |
|
</TableCell> |
|
</TableRow> |
|
)) |
|
: null} |
|
</TableBody> |
|
</Table> |
|
</Card> |
|
</Col> |
|
<Col numColSpan={1}> |
|
<div className="flex justify-start"> |
|
<Button |
|
className="mr-4 mb-5" |
|
onClick={() => setIsAddAdminModalVisible(true)} |
|
> |
|
+ Add admin |
|
</Button> |
|
<Modal |
|
title="Add admin" |
|
visible={isAddAdminModalVisible} |
|
width={800} |
|
footer={null} |
|
onOk={handleAdminOk} |
|
onCancel={handleAdminCancel} |
|
> |
|
{addMemberForm(handleAdminCreate)} |
|
</Modal> |
|
<OnboardingModal |
|
isInvitationLinkModalVisible={isInvitationLinkModalVisible} |
|
setIsInvitationLinkModalVisible={setIsInvitationLinkModalVisible} |
|
baseUrl={baseUrl} |
|
invitationLinkData={invitationLinkData} |
|
/> |
|
<Button |
|
className="mb-5" |
|
onClick={() => setIsAddMemberModalVisible(true)} |
|
> |
|
+ Add viewer |
|
</Button> |
|
<Modal |
|
title="Add viewer" |
|
visible={isAddMemberModalVisible} |
|
width={800} |
|
footer={null} |
|
onOk={handleMemberOk} |
|
onCancel={handleMemberCancel} |
|
> |
|
{addMemberForm(handleMemberCreate)} |
|
</Modal> |
|
</div> |
|
</Col> |
|
</Grid> |
|
<Grid > |
|
<Card> |
|
<Title level={4}> ✨ Security Settings</Title> |
|
<div style={{ display: 'flex', flexDirection: 'column', gap: '1rem', marginTop: '1rem' }}> |
|
<div> |
|
<Button onClick={() => premiumUser === true ? setIsAddSSOModalVisible(true) : message.error("Only premium users can add SSO")}>Add SSO</Button> |
|
</div> |
|
<div> |
|
<Button onClick={handleShowAllowedIPs}>Allowed IPs</Button> |
|
</div> |
|
</div> |
|
</Card> |
|
|
|
<div className="flex justify-start mb-4"> |
|
|
|
<Modal |
|
title="Add SSO" |
|
visible={isAddSSOModalVisible} |
|
width={800} |
|
footer={null} |
|
onOk={handleAddSSOOk} |
|
onCancel={handleAddSSOCancel} |
|
> |
|
<Form |
|
form={form} |
|
onFinish={handleShowInstructions} |
|
labelCol={{ span: 8 }} |
|
wrapperCol={{ span: 16 }} |
|
labelAlign="left" |
|
> |
|
<> |
|
<Form.Item |
|
label="Admin Email" |
|
name="user_email" |
|
rules={[ |
|
{ |
|
required: true, |
|
message: "Please enter the email of the proxy admin", |
|
}, |
|
]} |
|
> |
|
<Input /> |
|
</Form.Item> |
|
<Form.Item |
|
label="PROXY BASE URL" |
|
name="proxy_base_url" |
|
rules={[ |
|
{ |
|
required: true, |
|
message: "Please enter the proxy base url", |
|
}, |
|
]} |
|
> |
|
<Input /> |
|
</Form.Item> |
|
|
|
<Form.Item |
|
label="GOOGLE CLIENT ID" |
|
name="google_client_id" |
|
rules={[ |
|
{ |
|
required: true, |
|
message: "Please enter the google client id", |
|
}, |
|
]} |
|
> |
|
<Input.Password /> |
|
</Form.Item> |
|
|
|
<Form.Item |
|
label="GOOGLE CLIENT SECRET" |
|
name="google_client_secret" |
|
rules={[ |
|
{ |
|
required: true, |
|
message: "Please enter the google client secret", |
|
}, |
|
]} |
|
> |
|
<Input.Password /> |
|
</Form.Item> |
|
</> |
|
<div style={{ textAlign: "right", marginTop: "10px" }}> |
|
<Button2 htmlType="submit">Save</Button2> |
|
</div> |
|
</Form> |
|
</Modal> |
|
<Modal |
|
title="SSO Setup Instructions" |
|
visible={isInstructionsModalVisible} |
|
width={800} |
|
footer={null} |
|
onOk={handleInstructionsOk} |
|
onCancel={handleInstructionsCancel} |
|
> |
|
<p>Follow these steps to complete the SSO setup:</p> |
|
<Text className="mt-2">1. DO NOT Exit this TAB</Text> |
|
<Text className="mt-2"> |
|
2. Open a new tab, visit your proxy base url |
|
</Text> |
|
<Text className="mt-2"> |
|
3. Confirm your SSO is configured correctly and you can login on |
|
the new Tab |
|
</Text> |
|
<Text className="mt-2"> |
|
4. If Step 3 is successful, you can close this tab |
|
</Text> |
|
<div style={{ textAlign: "right", marginTop: "10px" }}> |
|
<Button2 onClick={handleInstructionsOk}>Done</Button2> |
|
</div> |
|
</Modal> |
|
<Modal |
|
title="Manage Allowed IP Addresses" |
|
width={800} |
|
visible={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" |
|
visible={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" |
|
visible={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> |
|
</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> |
|
</Grid> |
|
</div> |
|
); |
|
}; |
|
|
|
export default AdminPanel; |
|
|