mirror of
https://github.com/danswer-ai/danswer.git
synced 2025-04-02 08:58:11 +02:00
Re-style user group pages
This commit is contained in:
parent
a52711967f
commit
ce870ff577
@ -1,8 +1,5 @@
|
||||
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.db.document import prepare_to_modify_documents
|
||||
from danswer.db.engine import get_sqlalchemy_engine
|
||||
|
@ -29,11 +29,11 @@ export const ConnectorEditor = ({
|
||||
py-1
|
||||
rounded-lg
|
||||
border
|
||||
border-gray-700
|
||||
border-border
|
||||
w-fit
|
||||
flex
|
||||
cursor-pointer ` +
|
||||
(isSelected ? " bg-gray-600" : " hover:bg-gray-700")
|
||||
(isSelected ? " bg-hover" : " hover:bg-hover-light")
|
||||
}
|
||||
onClick={() => {
|
||||
if (isSelected) {
|
||||
|
@ -42,8 +42,8 @@ export const UserEditor = ({
|
||||
px-2
|
||||
py-1
|
||||
border
|
||||
border-gray-700
|
||||
hover:bg-gray-900
|
||||
border-border
|
||||
hover:bg-hover-light
|
||||
cursor-pointer`}
|
||||
>
|
||||
{selectedUser.email} <FiX className="ml-1 my-auto" />
|
||||
@ -73,7 +73,7 @@ export const UserEditor = ({
|
||||
]);
|
||||
}}
|
||||
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" />
|
||||
{option.name}
|
||||
<div className="ml-auto my-auto">
|
||||
|
@ -1,13 +1,16 @@
|
||||
import { ArrayHelpers, FieldArray, Form, Formik } from "formik";
|
||||
import { Form, Formik } from "formik";
|
||||
import * as Yup from "yup";
|
||||
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 { ConnectorTitle } from "@/components/admin/connectors/ConnectorTitle";
|
||||
import { createUserGroup } from "./lib";
|
||||
import { UserGroup } from "./types";
|
||||
import { UserEditor } from "./UserEditor";
|
||||
import { ConnectorEditor } from "./ConnectorEditor";
|
||||
import { Modal } from "@/components/Modal";
|
||||
import { XIcon } from "@/components/icons/icons";
|
||||
import { Button, Divider } from "@tremor/react";
|
||||
|
||||
interface UserGroupCreationFormProps {
|
||||
onClose: () => void;
|
||||
@ -27,119 +30,123 @@ export const UserGroupCreationForm = ({
|
||||
const isUpdate = existingUserGroup !== undefined;
|
||||
|
||||
return (
|
||||
<div>
|
||||
<div
|
||||
className="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center z-30"
|
||||
onClick={onClose}
|
||||
>
|
||||
<div
|
||||
className="bg-gray-800 rounded-lg border border-gray-700 shadow-lg relative w-1/2 text-sm"
|
||||
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",
|
||||
});
|
||||
}
|
||||
}}
|
||||
<Modal onOutsideClick={onClose}>
|
||||
<div className="px-8 py-6 bg-background">
|
||||
<h2 className="text-xl font-bold flex">
|
||||
{isUpdate ? "Update a User Group" : "Create a new User Group"}
|
||||
<div
|
||||
onClick={onClose}
|
||||
className="ml-auto hover:bg-hover p-1.5 rounded"
|
||||
>
|
||||
{({ isSubmitting, values, setFieldValue }) => (
|
||||
<Form>
|
||||
<h2 className="text-xl font-bold mb-3 border-b border-gray-600 pt-4 pb-3 bg-gray-700 px-6">
|
||||
{isUpdate ? "Update a User Group" : "Create a new User Group"}
|
||||
<XIcon
|
||||
size={20}
|
||||
className="my-auto flex flex-shrink-0 cursor-pointer"
|
||||
/>
|
||||
</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>
|
||||
<div className="p-4">
|
||||
<TextFormField
|
||||
name="name"
|
||||
label="Name:"
|
||||
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>
|
||||
<p className="mb-3 text-xs">
|
||||
All documents indexed by the selected connectors will be
|
||||
visible to users in this group.
|
||||
</p>
|
||||
|
||||
<ConnectorEditor
|
||||
allCCPairs={ccPairs}
|
||||
selectedCCPairIds={values.cc_pair_ids}
|
||||
setSetCCPairIds={(ccPairsIds) =>
|
||||
setFieldValue("cc_pair_ids", ccPairsIds)
|
||||
<ConnectorEditor
|
||||
allCCPairs={ccPairs}
|
||||
selectedCCPairIds={values.cc_pair_ids}
|
||||
setSetCCPairIds={(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>
|
||||
</Form>
|
||||
)}
|
||||
</Formik>
|
||||
</div>
|
||||
<div className="flex">
|
||||
<Button
|
||||
type="submit"
|
||||
size="xs"
|
||||
color="green"
|
||||
disabled={isSubmitting}
|
||||
className="mx-auto w-64"
|
||||
>
|
||||
{isUpdate ? "Update!" : "Create!"}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</Form>
|
||||
)}
|
||||
</Formik>
|
||||
</div>
|
||||
</div>
|
||||
</Modal>
|
||||
);
|
||||
};
|
||||
|
@ -1,5 +1,13 @@
|
||||
"use client";
|
||||
|
||||
import {
|
||||
Table,
|
||||
TableHead,
|
||||
TableRow,
|
||||
TableHeaderCell,
|
||||
TableBody,
|
||||
TableCell,
|
||||
} from "@tremor/react";
|
||||
import { UserGroup } from "./types";
|
||||
import { PopupSpec } from "@/components/admin/connectors/Popup";
|
||||
import { LoadingAnimation } from "@/components/Loading";
|
||||
@ -8,14 +16,16 @@ import { ConnectorTitle } from "@/components/admin/connectors/ConnectorTitle";
|
||||
import { TrashIcon } from "@/components/icons/icons";
|
||||
import { deleteUserGroup } from "./lib";
|
||||
import { useRouter } from "next/navigation";
|
||||
import { FiUser } from "react-icons/fi";
|
||||
import { FiEdit, FiUser } from "react-icons/fi";
|
||||
import { User } from "@/lib/types";
|
||||
import Link from "next/link";
|
||||
import { DeleteButton } from "@/components/DeleteButton";
|
||||
|
||||
const MAX_USERS_TO_DISPLAY = 6;
|
||||
|
||||
const SimpleUserDisplay = ({ user }: { user: User }) => {
|
||||
return (
|
||||
<div className="flex my-0.5 text-gray-200">
|
||||
<div className="flex my-0.5">
|
||||
<FiUser className="mr-2 my-auto" /> {user.email}
|
||||
</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 (
|
||||
<div>
|
||||
<BasicTable
|
||||
|
@ -50,8 +50,8 @@ export const AddConnectorForm: React.FC<AddConnectorFormProps> = ({
|
||||
py-1
|
||||
my-1
|
||||
border
|
||||
border-gray-700
|
||||
hover:bg-gray-900
|
||||
border-border
|
||||
hover:bg-hover
|
||||
cursor-pointer`}
|
||||
>
|
||||
<ConnectorTitle
|
||||
@ -99,7 +99,7 @@ export const AddConnectorForm: React.FC<AddConnectorFormProps> = ({
|
||||
]);
|
||||
}}
|
||||
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">
|
||||
<ConnectorTitle
|
||||
ccPairId={option?.metadata?.ccPairId as number}
|
||||
|
@ -3,15 +3,24 @@
|
||||
import { usePopup } from "@/components/admin/connectors/Popup";
|
||||
import { useState } from "react";
|
||||
import { UserGroup } from "../types";
|
||||
import { BasicTable } from "@/components/admin/connectors/BasicTable";
|
||||
import { ConnectorTitle } from "@/components/admin/connectors/ConnectorTitle";
|
||||
import { Button } from "@/components/Button";
|
||||
import { AddMemberForm } from "./AddMemberForm";
|
||||
import { TrashIcon } from "@/components/icons/icons";
|
||||
import { updateUserGroup } from "./lib";
|
||||
import { LoadingAnimation } from "@/components/Loading";
|
||||
import { ConnectorIndexingStatus, User } from "@/lib/types";
|
||||
import { AddConnectorForm } from "./AddConnectorForm";
|
||||
import {
|
||||
Table,
|
||||
TableHead,
|
||||
TableRow,
|
||||
TableHeaderCell,
|
||||
TableBody,
|
||||
TableCell,
|
||||
Divider,
|
||||
Button,
|
||||
Text,
|
||||
} from "@tremor/react";
|
||||
import { DeleteButton } from "@/components/DeleteButton";
|
||||
|
||||
interface GroupDisplayProps {
|
||||
users: User[];
|
||||
@ -35,76 +44,87 @@ export const GroupDisplay = ({
|
||||
{popup}
|
||||
|
||||
<div className="text-sm mb-3 flex">
|
||||
<b className="mr-1">Status:</b>{" "}
|
||||
<Text className="mr-1">Status:</Text>{" "}
|
||||
{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" />
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<Divider />
|
||||
|
||||
<div className="flex w-full">
|
||||
<h2 className="text-xl font-bold">Users</h2>
|
||||
</div>
|
||||
|
||||
<div className="mt-2">
|
||||
{userGroup.users.length > 0 ? (
|
||||
<BasicTable
|
||||
columns={[
|
||||
{
|
||||
header: "Email",
|
||||
key: "email",
|
||||
},
|
||||
{
|
||||
header: "Remove User",
|
||||
key: "remove",
|
||||
alignment: "right",
|
||||
},
|
||||
]}
|
||||
data={userGroup.users.map((user) => {
|
||||
return {
|
||||
email: <div>{user.email}</div>,
|
||||
remove: (
|
||||
<div className="flex">
|
||||
<div
|
||||
className="cursor-pointer ml-auto mr-1"
|
||||
onClick={async () => {
|
||||
const response = await updateUserGroup(userGroup.id, {
|
||||
user_ids: userGroup.users
|
||||
.filter(
|
||||
(userGroupUser) => userGroupUser.id !== user.id
|
||||
)
|
||||
.map((userGroupUser) => userGroupUser.id),
|
||||
cc_pair_ids: userGroup.cc_pairs.map(
|
||||
(ccPair) => ccPair.id
|
||||
),
|
||||
});
|
||||
if (response.ok) {
|
||||
setPopup({
|
||||
message: "Successfully removed user from group",
|
||||
type: "success",
|
||||
});
|
||||
} else {
|
||||
const responseJson = await response.json();
|
||||
const errorMsg =
|
||||
responseJson.detail || responseJson.message;
|
||||
setPopup({
|
||||
message: `Error removing user from group - ${errorMsg}`,
|
||||
type: "error",
|
||||
});
|
||||
}
|
||||
refreshUserGroup();
|
||||
}}
|
||||
>
|
||||
<TrashIcon />
|
||||
</div>
|
||||
</div>
|
||||
),
|
||||
};
|
||||
})}
|
||||
/>
|
||||
<>
|
||||
<Table className="overflow-visible">
|
||||
<TableHead>
|
||||
<TableRow>
|
||||
<TableHeaderCell>Email</TableHeaderCell>
|
||||
<TableHeaderCell className="flex w-full">
|
||||
<div className="ml-auto">Remove User</div>
|
||||
</TableHeaderCell>
|
||||
</TableRow>
|
||||
</TableHead>
|
||||
<TableBody>
|
||||
{userGroup.users.map((user) => {
|
||||
return (
|
||||
<TableRow key={user.id}>
|
||||
<TableCell className="whitespace-normal break-all">
|
||||
{user.email}
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
<div className="flex w-full">
|
||||
<div className="ml-auto m-2">
|
||||
<DeleteButton
|
||||
onClick={async () => {
|
||||
const response = await updateUserGroup(
|
||||
userGroup.id,
|
||||
{
|
||||
user_ids: userGroup.users
|
||||
.filter(
|
||||
(userGroupUser) =>
|
||||
userGroupUser.id !== user.id
|
||||
)
|
||||
.map((userGroupUser) => userGroupUser.id),
|
||||
cc_pair_ids: userGroup.cc_pairs.map(
|
||||
(ccPair) => ccPair.id
|
||||
),
|
||||
}
|
||||
);
|
||||
if (response.ok) {
|
||||
setPopup({
|
||||
message:
|
||||
"Successfully removed user from group",
|
||||
type: "success",
|
||||
});
|
||||
} else {
|
||||
const responseJson = await response.json();
|
||||
const errorMsg =
|
||||
responseJson.detail || responseJson.message;
|
||||
setPopup({
|
||||
message: `Error removing user from group - ${errorMsg}`,
|
||||
type: "error",
|
||||
});
|
||||
}
|
||||
refreshUserGroup();
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
);
|
||||
})}
|
||||
</TableBody>
|
||||
</Table>
|
||||
</>
|
||||
) : (
|
||||
<div className="text-sm">No users in this group...</div>
|
||||
)}
|
||||
@ -112,6 +132,8 @@ export const GroupDisplay = ({
|
||||
|
||||
<Button
|
||||
className="mt-3"
|
||||
size="xs"
|
||||
color="green"
|
||||
onClick={() => setAddMemberFormVisible(true)}
|
||||
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">
|
||||
{userGroup.cc_pairs.length > 0 ? (
|
||||
<BasicTable
|
||||
columns={[
|
||||
{
|
||||
header: "Connector",
|
||||
key: "connector_name",
|
||||
},
|
||||
{
|
||||
header: "Remove Connector",
|
||||
key: "delete",
|
||||
alignment: "right",
|
||||
},
|
||||
]}
|
||||
data={userGroup.cc_pairs.map((ccPair) => {
|
||||
return {
|
||||
connector_name:
|
||||
(
|
||||
<ConnectorTitle
|
||||
connector={ccPair.connector}
|
||||
ccPairId={ccPair.id}
|
||||
ccPairName={ccPair.name}
|
||||
/>
|
||||
) || "",
|
||||
delete: (
|
||||
<div className="flex">
|
||||
<div
|
||||
className="cursor-pointer ml-auto mr-1"
|
||||
onClick={async () => {
|
||||
const response = await updateUserGroup(userGroup.id, {
|
||||
user_ids: userGroup.users.map(
|
||||
(userGroupUser) => userGroupUser.id
|
||||
),
|
||||
cc_pair_ids: userGroup.cc_pairs
|
||||
.filter(
|
||||
(userGroupCCPair) =>
|
||||
userGroupCCPair.id != ccPair.id
|
||||
)
|
||||
.map((ccPair) => ccPair.id),
|
||||
});
|
||||
if (response.ok) {
|
||||
setPopup({
|
||||
message:
|
||||
"Successfully removed connector from group",
|
||||
type: "success",
|
||||
});
|
||||
} else {
|
||||
const responseJson = await response.json();
|
||||
const errorMsg =
|
||||
responseJson.detail || responseJson.message;
|
||||
setPopup({
|
||||
message: `Error removing connector from group - ${errorMsg}`,
|
||||
type: "error",
|
||||
});
|
||||
}
|
||||
refreshUserGroup();
|
||||
}}
|
||||
>
|
||||
<TrashIcon />
|
||||
</div>
|
||||
</div>
|
||||
),
|
||||
};
|
||||
})}
|
||||
/>
|
||||
<>
|
||||
<Table className="overflow-visible">
|
||||
<TableHead>
|
||||
<TableRow>
|
||||
<TableHeaderCell>Connector</TableHeaderCell>
|
||||
<TableHeaderCell className="flex w-full">
|
||||
<div className="ml-auto">Remove Connector</div>
|
||||
</TableHeaderCell>
|
||||
</TableRow>
|
||||
</TableHead>
|
||||
<TableBody>
|
||||
{userGroup.cc_pairs.map((ccPair) => {
|
||||
return (
|
||||
<TableRow key={ccPair.id}>
|
||||
<TableCell className="whitespace-normal break-all">
|
||||
<ConnectorTitle
|
||||
connector={ccPair.connector}
|
||||
ccPairId={ccPair.id}
|
||||
ccPairName={ccPair.name}
|
||||
/>
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
<div className="flex w-full">
|
||||
<div className="ml-auto m-2">
|
||||
<DeleteButton
|
||||
onClick={async () => {
|
||||
const response = await updateUserGroup(
|
||||
userGroup.id,
|
||||
{
|
||||
user_ids: userGroup.users.map(
|
||||
(userGroupUser) => userGroupUser.id
|
||||
),
|
||||
cc_pair_ids: userGroup.cc_pairs
|
||||
.filter(
|
||||
(userGroupCCPair) =>
|
||||
userGroupCCPair.id != ccPair.id
|
||||
)
|
||||
.map((ccPair) => ccPair.id),
|
||||
}
|
||||
);
|
||||
if (response.ok) {
|
||||
setPopup({
|
||||
message:
|
||||
"Successfully removed connector from group",
|
||||
type: "success",
|
||||
});
|
||||
} else {
|
||||
const responseJson = await response.json();
|
||||
const errorMsg =
|
||||
responseJson.detail || responseJson.message;
|
||||
setPopup({
|
||||
message: `Error removing connector from group - ${errorMsg}`,
|
||||
type: "error",
|
||||
});
|
||||
}
|
||||
refreshUserGroup();
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
);
|
||||
})}
|
||||
</TableBody>
|
||||
</Table>
|
||||
</>
|
||||
) : (
|
||||
<div className="text-sm">No connectors in this group...</div>
|
||||
)}
|
||||
@ -204,6 +232,8 @@ export const GroupDisplay = ({
|
||||
<Button
|
||||
className="mt-3"
|
||||
onClick={() => setAddConnectorFormVisible(true)}
|
||||
size="xs"
|
||||
color="green"
|
||||
disabled={!userGroup.is_up_to_date}
|
||||
>
|
||||
Add Connectors
|
||||
|
@ -7,6 +7,8 @@ import { useSpecificUserGroup } from "./hook";
|
||||
import { ThreeDotsLoader } from "@/components/Loading";
|
||||
import { useConnectorCredentialIndexingStatus, useUsers } from "@/lib/hooks";
|
||||
import { useRouter } from "next/navigation";
|
||||
import { BackButton } from "@/components/BackButton";
|
||||
import { AdminPageTitle } from "@/components/admin/Title";
|
||||
|
||||
const Page = ({ params }: { params: { groupId: string } }) => {
|
||||
const router = useRouter();
|
||||
@ -50,19 +52,12 @@ const Page = ({ params }: { params: { groupId: string } }) => {
|
||||
|
||||
return (
|
||||
<div className="mx-auto container">
|
||||
<div
|
||||
className="my-auto flex mb-1 hover:bg-gray-700 w-fit pr-2 cursor-pointer rounded-lg"
|
||||
onClick={() => router.back()}
|
||||
>
|
||||
<FiChevronLeft className="mr-1 my-auto" />
|
||||
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>
|
||||
<BackButton />
|
||||
|
||||
<AdminPageTitle
|
||||
title={userGroup.name || "Unknown"}
|
||||
icon={<GroupsIcon size={32} />}
|
||||
/>
|
||||
|
||||
{userGroup ? (
|
||||
<GroupDisplay
|
||||
|
@ -5,10 +5,11 @@ import { UserGroupsTable } from "./UserGroupsTable";
|
||||
import { UserGroupCreationForm } from "./UserGroupCreationForm";
|
||||
import { usePopup } from "@/components/admin/connectors/Popup";
|
||||
import { useState } from "react";
|
||||
import { Button } from "@/components/Button";
|
||||
import { ThreeDotsLoader } from "@/components/Loading";
|
||||
import { useConnectorCredentialIndexingStatus, useUsers } from "@/lib/hooks";
|
||||
import { useUserGroups } from "./hooks";
|
||||
import { AdminPageTitle } from "@/components/admin/Title";
|
||||
import { Button, Divider } from "@tremor/react";
|
||||
|
||||
const Main = () => {
|
||||
const { popup, setPopup } = usePopup();
|
||||
@ -48,13 +49,20 @@ const Main = () => {
|
||||
<>
|
||||
{popup}
|
||||
<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>
|
||||
<UserGroupsTable
|
||||
userGroups={data}
|
||||
setPopup={setPopup}
|
||||
refresh={refreshUserGroups}
|
||||
/>
|
||||
{data.length > 0 && (
|
||||
<div>
|
||||
<Divider />
|
||||
<UserGroupsTable
|
||||
userGroups={data}
|
||||
setPopup={setPopup}
|
||||
refresh={refreshUserGroups}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
{showForm && (
|
||||
<UserGroupCreationForm
|
||||
onClose={() => {
|
||||
@ -73,10 +81,10 @@ const Main = () => {
|
||||
const Page = () => {
|
||||
return (
|
||||
<div className="mx-auto container">
|
||||
<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">Manage Users Groups</h1>
|
||||
</div>
|
||||
<AdminPageTitle
|
||||
title="Manage Users Groups"
|
||||
icon={<GroupsIcon size={32} />}
|
||||
/>
|
||||
|
||||
<Main />
|
||||
</div>
|
||||
|
@ -7,7 +7,6 @@ import { DatabaseIcon } from "@/components/icons/icons";
|
||||
export default function QueryHistoryPage() {
|
||||
return (
|
||||
<main className="pt-4 mx-auto container">
|
||||
|
||||
<AdminPageTitle title="Query History" icon={<DatabaseIcon size={32} />} />
|
||||
|
||||
<QueryHistoryTable />
|
||||
|
@ -4,7 +4,7 @@ export function DeleteButton({
|
||||
onClick,
|
||||
disabled,
|
||||
}: {
|
||||
onClick?: () => void;
|
||||
onClick?: (event: React.MouseEvent<HTMLElement>) => void | Promise<void>;
|
||||
disabled?: boolean;
|
||||
}) {
|
||||
return (
|
||||
|
Loading…
x
Reference in New Issue
Block a user