mirror of
https://github.com/danswer-ai/danswer.git
synced 2025-10-10 21:26:01 +02:00
Add some additional FE components
This commit is contained in:
@@ -1,292 +1,9 @@
|
|||||||
import { Header } from "@/components/Header";
|
import { Layout } from "@/components/admin/Layout";
|
||||||
import { Sidebar } from "@/components/admin/connectors/Sidebar";
|
|
||||||
import {
|
|
||||||
NotebookIcon,
|
|
||||||
GithubIcon,
|
|
||||||
GlobeIcon,
|
|
||||||
GoogleDriveIcon,
|
|
||||||
SlackIcon,
|
|
||||||
KeyIcon,
|
|
||||||
BookstackIcon,
|
|
||||||
ConfluenceIcon,
|
|
||||||
GuruIcon,
|
|
||||||
GongIcon,
|
|
||||||
FileIcon,
|
|
||||||
JiraIcon,
|
|
||||||
SlabIcon,
|
|
||||||
NotionIcon,
|
|
||||||
ZulipIcon,
|
|
||||||
ProductboardIcon,
|
|
||||||
LinearIcon,
|
|
||||||
UsersIcon,
|
|
||||||
ThumbsUpIcon,
|
|
||||||
HubSpotIcon,
|
|
||||||
BookmarkIcon,
|
|
||||||
CPUIcon,
|
|
||||||
} from "@/components/icons/icons";
|
|
||||||
import { getAuthDisabledSS, getCurrentUserSS } from "@/lib/userSS";
|
|
||||||
import { redirect } from "next/navigation";
|
|
||||||
|
|
||||||
export default async function AdminLayout({
|
export default async function AdminLayout({
|
||||||
children,
|
children,
|
||||||
}: {
|
}: {
|
||||||
children: React.ReactNode;
|
children: React.ReactNode;
|
||||||
}) {
|
}) {
|
||||||
const [authDisabled, user] = await Promise.all([
|
return await Layout({ children });
|
||||||
getAuthDisabledSS(),
|
|
||||||
getCurrentUserSS(),
|
|
||||||
]);
|
|
||||||
|
|
||||||
if (!authDisabled) {
|
|
||||||
if (!user) {
|
|
||||||
return redirect("/auth/login");
|
|
||||||
}
|
|
||||||
if (user.role !== "admin") {
|
|
||||||
return redirect("/");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div>
|
|
||||||
<Header user={user} />
|
|
||||||
<div className="bg-gray-900 pt-8 pb-8 flex">
|
|
||||||
<Sidebar
|
|
||||||
title="Connector"
|
|
||||||
collections={[
|
|
||||||
{
|
|
||||||
name: "Indexing",
|
|
||||||
items: [
|
|
||||||
{
|
|
||||||
name: (
|
|
||||||
<div className="flex">
|
|
||||||
<NotebookIcon size={18} />
|
|
||||||
<div className="ml-1">Status</div>
|
|
||||||
</div>
|
|
||||||
),
|
|
||||||
link: "/admin/indexing/status",
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "Connector Settings",
|
|
||||||
items: [
|
|
||||||
{
|
|
||||||
name: (
|
|
||||||
<div className="flex">
|
|
||||||
<SlackIcon size={16} />
|
|
||||||
<div className="ml-1">Slack</div>
|
|
||||||
</div>
|
|
||||||
),
|
|
||||||
link: "/admin/connectors/slack",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: (
|
|
||||||
<div className="flex">
|
|
||||||
<GithubIcon size={16} />
|
|
||||||
<div className="ml-1">Github</div>
|
|
||||||
</div>
|
|
||||||
),
|
|
||||||
link: "/admin/connectors/github",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: (
|
|
||||||
<div className="flex">
|
|
||||||
<GoogleDriveIcon size={16} />
|
|
||||||
<div className="ml-1">Google Drive</div>
|
|
||||||
</div>
|
|
||||||
),
|
|
||||||
link: "/admin/connectors/google-drive",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: (
|
|
||||||
<div className="flex">
|
|
||||||
<ConfluenceIcon size={16} />
|
|
||||||
<div className="ml-1">Confluence</div>
|
|
||||||
</div>
|
|
||||||
),
|
|
||||||
link: "/admin/connectors/confluence",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: (
|
|
||||||
<div className="flex">
|
|
||||||
<JiraIcon size={16} />
|
|
||||||
<div className="ml-1">Jira</div>
|
|
||||||
</div>
|
|
||||||
),
|
|
||||||
link: "/admin/connectors/jira",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: (
|
|
||||||
<div className="flex">
|
|
||||||
<LinearIcon size={16} />
|
|
||||||
<div className="ml-1">Linear</div>
|
|
||||||
</div>
|
|
||||||
),
|
|
||||||
link: "/admin/connectors/linear",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: (
|
|
||||||
<div className="flex">
|
|
||||||
<ProductboardIcon size={16} />
|
|
||||||
<div className="ml-1">Productboard</div>
|
|
||||||
</div>
|
|
||||||
),
|
|
||||||
link: "/admin/connectors/productboard",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: (
|
|
||||||
<div className="flex">
|
|
||||||
<SlabIcon size={16} />
|
|
||||||
<div className="ml-1">Slab</div>
|
|
||||||
</div>
|
|
||||||
),
|
|
||||||
link: "/admin/connectors/slab",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: (
|
|
||||||
<div className="flex">
|
|
||||||
<NotionIcon size={16} />
|
|
||||||
<div className="ml-1">Notion</div>
|
|
||||||
</div>
|
|
||||||
),
|
|
||||||
link: "/admin/connectors/notion",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: (
|
|
||||||
<div className="flex">
|
|
||||||
<GuruIcon size={16} />
|
|
||||||
<div className="ml-1">Guru</div>
|
|
||||||
</div>
|
|
||||||
),
|
|
||||||
link: "/admin/connectors/guru",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: (
|
|
||||||
<div className="flex">
|
|
||||||
<GongIcon size={16} />
|
|
||||||
<div className="ml-1">Gong</div>
|
|
||||||
</div>
|
|
||||||
),
|
|
||||||
link: "/admin/connectors/gong",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: (
|
|
||||||
<div className="flex">
|
|
||||||
<BookstackIcon size={16} />
|
|
||||||
<div className="ml-1">BookStack</div>
|
|
||||||
</div>
|
|
||||||
),
|
|
||||||
link: "/admin/connectors/bookstack",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: (
|
|
||||||
<div className="flex">
|
|
||||||
<ZulipIcon size={16} />
|
|
||||||
<div className="ml-1">Zulip</div>
|
|
||||||
</div>
|
|
||||||
),
|
|
||||||
link: "/admin/connectors/zulip",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: (
|
|
||||||
<div className="flex">
|
|
||||||
<GlobeIcon size={16} />
|
|
||||||
<div className="ml-1">Web</div>
|
|
||||||
</div>
|
|
||||||
),
|
|
||||||
link: "/admin/connectors/web",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: (
|
|
||||||
<div className="flex">
|
|
||||||
<FileIcon size={16} />
|
|
||||||
<div className="ml-1">File</div>
|
|
||||||
</div>
|
|
||||||
),
|
|
||||||
link: "/admin/connectors/file",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: (
|
|
||||||
<div className="flex">
|
|
||||||
<HubSpotIcon size={16} />
|
|
||||||
<div className="ml-1">HubSpot</div>
|
|
||||||
</div>
|
|
||||||
),
|
|
||||||
link: "/admin/connectors/hubspot",
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "Keys",
|
|
||||||
items: [
|
|
||||||
{
|
|
||||||
name: (
|
|
||||||
<div className="flex">
|
|
||||||
<KeyIcon size={18} />
|
|
||||||
<div className="ml-1">OpenAI</div>
|
|
||||||
</div>
|
|
||||||
),
|
|
||||||
link: "/admin/keys/openai",
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "User Management",
|
|
||||||
items: [
|
|
||||||
{
|
|
||||||
name: (
|
|
||||||
<div className="flex">
|
|
||||||
<UsersIcon size={18} />
|
|
||||||
<div className="ml-1">Users</div>
|
|
||||||
</div>
|
|
||||||
),
|
|
||||||
link: "/admin/users",
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "Document Management",
|
|
||||||
items: [
|
|
||||||
{
|
|
||||||
name: (
|
|
||||||
<div className="flex">
|
|
||||||
<BookmarkIcon size={18} />
|
|
||||||
<div className="ml-1">Document Sets</div>
|
|
||||||
</div>
|
|
||||||
),
|
|
||||||
link: "/admin/documents/sets",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: (
|
|
||||||
<div className="flex">
|
|
||||||
<ThumbsUpIcon size={18} />
|
|
||||||
<div className="ml-1">Feedback</div>
|
|
||||||
</div>
|
|
||||||
),
|
|
||||||
link: "/admin/documents/feedback",
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "Bots",
|
|
||||||
items: [
|
|
||||||
{
|
|
||||||
name: (
|
|
||||||
<div className="flex">
|
|
||||||
<CPUIcon size={18} />
|
|
||||||
<div className="ml-1">Slack Bot</div>
|
|
||||||
</div>
|
|
||||||
),
|
|
||||||
link: "/admin/bot",
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
]}
|
|
||||||
/>
|
|
||||||
<div className="px-12 min-h-screen bg-gray-900 text-gray-100 w-full">
|
|
||||||
{children}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
280
web/src/components/Dropdown.tsx
Normal file
280
web/src/components/Dropdown.tsx
Normal file
@@ -0,0 +1,280 @@
|
|||||||
|
import { ChangeEvent, FC, useEffect, useRef, useState } from "react";
|
||||||
|
import { ChevronDownIcon } from "./icons/icons";
|
||||||
|
|
||||||
|
export interface Option {
|
||||||
|
name: string;
|
||||||
|
value: string;
|
||||||
|
description?: string;
|
||||||
|
metadata?: { [key: string]: any };
|
||||||
|
}
|
||||||
|
|
||||||
|
interface DropdownProps {
|
||||||
|
options: Option[];
|
||||||
|
selected: string;
|
||||||
|
onSelect: (selected: Option) => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const Dropdown: FC<DropdownProps> = ({
|
||||||
|
options,
|
||||||
|
selected,
|
||||||
|
onSelect,
|
||||||
|
}) => {
|
||||||
|
const [isOpen, setIsOpen] = useState(false);
|
||||||
|
const dropdownRef = useRef<HTMLDivElement>(null);
|
||||||
|
|
||||||
|
const selectedName = options.find(
|
||||||
|
(option) => option.value === selected
|
||||||
|
)?.name;
|
||||||
|
|
||||||
|
const handleSelect = (option: Option) => {
|
||||||
|
onSelect(option);
|
||||||
|
setIsOpen(false);
|
||||||
|
};
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const handleClickOutside = (event: MouseEvent) => {
|
||||||
|
if (
|
||||||
|
dropdownRef.current &&
|
||||||
|
!dropdownRef.current.contains(event.target as Node)
|
||||||
|
) {
|
||||||
|
setIsOpen(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
document.addEventListener("mousedown", handleClickOutside);
|
||||||
|
return () => {
|
||||||
|
document.removeEventListener("mousedown", handleClickOutside);
|
||||||
|
};
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="relative inline-block text-left w-full" ref={dropdownRef}>
|
||||||
|
<div>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
className={`inline-flex
|
||||||
|
justify-center
|
||||||
|
w-full
|
||||||
|
px-4
|
||||||
|
py-3
|
||||||
|
text-sm
|
||||||
|
bg-gray-700
|
||||||
|
border
|
||||||
|
border-gray-300
|
||||||
|
rounded-md
|
||||||
|
shadow-sm
|
||||||
|
hover:bg-gray-700
|
||||||
|
focus:ring focus:ring-offset-0 focus:ring-1 focus:ring-offset-gray-800 focus:ring-blue-800
|
||||||
|
`}
|
||||||
|
id="options-menu"
|
||||||
|
aria-expanded="true"
|
||||||
|
aria-haspopup="true"
|
||||||
|
onClick={() => setIsOpen(!isOpen)}
|
||||||
|
>
|
||||||
|
{selectedName ? <p>{selectedName}</p> : "Select an option..."}
|
||||||
|
<ChevronDownIcon className="text-gray-400 my-auto ml-auto" />
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{isOpen ? (
|
||||||
|
<div className="origin-top-right absolute left-0 mt-3 w-full rounded-md shadow-lg bg-gray-700 border-2 border-gray-600">
|
||||||
|
<div
|
||||||
|
role="menu"
|
||||||
|
aria-orientation="vertical"
|
||||||
|
aria-labelledby="options-menu"
|
||||||
|
>
|
||||||
|
{options.map((option, index) => (
|
||||||
|
<button
|
||||||
|
key={index}
|
||||||
|
onClick={() => handleSelect(option)}
|
||||||
|
className={
|
||||||
|
`w-full text-left block px-4 py-2.5 text-sm hover:bg-gray-800` +
|
||||||
|
(index !== 0 ? " border-t-2 border-gray-600" : "")
|
||||||
|
}
|
||||||
|
role="menuitem"
|
||||||
|
>
|
||||||
|
<p className="font-medium">{option.name}</p>
|
||||||
|
{option.description && (
|
||||||
|
<div>
|
||||||
|
<p className="text-xs text-gray-300">
|
||||||
|
{option.description}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</button>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
) : null}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const StandardDropdownOption = ({
|
||||||
|
index,
|
||||||
|
option,
|
||||||
|
handleSelect,
|
||||||
|
}: {
|
||||||
|
index: number;
|
||||||
|
option: Option;
|
||||||
|
handleSelect: (option: Option) => void;
|
||||||
|
}) => {
|
||||||
|
return (
|
||||||
|
<button
|
||||||
|
onClick={() => handleSelect(option)}
|
||||||
|
className={`w-full text-left block px-4 py-2.5 text-sm hover:bg-gray-800 ${
|
||||||
|
index !== 0 ? " border-t-2 border-gray-600" : ""
|
||||||
|
}`}
|
||||||
|
role="menuitem"
|
||||||
|
>
|
||||||
|
<p className="font-medium">{option.name}</p>
|
||||||
|
{option.description && (
|
||||||
|
<div>
|
||||||
|
<p className="text-xs text-gray-300">{option.description}</p>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</button>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
interface MultiSelectDropdownProps {
|
||||||
|
options: Option[];
|
||||||
|
onSelect: (selected: Option) => void;
|
||||||
|
itemComponent?: FC<{ option: Option }>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const SearchMultiSelectDropdown: FC<MultiSelectDropdownProps> = ({
|
||||||
|
options,
|
||||||
|
onSelect,
|
||||||
|
itemComponent,
|
||||||
|
}) => {
|
||||||
|
const [isOpen, setIsOpen] = useState(false);
|
||||||
|
const [searchTerm, setSearchTerm] = useState("");
|
||||||
|
const dropdownRef = useRef<HTMLDivElement>(null);
|
||||||
|
|
||||||
|
const handleSelect = (option: Option) => {
|
||||||
|
onSelect(option);
|
||||||
|
setIsOpen(false);
|
||||||
|
setSearchTerm(""); // Clear search term after selection
|
||||||
|
};
|
||||||
|
|
||||||
|
const filteredOptions = options.filter((option) =>
|
||||||
|
option.name.toLowerCase().includes(searchTerm.toLowerCase())
|
||||||
|
);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const handleClickOutside = (event: MouseEvent) => {
|
||||||
|
if (
|
||||||
|
dropdownRef.current &&
|
||||||
|
!dropdownRef.current.contains(event.target as Node)
|
||||||
|
) {
|
||||||
|
setIsOpen(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
document.addEventListener("mousedown", handleClickOutside);
|
||||||
|
return () => {
|
||||||
|
document.removeEventListener("mousedown", handleClickOutside);
|
||||||
|
};
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="relative inline-block text-left w-full" ref={dropdownRef}>
|
||||||
|
<div>
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
placeholder="Search..."
|
||||||
|
value={searchTerm}
|
||||||
|
onChange={(e: ChangeEvent<HTMLInputElement>) => {
|
||||||
|
if (!searchTerm) {
|
||||||
|
setIsOpen(true);
|
||||||
|
}
|
||||||
|
if (!e.target.value) {
|
||||||
|
setIsOpen(false);
|
||||||
|
}
|
||||||
|
setSearchTerm(e.target.value);
|
||||||
|
}}
|
||||||
|
onFocus={() => setIsOpen(true)}
|
||||||
|
className={`inline-flex
|
||||||
|
justify-between
|
||||||
|
w-full
|
||||||
|
px-4
|
||||||
|
py-2
|
||||||
|
text-sm
|
||||||
|
bg-gray-700
|
||||||
|
rounded-md
|
||||||
|
shadow-sm
|
||||||
|
focus:ring focus:ring-offset-0 focus:ring-1 focus:ring-offset-gray-800 focus:ring-blue-800`}
|
||||||
|
onClick={(e) => e.stopPropagation()}
|
||||||
|
/>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
className={`absolute top-0 right-0
|
||||||
|
text-sm
|
||||||
|
h-full px-2 border-l border-gray-800`}
|
||||||
|
aria-expanded="true"
|
||||||
|
aria-haspopup="true"
|
||||||
|
onClick={() => setIsOpen(!isOpen)}
|
||||||
|
>
|
||||||
|
<ChevronDownIcon className="text-gray-400 my-auto" />
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{isOpen && (
|
||||||
|
<div
|
||||||
|
className={`origin-top-right
|
||||||
|
absolute
|
||||||
|
left-0
|
||||||
|
mt-3
|
||||||
|
w-full
|
||||||
|
rounded-md
|
||||||
|
shadow-lg
|
||||||
|
bg-gray-700
|
||||||
|
border-2
|
||||||
|
border-gray-600
|
||||||
|
max-h-80
|
||||||
|
overflow-y-auto
|
||||||
|
overscroll-contain`}
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
role="menu"
|
||||||
|
aria-orientation="vertical"
|
||||||
|
aria-labelledby="options-menu"
|
||||||
|
>
|
||||||
|
{filteredOptions.length ? (
|
||||||
|
filteredOptions.map((option, index) =>
|
||||||
|
itemComponent ? (
|
||||||
|
<div
|
||||||
|
key={option.name}
|
||||||
|
onClick={() => {
|
||||||
|
setIsOpen(false);
|
||||||
|
handleSelect(option);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{itemComponent({ option })}
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
<StandardDropdownOption
|
||||||
|
key={index}
|
||||||
|
option={option}
|
||||||
|
index={index}
|
||||||
|
handleSelect={handleSelect}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
)
|
||||||
|
) : (
|
||||||
|
<button
|
||||||
|
key={0}
|
||||||
|
className={`w-full text-left block px-4 py-2.5 text-sm hover:bg-gray-800`}
|
||||||
|
role="menuitem"
|
||||||
|
onClick={() => setIsOpen(false)}
|
||||||
|
>
|
||||||
|
No matches found...
|
||||||
|
</button>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
38
web/src/components/Modal.tsx
Normal file
38
web/src/components/Modal.tsx
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
interface ModalProps {
|
||||||
|
children: JSX.Element | string;
|
||||||
|
onOutsideClick: () => void;
|
||||||
|
title?: JSX.Element | string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const Modal: React.FC<ModalProps> = ({
|
||||||
|
children,
|
||||||
|
onOutsideClick,
|
||||||
|
title,
|
||||||
|
}) => {
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<div
|
||||||
|
className={`
|
||||||
|
fixed inset-0 bg-black bg-opacity-50
|
||||||
|
flex items-center justify-center z-50
|
||||||
|
`}
|
||||||
|
onClick={onOutsideClick}
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
className={`
|
||||||
|
bg-gray-800 rounded-lg border border-gray-700
|
||||||
|
shadow-lg relative w-1/2 text-sm
|
||||||
|
`}
|
||||||
|
onClick={(event) => event.stopPropagation()}
|
||||||
|
>
|
||||||
|
{title && (
|
||||||
|
<h2 className="text-xl font-bold mb-3 border-b border-gray-600 pt-4 pb-3 bg-gray-700 px-6">
|
||||||
|
{title}
|
||||||
|
</h2>
|
||||||
|
)}
|
||||||
|
{children}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
278
web/src/components/admin/Layout.tsx
Normal file
278
web/src/components/admin/Layout.tsx
Normal file
@@ -0,0 +1,278 @@
|
|||||||
|
import { Header } from "@/components/Header";
|
||||||
|
import { Sidebar } from "@/components/admin/connectors/Sidebar";
|
||||||
|
import {
|
||||||
|
NotebookIcon,
|
||||||
|
GithubIcon,
|
||||||
|
GlobeIcon,
|
||||||
|
GoogleDriveIcon,
|
||||||
|
SlackIcon,
|
||||||
|
KeyIcon,
|
||||||
|
BookstackIcon,
|
||||||
|
ConfluenceIcon,
|
||||||
|
GuruIcon,
|
||||||
|
FileIcon,
|
||||||
|
JiraIcon,
|
||||||
|
SlabIcon,
|
||||||
|
NotionIcon,
|
||||||
|
ZulipIcon,
|
||||||
|
ProductboardIcon,
|
||||||
|
LinearIcon,
|
||||||
|
UsersIcon,
|
||||||
|
ThumbsUpIcon,
|
||||||
|
HubSpotIcon,
|
||||||
|
BookmarkIcon,
|
||||||
|
CPUIcon,
|
||||||
|
} from "@/components/icons/icons";
|
||||||
|
import { getAuthDisabledSS, getCurrentUserSS } from "@/lib/userSS";
|
||||||
|
import { redirect } from "next/navigation";
|
||||||
|
|
||||||
|
export async function Layout({ children }: { children: React.ReactNode }) {
|
||||||
|
const [authDisabled, user] = await Promise.all([
|
||||||
|
getAuthDisabledSS(),
|
||||||
|
getCurrentUserSS(),
|
||||||
|
]);
|
||||||
|
|
||||||
|
if (!authDisabled) {
|
||||||
|
if (!user) {
|
||||||
|
return redirect("/auth/login");
|
||||||
|
}
|
||||||
|
if (user.role !== "admin") {
|
||||||
|
return redirect("/");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<Header user={user} />
|
||||||
|
<div className="bg-gray-900 pt-8 pb-8 flex">
|
||||||
|
<Sidebar
|
||||||
|
title="Connector"
|
||||||
|
collections={[
|
||||||
|
{
|
||||||
|
name: "Indexing",
|
||||||
|
items: [
|
||||||
|
{
|
||||||
|
name: (
|
||||||
|
<div className="flex">
|
||||||
|
<NotebookIcon size={18} />
|
||||||
|
<div className="ml-1">Status</div>
|
||||||
|
</div>
|
||||||
|
),
|
||||||
|
link: "/admin/indexing/status",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Connector Settings",
|
||||||
|
items: [
|
||||||
|
{
|
||||||
|
name: (
|
||||||
|
<div className="flex">
|
||||||
|
<SlackIcon size={16} />
|
||||||
|
<div className="ml-1">Slack</div>
|
||||||
|
</div>
|
||||||
|
),
|
||||||
|
link: "/admin/connectors/slack",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: (
|
||||||
|
<div className="flex">
|
||||||
|
<GithubIcon size={16} />
|
||||||
|
<div className="ml-1">Github</div>
|
||||||
|
</div>
|
||||||
|
),
|
||||||
|
link: "/admin/connectors/github",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: (
|
||||||
|
<div className="flex">
|
||||||
|
<GoogleDriveIcon size={16} />
|
||||||
|
<div className="ml-1">Google Drive</div>
|
||||||
|
</div>
|
||||||
|
),
|
||||||
|
link: "/admin/connectors/google-drive",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: (
|
||||||
|
<div className="flex">
|
||||||
|
<ConfluenceIcon size={16} />
|
||||||
|
<div className="ml-1">Confluence</div>
|
||||||
|
</div>
|
||||||
|
),
|
||||||
|
link: "/admin/connectors/confluence",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: (
|
||||||
|
<div className="flex">
|
||||||
|
<JiraIcon size={16} />
|
||||||
|
<div className="ml-1">Jira</div>
|
||||||
|
</div>
|
||||||
|
),
|
||||||
|
link: "/admin/connectors/jira",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: (
|
||||||
|
<div className="flex">
|
||||||
|
<LinearIcon size={16} />
|
||||||
|
<div className="ml-1">Linear</div>
|
||||||
|
</div>
|
||||||
|
),
|
||||||
|
link: "/admin/connectors/linear",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: (
|
||||||
|
<div className="flex">
|
||||||
|
<ProductboardIcon size={16} />
|
||||||
|
<div className="ml-1">Productboard</div>
|
||||||
|
</div>
|
||||||
|
),
|
||||||
|
link: "/admin/connectors/productboard",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: (
|
||||||
|
<div className="flex">
|
||||||
|
<SlabIcon size={16} />
|
||||||
|
<div className="ml-1">Slab</div>
|
||||||
|
</div>
|
||||||
|
),
|
||||||
|
link: "/admin/connectors/slab",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: (
|
||||||
|
<div className="flex">
|
||||||
|
<NotionIcon size={16} />
|
||||||
|
<div className="ml-1">Notion</div>
|
||||||
|
</div>
|
||||||
|
),
|
||||||
|
link: "/admin/connectors/notion",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: (
|
||||||
|
<div className="flex">
|
||||||
|
<GuruIcon size={16} />
|
||||||
|
<div className="ml-1">Guru</div>
|
||||||
|
</div>
|
||||||
|
),
|
||||||
|
link: "/admin/connectors/guru",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: (
|
||||||
|
<div className="flex">
|
||||||
|
<BookstackIcon size={16} />
|
||||||
|
<div className="ml-1">BookStack</div>
|
||||||
|
</div>
|
||||||
|
),
|
||||||
|
link: "/admin/connectors/bookstack",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: (
|
||||||
|
<div className="flex">
|
||||||
|
<ZulipIcon size={16} />
|
||||||
|
<div className="ml-1">Zulip</div>
|
||||||
|
</div>
|
||||||
|
),
|
||||||
|
link: "/admin/connectors/zulip",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: (
|
||||||
|
<div className="flex">
|
||||||
|
<GlobeIcon size={16} />
|
||||||
|
<div className="ml-1">Web</div>
|
||||||
|
</div>
|
||||||
|
),
|
||||||
|
link: "/admin/connectors/web",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: (
|
||||||
|
<div className="flex">
|
||||||
|
<FileIcon size={16} />
|
||||||
|
<div className="ml-1">File</div>
|
||||||
|
</div>
|
||||||
|
),
|
||||||
|
link: "/admin/connectors/file",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: (
|
||||||
|
<div className="flex">
|
||||||
|
<HubSpotIcon size={16} />
|
||||||
|
<div className="ml-1">HubSpot</div>
|
||||||
|
</div>
|
||||||
|
),
|
||||||
|
link: "/admin/connectors/hubspot",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Keys",
|
||||||
|
items: [
|
||||||
|
{
|
||||||
|
name: (
|
||||||
|
<div className="flex">
|
||||||
|
<KeyIcon size={18} />
|
||||||
|
<div className="ml-1">OpenAI</div>
|
||||||
|
</div>
|
||||||
|
),
|
||||||
|
link: "/admin/keys/openai",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "User Management",
|
||||||
|
items: [
|
||||||
|
{
|
||||||
|
name: (
|
||||||
|
<div className="flex">
|
||||||
|
<UsersIcon size={18} />
|
||||||
|
<div className="ml-1">Users</div>
|
||||||
|
</div>
|
||||||
|
),
|
||||||
|
link: "/admin/users",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Document Management",
|
||||||
|
items: [
|
||||||
|
{
|
||||||
|
name: (
|
||||||
|
<div className="flex">
|
||||||
|
<BookmarkIcon size={18} />
|
||||||
|
<div className="ml-1">Document Sets</div>
|
||||||
|
</div>
|
||||||
|
),
|
||||||
|
link: "/admin/documents/sets",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: (
|
||||||
|
<div className="flex">
|
||||||
|
<ThumbsUpIcon size={18} />
|
||||||
|
<div className="ml-1">Feedback</div>
|
||||||
|
</div>
|
||||||
|
),
|
||||||
|
link: "/admin/documents/feedback",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Bots",
|
||||||
|
items: [
|
||||||
|
{
|
||||||
|
name: (
|
||||||
|
<div className="flex">
|
||||||
|
<CPUIcon size={18} />
|
||||||
|
<div className="ml-1">Slack Bot</div>
|
||||||
|
</div>
|
||||||
|
),
|
||||||
|
link: "/admin/bot",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
]}
|
||||||
|
/>
|
||||||
|
<div className="px-12 min-h-screen bg-gray-900 text-gray-100 w-full">
|
||||||
|
{children}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
@@ -14,9 +14,14 @@ type TableData = {
|
|||||||
interface BasicTableProps {
|
interface BasicTableProps {
|
||||||
columns: Column[];
|
columns: Column[];
|
||||||
data: TableData[];
|
data: TableData[];
|
||||||
|
onSelect?: (row: TableData) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const BasicTable: FC<BasicTableProps> = ({ columns, data }) => {
|
export const BasicTable: FC<BasicTableProps> = ({
|
||||||
|
columns,
|
||||||
|
data,
|
||||||
|
onSelect,
|
||||||
|
}) => {
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<table className="w-full table-auto">
|
<table className="w-full table-auto">
|
||||||
@@ -46,7 +51,14 @@ export const BasicTable: FC<BasicTableProps> = ({ columns, data }) => {
|
|||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
{data.map((row, rowIndex) => (
|
{data.map((row, rowIndex) => (
|
||||||
<tr key={rowIndex} className="text-sm">
|
<tr
|
||||||
|
key={rowIndex}
|
||||||
|
className={
|
||||||
|
"text-sm" +
|
||||||
|
(onSelect ? " hover:bg-gray-800 cursor-pointer" : "")
|
||||||
|
}
|
||||||
|
onClick={() => onSelect && onSelect(row)}
|
||||||
|
>
|
||||||
{columns.map((column, colIndex) => {
|
{columns.map((column, colIndex) => {
|
||||||
const isRightAligned = column?.alignment === "right";
|
const isRightAligned = column?.alignment === "right";
|
||||||
return (
|
return (
|
||||||
|
@@ -9,8 +9,7 @@ import {
|
|||||||
} from "formik";
|
} from "formik";
|
||||||
import * as Yup from "yup";
|
import * as Yup from "yup";
|
||||||
import { FormBodyBuilder } from "./types";
|
import { FormBodyBuilder } from "./types";
|
||||||
import { FC, useEffect, useRef, useState } from "react";
|
import { Dropdown, Option } from "@/components/Dropdown";
|
||||||
import { ChevronDownIcon } from "@/components/icons/icons";
|
|
||||||
|
|
||||||
interface TextFormFieldProps {
|
interface TextFormFieldProps {
|
||||||
name: string;
|
name: string;
|
||||||
@@ -182,12 +181,6 @@ export function TextArrayFieldBuilder<T extends Yup.AnyObject>(
|
|||||||
return _TextArrayField;
|
return _TextArrayField;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface Option {
|
|
||||||
name: string;
|
|
||||||
value: string;
|
|
||||||
description?: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
interface SelectorFormFieldProps {
|
interface SelectorFormFieldProps {
|
||||||
name: string;
|
name: string;
|
||||||
label: string;
|
label: string;
|
||||||
@@ -227,101 +220,3 @@ export function SelectorFormField({
|
|||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
interface DropdownProps {
|
|
||||||
options: Option[];
|
|
||||||
selected: string;
|
|
||||||
onSelect: (selected: Option) => void;
|
|
||||||
}
|
|
||||||
|
|
||||||
const Dropdown: FC<DropdownProps> = ({ options, selected, onSelect }) => {
|
|
||||||
const [isOpen, setIsOpen] = useState(false);
|
|
||||||
const dropdownRef = useRef<HTMLDivElement>(null);
|
|
||||||
|
|
||||||
const selectedName = options.find(
|
|
||||||
(option) => option.value === selected
|
|
||||||
)?.name;
|
|
||||||
|
|
||||||
const handleSelect = (option: Option) => {
|
|
||||||
onSelect(option);
|
|
||||||
setIsOpen(false);
|
|
||||||
};
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
const handleClickOutside = (event: MouseEvent) => {
|
|
||||||
if (
|
|
||||||
dropdownRef.current &&
|
|
||||||
!dropdownRef.current.contains(event.target as Node)
|
|
||||||
) {
|
|
||||||
setIsOpen(false);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
document.addEventListener("mousedown", handleClickOutside);
|
|
||||||
return () => {
|
|
||||||
document.removeEventListener("mousedown", handleClickOutside);
|
|
||||||
};
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className="relative inline-block text-left w-full" ref={dropdownRef}>
|
|
||||||
<div>
|
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
className={`inline-flex
|
|
||||||
justify-center
|
|
||||||
w-full
|
|
||||||
px-4
|
|
||||||
py-3
|
|
||||||
text-sm
|
|
||||||
bg-gray-700
|
|
||||||
border
|
|
||||||
border-gray-300
|
|
||||||
rounded-md
|
|
||||||
shadow-sm
|
|
||||||
hover:bg-gray-700
|
|
||||||
focus:ring focus:ring-offset-0 focus:ring-1 focus:ring-offset-gray-800 focus:ring-blue-800
|
|
||||||
`}
|
|
||||||
id="options-menu"
|
|
||||||
aria-expanded="true"
|
|
||||||
aria-haspopup="true"
|
|
||||||
onClick={() => setIsOpen(!isOpen)}
|
|
||||||
>
|
|
||||||
{selectedName ? <p>{selectedName}</p> : "Select an option..."}
|
|
||||||
<ChevronDownIcon className="text-gray-400 my-auto ml-auto" />
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{isOpen ? (
|
|
||||||
<div className="origin-top-right absolute left-0 mt-3 w-full rounded-md shadow-lg bg-gray-700 border-2 border-gray-600">
|
|
||||||
<div
|
|
||||||
role="menu"
|
|
||||||
aria-orientation="vertical"
|
|
||||||
aria-labelledby="options-menu"
|
|
||||||
>
|
|
||||||
{options.map((option, index) => (
|
|
||||||
<button
|
|
||||||
key={index}
|
|
||||||
onClick={() => handleSelect(option)}
|
|
||||||
className={
|
|
||||||
`w-full text-left block px-4 py-2.5 text-sm hover:bg-gray-800` +
|
|
||||||
(index !== 0 ? " border-t-2 border-gray-600" : "")
|
|
||||||
}
|
|
||||||
role="menuitem"
|
|
||||||
>
|
|
||||||
<p className="font-medium">{option.name}</p>
|
|
||||||
{option.description && (
|
|
||||||
<div>
|
|
||||||
<p className="text-xs text-gray-300">
|
|
||||||
{option.description}
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</button>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
) : null}
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
Reference in New Issue
Block a user