Re-style user group pages

This commit is contained in:
Weves 2023-12-16 17:22:38 -08:00 committed by Chris Weaver
parent a52711967f
commit ce870ff577
11 changed files with 442 additions and 272 deletions

View File

@ -1,8 +1,5 @@
from sqlalchemy.orm import Session from sqlalchemy.orm import Session
from danswer.document_index.factory import get_default_document_index
from danswer.document_index.interfaces import DocumentIndex
from danswer.document_index.interfaces import UpdateRequest
from danswer.access.access import get_access_for_documents from danswer.access.access import get_access_for_documents
from danswer.db.document import prepare_to_modify_documents from danswer.db.document import prepare_to_modify_documents
from danswer.db.engine import get_sqlalchemy_engine from danswer.db.engine import get_sqlalchemy_engine

View File

@ -29,11 +29,11 @@ export const ConnectorEditor = ({
py-1 py-1
rounded-lg rounded-lg
border border
border-gray-700 border-border
w-fit w-fit
flex flex
cursor-pointer ` + cursor-pointer ` +
(isSelected ? " bg-gray-600" : " hover:bg-gray-700") (isSelected ? " bg-hover" : " hover:bg-hover-light")
} }
onClick={() => { onClick={() => {
if (isSelected) { if (isSelected) {

View File

@ -42,8 +42,8 @@ export const UserEditor = ({
px-2 px-2
py-1 py-1
border border
border-gray-700 border-border
hover:bg-gray-900 hover:bg-hover-light
cursor-pointer`} cursor-pointer`}
> >
{selectedUser.email} <FiX className="ml-1 my-auto" /> {selectedUser.email} <FiX className="ml-1 my-auto" />
@ -73,7 +73,7 @@ export const UserEditor = ({
]); ]);
}} }}
itemComponent={({ option }) => ( itemComponent={({ option }) => (
<div className="flex px-4 py-2.5 hover:bg-gray-800 cursor-pointer"> <div className="flex px-4 py-2.5 cursor-pointer hover:bg-hover">
<UsersIcon className="mr-2 my-auto" /> <UsersIcon className="mr-2 my-auto" />
{option.name} {option.name}
<div className="ml-auto my-auto"> <div className="ml-auto my-auto">

View File

@ -1,13 +1,16 @@
import { ArrayHelpers, FieldArray, Form, Formik } from "formik"; import { Form, Formik } from "formik";
import * as Yup from "yup"; import * as Yup from "yup";
import { PopupSpec } from "@/components/admin/connectors/Popup"; import { PopupSpec } from "@/components/admin/connectors/Popup";
import { ConnectorIndexingStatus, DocumentSet, User } from "@/lib/types"; import { ConnectorIndexingStatus, User } from "@/lib/types";
import { TextFormField } from "@/components/admin/connectors/Field"; import { TextFormField } from "@/components/admin/connectors/Field";
import { ConnectorTitle } from "@/components/admin/connectors/ConnectorTitle"; import { ConnectorTitle } from "@/components/admin/connectors/ConnectorTitle";
import { createUserGroup } from "./lib"; import { createUserGroup } from "./lib";
import { UserGroup } from "./types"; import { UserGroup } from "./types";
import { UserEditor } from "./UserEditor"; import { UserEditor } from "./UserEditor";
import { ConnectorEditor } from "./ConnectorEditor"; import { ConnectorEditor } from "./ConnectorEditor";
import { Modal } from "@/components/Modal";
import { XIcon } from "@/components/icons/icons";
import { Button, Divider } from "@tremor/react";
interface UserGroupCreationFormProps { interface UserGroupCreationFormProps {
onClose: () => void; onClose: () => void;
@ -27,119 +30,123 @@ export const UserGroupCreationForm = ({
const isUpdate = existingUserGroup !== undefined; const isUpdate = existingUserGroup !== undefined;
return ( return (
<div> <Modal onOutsideClick={onClose}>
<div <div className="px-8 py-6 bg-background">
className="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center z-30" <h2 className="text-xl font-bold flex">
onClick={onClose} {isUpdate ? "Update a User Group" : "Create a new User Group"}
> <div
<div onClick={onClose}
className="bg-gray-800 rounded-lg border border-gray-700 shadow-lg relative w-1/2 text-sm" className="ml-auto hover:bg-hover p-1.5 rounded"
onClick={(event) => event.stopPropagation()}
>
<Formik
initialValues={{
name: existingUserGroup ? existingUserGroup.name : "",
user_ids: [] as string[],
cc_pair_ids: [] as number[],
}}
validationSchema={Yup.object().shape({
name: Yup.string().required("Please enter a name for the group"),
user_ids: Yup.array().of(Yup.string().required()),
cc_pair_ids: Yup.array().of(Yup.number().required()),
})}
onSubmit={async (values, formikHelpers) => {
formikHelpers.setSubmitting(true);
let response;
response = await createUserGroup(values);
formikHelpers.setSubmitting(false);
if (response.ok) {
setPopup({
message: isUpdate
? "Successfully updated user group!"
: "Successfully created user group!",
type: "success",
});
onClose();
} else {
const responseJson = await response.json();
const errorMsg = responseJson.detail || responseJson.message;
setPopup({
message: isUpdate
? `Error updating user group - ${errorMsg}`
: `Error creating user group - ${errorMsg}`,
type: "error",
});
}
}}
> >
{({ isSubmitting, values, setFieldValue }) => ( <XIcon
<Form> size={20}
<h2 className="text-xl font-bold mb-3 border-b border-gray-600 pt-4 pb-3 bg-gray-700 px-6"> className="my-auto flex flex-shrink-0 cursor-pointer"
{isUpdate ? "Update a User Group" : "Create a new User Group"} />
</div>
</h2>
<Divider />
<Formik
initialValues={{
name: existingUserGroup ? existingUserGroup.name : "",
user_ids: [] as string[],
cc_pair_ids: [] as number[],
}}
validationSchema={Yup.object().shape({
name: Yup.string().required("Please enter a name for the group"),
user_ids: Yup.array().of(Yup.string().required()),
cc_pair_ids: Yup.array().of(Yup.number().required()),
})}
onSubmit={async (values, formikHelpers) => {
formikHelpers.setSubmitting(true);
let response;
response = await createUserGroup(values);
formikHelpers.setSubmitting(false);
if (response.ok) {
setPopup({
message: isUpdate
? "Successfully updated user group!"
: "Successfully created user group!",
type: "success",
});
onClose();
} else {
const responseJson = await response.json();
const errorMsg = responseJson.detail || responseJson.message;
setPopup({
message: isUpdate
? `Error updating user group - ${errorMsg}`
: `Error creating user group - ${errorMsg}`,
type: "error",
});
}
}}
>
{({ isSubmitting, values, setFieldValue }) => (
<Form>
<div className="p-4">
<TextFormField
name="name"
label="Name:"
placeholder="A name for the User Group"
disabled={isUpdate}
autoCompleteDisabled={true}
/>
<Divider />
<h2 className="mb-1 font-medium">
Select which connectors this group has access to:
</h2> </h2>
<div className="p-4"> <p className="mb-3 text-xs">
<TextFormField All documents indexed by the selected connectors will be
name="name" visible to users in this group.
label="Name:" </p>
placeholder="A name for the User Group"
disabled={isUpdate}
autoCompleteDisabled={true}
/>
<div className="border-t border-gray-600 py-2" />
<h2 className="mb-1 font-medium">
Select which connectors this group has access to:
</h2>
<p className="mb-3 text-xs">
All documents indexed by the selected connectors will be
visible to users in this group.
</p>
<ConnectorEditor <ConnectorEditor
allCCPairs={ccPairs} allCCPairs={ccPairs}
selectedCCPairIds={values.cc_pair_ids} selectedCCPairIds={values.cc_pair_ids}
setSetCCPairIds={(ccPairsIds) => setSetCCPairIds={(ccPairsIds) =>
setFieldValue("cc_pair_ids", ccPairsIds) setFieldValue("cc_pair_ids", ccPairsIds)
}
/>
<Divider />
<h2 className="mb-1 font-medium">
Select which Users should be a part of this Group.
</h2>
<p className="mb-3 text-xs">
All selected users will be able to search through all
documents indexed by the selected connectors.
</p>
<div className="mb-3 gap-2">
<UserEditor
selectedUserIds={values.user_ids}
setSelectedUserIds={(userIds) =>
setFieldValue("user_ids", userIds)
} }
allUsers={users}
existingUsers={[]}
/> />
<div className="border-t border-gray-600 py-2" />
<h2 className="mb-1 font-medium">
Select which Users should be a part of this Group.
</h2>
<p className="mb-3 text-xs">
All selected users will be able to search through all
documents indexed by the selected connectors.
</p>
<div className="mb-3 gap-2">
<UserEditor
selectedUserIds={values.user_ids}
setSelectedUserIds={(userIds) =>
setFieldValue("user_ids", userIds)
}
allUsers={users}
existingUsers={[]}
/>
</div>
<div className="flex">
<button
type="submit"
disabled={isSubmitting}
className={
"bg-slate-500 hover:bg-slate-700 text-white " +
"font-bold py-2 px-4 rounded focus:outline-none " +
"focus:shadow-outline w-full max-w-sm mx-auto"
}
>
{isUpdate ? "Update!" : "Create!"}
</button>
</div>
</div> </div>
</Form> <div className="flex">
)} <Button
</Formik> type="submit"
</div> size="xs"
color="green"
disabled={isSubmitting}
className="mx-auto w-64"
>
{isUpdate ? "Update!" : "Create!"}
</Button>
</div>
</div>
</Form>
)}
</Formik>
</div> </div>
</div> </Modal>
); );
}; };

View File

@ -1,5 +1,13 @@
"use client"; "use client";
import {
Table,
TableHead,
TableRow,
TableHeaderCell,
TableBody,
TableCell,
} from "@tremor/react";
import { UserGroup } from "./types"; import { UserGroup } from "./types";
import { PopupSpec } from "@/components/admin/connectors/Popup"; import { PopupSpec } from "@/components/admin/connectors/Popup";
import { LoadingAnimation } from "@/components/Loading"; import { LoadingAnimation } from "@/components/Loading";
@ -8,14 +16,16 @@ import { ConnectorTitle } from "@/components/admin/connectors/ConnectorTitle";
import { TrashIcon } from "@/components/icons/icons"; import { TrashIcon } from "@/components/icons/icons";
import { deleteUserGroup } from "./lib"; import { deleteUserGroup } from "./lib";
import { useRouter } from "next/navigation"; import { useRouter } from "next/navigation";
import { FiUser } from "react-icons/fi"; import { FiEdit, FiUser } from "react-icons/fi";
import { User } from "@/lib/types"; import { User } from "@/lib/types";
import Link from "next/link";
import { DeleteButton } from "@/components/DeleteButton";
const MAX_USERS_TO_DISPLAY = 6; const MAX_USERS_TO_DISPLAY = 6;
const SimpleUserDisplay = ({ user }: { user: User }) => { const SimpleUserDisplay = ({ user }: { user: User }) => {
return ( return (
<div className="flex my-0.5 text-gray-200"> <div className="flex my-0.5">
<FiUser className="mr-2 my-auto" /> {user.email} <FiUser className="mr-2 my-auto" /> {user.email}
</div> </div>
); );
@ -45,6 +55,130 @@ export const UserGroupsTable = ({
} }
}); });
return (
<div>
<Table className="overflow-visible">
<TableHead>
<TableRow>
<TableHeaderCell>Name</TableHeaderCell>
<TableHeaderCell>Connectors</TableHeaderCell>
<TableHeaderCell>Users</TableHeaderCell>
<TableHeaderCell>Status</TableHeaderCell>
<TableHeaderCell>Delete</TableHeaderCell>
</TableRow>
</TableHead>
<TableBody>
{userGroups
.filter((userGroup) => !userGroup.is_up_for_deletion)
.map((userGroup) => {
return (
<TableRow key={userGroup.id}>
<TableCell>
<Link
className="whitespace-normal break-all flex cursor-pointer p-2 rounded hover:bg-hover w-fit"
href={`/admin/groups/${userGroup.id}`}
>
<FiEdit className="my-auto mr-2" />
<p className="text font-medium">{userGroup.name}</p>
</Link>
</TableCell>
<TableCell>
{userGroup.cc_pairs.length > 0 ? (
<div>
{userGroup.cc_pairs.map((ccPairDescriptor, ind) => {
return (
<div
className={
ind !== userGroup.cc_pairs.length - 1
? "mb-3"
: ""
}
key={ccPairDescriptor.id}
>
<ConnectorTitle
connector={ccPairDescriptor.connector}
ccPairId={ccPairDescriptor.id}
ccPairName={ccPairDescriptor.name}
showMetadata={false}
/>
</div>
);
})}
</div>
) : (
"-"
)}
</TableCell>
<TableCell>
{userGroup.users.length > 0 ? (
<div>
{userGroup.users.length <= MAX_USERS_TO_DISPLAY ? (
userGroup.users.map((user) => {
return (
<SimpleUserDisplay key={user.id} user={user} />
);
})
) : (
<div>
{userGroup.users
.slice(0, MAX_USERS_TO_DISPLAY)
.map((user) => {
return (
<SimpleUserDisplay
key={user.id}
user={user}
/>
);
})}
<div>
+ {userGroup.users.length - MAX_USERS_TO_DISPLAY}{" "}
more
</div>
</div>
)}
</div>
) : (
"-"
)}
</TableCell>
<TableCell>
{userGroup.is_up_to_date ? (
<div className="text-success">Up to date!</div>
) : (
<div className="w-10">
<LoadingAnimation text="Syncing" />
</div>
)}
</TableCell>
<TableCell>
<DeleteButton
onClick={async (event) => {
event.stopPropagation();
const response = await deleteUserGroup(userGroup.id);
if (response.ok) {
setPopup({
message: `User Group "${userGroup.name}" deleted`,
type: "success",
});
} else {
const errorMsg = (await response.json()).detail;
setPopup({
message: `Failed to delete User Group - ${errorMsg}`,
type: "error",
});
}
refresh();
}}
/>
</TableCell>
</TableRow>
);
})}
</TableBody>
</Table>
</div>
);
return ( return (
<div> <div>
<BasicTable <BasicTable

View File

@ -50,8 +50,8 @@ export const AddConnectorForm: React.FC<AddConnectorFormProps> = ({
py-1 py-1
my-1 my-1
border border
border-gray-700 border-border
hover:bg-gray-900 hover:bg-hover
cursor-pointer`} cursor-pointer`}
> >
<ConnectorTitle <ConnectorTitle
@ -99,7 +99,7 @@ export const AddConnectorForm: React.FC<AddConnectorFormProps> = ({
]); ]);
}} }}
itemComponent={({ option }) => ( itemComponent={({ option }) => (
<div className="flex px-4 py-2.5 hover:bg-gray-800 cursor-pointer"> <div className="flex px-4 py-2.5 hover:bg-hover cursor-pointer">
<div className="my-auto"> <div className="my-auto">
<ConnectorTitle <ConnectorTitle
ccPairId={option?.metadata?.ccPairId as number} ccPairId={option?.metadata?.ccPairId as number}

View File

@ -3,15 +3,24 @@
import { usePopup } from "@/components/admin/connectors/Popup"; import { usePopup } from "@/components/admin/connectors/Popup";
import { useState } from "react"; import { useState } from "react";
import { UserGroup } from "../types"; import { UserGroup } from "../types";
import { BasicTable } from "@/components/admin/connectors/BasicTable";
import { ConnectorTitle } from "@/components/admin/connectors/ConnectorTitle"; import { ConnectorTitle } from "@/components/admin/connectors/ConnectorTitle";
import { Button } from "@/components/Button";
import { AddMemberForm } from "./AddMemberForm"; import { AddMemberForm } from "./AddMemberForm";
import { TrashIcon } from "@/components/icons/icons";
import { updateUserGroup } from "./lib"; import { updateUserGroup } from "./lib";
import { LoadingAnimation } from "@/components/Loading"; import { LoadingAnimation } from "@/components/Loading";
import { ConnectorIndexingStatus, User } from "@/lib/types"; import { ConnectorIndexingStatus, User } from "@/lib/types";
import { AddConnectorForm } from "./AddConnectorForm"; import { AddConnectorForm } from "./AddConnectorForm";
import {
Table,
TableHead,
TableRow,
TableHeaderCell,
TableBody,
TableCell,
Divider,
Button,
Text,
} from "@tremor/react";
import { DeleteButton } from "@/components/DeleteButton";
interface GroupDisplayProps { interface GroupDisplayProps {
users: User[]; users: User[];
@ -35,76 +44,87 @@ export const GroupDisplay = ({
{popup} {popup}
<div className="text-sm mb-3 flex"> <div className="text-sm mb-3 flex">
<b className="mr-1">Status:</b>{" "} <Text className="mr-1">Status:</Text>{" "}
{userGroup.is_up_to_date ? ( {userGroup.is_up_to_date ? (
<div className="text-emerald-600">Up to date</div> <div className="text-success font-bold">Up to date</div>
) : ( ) : (
<div className="text-gray-300"> <div className="text-accent font-bold">
<LoadingAnimation text="Syncing" /> <LoadingAnimation text="Syncing" />
</div> </div>
)} )}
</div> </div>
<Divider />
<div className="flex w-full"> <div className="flex w-full">
<h2 className="text-xl font-bold">Users</h2> <h2 className="text-xl font-bold">Users</h2>
</div> </div>
<div className="mt-2"> <div className="mt-2">
{userGroup.users.length > 0 ? ( {userGroup.users.length > 0 ? (
<BasicTable <>
columns={[ <Table className="overflow-visible">
{ <TableHead>
header: "Email", <TableRow>
key: "email", <TableHeaderCell>Email</TableHeaderCell>
}, <TableHeaderCell className="flex w-full">
{ <div className="ml-auto">Remove User</div>
header: "Remove User", </TableHeaderCell>
key: "remove", </TableRow>
alignment: "right", </TableHead>
}, <TableBody>
]} {userGroup.users.map((user) => {
data={userGroup.users.map((user) => { return (
return { <TableRow key={user.id}>
email: <div>{user.email}</div>, <TableCell className="whitespace-normal break-all">
remove: ( {user.email}
<div className="flex"> </TableCell>
<div <TableCell>
className="cursor-pointer ml-auto mr-1" <div className="flex w-full">
onClick={async () => { <div className="ml-auto m-2">
const response = await updateUserGroup(userGroup.id, { <DeleteButton
user_ids: userGroup.users onClick={async () => {
.filter( const response = await updateUserGroup(
(userGroupUser) => userGroupUser.id !== user.id userGroup.id,
) {
.map((userGroupUser) => userGroupUser.id), user_ids: userGroup.users
cc_pair_ids: userGroup.cc_pairs.map( .filter(
(ccPair) => ccPair.id (userGroupUser) =>
), userGroupUser.id !== user.id
}); )
if (response.ok) { .map((userGroupUser) => userGroupUser.id),
setPopup({ cc_pair_ids: userGroup.cc_pairs.map(
message: "Successfully removed user from group", (ccPair) => ccPair.id
type: "success", ),
}); }
} else { );
const responseJson = await response.json(); if (response.ok) {
const errorMsg = setPopup({
responseJson.detail || responseJson.message; message:
setPopup({ "Successfully removed user from group",
message: `Error removing user from group - ${errorMsg}`, type: "success",
type: "error", });
}); } else {
} const responseJson = await response.json();
refreshUserGroup(); const errorMsg =
}} responseJson.detail || responseJson.message;
> setPopup({
<TrashIcon /> message: `Error removing user from group - ${errorMsg}`,
</div> type: "error",
</div> });
), }
}; refreshUserGroup();
})} }}
/> />
</div>
</div>
</TableCell>
</TableRow>
);
})}
</TableBody>
</Table>
</>
) : ( ) : (
<div className="text-sm">No users in this group...</div> <div className="text-sm">No users in this group...</div>
)} )}
@ -112,6 +132,8 @@ export const GroupDisplay = ({
<Button <Button
className="mt-3" className="mt-3"
size="xs"
color="green"
onClick={() => setAddMemberFormVisible(true)} onClick={() => setAddMemberFormVisible(true)}
disabled={!userGroup.is_up_to_date} disabled={!userGroup.is_up_to_date}
> >
@ -130,72 +152,78 @@ export const GroupDisplay = ({
/> />
)} )}
<h2 className="text-xl font-bold mt-4">Connectors</h2> <Divider />
<h2 className="text-xl font-bold mt-8">Connectors</h2>
<div className="mt-2"> <div className="mt-2">
{userGroup.cc_pairs.length > 0 ? ( {userGroup.cc_pairs.length > 0 ? (
<BasicTable <>
columns={[ <Table className="overflow-visible">
{ <TableHead>
header: "Connector", <TableRow>
key: "connector_name", <TableHeaderCell>Connector</TableHeaderCell>
}, <TableHeaderCell className="flex w-full">
{ <div className="ml-auto">Remove Connector</div>
header: "Remove Connector", </TableHeaderCell>
key: "delete", </TableRow>
alignment: "right", </TableHead>
}, <TableBody>
]} {userGroup.cc_pairs.map((ccPair) => {
data={userGroup.cc_pairs.map((ccPair) => { return (
return { <TableRow key={ccPair.id}>
connector_name: <TableCell className="whitespace-normal break-all">
( <ConnectorTitle
<ConnectorTitle connector={ccPair.connector}
connector={ccPair.connector} ccPairId={ccPair.id}
ccPairId={ccPair.id} ccPairName={ccPair.name}
ccPairName={ccPair.name} />
/> </TableCell>
) || "", <TableCell>
delete: ( <div className="flex w-full">
<div className="flex"> <div className="ml-auto m-2">
<div <DeleteButton
className="cursor-pointer ml-auto mr-1" onClick={async () => {
onClick={async () => { const response = await updateUserGroup(
const response = await updateUserGroup(userGroup.id, { userGroup.id,
user_ids: userGroup.users.map( {
(userGroupUser) => userGroupUser.id user_ids: userGroup.users.map(
), (userGroupUser) => userGroupUser.id
cc_pair_ids: userGroup.cc_pairs ),
.filter( cc_pair_ids: userGroup.cc_pairs
(userGroupCCPair) => .filter(
userGroupCCPair.id != ccPair.id (userGroupCCPair) =>
) userGroupCCPair.id != ccPair.id
.map((ccPair) => ccPair.id), )
}); .map((ccPair) => ccPair.id),
if (response.ok) { }
setPopup({ );
message: if (response.ok) {
"Successfully removed connector from group", setPopup({
type: "success", message:
}); "Successfully removed connector from group",
} else { type: "success",
const responseJson = await response.json(); });
const errorMsg = } else {
responseJson.detail || responseJson.message; const responseJson = await response.json();
setPopup({ const errorMsg =
message: `Error removing connector from group - ${errorMsg}`, responseJson.detail || responseJson.message;
type: "error", setPopup({
}); message: `Error removing connector from group - ${errorMsg}`,
} type: "error",
refreshUserGroup(); });
}} }
> refreshUserGroup();
<TrashIcon /> }}
</div> />
</div> </div>
), </div>
}; </TableCell>
})} </TableRow>
/> );
})}
</TableBody>
</Table>
</>
) : ( ) : (
<div className="text-sm">No connectors in this group...</div> <div className="text-sm">No connectors in this group...</div>
)} )}
@ -204,6 +232,8 @@ export const GroupDisplay = ({
<Button <Button
className="mt-3" className="mt-3"
onClick={() => setAddConnectorFormVisible(true)} onClick={() => setAddConnectorFormVisible(true)}
size="xs"
color="green"
disabled={!userGroup.is_up_to_date} disabled={!userGroup.is_up_to_date}
> >
Add Connectors Add Connectors

View File

@ -7,6 +7,8 @@ import { useSpecificUserGroup } from "./hook";
import { ThreeDotsLoader } from "@/components/Loading"; import { ThreeDotsLoader } from "@/components/Loading";
import { useConnectorCredentialIndexingStatus, useUsers } from "@/lib/hooks"; import { useConnectorCredentialIndexingStatus, useUsers } from "@/lib/hooks";
import { useRouter } from "next/navigation"; import { useRouter } from "next/navigation";
import { BackButton } from "@/components/BackButton";
import { AdminPageTitle } from "@/components/admin/Title";
const Page = ({ params }: { params: { groupId: string } }) => { const Page = ({ params }: { params: { groupId: string } }) => {
const router = useRouter(); const router = useRouter();
@ -50,19 +52,12 @@ const Page = ({ params }: { params: { groupId: string } }) => {
return ( return (
<div className="mx-auto container"> <div className="mx-auto container">
<div <BackButton />
className="my-auto flex mb-1 hover:bg-gray-700 w-fit pr-2 cursor-pointer rounded-lg"
onClick={() => router.back()} <AdminPageTitle
> title={userGroup.name || "Unknown"}
<FiChevronLeft className="mr-1 my-auto" /> icon={<GroupsIcon size={32} />}
Back />
</div>
<div className="border-solid border-gray-600 border-b pb-2 mb-4 flex">
<GroupsIcon size={32} />
<h1 className="text-3xl font-bold pl-2">
{userGroup ? userGroup.name : <FiAlertCircle />}
</h1>
</div>
{userGroup ? ( {userGroup ? (
<GroupDisplay <GroupDisplay

View File

@ -5,10 +5,11 @@ import { UserGroupsTable } from "./UserGroupsTable";
import { UserGroupCreationForm } from "./UserGroupCreationForm"; import { UserGroupCreationForm } from "./UserGroupCreationForm";
import { usePopup } from "@/components/admin/connectors/Popup"; import { usePopup } from "@/components/admin/connectors/Popup";
import { useState } from "react"; import { useState } from "react";
import { Button } from "@/components/Button";
import { ThreeDotsLoader } from "@/components/Loading"; import { ThreeDotsLoader } from "@/components/Loading";
import { useConnectorCredentialIndexingStatus, useUsers } from "@/lib/hooks"; import { useConnectorCredentialIndexingStatus, useUsers } from "@/lib/hooks";
import { useUserGroups } from "./hooks"; import { useUserGroups } from "./hooks";
import { AdminPageTitle } from "@/components/admin/Title";
import { Button, Divider } from "@tremor/react";
const Main = () => { const Main = () => {
const { popup, setPopup } = usePopup(); const { popup, setPopup } = usePopup();
@ -48,13 +49,20 @@ const Main = () => {
<> <>
{popup} {popup}
<div className="my-3"> <div className="my-3">
<Button onClick={() => setShowForm(true)}>Create New User Group</Button> <Button size="xs" color="green" onClick={() => setShowForm(true)}>
Create New User Group
</Button>
</div> </div>
<UserGroupsTable {data.length > 0 && (
userGroups={data} <div>
setPopup={setPopup} <Divider />
refresh={refreshUserGroups} <UserGroupsTable
/> userGroups={data}
setPopup={setPopup}
refresh={refreshUserGroups}
/>
</div>
)}
{showForm && ( {showForm && (
<UserGroupCreationForm <UserGroupCreationForm
onClose={() => { onClose={() => {
@ -73,10 +81,10 @@ const Main = () => {
const Page = () => { const Page = () => {
return ( return (
<div className="mx-auto container"> <div className="mx-auto container">
<div className="border-solid border-gray-600 border-b pb-2 mb-4 flex"> <AdminPageTitle
<GroupsIcon size={32} /> title="Manage Users Groups"
<h1 className="text-3xl font-bold pl-2">Manage Users Groups</h1> icon={<GroupsIcon size={32} />}
</div> />
<Main /> <Main />
</div> </div>

View File

@ -7,7 +7,6 @@ import { DatabaseIcon } from "@/components/icons/icons";
export default function QueryHistoryPage() { export default function QueryHistoryPage() {
return ( return (
<main className="pt-4 mx-auto container"> <main className="pt-4 mx-auto container">
<AdminPageTitle title="Query History" icon={<DatabaseIcon size={32} />} /> <AdminPageTitle title="Query History" icon={<DatabaseIcon size={32} />} />
<QueryHistoryTable /> <QueryHistoryTable />

View File

@ -4,7 +4,7 @@ export function DeleteButton({
onClick, onClick,
disabled, disabled,
}: { }: {
onClick?: () => void; onClick?: (event: React.MouseEvent<HTMLElement>) => void | Promise<void>;
disabled?: boolean; disabled?: boolean;
}) { }) {
return ( return (