forked from phoenix/litellm-mirror
feat: e2e flow complete - admin can invite new users to proxy via invite links
Completes https://github.com/BerriAI/litellm/issues/3863
This commit is contained in:
parent
073bca78d4
commit
293d5cf1f2
6 changed files with 614 additions and 242 deletions
|
@ -4,6 +4,7 @@
|
|||
*/
|
||||
import React, { useState, useEffect } from "react";
|
||||
import { Typography } from "antd";
|
||||
import { useRouter } from "next/navigation";
|
||||
import {
|
||||
Button as Button2,
|
||||
Modal,
|
||||
|
@ -13,6 +14,7 @@ import {
|
|||
InputNumber,
|
||||
message,
|
||||
} from "antd";
|
||||
import { CopyToClipboard } from "react-copy-to-clipboard";
|
||||
import { Select, SelectItem } from "@tremor/react";
|
||||
import {
|
||||
Table,
|
||||
|
@ -28,6 +30,7 @@ import {
|
|||
Text,
|
||||
Grid,
|
||||
Callout,
|
||||
Divider,
|
||||
} from "@tremor/react";
|
||||
import { PencilAltIcon } from "@heroicons/react/outline";
|
||||
interface AdminPanelProps {
|
||||
|
@ -36,67 +39,94 @@ interface AdminPanelProps {
|
|||
setTeams: React.Dispatch<React.SetStateAction<Object[] | null>>;
|
||||
showSSOBanner: boolean;
|
||||
}
|
||||
|
||||
interface InvitationLink {
|
||||
id: string;
|
||||
user_id: string;
|
||||
is_accepted: boolean;
|
||||
accepted_at: Date | null;
|
||||
expires_at: Date;
|
||||
created_at: Date;
|
||||
created_by: string;
|
||||
updated_at: Date;
|
||||
updated_by: string;
|
||||
}
|
||||
import {
|
||||
userUpdateUserCall,
|
||||
Member,
|
||||
userGetAllUsersCall,
|
||||
User,
|
||||
setCallbacksCall,
|
||||
invitationCreateCall,
|
||||
} from "./networking";
|
||||
|
||||
const AdminPanel: React.FC<AdminPanelProps> = ({
|
||||
searchParams,
|
||||
accessToken,
|
||||
showSSOBanner
|
||||
showSSOBanner,
|
||||
}) => {
|
||||
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, setInvitationLinkModalVisible] =
|
||||
useState(false);
|
||||
const [isAddMemberModalVisible, setIsAddMemberModalVisible] = useState(false);
|
||||
const [isAddAdminModalVisible, setIsAddAdminModalVisible] = useState(false);
|
||||
const [isUpdateMemberModalVisible, setIsUpdateModalModalVisible] = useState(false);
|
||||
const [isUpdateMemberModalVisible, setIsUpdateModalModalVisible] =
|
||||
useState(false);
|
||||
const [isAddSSOModalVisible, setIsAddSSOModalVisible] = useState(false);
|
||||
const [isInstructionsModalVisible, setIsInstructionsModalVisible] = useState(false);
|
||||
const [isInstructionsModalVisible, setIsInstructionsModalVisible] =
|
||||
useState(false);
|
||||
const router = useRouter();
|
||||
const [baseUrl, setBaseUrl] = useState("");
|
||||
|
||||
let nonSssoUrl;
|
||||
try {
|
||||
nonSssoUrl = window.location.origin;
|
||||
} catch (error) {
|
||||
nonSssoUrl = '<your-proxy-url>';
|
||||
nonSssoUrl = "<your-proxy-url>";
|
||||
}
|
||||
nonSssoUrl += '/fallback/login';
|
||||
nonSssoUrl += "/fallback/login";
|
||||
|
||||
const handleAddSSOOk = () => {
|
||||
|
||||
setIsAddSSOModalVisible(false);
|
||||
form.resetFields();
|
||||
};
|
||||
const handleAddSSOOk = () => {
|
||||
setIsAddSSOModalVisible(false);
|
||||
form.resetFields();
|
||||
};
|
||||
|
||||
const handleAddSSOCancel = () => {
|
||||
setIsAddSSOModalVisible(false);
|
||||
form.resetFields();
|
||||
};
|
||||
const handleAddSSOCancel = () => {
|
||||
setIsAddSSOModalVisible(false);
|
||||
form.resetFields();
|
||||
};
|
||||
|
||||
const handleShowInstructions = (formValues: Record<string, any>) => {
|
||||
handleAdminCreate(formValues);
|
||||
handleSSOUpdate(formValues);
|
||||
setIsAddSSOModalVisible(false);
|
||||
setIsInstructionsModalVisible(true);
|
||||
// Optionally, you can call handleSSOUpdate here with the formValues
|
||||
};
|
||||
const handleShowInstructions = (formValues: Record<string, any>) => {
|
||||
handleAdminCreate(formValues);
|
||||
handleSSOUpdate(formValues);
|
||||
setIsAddSSOModalVisible(false);
|
||||
setIsInstructionsModalVisible(true);
|
||||
// Optionally, you can call handleSSOUpdate here with the formValues
|
||||
};
|
||||
|
||||
const handleInstructionsOk = () => {
|
||||
setIsInstructionsModalVisible(false);
|
||||
};
|
||||
const handleInstructionsOk = () => {
|
||||
setIsInstructionsModalVisible(false);
|
||||
};
|
||||
|
||||
const handleInstructionsCancel = () => {
|
||||
setIsInstructionsModalVisible(false);
|
||||
};
|
||||
const handleInstructionsCancel = () => {
|
||||
setIsInstructionsModalVisible(false);
|
||||
};
|
||||
|
||||
const roles = ["proxy_admin", "proxy_admin_viewer"]
|
||||
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
|
||||
|
@ -167,82 +197,97 @@ const handleInstructionsCancel = () => {
|
|||
const handleMemberUpdateCancel = () => {
|
||||
setIsUpdateModalModalVisible(false);
|
||||
memberForm.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-4">
|
||||
<Input
|
||||
name="user_email"
|
||||
className="px-3 py-2 border rounded-md w-full"
|
||||
/>
|
||||
</Form.Item>
|
||||
<div className="text-center mb-4">OR</div>
|
||||
<Form.Item label="User ID" name="user_id" className="mb-4">
|
||||
<Input
|
||||
name="user_id"
|
||||
className="px-3 py-2 border rounded-md w-full"
|
||||
/>
|
||||
</Form.Item>
|
||||
</>
|
||||
<div style={{ textAlign: "right", marginTop: "10px" }}>
|
||||
<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"
|
||||
const addMemberForm = (handleMemberCreate: HandleMemberCreate) => {
|
||||
return (
|
||||
<Form
|
||||
form={form}
|
||||
onFinish={handleMemberCreate}
|
||||
labelCol={{ span: 8 }}
|
||||
wrapperCol={{ span: 16 }}
|
||||
labelAlign="left"
|
||||
>
|
||||
<Input value={userID} disabled />
|
||||
</Form.Item>
|
||||
</>
|
||||
<div style={{ textAlign: "right", marginTop: "10px" }}>
|
||||
<Button2 htmlType="submit">Update role</Button2>
|
||||
</div>
|
||||
</Form>
|
||||
}
|
||||
<>
|
||||
<Form.Item label="Email" name="user_email" className="mb-4">
|
||||
<Input
|
||||
name="user_email"
|
||||
className="px-3 py-2 border rounded-md w-full"
|
||||
/>
|
||||
</Form.Item>
|
||||
<div className="text-center mb-4">OR</div>
|
||||
<Form.Item label="User ID" name="user_id" className="mb-4">
|
||||
<Input
|
||||
name="user_id"
|
||||
className="px-3 py-2 border rounded-md w-full"
|
||||
/>
|
||||
</Form.Item>
|
||||
</>
|
||||
<div style={{ textAlign: "right", marginTop: "10px" }}>
|
||||
<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{
|
||||
try {
|
||||
if (accessToken != null && admins != null) {
|
||||
message.info("Making API Call");
|
||||
const response: any = await userUpdateUserCall(accessToken, formValues, null);
|
||||
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) => {
|
||||
|
@ -257,20 +302,24 @@ const handleInstructionsCancel = () => {
|
|||
admins.push(response);
|
||||
// If new user is found, update it
|
||||
setAdmins(admins); // Set the new state
|
||||
}
|
||||
message.success("Refresh tab to see updated user role")
|
||||
}
|
||||
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");
|
||||
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
|
||||
const foundIndex = admins.findIndex((user) => {
|
||||
|
@ -301,12 +350,23 @@ const handleInstructionsCancel = () => {
|
|||
user_email: formValues.user_email,
|
||||
user_id: formValues.user_id,
|
||||
};
|
||||
const response: any = await userUpdateUserCall(accessToken, formValues, "proxy_admin");
|
||||
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);
|
||||
setInvitationLinkModalVisible(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=${response.user_id}`
|
||||
`user.user_id=${user.user_id}; response.user_id=${user_id}`
|
||||
);
|
||||
return user.user_id === response.user_id;
|
||||
});
|
||||
|
@ -336,22 +396,24 @@ const handleInstructionsCancel = () => {
|
|||
},
|
||||
};
|
||||
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.{" "}
|
||||
{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>
|
||||
|
@ -370,20 +432,29 @@ const handleInstructionsCancel = () => {
|
|||
{member["user_email"]
|
||||
? member["user_email"]
|
||||
: member["user_id"]
|
||||
? member["user_id"]
|
||||
: null}
|
||||
? member["user_id"]
|
||||
: null}
|
||||
</TableCell>
|
||||
<TableCell>{member["user_role"]}</TableCell>
|
||||
<TableCell>
|
||||
<Icon icon={PencilAltIcon} size="sm" onClick={() => setIsUpdateModalModalVisible(true)}/>
|
||||
<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"])}
|
||||
onCancel={handleMemberUpdateCancel}
|
||||
>
|
||||
{modifyMemberForm(
|
||||
handleMemberUpdate,
|
||||
member["user_role"],
|
||||
member["user_id"]
|
||||
)}
|
||||
</Modal>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
|
@ -394,129 +465,183 @@ const handleInstructionsCancel = () => {
|
|||
</Card>
|
||||
</Col>
|
||||
<Col numColSpan={1}>
|
||||
<div className="flex justify-start">
|
||||
<Button
|
||||
<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>
|
||||
<Button
|
||||
</Button>
|
||||
<Modal
|
||||
title="Add admin"
|
||||
visible={isAddAdminModalVisible}
|
||||
width={800}
|
||||
footer={null}
|
||||
onOk={handleAdminOk}
|
||||
onCancel={handleAdminCancel}
|
||||
>
|
||||
{addMemberForm(handleAdminCreate)}
|
||||
</Modal>
|
||||
<Modal
|
||||
title="Invitation Link"
|
||||
visible={isInvitationLinkModalVisible}
|
||||
width={600}
|
||||
footer={null}
|
||||
onOk={handleAdminOk}
|
||||
onCancel={handleAdminCancel}
|
||||
>
|
||||
{/* {JSON.stringify(invitationLinkData)} */}
|
||||
<Paragraph>
|
||||
Copy and send the generated link to onboard this user to the
|
||||
proxy.
|
||||
</Paragraph>
|
||||
<div className="flex justify-between pt-5 pb-2">
|
||||
<Text className="text-base">User ID</Text>
|
||||
<Text>{invitationLinkData?.user_id}</Text>
|
||||
</div>
|
||||
<div className="flex justify-between pt-5 pb-2">
|
||||
<Text>Invitation Link</Text>
|
||||
<Text>
|
||||
{baseUrl}/onboarding/{invitationLinkData?.id}
|
||||
</Text>
|
||||
</div>
|
||||
<div className="flex justify-end mt-5">
|
||||
<div></div>
|
||||
<CopyToClipboard
|
||||
text={`${baseUrl}/onboarding/${invitationLinkData?.id}`}
|
||||
onCopy={() => message.success("Copied!")}
|
||||
>
|
||||
<Button variant="primary">Copy invitation link</Button>
|
||||
</CopyToClipboard>
|
||||
</div>
|
||||
</Modal>
|
||||
<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>
|
||||
</Button>
|
||||
<Modal
|
||||
title="Add viewer"
|
||||
visible={isAddMemberModalVisible}
|
||||
width={800}
|
||||
footer={null}
|
||||
onOk={handleMemberOk}
|
||||
onCancel={handleMemberCancel}
|
||||
>
|
||||
{addMemberForm(handleMemberCreate)}
|
||||
</Modal>
|
||||
</div>
|
||||
</Col>
|
||||
</Grid>
|
||||
<Grid>
|
||||
<Title level={4}>Add SSO</Title>
|
||||
<div className="flex justify-start mb-4">
|
||||
<Button onClick={() => setIsAddSSOModalVisible(true)}>Add SSO</Button>
|
||||
<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" }]}
|
||||
<Title level={4}>Add SSO</Title>
|
||||
<div className="flex justify-start mb-4">
|
||||
<Button onClick={() => setIsAddSSOModalVisible(true)}>Add SSO</Button>
|
||||
<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"
|
||||
>
|
||||
<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="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 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>
|
||||
</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>
|
||||
<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>
|
||||
</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>
|
||||
);
|
||||
};
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue