-
- {indexAttempt.error_msg || "-"}
-
+ {indexAttempt.error_count > 0 && (
+
+
+
+ View Errors
+
+
+ )}
+
+ {indexAttempt.status === "success" && (
+
+ {"-"}
+
+ )}
+
+ {indexAttempt.status === "failed" &&
+ indexAttempt.error_msg && (
+
+ {indexAttempt.error_msg}
+
+ )}
+
{indexAttempt.full_exception_trace && (
{
diff --git a/web/src/app/admin/connector/[ccPairId]/ModifyStatusButtonCluster.tsx b/web/src/app/admin/connector/[ccPairId]/ModifyStatusButtonCluster.tsx
index 83d6363f621..10460459e32 100644
--- a/web/src/app/admin/connector/[ccPairId]/ModifyStatusButtonCluster.tsx
+++ b/web/src/app/admin/connector/[ccPairId]/ModifyStatusButtonCluster.tsx
@@ -1,11 +1,11 @@
"use client";
import { Button } from "@tremor/react";
-import { CCPairFullInfo } from "./types";
+import { CCPairFullInfo, ConnectorCredentialPairStatus } from "./types";
import { usePopup } from "@/components/admin/connectors/Popup";
-import { disableConnector } from "@/lib/connector";
import { mutate } from "swr";
import { buildCCPairInfoUrl } from "./lib";
+import { setCCPairStatus } from "@/lib/ccPair";
export function ModifyStatusButtonCluster({
ccPair,
@@ -17,13 +17,16 @@ export function ModifyStatusButtonCluster({
return (
<>
{popup}
- {ccPair.connector.disabled ? (
+ {ccPair.status === ConnectorCredentialPairStatus.PAUSED ? (
>
);
diff --git a/web/src/app/admin/connector/[ccPairId]/lib.ts b/web/src/app/admin/connector/[ccPairId]/lib.ts
index e83f3d406d0..c2d02b23d75 100644
--- a/web/src/app/admin/connector/[ccPairId]/lib.ts
+++ b/web/src/app/admin/connector/[ccPairId]/lib.ts
@@ -1,3 +1,13 @@
+import { ValidSources } from "@/lib/types";
+
export function buildCCPairInfoUrl(ccPairId: string | number) {
return `/api/manage/admin/cc-pair/${ccPairId}`;
}
+
+export function buildSimilarCredentialInfoURL(
+ source_type: ValidSources,
+ get_editable: boolean = false
+) {
+ const base = `/api/manage/admin/similar-credentials/${source_type}`;
+ return get_editable ? `${base}?get_editable=True` : base;
+}
diff --git a/web/src/app/admin/connector/[ccPairId]/page.tsx b/web/src/app/admin/connector/[ccPairId]/page.tsx
index 7e613461bfc..f5da225a867 100644
--- a/web/src/app/admin/connector/[ccPairId]/page.tsx
+++ b/web/src/app/admin/connector/[ccPairId]/page.tsx
@@ -1,23 +1,29 @@
"use client";
-import { CCPairFullInfo } from "./types";
+import { CCPairFullInfo, ConnectorCredentialPairStatus } from "./types";
import { HealthCheckBanner } from "@/components/health/healthcheck";
import { CCPairStatus } from "@/components/Status";
import { BackButton } from "@/components/BackButton";
-import { Divider, Title } from "@tremor/react";
+import { Button, Divider, Title } from "@tremor/react";
import { IndexingAttemptsTable } from "./IndexingAttemptsTable";
-import { Text } from "@tremor/react";
-import { ConfigDisplay } from "./ConfigDisplay";
+import { AdvancedConfigDisplay, ConfigDisplay } from "./ConfigDisplay";
import { ModifyStatusButtonCluster } from "./ModifyStatusButtonCluster";
import { DeletionButton } from "./DeletionButton";
import { ErrorCallout } from "@/components/ErrorCallout";
import { ReIndexButton } from "./ReIndexButton";
import { isCurrentlyDeleting } from "@/lib/documentDeletion";
import { ValidSources } from "@/lib/types";
-import useSWR from "swr";
+import useSWR, { mutate } from "swr";
import { errorHandlingFetcher } from "@/lib/fetcher";
import { ThreeDotsLoader } from "@/components/Loading";
+import CredentialSection from "@/components/credentials/CredentialSection";
import { buildCCPairInfoUrl } from "./lib";
+import { SourceIcon } from "@/components/SourceIcon";
+import { credentialTemplates } from "@/lib/connectors/credentials";
+import { useEffect, useRef, useState } from "react";
+import { CheckmarkIcon, EditIcon, XIcon } from "@/components/icons/icons";
+import { usePopup } from "@/components/admin/connectors/Popup";
+import { updateConnectorCredentialPairName } from "@/lib/connector";
// since the uploaded files are cleaned up after some period of time
// re-indexing will not work for the file connector. Also, it would not
@@ -35,6 +41,43 @@ function Main({ ccPairId }: { ccPairId: number }) {
{ refreshInterval: 5000 } // 5 seconds
);
+ const [editableName, setEditableName] = useState(ccPair?.name || "");
+ const [isEditing, setIsEditing] = useState(false);
+ const inputRef = useRef
(null);
+
+ const { popup, setPopup } = usePopup();
+ useEffect(() => {
+ if (isEditing && inputRef.current) {
+ inputRef.current.focus();
+ }
+ }, [isEditing]);
+ const handleNameChange = (e: React.ChangeEvent) => {
+ setEditableName(e.target.value);
+ };
+
+ const handleUpdateName = async () => {
+ try {
+ const response = await updateConnectorCredentialPairName(
+ ccPair?.id!,
+ editableName
+ );
+ if (!response.ok) {
+ throw new Error(await response.text());
+ }
+ mutate(buildCCPairInfoUrl(ccPairId));
+ setIsEditing(false);
+ setPopup({
+ message: "Connector name updated successfully",
+ type: "success",
+ });
+ } catch (error) {
+ setPopup({
+ message: `Failed to update connector name`,
+ type: "error",
+ });
+ }
+ };
+
if (isLoading) {
return ;
}
@@ -49,7 +92,7 @@ function Main({ ccPairId }: { ccPairId: number }) {
}
const lastIndexAttempt = ccPair.index_attempts[0];
- const isDeleting = isCurrentlyDeleting(ccPair.latest_deletion_attempt);
+ const isDeleting = ccPair.status === ConnectorCredentialPairStatus.DELETING;
// figure out if we need to artificially deflate the number of docs indexed.
// This is required since the total number of docs indexed by a CC Pair is
@@ -61,72 +104,143 @@ function Main({ ccPairId }: { ccPairId: number }) {
? lastIndexAttempt.total_docs_indexed
: ccPair.num_docs_indexed;
+ const refresh = () => {
+ mutate(buildCCPairInfoUrl(ccPairId));
+ };
+
+ const startEditing = () => {
+ setEditableName(ccPair.name);
+ setIsEditing(true);
+ };
+
+ const resetEditing = () => {
+ setIsEditing(false);
+ setEditableName(ccPair.name);
+ };
+
+ const {
+ prune_freq: pruneFreq,
+ refresh_freq: refreshFreq,
+ indexing_start: indexingStart,
+ } = ccPair.connector;
return (
<>
+ {popup}
-
{ccPair.name}
-
-
+ {ccPair.is_editable_for_current_user && isEditing ? (
+
+
+
+
+
+ ) : (
+
+ ccPair.is_editable_for_current_user && startEditing()
+ }
+ className={`group flex ${ccPair.is_editable_for_current_user ? "cursor-pointer" : ""} text-3xl text-emphasis gap-x-2 items-center font-bold`}
+ >
+ {ccPair.name}
+ {ccPair.is_editable_for_current_user && (
+
+ )}
+
+ )}
+
+ {ccPair.is_editable_for_current_user && (
+
+ {!CONNECTOR_TYPES_THAT_CANT_REINDEX.includes(
+ ccPair.connector.source
+ ) && (
+
+ )}
+ {!isDeleting && }
+
+ )}
+
-
Total Documents Indexed:{" "}
{totalDocsIndexed}
+ {!ccPair.is_editable_for_current_user && (
+
+ {ccPair.is_public
+ ? "Public connectors are not editable by curators."
+ : "This connector belongs to groups where you don't have curator permissions, so it's not editable."}
+
+ )}
+ {credentialTemplates[ccPair.connector.source] &&
+ ccPair.is_editable_for_current_user && (
+ <>
+
-
+ Credentials
+ refresh()}
+ />
+ >
+ )}
+
+
+ {(pruneFreq || indexingStart || refreshFreq) && (
+
+ )}
+
{/* NOTE: no divider / title here for `ConfigDisplay` since it is optional and we need
to render these conditionally.*/}
-
Indexing Attempts
-
- {!CONNECTOR_TYPES_THAT_CANT_REINDEX.includes(
- ccPair.connector.source
- ) && (
-
- )}
-
-
-
-
-
Delete Connector
-
- Deleting the connector will also delete all associated documents.
-
-
-
-
+
+
+ {ccPair.is_editable_for_current_user && (
-
+ )}
-
- {/* TODO: add document search*/}
>
);
}
@@ -136,10 +250,6 @@ export default function Page({ params }: { params: { ccPairId: string } }) {
return (
);
diff --git a/web/src/app/admin/connector/[ccPairId]/types.ts b/web/src/app/admin/connector/[ccPairId]/types.ts
index ab4921180cf..1cc43311e21 100644
--- a/web/src/app/admin/connector/[ccPairId]/types.ts
+++ b/web/src/app/admin/connector/[ccPairId]/types.ts
@@ -1,16 +1,22 @@
-import {
- Connector,
- Credential,
- DeletionAttemptSnapshot,
- IndexAttemptSnapshot,
-} from "@/lib/types";
+import { Connector } from "@/lib/connectors/connectors";
+import { Credential } from "@/lib/connectors/credentials";
+import { DeletionAttemptSnapshot, IndexAttemptSnapshot } from "@/lib/types";
+
+export enum ConnectorCredentialPairStatus {
+ ACTIVE = "ACTIVE",
+ PAUSED = "PAUSED",
+ DELETING = "DELETING",
+}
export interface CCPairFullInfo {
id: number;
name: string;
+ status: ConnectorCredentialPairStatus;
num_docs_indexed: number;
connector: Connector
;
credential: Credential;
index_attempts: IndexAttemptSnapshot[];
latest_deletion_attempt: DeletionAttemptSnapshot | null;
+ is_public: boolean;
+ is_editable_for_current_user: boolean;
}
diff --git a/web/src/app/admin/connectors/[connector]/AddConnectorPage.tsx b/web/src/app/admin/connectors/[connector]/AddConnectorPage.tsx
new file mode 100644
index 00000000000..dd8d19ca720
--- /dev/null
+++ b/web/src/app/admin/connectors/[connector]/AddConnectorPage.tsx
@@ -0,0 +1,617 @@
+"use client";
+
+import * as Yup from "yup";
+import { TrashIcon } from "@/components/icons/icons";
+import { errorHandlingFetcher } from "@/lib/fetcher";
+import useSWR, { mutate } from "swr";
+import { HealthCheckBanner } from "@/components/health/healthcheck";
+
+import { Card, Divider, Title } from "@tremor/react";
+import { AdminPageTitle } from "@/components/admin/Title";
+import { buildSimilarCredentialInfoURL } from "@/app/admin/connector/[ccPairId]/lib";
+import { usePopup } from "@/components/admin/connectors/Popup";
+import { useFormContext } from "@/components/context/FormContext";
+import { getSourceDisplayName } from "@/lib/sources";
+import { SourceIcon } from "@/components/SourceIcon";
+import { useRef, useState, useEffect } from "react";
+import { submitConnector } from "@/components/admin/connectors/ConnectorForm";
+import { deleteCredential, linkCredential } from "@/lib/credential";
+import { submitFiles } from "./pages/utils/files";
+import { submitGoogleSite } from "./pages/utils/google_site";
+import AdvancedFormPage from "./pages/Advanced";
+import DynamicConnectionForm from "./pages/DynamicConnectorCreationForm";
+import CreateCredential from "@/components/credentials/actions/CreateCredential";
+import ModifyCredential from "@/components/credentials/actions/ModifyCredential";
+import { ValidSources } from "@/lib/types";
+import { Credential, credentialTemplates } from "@/lib/connectors/credentials";
+import {
+ ConnectionConfiguration,
+ connectorConfigs,
+} from "@/lib/connectors/connectors";
+import { Modal } from "@/components/Modal";
+import { ArrowRight } from "@phosphor-icons/react";
+import { ArrowLeft } from "@phosphor-icons/react/dist/ssr";
+import { FiPlus } from "react-icons/fi";
+import GDriveMain from "./pages/gdrive/GoogleDrivePage";
+import { GmailMain } from "./pages/gmail/GmailPage";
+import {
+ useGmailCredentials,
+ useGoogleDriveCredentials,
+} from "./pages/utils/hooks";
+import { Formik, FormikProps } from "formik";
+import {
+ IsPublicGroupSelector,
+ IsPublicGroupSelectorFormType,
+} from "@/components/IsPublicGroupSelector";
+import { usePaidEnterpriseFeaturesEnabled } from "@/components/settings/usePaidEnterpriseFeaturesEnabled";
+import { AdminBooleanFormField } from "@/components/credentials/CredentialFields";
+
+export type AdvancedConfigFinal = {
+ pruneFreq: number | null;
+ refreshFreq: number | null;
+ indexingStart: Date | null;
+};
+
+export default function AddConnector({
+ connector,
+}: {
+ connector: ValidSources;
+}) {
+ const [currentCredential, setCurrentCredential] =
+ useState | null>(null);
+
+ const { data: credentials } = useSWR[]>(
+ buildSimilarCredentialInfoURL(connector),
+ errorHandlingFetcher,
+ { refreshInterval: 5000 }
+ );
+
+ const { data: editableCredentials } = useSWR[]>(
+ buildSimilarCredentialInfoURL(connector, true),
+ errorHandlingFetcher,
+ { refreshInterval: 5000 }
+ );
+ const [selectedFiles, setSelectedFiles] = useState([]);
+
+ const credentialTemplate = credentialTemplates[connector];
+
+ const {
+ setFormStep,
+ setAllowAdvanced,
+ setAlowCreate,
+ formStep,
+ nextFormStep,
+ prevFormStep,
+ } = useFormContext();
+
+ const { popup, setPopup } = usePopup();
+
+ const configuration: ConnectionConfiguration = connectorConfigs[connector];
+ const [formValues, setFormValues] = useState<
+ Record & IsPublicGroupSelectorFormType
+ >({
+ name: "",
+ groups: [],
+ is_public: false,
+ ...configuration.values.reduce(
+ (acc, field) => {
+ if (field.type === "list") {
+ acc[field.name] = field.default || [];
+ } else if (field.type === "checkbox") {
+ acc[field.name] = field.default || false;
+ } else if (field.default !== undefined) {
+ acc[field.name] = field.default;
+ }
+ return acc;
+ },
+ {} as { [record: string]: any }
+ ),
+ });
+
+ const isPaidEnterpriseFeaturesEnabled = usePaidEnterpriseFeaturesEnabled();
+
+ // Default to 10 minutes unless otherwise specified
+ const defaultAdvancedSettings = {
+ refreshFreq: formValues.overrideDefaultFreq || 10,
+ pruneFreq: 30,
+ indexingStart: null as string | null,
+ };
+
+ const [advancedSettings, setAdvancedSettings] = useState(
+ defaultAdvancedSettings
+ );
+
+ const [createConnectorToggle, setCreateConnectorToggle] = useState(false);
+ const formRef = useRef>(null);
+
+ const [isFormValid, setIsFormValid] = useState(false);
+
+ const handleFormStatusChange = (isValid: boolean) => {
+ setIsFormValid(isValid || connector == "file");
+ };
+
+ const { liveGDriveCredential } = useGoogleDriveCredentials();
+
+ const { liveGmailCredential } = useGmailCredentials();
+
+ const credentialActivated =
+ (connector === "google_drive" && liveGDriveCredential) ||
+ (connector === "gmail" && liveGmailCredential) ||
+ currentCredential;
+
+ const noCredentials = credentialTemplate == null;
+
+ if (noCredentials && 1 != formStep) {
+ setFormStep(Math.max(1, formStep));
+ }
+
+ if (!noCredentials && !credentialActivated && formStep != 0) {
+ setFormStep(Math.min(formStep, 0));
+ }
+
+ const resetAdvancedConfigs = (formikProps: FormikProps) => {
+ formikProps.resetForm({ values: defaultAdvancedSettings });
+ setAdvancedSettings(defaultAdvancedSettings);
+ };
+
+ const convertStringToDateTime = (indexingStart: string | null) => {
+ return indexingStart ? new Date(indexingStart) : null;
+ };
+
+ const createConnector = async () => {
+ const {
+ name,
+ groups,
+ is_public: isPublic,
+ ...connector_specific_config
+ } = formValues;
+ const { pruneFreq, indexingStart, refreshFreq } = advancedSettings;
+
+ // Apply transforms from connectors.ts configuration
+ const transformedConnectorSpecificConfig = Object.entries(
+ connector_specific_config
+ ).reduce(
+ (acc, [key, value]) => {
+ const matchingConfigValue = configuration.values.find(
+ (configValue) => configValue.name === key
+ );
+ if (
+ matchingConfigValue &&
+ "transform" in matchingConfigValue &&
+ matchingConfigValue.transform
+ ) {
+ acc[key] = matchingConfigValue.transform(value as string[]);
+ } else {
+ acc[key] = value;
+ }
+ return acc;
+ },
+ {} as Record
+ );
+
+ const AdvancedConfig: AdvancedConfigFinal = {
+ pruneFreq: advancedSettings.pruneFreq * 60 * 60 * 24,
+ indexingStart: convertStringToDateTime(indexingStart),
+ refreshFreq: advancedSettings.refreshFreq * 60,
+ };
+
+ // google sites-specific handling
+ if (connector == "google_site") {
+ const response = await submitGoogleSite(
+ selectedFiles,
+ formValues?.base_url,
+ setPopup,
+ AdvancedConfig,
+ name
+ );
+ if (response) {
+ setTimeout(() => {
+ window.open("/admin/indexing/status", "_self");
+ }, 1000);
+ }
+ return;
+ }
+
+ // file-specific handling
+ if (connector == "file" && selectedFiles.length > 0) {
+ const response = await submitFiles(
+ selectedFiles,
+ setPopup,
+ setSelectedFiles,
+ name,
+ AdvancedConfig,
+ isPublic,
+ groups
+ );
+ if (response) {
+ setTimeout(() => {
+ window.open("/admin/indexing/status", "_self");
+ }, 1000);
+ }
+ return;
+ }
+
+ const { message, isSuccess, response } = await submitConnector(
+ {
+ connector_specific_config: transformedConnectorSpecificConfig,
+ input_type: connector == "web" ? "load_state" : "poll", // single case
+ name: name,
+ source: connector,
+ refresh_freq: refreshFreq * 60 || null,
+ prune_freq: pruneFreq * 60 * 60 * 24 || null,
+ indexing_start: convertStringToDateTime(indexingStart),
+ is_public: isPublic,
+ groups: groups,
+ },
+ undefined,
+ credentialActivated ? false : true,
+ isPublic
+ );
+ // If no credential
+ if (!credentialActivated) {
+ if (isSuccess) {
+ setPopup({
+ message: "Connector created! Redirecting to connector home page",
+ type: "success",
+ });
+ setTimeout(() => {
+ window.open("/admin/indexing/status", "_self");
+ }, 1000);
+ } else {
+ setPopup({ message: message, type: "error" });
+ }
+ }
+
+ // Without credential
+ if (credentialActivated && isSuccess && response) {
+ const credential =
+ currentCredential || liveGDriveCredential || liveGmailCredential;
+ const linkCredentialResponse = await linkCredential(
+ response.id,
+ credential?.id!,
+ name,
+ isPublic,
+ groups
+ );
+ if (linkCredentialResponse.ok) {
+ setPopup({
+ message: "Connector created! Redirecting to connector home page",
+ type: "success",
+ });
+ setTimeout(() => {
+ window.open("/admin/indexing/status", "_self");
+ }, 1000);
+ } else {
+ const errorData = await linkCredentialResponse.json();
+ setPopup({
+ message: errorData.message,
+ type: "error",
+ });
+ }
+ } else if (isSuccess) {
+ setPopup({
+ message:
+ "Credential created succsfully! Redirecting to connector home page",
+ type: "success",
+ });
+ } else {
+ setPopup({ message: message, type: "error" });
+ }
+ };
+
+ const displayName = getSourceDisplayName(connector) || connector;
+ if (!credentials || !editableCredentials) {
+ return <>>;
+ }
+
+ const refresh = () => {
+ mutate(buildSimilarCredentialInfoURL(connector));
+ };
+ const onDeleteCredential = async (credential: Credential) => {
+ const response = await deleteCredential(credential.id, true);
+ if (response.ok) {
+ setPopup({
+ message: "Credential deleted successfully!",
+ type: "success",
+ });
+ } else {
+ const errorData = await response.json();
+ setPopup({
+ message: errorData.message,
+ type: "error",
+ });
+ }
+ };
+
+ const onSwap = async (selectedCredential: Credential) => {
+ setCurrentCredential(selectedCredential);
+ setAlowCreate(true);
+ setPopup({
+ message: "Swapped credential successfully!",
+ type: "success",
+ });
+ refresh();
+ };
+
+ const validationSchema = Yup.object().shape({
+ name: Yup.string().required("Connector Name is required"),
+ ...configuration.values.reduce(
+ (acc, field) => {
+ let schema: any =
+ field.type === "list"
+ ? Yup.array().of(Yup.string())
+ : field.type === "checkbox"
+ ? Yup.boolean()
+ : Yup.string();
+
+ if (!field.optional) {
+ schema = schema.required(`${field.label} is required`);
+ }
+ acc[field.name] = schema;
+ return acc;
+ },
+ {} as Record
+ ),
+ });
+
+ const advancedValidationSchema = Yup.object().shape({
+ indexingStart: Yup.string().nullable(),
+ pruneFreq: Yup.number().min(0, "Prune frequency must be non-negative"),
+ refreshFreq: Yup.number().min(0, "Refresh frequency must be non-negative"),
+ });
+
+ const isFormSubmittable = (values: any) => {
+ return (
+ values.name.trim() !== "" &&
+ Object.keys(values).every((key) => {
+ const field = configuration.values.find((f) => f.name === key);
+ return field?.optional || values[key] !== "";
+ })
+ );
+ };
+
+ return (
+
+ {popup}
+
+
+
+
+
}
+ title={displayName}
+ />
+
+ {formStep == 0 &&
+ (connector == "google_drive" ? (
+ <>
+
+ Select a credential
+
+
+
+
+
+ >
+ ) : connector == "gmail" ? (
+ <>
+
+ Select a credential
+
+
+
+
+
+ >
+ ) : (
+ <>
+
+ Select a credential
+
+ {!createConnectorToggle && (
+
+ )}
+
+ {!(connector == "google_drive") && createConnectorToggle && (
+ setCreateConnectorToggle(false)}
+ >
+ <>
+
+ Create a {getSourceDisplayName(connector)} credential
+
+ setCreateConnectorToggle(false)}
+ />
+ >
+
+ )}
+
+
+
+
+ >
+ ))}
+
+ {formStep == 1 && (
+ <>
+
+ {
+ // Can be utilized for logging purposes
+ }}
+ >
+ {(formikProps) => {
+ setFormValues(formikProps.values);
+ handleFormStatusChange(
+ formikProps.isValid && isFormSubmittable(formikProps.values)
+ );
+ setAllowAdvanced(
+ formikProps.isValid && isFormSubmittable(formikProps.values)
+ );
+
+ return (
+
+
+ {isPaidEnterpriseFeaturesEnabled && (
+ <>
+
+ >
+ )}
+
+ );
+ }}
+
+
+
+ {!noCredentials ? (
+
+ ) : (
+
+ )}
+
+
+ {!(connector == "file") && (
+
+
+
+ )}
+
+ >
+ )}
+
+ {formStep === 2 && (
+ <>
+
+ {}}
+ >
+ {(formikProps) => {
+ setAdvancedSettings(formikProps.values);
+
+ return (
+ <>
+
+
+
+
+ >
+ );
+ }}
+
+
+
+
+
+
+ >
+ )}
+
+ );
+}
diff --git a/web/src/app/admin/connectors/[connector]/ConnectorWrapper.tsx b/web/src/app/admin/connectors/[connector]/ConnectorWrapper.tsx
new file mode 100644
index 00000000000..345ace085bc
--- /dev/null
+++ b/web/src/app/admin/connectors/[connector]/ConnectorWrapper.tsx
@@ -0,0 +1,37 @@
+"use client";
+
+import { ValidSources } from "@/lib/types";
+import AddConnector from "./AddConnectorPage";
+import { FormProvider } from "@/components/context/FormContext";
+import Sidebar from "./Sidebar";
+import { HeaderTitle } from "@/components/header/HeaderTitle";
+import { Button } from "@tremor/react";
+import { isValidSource } from "@/lib/sources";
+
+export default function ConnectorWrapper({ connector }: { connector: string }) {
+ return (
+
+
+
+
+ {!isValidSource(connector) ? (
+
+
+ ‘{connector}‘ is not a valid Connector Type!
+
+
+
+ ) : (
+
+ )}
+
+
+
+ );
+}
diff --git a/web/src/app/admin/connectors/[connector]/Sidebar.tsx b/web/src/app/admin/connectors/[connector]/Sidebar.tsx
new file mode 100644
index 00000000000..97275843e0c
--- /dev/null
+++ b/web/src/app/admin/connectors/[connector]/Sidebar.tsx
@@ -0,0 +1,116 @@
+import { useFormContext } from "@/components/context/FormContext";
+import { HeaderTitle } from "@/components/header/HeaderTitle";
+
+import { BackIcon, SettingsIcon } from "@/components/icons/icons";
+import { Logo } from "@/components/Logo";
+import { SettingsContext } from "@/components/settings/SettingsProvider";
+import { credentialTemplates } from "@/lib/connectors/credentials";
+import Link from "next/link";
+import { useContext } from "react";
+
+export default function Sidebar() {
+ const { formStep, setFormStep, connector, allowAdvanced, allowCreate } =
+ useFormContext();
+ const combinedSettings = useContext(SettingsContext);
+ if (!combinedSettings) {
+ return null;
+ }
+ const enterpriseSettings = combinedSettings.enterpriseSettings;
+ const noCredential = credentialTemplates[connector] == null;
+
+ const settingSteps = [
+ ...(!noCredential ? ["Credential"] : []),
+ "Connector",
+ ...(connector == "file" ? [] : ["Advanced (optional)"]),
+ ];
+
+ return (
+
+
+
+
+
+
+
+
+
+ {enterpriseSettings && enterpriseSettings.application_name ? (
+ {enterpriseSettings.application_name}
+ ) : (
+
+ EveAI
+
+ )}
+
+
+
+
+
+
+
+
+ {connector != "file" && (
+
+ )}
+ {settingSteps.map((step, index) => {
+ const allowed =
+ (step == "Connector" && allowCreate) ||
+ (step == "Advanced (optional)" && allowAdvanced) ||
+ index <= formStep;
+
+ return (
+
{
+ if (allowed) {
+ setFormStep(index - (noCredential ? 1 : 0));
+ }
+ }}
+ >
+
+
+ {formStep === index && (
+
+ )}
+
+
+
+ {step}
+
+
+ );
+ })}
+
+
+
+
+
+
+ );
+}
diff --git a/web/src/app/admin/connectors/google-drive/auth/callback/route.ts b/web/src/app/admin/connectors/[connector]/auth/callback/route.ts
similarity index 54%
rename from web/src/app/admin/connectors/google-drive/auth/callback/route.ts
rename to web/src/app/admin/connectors/[connector]/auth/callback/route.ts
index 3a82df64c8f..9d80e1b2fd2 100644
--- a/web/src/app/admin/connectors/google-drive/auth/callback/route.ts
+++ b/web/src/app/admin/connectors/[connector]/auth/callback/route.ts
@@ -2,13 +2,16 @@ import { getDomain } from "@/lib/redirectSS";
import { buildUrl } from "@/lib/utilsSS";
import { NextRequest, NextResponse } from "next/server";
import { cookies } from "next/headers";
-import { GOOGLE_DRIVE_AUTH_IS_ADMIN_COOKIE_NAME } from "@/lib/constants";
+import {
+ GMAIL_AUTH_IS_ADMIN_COOKIE_NAME,
+ GOOGLE_DRIVE_AUTH_IS_ADMIN_COOKIE_NAME,
+} from "@/lib/constants";
import { processCookies } from "@/lib/userSS";
export const GET = async (request: NextRequest) => {
- // Wrapper around the FastAPI endpoint /connectors/google-drive/callback,
- // which adds back a redirect to the Google Drive admin page.
- const url = new URL(buildUrl("/manage/connector/google-drive/callback"));
+ const connector = request.url.includes("gmail") ? "gmail" : "google-drive";
+ const callbackEndpoint = `/manage/connector/${connector}/callback`;
+ const url = new URL(buildUrl(callbackEndpoint));
url.search = request.nextUrl.search;
const response = await fetch(url.toString(), {
@@ -19,20 +22,22 @@ export const GET = async (request: NextRequest) => {
if (!response.ok) {
console.log(
- "Error in Google Drive callback:",
+ `Error in ${connector} callback:`,
(await response.json()).detail
);
return NextResponse.redirect(new URL("/auth/error", getDomain(request)));
}
- if (
- cookies()
- .get(GOOGLE_DRIVE_AUTH_IS_ADMIN_COOKIE_NAME)
- ?.value?.toLowerCase() === "true"
- ) {
+ const authCookieName =
+ connector === "gmail"
+ ? GMAIL_AUTH_IS_ADMIN_COOKIE_NAME
+ : GOOGLE_DRIVE_AUTH_IS_ADMIN_COOKIE_NAME;
+
+ if (cookies().get(authCookieName)?.value?.toLowerCase() === "true") {
return NextResponse.redirect(
- new URL("/admin/connectors/google-drive", getDomain(request))
+ new URL(`/admin/connectors/${connector}`, getDomain(request))
);
}
+
return NextResponse.redirect(new URL("/user/connectors", getDomain(request)));
};
diff --git a/web/src/app/admin/connectors/[connector]/page.tsx b/web/src/app/admin/connectors/[connector]/page.tsx
new file mode 100644
index 00000000000..265d6922ebc
--- /dev/null
+++ b/web/src/app/admin/connectors/[connector]/page.tsx
@@ -0,0 +1,9 @@
+import ConnectorWrapper from "./ConnectorWrapper";
+
+export default async function Page({
+ params,
+}: {
+ params: { connector: string };
+}) {
+ return ;
+}
diff --git a/web/src/app/admin/connectors/[connector]/pages/Advanced.tsx b/web/src/app/admin/connectors/[connector]/pages/Advanced.tsx
new file mode 100644
index 00000000000..470ab8d2a77
--- /dev/null
+++ b/web/src/app/admin/connectors/[connector]/pages/Advanced.tsx
@@ -0,0 +1,68 @@
+import React, { Dispatch, forwardRef, SetStateAction } from "react";
+import { Formik, Form, FormikProps } from "formik";
+import * as Yup from "yup";
+import NumberInput from "./ConnectorInput/NumberInput";
+import { TextFormField } from "@/components/admin/connectors/Field";
+
+interface AdvancedFormPageProps {
+ formikProps: FormikProps<{
+ indexingStart: string | null;
+ pruneFreq: number;
+ refreshFreq: number;
+ }>;
+}
+
+const AdvancedFormPage = forwardRef, AdvancedFormPageProps>(
+ ({ formikProps }, ref) => {
+ const { indexingStart, refreshFreq, pruneFreq } = formikProps.values;
+
+ return (
+
+
+ Advanced Configuration
+
+
+
+
+ );
+ }
+);
+
+AdvancedFormPage.displayName = "AdvancedFormPage";
+export default AdvancedFormPage;
diff --git a/web/src/app/admin/connectors/[connector]/pages/ConnectorInput/FileInput.tsx b/web/src/app/admin/connectors/[connector]/pages/ConnectorInput/FileInput.tsx
new file mode 100644
index 00000000000..50af2dfff70
--- /dev/null
+++ b/web/src/app/admin/connectors/[connector]/pages/ConnectorInput/FileInput.tsx
@@ -0,0 +1,37 @@
+import { FileUpload } from "@/components/admin/connectors/FileUpload";
+import CredentialSubText from "@/components/credentials/CredentialFields";
+
+interface FileInputProps {
+ name: string;
+ label: string;
+ optional?: boolean;
+ description?: string;
+ selectedFiles: File[];
+ setSelectedFiles: (files: File[]) => void;
+}
+
+export default function FileInput({
+ name,
+ label,
+ optional = false,
+ description,
+ selectedFiles,
+ setSelectedFiles,
+}: FileInputProps) {
+ return (
+ <>
+
+ {description && {description}}
+
+ >
+ );
+}
diff --git a/web/src/app/admin/connectors/[connector]/pages/ConnectorInput/ListInput.tsx b/web/src/app/admin/connectors/[connector]/pages/ConnectorInput/ListInput.tsx
new file mode 100644
index 00000000000..059d08d539d
--- /dev/null
+++ b/web/src/app/admin/connectors/[connector]/pages/ConnectorInput/ListInput.tsx
@@ -0,0 +1,74 @@
+import CredentialSubText from "@/components/credentials/CredentialFields";
+import { TrashIcon } from "@/components/icons/icons";
+import { ListOption } from "@/lib/connectors/connectors";
+import { Field, FieldArray, useField } from "formik";
+import { FaPlus } from "react-icons/fa";
+
+export default function ListInput({
+ field,
+ onUpdate,
+}: {
+ field: ListOption;
+ onUpdate?: (values: string[]) => void;
+}) {
+ const [fieldProps, , helpers] = useField(field.name);
+
+ return (
+
+ {({ push, remove }) => (
+
+
+ {field.description && (
+
{field.description}
+ )}
+
+ {fieldProps.value.map((value: string, index: number) => (
+
+
+
+
+ ))}
+
+
+
+ )}
+
+ );
+}
diff --git a/web/src/app/admin/connectors/[connector]/pages/ConnectorInput/NumberInput.tsx b/web/src/app/admin/connectors/[connector]/pages/ConnectorInput/NumberInput.tsx
new file mode 100644
index 00000000000..5a9f5041b5d
--- /dev/null
+++ b/web/src/app/admin/connectors/[connector]/pages/ConnectorInput/NumberInput.tsx
@@ -0,0 +1,42 @@
+import { SubLabel } from "@/components/admin/connectors/Field";
+import { Field } from "formik";
+
+export default function NumberInput({
+ label,
+ value,
+ optional,
+ description,
+ name,
+ showNeverIfZero,
+}: {
+ value?: number;
+ label: string;
+ name: string;
+ optional?: boolean;
+ description?: string;
+ showNeverIfZero?: boolean;
+}) {
+ return (
+
+
+ {description && {description}}
+
+
+
+ );
+}
diff --git a/web/src/app/admin/connectors/[connector]/pages/ConnectorInput/SelectInput.tsx b/web/src/app/admin/connectors/[connector]/pages/ConnectorInput/SelectInput.tsx
new file mode 100644
index 00000000000..e01a02dc323
--- /dev/null
+++ b/web/src/app/admin/connectors/[connector]/pages/ConnectorInput/SelectInput.tsx
@@ -0,0 +1,45 @@
+import CredentialSubText from "@/components/credentials/CredentialFields";
+import { ListOption, SelectOption } from "@/lib/connectors/connectors";
+import { Field } from "formik";
+
+export default function SelectInput({
+ field,
+ value,
+ onChange,
+}: {
+ field: SelectOption;
+ value: any;
+ onChange?: (e: Event) => void;
+}) {
+ return (
+ <>
+
+ {field.description && (
+ {field.description}
+ )}
+
+
+
+ {field.options?.map((option: any) => (
+
+ ))}
+
+ >
+ );
+}
diff --git a/web/src/app/admin/connectors/[connector]/pages/DynamicConnectorCreationForm.tsx b/web/src/app/admin/connectors/[connector]/pages/DynamicConnectorCreationForm.tsx
new file mode 100644
index 00000000000..507b976f9a8
--- /dev/null
+++ b/web/src/app/admin/connectors/[connector]/pages/DynamicConnectorCreationForm.tsx
@@ -0,0 +1,115 @@
+import React, {
+ ChangeEvent,
+ Dispatch,
+ FC,
+ SetStateAction,
+ useEffect,
+ useState,
+} from "react";
+import { Formik, Form, Field, FieldArray, FormikProps } from "formik";
+import * as Yup from "yup";
+import { FaPlus } from "react-icons/fa";
+import { useUserGroups } from "@/lib/hooks";
+import { UserGroup, User, UserRole } from "@/lib/types";
+import { Divider } from "@tremor/react";
+import CredentialSubText, {
+ AdminBooleanFormField,
+} from "@/components/credentials/CredentialFields";
+import { TrashIcon } from "@/components/icons/icons";
+import { FileUpload } from "@/components/admin/connectors/FileUpload";
+import { ConnectionConfiguration } from "@/lib/connectors/connectors";
+import { useFormContext } from "@/components/context/FormContext";
+import { usePaidEnterpriseFeaturesEnabled } from "@/components/settings/usePaidEnterpriseFeaturesEnabled";
+import { Text } from "@tremor/react";
+import { getCurrentUser } from "@/lib/user";
+import { FiUsers } from "react-icons/fi";
+import SelectInput from "./ConnectorInput/SelectInput";
+import NumberInput from "./ConnectorInput/NumberInput";
+import { TextFormField } from "@/components/admin/connectors/Field";
+import ListInput from "./ConnectorInput/ListInput";
+import FileInput from "./ConnectorInput/FileInput";
+
+export interface DynamicConnectionFormProps {
+ config: ConnectionConfiguration;
+ selectedFiles: File[];
+ setSelectedFiles: Dispatch>;
+ values: any;
+}
+
+const DynamicConnectionForm: FC = ({
+ config,
+ selectedFiles,
+ setSelectedFiles,
+ values,
+}) => {
+ return (
+ <>
+ {config.description}
+
+ {config.subtext && (
+ {config.subtext}
+ )}
+
+
+
+ {config.values.map((field) => {
+ if (!field.hidden) {
+ return (
+
+ {field.type == "file" ? (
+
+ ) : field.type == "zip" ? (
+
+ ) : field.type === "list" ? (
+
+ ) : field.type === "select" ? (
+
+ ) : field.type === "number" ? (
+
+ ) : field.type === "checkbox" ? (
+
+ ) : (
+
+ )}
+
+ );
+ }
+ })}
+ >
+ );
+};
+
+export default DynamicConnectionForm;
diff --git a/web/src/app/admin/connectors/[connector]/pages/formelements/NumberInput.tsx b/web/src/app/admin/connectors/[connector]/pages/formelements/NumberInput.tsx
new file mode 100644
index 00000000000..5a9f5041b5d
--- /dev/null
+++ b/web/src/app/admin/connectors/[connector]/pages/formelements/NumberInput.tsx
@@ -0,0 +1,42 @@
+import { SubLabel } from "@/components/admin/connectors/Field";
+import { Field } from "formik";
+
+export default function NumberInput({
+ label,
+ value,
+ optional,
+ description,
+ name,
+ showNeverIfZero,
+}: {
+ value?: number;
+ label: string;
+ name: string;
+ optional?: boolean;
+ description?: string;
+ showNeverIfZero?: boolean;
+}) {
+ return (
+
+
+ {description && {description}}
+
+
+
+ );
+}
diff --git a/web/src/app/admin/connectors/google-drive/Credential.tsx b/web/src/app/admin/connectors/[connector]/pages/gdrive/Credential.tsx
similarity index 86%
rename from web/src/app/admin/connectors/google-drive/Credential.tsx
rename to web/src/app/admin/connectors/[connector]/pages/gdrive/Credential.tsx
index 849057e71e3..a320d466b40 100644
--- a/web/src/app/admin/connectors/google-drive/Credential.tsx
+++ b/web/src/app/admin/connectors/[connector]/pages/gdrive/Credential.tsx
@@ -4,11 +4,6 @@ import { useState } from "react";
import { useSWRConfig } from "swr";
import * as Yup from "yup";
import { useRouter } from "next/navigation";
-import {
- Credential,
- GoogleDriveCredentialJson,
- GoogleDriveServiceAccountCredentialJson,
-} from "@/lib/types";
import { adminDeleteCredential } from "@/lib/credential";
import { setupGoogleDriveOAuth } from "@/lib/googleDrive";
import { GOOGLE_DRIVE_AUTH_IS_ADMIN_COOKIE_NAME } from "@/lib/constants";
@@ -16,10 +11,15 @@ import Cookies from "js-cookie";
import { TextFormField } from "@/components/admin/connectors/Field";
import { Form, Formik } from "formik";
import { Card } from "@tremor/react";
+import {
+ Credential,
+ GoogleDriveCredentialJson,
+ GoogleDriveServiceAccountCredentialJson,
+} from "@/lib/connectors/credentials";
type GoogleDriveCredentialJsonTypes = "authorized_user" | "service_account";
-const DriveJsonUpload = ({
+export const DriveJsonUpload = ({
setPopup,
}: {
setPopup: (popupSpec: PopupSpec | null) => void;
@@ -33,8 +33,8 @@ const DriveJsonUpload = ({
<>
void;
appCredentialData?: { client_id: string };
serviceAccountCredentialData?: { service_account_email: string };
+ isAdmin: boolean;
}
export const DriveJsonUploadSection = ({
setPopup,
appCredentialData,
serviceAccountCredentialData,
+ isAdmin,
}: DriveJsonUploadSectionProps) => {
const { mutate } = useSWRConfig();
@@ -165,38 +167,48 @@ export const DriveJsonUploadSection = ({
{serviceAccountCredentialData.service_account_email}
-
- If you want to update these credentials, delete the existing
- credentials through the button below, and then upload a new
- credentials JSON.
-
-
+ {isAdmin ? (
+ <>
+
+ If you want to update these credentials, delete the existing
+ credentials through the button below, and then upload a new
+ credentials JSON.
+
+
+ >
+ ) : (
+ <>
+
+ To change these credentials, please contact an administrator.
+
+ >
+ )}
);
}
@@ -242,6 +254,17 @@ export const DriveJsonUploadSection = ({
);
}
+ if (!isAdmin) {
+ return (
+
+
+ Curators are unable to set up the google drive credentials. To add a
+ Google Drive connector, please contact an administrator.
+
+
+ );
+ }
+
return (
diff --git a/web/src/app/admin/connectors/[connector]/pages/gdrive/GoogleDrivePage.tsx b/web/src/app/admin/connectors/[connector]/pages/gdrive/GoogleDrivePage.tsx
new file mode 100644
index 00000000000..4494e4b22ee
--- /dev/null
+++ b/web/src/app/admin/connectors/[connector]/pages/gdrive/GoogleDrivePage.tsx
@@ -0,0 +1,155 @@
+"use client";
+
+import React from "react";
+import { useState, useEffect } from "react";
+import useSWR from "swr";
+import { FetchError, errorHandlingFetcher } from "@/lib/fetcher";
+import { ErrorCallout } from "@/components/ErrorCallout";
+import { LoadingAnimation } from "@/components/Loading";
+import { usePopup } from "@/components/admin/connectors/Popup";
+import { ConnectorIndexingStatus } from "@/lib/types";
+import { getCurrentUser } from "@/lib/user";
+import { User, UserRole } from "@/lib/types";
+import { usePublicCredentials } from "@/lib/hooks";
+import { Title } from "@tremor/react";
+import { DriveJsonUploadSection, DriveOAuthSection } from "./Credential";
+import {
+ Credential,
+ GoogleDriveCredentialJson,
+ GoogleDriveServiceAccountCredentialJson,
+} from "@/lib/connectors/credentials";
+import { GoogleDriveConfig } from "@/lib/connectors/connectors";
+import { useUser } from "@/components/user/UserProvider";
+import { useConnectorCredentialIndexingStatus } from "@/lib/hooks";
+
+const GDriveMain = ({}: {}) => {
+ const { isLoadingUser, isAdmin } = useUser();
+
+ const {
+ data: appCredentialData,
+ isLoading: isAppCredentialLoading,
+ error: isAppCredentialError,
+ } = useSWR<{ client_id: string }, FetchError>(
+ "/api/manage/admin/connector/google-drive/app-credential",
+ errorHandlingFetcher
+ );
+
+ const {
+ data: serviceAccountKeyData,
+ isLoading: isServiceAccountKeyLoading,
+ error: isServiceAccountKeyError,
+ } = useSWR<{ service_account_email: string }, FetchError>(
+ "/api/manage/admin/connector/google-drive/service-account-key",
+ errorHandlingFetcher
+ );
+
+ const {
+ data: connectorIndexingStatuses,
+ isLoading: isConnectorIndexingStatusesLoading,
+ error: connectorIndexingStatusesError,
+ } = useConnectorCredentialIndexingStatus();
+ const {
+ data: credentialsData,
+ isLoading: isCredentialsLoading,
+ error: credentialsError,
+ refreshCredentials,
+ } = usePublicCredentials();
+
+ const { popup, setPopup } = usePopup();
+
+ const appCredentialSuccessfullyFetched =
+ appCredentialData ||
+ (isAppCredentialError && isAppCredentialError.status === 404);
+ const serviceAccountKeySuccessfullyFetched =
+ serviceAccountKeyData ||
+ (isServiceAccountKeyError && isServiceAccountKeyError.status === 404);
+
+ if (isLoadingUser) {
+ return <>>;
+ }
+
+ if (
+ (!appCredentialSuccessfullyFetched && isAppCredentialLoading) ||
+ (!serviceAccountKeySuccessfullyFetched && isServiceAccountKeyLoading) ||
+ (!connectorIndexingStatuses && isConnectorIndexingStatusesLoading) ||
+ (!credentialsData && isCredentialsLoading)
+ ) {
+ return (
+
+
+
+ );
+ }
+
+ if (credentialsError || !credentialsData) {
+ return
;
+ }
+
+ if (connectorIndexingStatusesError || !connectorIndexingStatuses) {
+ return
;
+ }
+
+ if (
+ !appCredentialSuccessfullyFetched ||
+ !serviceAccountKeySuccessfullyFetched
+ ) {
+ return (
+
+ );
+ }
+
+ const googleDrivePublicCredential:
+ | Credential
+ | undefined = credentialsData.find(
+ (credential) =>
+ credential.credential_json?.google_drive_tokens && credential.admin_public
+ );
+ const googleDriveServiceAccountCredential:
+ | Credential
+ | undefined = credentialsData.find(
+ (credential) => credential.credential_json?.google_drive_service_account_key
+ );
+ const googleDriveConnectorIndexingStatuses: ConnectorIndexingStatus<
+ GoogleDriveConfig,
+ GoogleDriveCredentialJson
+ >[] = connectorIndexingStatuses.filter(
+ (connectorIndexingStatus) =>
+ connectorIndexingStatus.connector.source === "google_drive"
+ );
+
+ return (
+ <>
+ {popup}
+
+ Step 1: Provide your Credentials
+
+
+
+ {isAdmin && (
+ <>
+
+ Step 2: Authenticate with Danswer
+
+ 0}
+ />
+ >
+ )}
+ >
+ );
+};
+
+export default GDriveMain;
diff --git a/web/src/app/admin/connectors/gmail/Credential.tsx b/web/src/app/admin/connectors/[connector]/pages/gmail/Credential.tsx
similarity index 86%
rename from web/src/app/admin/connectors/gmail/Credential.tsx
rename to web/src/app/admin/connectors/[connector]/pages/gmail/Credential.tsx
index 68f5bba2d20..8b456884f1a 100644
--- a/web/src/app/admin/connectors/gmail/Credential.tsx
+++ b/web/src/app/admin/connectors/[connector]/pages/gmail/Credential.tsx
@@ -4,11 +4,6 @@ import { useState } from "react";
import { useSWRConfig } from "swr";
import * as Yup from "yup";
import { useRouter } from "next/navigation";
-import {
- Credential,
- GmailCredentialJson,
- GmailServiceAccountCredentialJson,
-} from "@/lib/types";
import { adminDeleteCredential } from "@/lib/credential";
import { setupGmailOAuth } from "@/lib/gmail";
import { GMAIL_AUTH_IS_ADMIN_COOKIE_NAME } from "@/lib/constants";
@@ -16,6 +11,11 @@ import Cookies from "js-cookie";
import { TextFormField } from "@/components/admin/connectors/Field";
import { Form, Formik } from "formik";
import { Card } from "@tremor/react";
+import {
+ Credential,
+ GmailCredentialJson,
+ GmailServiceAccountCredentialJson,
+} from "@/lib/connectors/credentials";
type GmailCredentialJsonTypes = "authorized_user" | "service_account";
@@ -33,8 +33,8 @@ const DriveJsonUpload = ({
<>
void;
appCredentialData?: { client_id: string };
serviceAccountCredentialData?: { service_account_email: string };
+ isAdmin: boolean;
}
export const GmailJsonUploadSection = ({
setPopup,
appCredentialData,
serviceAccountCredentialData,
+ isAdmin,
}: DriveJsonUploadSectionProps) => {
const { mutate } = useSWRConfig();
@@ -163,36 +165,48 @@ export const GmailJsonUploadSection = ({
{serviceAccountCredentialData.service_account_email}
-
- If you want to update these credentials, delete the existing
- credentials through the button below, and then upload a new
- credentials JSON.
-
-
+ {isAdmin ? (
+ <>
+
+ If you want to update these credentials, delete the existing
+ credentials through the button below, and then upload a new
+ credentials JSON.
+
+
+ >
+ ) : (
+ <>
+
+ To change these credentials, please contact an administrator.
+
+ >
+ )}
);
}
@@ -238,6 +252,17 @@ export const GmailJsonUploadSection = ({
);
}
+ if (!isAdmin) {
+ return (
+
+
+ Curators are unable to set up the Gmail credentials. To add a Gmail
+ connector, please contact an administrator.
+
+
+ );
+ }
+
return (
diff --git a/web/src/app/admin/connectors/[connector]/pages/gmail/GmailPage.tsx b/web/src/app/admin/connectors/[connector]/pages/gmail/GmailPage.tsx
new file mode 100644
index 00000000000..5f52eb31013
--- /dev/null
+++ b/web/src/app/admin/connectors/[connector]/pages/gmail/GmailPage.tsx
@@ -0,0 +1,159 @@
+"use client";
+
+import useSWR from "swr";
+import { errorHandlingFetcher } from "@/lib/fetcher";
+import { LoadingAnimation } from "@/components/Loading";
+import { usePopup } from "@/components/admin/connectors/Popup";
+import { ConnectorIndexingStatus } from "@/lib/types";
+import { getCurrentUser } from "@/lib/user";
+import { User, UserRole } from "@/lib/types";
+import {
+ Credential,
+ GmailCredentialJson,
+ GmailServiceAccountCredentialJson,
+} from "@/lib/connectors/credentials";
+import { GmailOAuthSection, GmailJsonUploadSection } from "./Credential";
+import { usePublicCredentials } from "@/lib/hooks";
+import { Title } from "@tremor/react";
+import { GmailConfig } from "@/lib/connectors/connectors";
+import { useState, useEffect } from "react";
+import { useUser } from "@/components/user/UserProvider";
+import { useConnectorCredentialIndexingStatus } from "@/lib/hooks";
+
+export const GmailMain = () => {
+ const { isLoadingUser, isAdmin } = useUser();
+
+ const {
+ data: appCredentialData,
+ isLoading: isAppCredentialLoading,
+ error: isAppCredentialError,
+ } = useSWR<{ client_id: string }>(
+ "/api/manage/admin/connector/gmail/app-credential",
+ errorHandlingFetcher
+ );
+ const {
+ data: serviceAccountKeyData,
+ isLoading: isServiceAccountKeyLoading,
+ error: isServiceAccountKeyError,
+ } = useSWR<{ service_account_email: string }>(
+ "/api/manage/admin/connector/gmail/service-account-key",
+ errorHandlingFetcher
+ );
+ const {
+ data: connectorIndexingStatuses,
+ isLoading: isConnectorIndexingStatusesLoading,
+ error: connectorIndexingStatusesError,
+ } = useConnectorCredentialIndexingStatus();
+
+ const {
+ data: credentialsData,
+ isLoading: isCredentialsLoading,
+ error: credentialsError,
+ refreshCredentials,
+ } = usePublicCredentials();
+
+ const { popup, setPopup } = usePopup();
+
+ const appCredentialSuccessfullyFetched =
+ appCredentialData ||
+ (isAppCredentialError && isAppCredentialError.status === 404);
+ const serviceAccountKeySuccessfullyFetched =
+ serviceAccountKeyData ||
+ (isServiceAccountKeyError && isServiceAccountKeyError.status === 404);
+
+ if (isLoadingUser) {
+ return <>>;
+ }
+
+ if (
+ (!appCredentialSuccessfullyFetched && isAppCredentialLoading) ||
+ (!serviceAccountKeySuccessfullyFetched && isServiceAccountKeyLoading) ||
+ (!connectorIndexingStatuses && isConnectorIndexingStatusesLoading) ||
+ (!credentialsData && isCredentialsLoading)
+ ) {
+ return (
+
+
+
+ );
+ }
+
+ if (credentialsError || !credentialsData) {
+ return (
+
+
Failed to load credentials.
+
+ );
+ }
+
+ if (connectorIndexingStatusesError || !connectorIndexingStatuses) {
+ return (
+
+
Failed to load connectors.
+
+ );
+ }
+
+ if (
+ !appCredentialSuccessfullyFetched ||
+ !serviceAccountKeySuccessfullyFetched
+ ) {
+ return (
+
+
+ Error loading Gmail app credentials. Contact an administrator.
+
+
+ );
+ }
+
+ const gmailPublicCredential: Credential
| undefined =
+ credentialsData.find(
+ (credential) =>
+ credential.credential_json?.gmail_tokens && credential.admin_public
+ );
+ const gmailServiceAccountCredential:
+ | Credential
+ | undefined = credentialsData.find(
+ (credential) => credential.credential_json?.gmail_service_account_key
+ );
+ const gmailConnectorIndexingStatuses: ConnectorIndexingStatus<
+ GmailConfig,
+ GmailCredentialJson
+ >[] = connectorIndexingStatuses.filter(
+ (connectorIndexingStatus) =>
+ connectorIndexingStatus.connector.source === "gmail"
+ );
+
+ return (
+ <>
+ {popup}
+
+ Step 1: Provide your Credentials
+
+
+
+ {isAdmin && (
+ <>
+
+ Step 2: Authenticate with Danswer
+
+ 0}
+ />
+ >
+ )}
+ >
+ );
+};
diff --git a/web/src/app/admin/connectors/[connector]/pages/utils/files.ts b/web/src/app/admin/connectors/[connector]/pages/utils/files.ts
new file mode 100644
index 00000000000..d847efe89d1
--- /dev/null
+++ b/web/src/app/admin/connectors/[connector]/pages/utils/files.ts
@@ -0,0 +1,112 @@
+import { PopupSpec } from "@/components/admin/connectors/Popup";
+import { createConnector, runConnector } from "@/lib/connector";
+import { createCredential, linkCredential } from "@/lib/credential";
+import { FileConfig } from "@/lib/connectors/connectors";
+import { AdvancedConfigFinal } from "../../AddConnectorPage";
+
+export const submitFiles = async (
+ selectedFiles: File[],
+ setPopup: (popup: PopupSpec) => void,
+ setSelectedFiles: (files: File[]) => void,
+ name: string,
+ advancedConfig: AdvancedConfigFinal,
+ isPublic: boolean,
+ groups?: number[]
+) => {
+ const formData = new FormData();
+
+ selectedFiles.forEach((file) => {
+ formData.append("files", file);
+ });
+
+ const response = await fetch("/api/manage/admin/connector/file/upload", {
+ method: "POST",
+ body: formData,
+ });
+ const responseJson = await response.json();
+ if (!response.ok) {
+ setPopup({
+ message: `Unable to upload files - ${responseJson.detail}`,
+ type: "error",
+ });
+ return;
+ }
+
+ const filePaths = responseJson.file_paths as string[];
+
+ const [connectorErrorMsg, connector] = await createConnector({
+ name: "FileConnector-" + Date.now(),
+ source: "file",
+ input_type: "load_state",
+ connector_specific_config: {
+ file_locations: filePaths,
+ },
+ refresh_freq: null,
+ prune_freq: null,
+ indexing_start: null,
+ is_public: isPublic,
+ groups: groups,
+ });
+ if (connectorErrorMsg || !connector) {
+ setPopup({
+ message: `Unable to create connector - ${connectorErrorMsg}`,
+ type: "error",
+ });
+ return;
+ }
+
+ // Since there is no "real" credential associated with a file connector
+ // we create a dummy one here so that we can associate the CC Pair with a
+ // user. This is needed since the user for a CC Pair is found via the credential
+ // associated with it.
+ const createCredentialResponse = await createCredential({
+ credential_json: {},
+ admin_public: true,
+ source: "file",
+ curator_public: isPublic,
+ groups: groups,
+ name,
+ });
+ if (!createCredentialResponse.ok) {
+ const errorMsg = await createCredentialResponse.text();
+ setPopup({
+ message: `Error creating credential for CC Pair - ${errorMsg}`,
+ type: "error",
+ });
+ return;
+ false;
+ }
+ const credentialId = (await createCredentialResponse.json()).id;
+
+ const credentialResponse = await linkCredential(
+ connector.id,
+ credentialId,
+ name,
+ isPublic,
+ groups
+ );
+ if (!credentialResponse.ok) {
+ const credentialResponseJson = await credentialResponse.json();
+ setPopup({
+ message: `Unable to link connector to credential - ${credentialResponseJson.detail}`,
+ type: "error",
+ });
+ return false;
+ }
+
+ const runConnectorErrorMsg = await runConnector(connector.id, [0]);
+ if (runConnectorErrorMsg) {
+ setPopup({
+ message: `Unable to run connector - ${runConnectorErrorMsg}`,
+ type: "error",
+ });
+ return false;
+ }
+
+ setSelectedFiles([]);
+ setPopup({
+ type: "success",
+ message: "Successfully uploaded files!",
+ });
+ return true;
+};
diff --git a/web/src/app/admin/connectors/[connector]/pages/utils/google_site.ts b/web/src/app/admin/connectors/[connector]/pages/utils/google_site.ts
new file mode 100644
index 00000000000..f1689e8fcdf
--- /dev/null
+++ b/web/src/app/admin/connectors/[connector]/pages/utils/google_site.ts
@@ -0,0 +1,87 @@
+import { PopupSpec } from "@/components/admin/connectors/Popup";
+import { createConnector, runConnector } from "@/lib/connector";
+import { linkCredential } from "@/lib/credential";
+import { GoogleSitesConfig } from "@/lib/connectors/connectors";
+import { AdvancedConfigFinal } from "../../AddConnectorPage";
+
+export const submitGoogleSite = async (
+ selectedFiles: File[],
+ base_url: any,
+ setPopup: (popup: PopupSpec) => void,
+ advancedConfig: AdvancedConfigFinal,
+ name?: string
+) => {
+ const uploadCreateAndTriggerConnector = async () => {
+ const formData = new FormData();
+
+ selectedFiles.forEach((file) => {
+ formData.append("files", file);
+ });
+
+ const response = await fetch("/api/manage/admin/connector/file/upload", {
+ method: "POST",
+ body: formData,
+ });
+ const responseJson = await response.json();
+ if (!response.ok) {
+ setPopup({
+ message: `Unable to upload files - ${responseJson.detail}`,
+ type: "error",
+ });
+ return false;
+ }
+
+ const filePaths = responseJson.file_paths as string[];
+ const [connectorErrorMsg, connector] =
+ await createConnector({
+ name: name ? name : `GoogleSitesConnector-${base_url}`,
+ source: "google_sites",
+ input_type: "load_state",
+ connector_specific_config: {
+ base_url: base_url,
+ zip_path: filePaths[0],
+ },
+ refresh_freq: advancedConfig.refreshFreq,
+ prune_freq: advancedConfig.pruneFreq,
+ indexing_start: advancedConfig.indexingStart,
+ });
+ if (connectorErrorMsg || !connector) {
+ setPopup({
+ message: `Unable to create connector - ${connectorErrorMsg}`,
+ type: "error",
+ });
+ return false;
+ }
+
+ const credentialResponse = await linkCredential(connector.id, 0, base_url);
+ if (!credentialResponse.ok) {
+ const credentialResponseJson = await credentialResponse.json();
+ setPopup({
+ message: `Unable to link connector to credential - ${credentialResponseJson.detail}`,
+ type: "error",
+ });
+ return false;
+ }
+
+ const runConnectorErrorMsg = await runConnector(connector.id, [0]);
+ if (runConnectorErrorMsg) {
+ setPopup({
+ message: `Unable to run connector - ${runConnectorErrorMsg}`,
+ type: "error",
+ });
+ return false;
+ }
+ setPopup({
+ type: "success",
+ message: "Successfully created Google Site connector!",
+ });
+ return true;
+ };
+
+ try {
+ const response = await uploadCreateAndTriggerConnector();
+ return response;
+ } catch (e) {
+ return false;
+ }
+};
diff --git a/web/src/app/admin/connectors/[connector]/pages/utils/hooks.ts b/web/src/app/admin/connectors/[connector]/pages/utils/hooks.ts
new file mode 100644
index 00000000000..d3a48e3a26b
--- /dev/null
+++ b/web/src/app/admin/connectors/[connector]/pages/utils/hooks.ts
@@ -0,0 +1,65 @@
+import { GmailConfig } from "@/lib/connectors/connectors";
+
+export const gmailConnectorNameBuilder = (values: GmailConfig) =>
+ "GmailConnector";
+
+import { usePublicCredentials } from "@/lib/hooks";
+import {
+ Credential,
+ GmailCredentialJson,
+ GmailServiceAccountCredentialJson,
+ GoogleDriveCredentialJson,
+ GoogleDriveServiceAccountCredentialJson,
+} from "@/lib/connectors/credentials";
+
+export const useGmailCredentials = () => {
+ const {
+ data: credentialsData,
+ isLoading: isCredentialsLoading,
+ error: credentialsError,
+ refreshCredentials,
+ } = usePublicCredentials();
+
+ const gmailPublicCredential: Credential | undefined =
+ credentialsData?.find(
+ (credential) =>
+ credential.credential_json?.gmail_tokens && credential.admin_public
+ );
+
+ const gmailServiceAccountCredential:
+ | Credential
+ | undefined = credentialsData?.find(
+ (credential) => credential.credential_json?.gmail_service_account_key
+ );
+
+ const liveGmailCredential =
+ gmailPublicCredential || gmailServiceAccountCredential;
+
+ return {
+ liveGmailCredential,
+ };
+};
+
+export const useGoogleDriveCredentials = () => {
+ const { data: credentialsData } = usePublicCredentials();
+
+ const googleDrivePublicCredential:
+ | Credential
+ | undefined = credentialsData?.find(
+ (credential) =>
+ credential.credential_json?.google_drive_tokens && credential.admin_public
+ );
+
+ const googleDriveServiceAccountCredential:
+ | Credential
+ | undefined = credentialsData?.find(
+ (credential) => credential.credential_json?.google_drive_service_account_key
+ );
+
+ const liveGDriveCredential =
+ googleDrivePublicCredential || googleDriveServiceAccountCredential;
+
+ return {
+ liveGDriveCredential,
+ };
+};
diff --git a/web/src/app/admin/connectors/axero/page.tsx b/web/src/app/admin/connectors/axero/page.tsx
deleted file mode 100644
index 6d4a5af8bcd..00000000000
--- a/web/src/app/admin/connectors/axero/page.tsx
+++ /dev/null
@@ -1,260 +0,0 @@
-"use client";
-
-import * as Yup from "yup";
-import { AxeroIcon, TrashIcon } from "@/components/icons/icons";
-import { errorHandlingFetcher } from "@/lib/fetcher";
-import { ErrorCallout } from "@/components/ErrorCallout";
-import useSWR, { useSWRConfig } from "swr";
-import { LoadingAnimation } from "@/components/Loading";
-import { HealthCheckBanner } from "@/components/health/healthcheck";
-import {
- AxeroConfig,
- AxeroCredentialJson,
- ConnectorIndexingStatus,
- Credential,
-} from "@/lib/types";
-import { adminDeleteCredential, linkCredential } from "@/lib/credential";
-import { CredentialForm } from "@/components/admin/connectors/CredentialForm";
-import {
- TextFormField,
- TextArrayFieldBuilder,
-} from "@/components/admin/connectors/Field";
-import { ConnectorsTable } from "@/components/admin/connectors/table/ConnectorsTable";
-import { ConnectorForm } from "@/components/admin/connectors/ConnectorForm";
-import { usePublicCredentials } from "@/lib/hooks";
-import { Button, Card, Divider, Text, Title } from "@tremor/react";
-import { AdminPageTitle } from "@/components/admin/Title";
-
-const MainSection = () => {
- const { mutate } = useSWRConfig();
- const {
- data: connectorIndexingStatuses,
- isLoading: isConnectorIndexingStatusesLoading,
- error: connectorIndexingStatusesError,
- } = useSWR[]>(
- "/api/manage/admin/connector/indexing-status",
- errorHandlingFetcher
- );
-
- const {
- data: credentialsData,
- isLoading: isCredentialsLoading,
- error: credentialsError,
- refreshCredentials,
- } = usePublicCredentials();
-
- if (
- (!connectorIndexingStatuses && isConnectorIndexingStatusesLoading) ||
- (!credentialsData && isCredentialsLoading)
- ) {
- return ;
- }
-
- if (connectorIndexingStatusesError || !connectorIndexingStatuses) {
- return (
-
- );
- }
-
- if (credentialsError || !credentialsData) {
- return (
-
- );
- }
-
- const axeroConnectorIndexingStatuses: ConnectorIndexingStatus<
- AxeroConfig,
- AxeroCredentialJson
- >[] = connectorIndexingStatuses.filter(
- (connectorIndexingStatus) =>
- connectorIndexingStatus.connector.source === "axero"
- );
- const axeroCredential: Credential | undefined =
- credentialsData.find(
- (credential) => credential.credential_json?.axero_api_token
- );
-
- return (
- <>
-
- Step 1: Provide Axero API Key
-
- {axeroCredential ? (
- <>
-
- Existing Axero API Key:
-
- {axeroCredential.credential_json.axero_api_token}
-
-
-
- >
- ) : (
- <>
-
- To use the Axero connector, first follow the guide{" "}
-
- here
- {" "}
- to generate an API Key.
-
-
-
- formBody={
- <>
-
-
- >
- }
- validationSchema={Yup.object().shape({
- base_url: Yup.string().required(
- "Please enter the base URL of your Axero instance"
- ),
- axero_api_token: Yup.string().required(
- "Please enter your Axero API Token"
- ),
- })}
- initialValues={{
- base_url: "",
- axero_api_token: "",
- }}
- onSubmit={(isSuccess) => {
- if (isSuccess) {
- refreshCredentials();
- }
- }}
- />
-
- >
- )}
-
-
- Step 2: Which spaces do you want to connect?
-
-
- {axeroConnectorIndexingStatuses.length > 0 && (
- <>
-
- We pull the latest Articles, Blogs, Wikis and{" "}
- Forums once per day.
-
-
-
- connectorIndexingStatuses={axeroConnectorIndexingStatuses}
- liveCredential={axeroCredential}
- getCredential={(credential) =>
- credential.credential_json.axero_api_token
- }
- specialColumns={[
- {
- header: "Space",
- key: "spaces",
- getValue: (ccPairStatus) => {
- const connectorConfig =
- ccPairStatus.connector.connector_specific_config;
- return connectorConfig.spaces &&
- connectorConfig.spaces.length > 0
- ? connectorConfig.spaces.join(", ")
- : "";
- },
- },
- ]}
- onUpdate={() =>
- mutate("/api/manage/admin/connector/indexing-status")
- }
- onCredentialLink={async (connectorId) => {
- if (axeroCredential) {
- await linkCredential(connectorId, axeroCredential.id);
- mutate("/api/manage/admin/connector/indexing-status");
- }
- }}
- />
-
-
- >
- )}
-
- {axeroCredential ? (
-
- Configure an Axero Connector
-
- nameBuilder={(values) =>
- values.spaces
- ? `AxeroConnector-${values.spaces.join("_")}`
- : `AxeroConnector`
- }
- source="axero"
- inputType="poll"
- formBodyBuilder={(values) => {
- return (
- <>
-
- {TextArrayFieldBuilder({
- name: "spaces",
- label: "Space IDs:",
- subtext: `
- Specify zero or more Spaces to index (by the Space IDs). If no Space IDs
- are specified, all Spaces will be indexed.`,
- })(values)}
- >
- );
- }}
- validationSchema={Yup.object().shape({
- spaces: Yup.array()
- .of(Yup.string().required("Space Ids cannot be empty"))
- .required(),
- })}
- initialValues={{
- spaces: [],
- }}
- refreshFreq={60 * 60 * 24} // 1 day
- credentialId={axeroCredential.id}
- />
-
- ) : (
-
- Please provide your Axero API Token in Step 1 first! Once done with
- that, you can then specify which spaces you want to connect.
-
- )}
- >
- );
-};
-
-export default function Page() {
- return (
-
-
-
-
-
-
} title="Axero" />
-
-
-
- );
-}
diff --git a/web/src/app/admin/connectors/bookstack/page.tsx b/web/src/app/admin/connectors/bookstack/page.tsx
deleted file mode 100644
index dbf8bd367b1..00000000000
--- a/web/src/app/admin/connectors/bookstack/page.tsx
+++ /dev/null
@@ -1,261 +0,0 @@
-"use client";
-
-import * as Yup from "yup";
-import { BookstackIcon, TrashIcon } from "@/components/icons/icons";
-import { TextFormField } from "@/components/admin/connectors/Field";
-import { HealthCheckBanner } from "@/components/health/healthcheck";
-import { CredentialForm } from "@/components/admin/connectors/CredentialForm";
-import {
- BookstackCredentialJson,
- BookstackConfig,
- ConnectorIndexingStatus,
- Credential,
-} from "@/lib/types";
-import useSWR, { useSWRConfig } from "swr";
-import { errorHandlingFetcher } from "@/lib/fetcher";
-import { ErrorCallout } from "@/components/ErrorCallout";
-import { LoadingAnimation } from "@/components/Loading";
-import { adminDeleteCredential, linkCredential } from "@/lib/credential";
-import { ConnectorForm } from "@/components/admin/connectors/ConnectorForm";
-import { ConnectorsTable } from "@/components/admin/connectors/table/ConnectorsTable";
-import { usePopup } from "@/components/admin/connectors/Popup";
-import { usePublicCredentials } from "@/lib/hooks";
-import { AdminPageTitle } from "@/components/admin/Title";
-import { Card, Text, Title } from "@tremor/react";
-
-const Main = () => {
- const { popup, setPopup } = usePopup();
-
- const { mutate } = useSWRConfig();
- const {
- data: connectorIndexingStatuses,
- isLoading: isConnectorIndexingStatusesLoading,
- error: connectorIndexingStatusesError,
- } = useSWR[]>(
- "/api/manage/admin/connector/indexing-status",
- errorHandlingFetcher
- );
- const {
- data: credentialsData,
- isLoading: isCredentialsLoading,
- error: credentialsError,
- refreshCredentials,
- } = usePublicCredentials();
-
- if (
- (!connectorIndexingStatuses && isConnectorIndexingStatusesLoading) ||
- (!credentialsData && isCredentialsLoading)
- ) {
- return ;
- }
-
- if (
- (!connectorIndexingStatuses && isConnectorIndexingStatusesLoading) ||
- (!credentialsData && isCredentialsLoading)
- ) {
- return ;
- }
-
- if (connectorIndexingStatusesError || !connectorIndexingStatuses) {
- return (
-
- );
- }
-
- if (credentialsError || !credentialsData) {
- return (
-
- );
- }
-
- const bookstackConnectorIndexingStatuses: ConnectorIndexingStatus<
- BookstackConfig,
- BookstackCredentialJson
- >[] = connectorIndexingStatuses.filter(
- (connectorIndexingStatus) =>
- connectorIndexingStatus.connector.source === "bookstack"
- );
- const bookstackCredential: Credential | undefined =
- credentialsData.find(
- (credential) => credential.credential_json?.bookstack_api_token_id
- );
-
- return (
- <>
- {popup}
-
- Step 1: Provide your API details
-
-
- {bookstackCredential ? (
- <>
-
- Existing API Token:
-
- {bookstackCredential.credential_json?.bookstack_api_token_id}
-
-
-
- >
- ) : (
- <>
-
- To get started you'll need API token details for your BookStack
- instance. You can get these by editing your (or another) user
- account in BookStack and creating a token via the 'API
- Tokens' section at the bottom. Your user account will require
- to be assigned a BookStack role which has the 'Access system
- API' system permission assigned.
-
-
-
- formBody={
- <>
-
-
-
- >
- }
- validationSchema={Yup.object().shape({
- bookstack_base_url: Yup.string().required(
- "Please enter the base URL for your BookStack instance"
- ),
- bookstack_api_token_id: Yup.string().required(
- "Please enter your BookStack API token ID"
- ),
- bookstack_api_token_secret: Yup.string().required(
- "Please enter your BookStack API token secret"
- ),
- })}
- initialValues={{
- bookstack_base_url: "",
- bookstack_api_token_id: "",
- bookstack_api_token_secret: "",
- }}
- onSubmit={(isSuccess) => {
- if (isSuccess) {
- refreshCredentials();
- mutate("/api/manage/admin/connector/indexing-status");
- }
- }}
- />
-
- >
- )}
-
- {bookstackConnectorIndexingStatuses.length > 0 && (
- <>
-
- BookStack indexing status
-
-
- The latest page, chapter, book and shelf changes are fetched every
- 10 minutes.
-
-
-
- connectorIndexingStatuses={bookstackConnectorIndexingStatuses}
- liveCredential={bookstackCredential}
- getCredential={(credential) => {
- return (
-
-
{credential.credential_json.bookstack_api_token_id}
-
- );
- }}
- onCredentialLink={async (connectorId) => {
- if (bookstackCredential) {
- await linkCredential(connectorId, bookstackCredential.id);
- mutate("/api/manage/admin/connector/indexing-status");
- }
- }}
- onUpdate={() =>
- mutate("/api/manage/admin/connector/indexing-status")
- }
- />
-
- >
- )}
-
- {bookstackCredential &&
- bookstackConnectorIndexingStatuses.length === 0 && (
- <>
-
- Create Connection
-
- Press connect below to start the connection to your BookStack
- instance.
-
-
- nameBuilder={(values) => `BookStackConnector`}
- ccPairNameBuilder={(values) => `BookStackConnector`}
- source="bookstack"
- inputType="poll"
- formBody={<>>}
- validationSchema={Yup.object().shape({})}
- initialValues={{}}
- refreshFreq={10 * 60} // 10 minutes
- credentialId={bookstackCredential.id}
- />
-
- >
- )}
-
- {!bookstackCredential && (
- <>
-
- Please provide your API details in Step 1 first! Once done with
- that, you'll be able to start the connection then see indexing
- status.
-
- >
- )}
- >
- );
-};
-
-export default function Page() {
- return (
-
-
-
-
-
-
} title="Bookstack" />
-
-
-
- );
-}
diff --git a/web/src/app/admin/connectors/clickup/page.tsx b/web/src/app/admin/connectors/clickup/page.tsx
deleted file mode 100644
index 6a868bac743..00000000000
--- a/web/src/app/admin/connectors/clickup/page.tsx
+++ /dev/null
@@ -1,343 +0,0 @@
-"use client";
-
-import * as Yup from "yup";
-import { TrashIcon, ClickupIcon } from "@/components/icons/icons";
-import { errorHandlingFetcher } from "@/lib/fetcher";
-import useSWR, { useSWRConfig } from "swr";
-import { LoadingAnimation } from "@/components/Loading";
-import { HealthCheckBanner } from "@/components/health/healthcheck";
-import {
- ClickupConfig,
- ClickupCredentialJson,
- ConnectorIndexingStatus,
- Credential,
-} from "@/lib/types";
-import { adminDeleteCredential, linkCredential } from "@/lib/credential";
-import { CredentialForm } from "@/components/admin/connectors/CredentialForm";
-import {
- BooleanFormField,
- SelectorFormField,
- TextFormField,
- TextArrayFieldBuilder,
-} from "@/components/admin/connectors/Field";
-import { ConnectorsTable } from "@/components/admin/connectors/table/ConnectorsTable";
-import { ConnectorForm } from "@/components/admin/connectors/ConnectorForm";
-import { usePopup } from "@/components/admin/connectors/Popup";
-import { usePublicCredentials } from "@/lib/hooks";
-import { Title, Text, Card, Divider } from "@tremor/react";
-import { AdminPageTitle } from "@/components/admin/Title";
-
-const MainSection = () => {
- const { mutate } = useSWRConfig();
- const { popup, setPopup } = usePopup();
- const {
- data: connectorIndexingStatuses,
- isLoading: isConnectorIndexingStatusesLoading,
- error: isConnectorIndexingStatusesError,
- } = useSWR[]>(
- "/api/manage/admin/connector/indexing-status",
- errorHandlingFetcher
- );
-
- const {
- data: credentialsData,
- isLoading: isCredentialsLoading,
- error: isCredentialsError,
- refreshCredentials,
- } = usePublicCredentials();
-
- if (
- (!connectorIndexingStatuses && isConnectorIndexingStatusesLoading) ||
- (!credentialsData && isCredentialsLoading)
- ) {
- return ;
- }
-
- if (isConnectorIndexingStatusesError || !connectorIndexingStatuses) {
- return Failed to load connectors
;
- }
-
- if (isCredentialsError || !credentialsData) {
- return Failed to load credentials
;
- }
-
- const clickupConnectorIndexingStatuses: ConnectorIndexingStatus<
- ClickupConfig,
- ClickupCredentialJson
- >[] = connectorIndexingStatuses.filter(
- (connectorIndexingStatus) =>
- connectorIndexingStatus.connector.source === "clickup"
- );
-
- const clickupCredential: Credential | undefined =
- credentialsData.find(
- (credential) => credential.credential_json?.clickup_api_token
- );
-
- return (
- <>
- {popup}
-
- Step 1: Provide Credentials
-
-
- {clickupCredential ? (
- <>
-
- Existing Clickup API Token:
-
- {clickupCredential.credential_json.clickup_api_token}
-
-
-
- >
- ) : (
- <>
-
- To use the Clickup connector, you must first provide the API token
- and Team ID corresponding to your Clickup setup. See setup guide{" "}
-
- here
- {" "}
- for more detail.
-
-
-
- formBody={
- <>
-
-
- >
- }
- validationSchema={Yup.object().shape({
- clickup_api_token: Yup.string().required(
- "Please enter your Clickup API token"
- ),
- clickup_team_id: Yup.string().required(
- "Please enter your Team ID"
- ),
- })}
- initialValues={{
- clickup_api_token: "",
- clickup_team_id: "",
- }}
- onSubmit={(isSuccess) => {
- if (isSuccess) {
- refreshCredentials();
- }
- }}
- />
-
- >
- )}
-
-
- Step 2: Do you want to search particular space(s), folder(s), list(s),
- or entire workspace?
-
-
- {clickupConnectorIndexingStatuses.length > 0 && (
- <>
-
- We index the latest articles from either the entire workspace, or
- specified space(s), folder(s), list(s) listed below regularly.
-
-
-
- connectorIndexingStatuses={clickupConnectorIndexingStatuses}
- liveCredential={clickupCredential}
- getCredential={(credential) =>
- credential.credential_json.clickup_api_token
- }
- specialColumns={[
- {
- header: "Connector Type",
- key: "connector_type",
- getValue: (ccPairStatus) =>
- ccPairStatus.connector.connector_specific_config
- .connector_type,
- },
- {
- header: "ID(s)",
- key: "connector_ids",
- getValue: (ccPairStatus) =>
- ccPairStatus.connector.connector_specific_config
- .connector_ids &&
- ccPairStatus.connector.connector_specific_config
- .connector_ids.length > 0
- ? ccPairStatus.connector.connector_specific_config.connector_ids.join(
- ", "
- )
- : "",
- },
- {
- header: "Retrieve Task Comments?",
- key: "retrieve_task_comments",
- getValue: (ccPairStatus) =>
- ccPairStatus.connector.connector_specific_config
- .retrieve_task_comments
- ? "Yes"
- : "No",
- },
- ]}
- onUpdate={() =>
- mutate("/api/manage/admin/connector/indexing-status")
- }
- onCredentialLink={async (connectorId) => {
- if (clickupCredential) {
- await linkCredential(connectorId, clickupCredential.id);
- mutate("/api/manage/admin/connector/indexing-status");
- }
- }}
- />
-
-
- >
- )}
-
- {clickupCredential ? (
-
- Connect to a New Workspace
-
- nameBuilder={(values) =>
- values.connector_ids
- ? `ClickupConnector-${
- values.connector_type
- }-${values.connector_ids.join("_")}`
- : `ClickupConnector-${values.connector_type}`
- }
- source="clickup"
- inputType="poll"
- formBody={
- <>
-
- >
- }
- formBodyBuilder={(values) => {
- return (
- <>
-
- {TextArrayFieldBuilder({
- name: "connector_ids",
- label: "ID(s):",
- subtext: "Specify 0 or more id(s) to index from.",
- })(values)}
-
- >
- );
- }}
- validationSchema={Yup.object().shape({
- connector_type: Yup.string()
- .oneOf(["workspace", "space", "folder", "list"])
- .required("Please select the connector_type to index"),
- connector_ids: Yup.array()
- .of(Yup.string().required("ID(s) must be strings"))
- .test(
- "idsRequired",
- "At least 1 ID is required if space, folder or list is selected",
- function (value) {
- if (this.parent.connector_type === "workspace") return true;
- else if (value !== undefined && value.length > 0)
- return true;
- setPopup({
- type: "error",
- message: `Add at least one ${this.parent.connector_type} ID`,
- });
- return false;
- }
- ),
- retrieve_task_comments: Yup.boolean().required(),
- })}
- initialValues={{
- connector_type: "workspace",
- connector_ids: [],
- retrieve_task_comments: true,
- }}
- refreshFreq={10 * 60} // 10 minutes
- credentialId={clickupCredential.id}
- />
-
- ) : (
-
- Please provide your Clickup API token and Team ID in Step 1 first!
- Once done with that, you can then specify whether you want to make the
- entire workspace, or specified space(s), folder(s), list(s)
- searchable.
-
- )}
- >
- );
-};
-
-export default function Page() {
- return (
-
-
-
-
-
-
} title="Clickup" />
-
-
-
- );
-}
diff --git a/web/src/app/admin/connectors/confluence/page.tsx b/web/src/app/admin/connectors/confluence/page.tsx
deleted file mode 100644
index 981d63e9513..00000000000
--- a/web/src/app/admin/connectors/confluence/page.tsx
+++ /dev/null
@@ -1,342 +0,0 @@
-"use client";
-
-import * as Yup from "yup";
-import { ConfluenceIcon, TrashIcon } from "@/components/icons/icons";
-import {
- BooleanFormField,
- TextFormField,
-} from "@/components/admin/connectors/Field";
-import { HealthCheckBanner } from "@/components/health/healthcheck";
-import { CredentialForm } from "@/components/admin/connectors/CredentialForm";
-import {
- ConfluenceCredentialJson,
- ConfluenceConfig,
- ConnectorIndexingStatus,
- Credential,
-} from "@/lib/types";
-import useSWR, { useSWRConfig } from "swr";
-import { errorHandlingFetcher } from "@/lib/fetcher";
-import { ErrorCallout } from "@/components/ErrorCallout";
-import { LoadingAnimation } from "@/components/Loading";
-import { adminDeleteCredential, linkCredential } from "@/lib/credential";
-import { ConnectorForm } from "@/components/admin/connectors/ConnectorForm";
-import { ConnectorsTable } from "@/components/admin/connectors/table/ConnectorsTable";
-import { usePopup } from "@/components/admin/connectors/Popup";
-import { usePublicCredentials } from "@/lib/hooks";
-import { Card, Divider, Text, Title } from "@tremor/react";
-import { AdminPageTitle } from "@/components/admin/Title";
-
-const extractSpaceFromCloudUrl = (wikiUrl: string): string => {
- const parsedUrl = new URL(wikiUrl);
- const space = parsedUrl.pathname.split("/")[3];
- return space;
-};
-
-const extractSpaceFromDataCenterUrl = (wikiUrl: string): string => {
- const DISPLAY = "/display/";
-
- const parsedUrl = new URL(wikiUrl);
- const spaceSpecificSection = parsedUrl.pathname
- .split(DISPLAY)
- .slice(1)
- .join(DISPLAY);
- const space = spaceSpecificSection.split("/")[0];
- return space;
-};
-
-// Copied from the `extract_confluence_keys_from_url` function
-const extractSpaceFromUrl = (wikiUrl: string): string | null => {
- try {
- if (
- wikiUrl.includes(".atlassian.net/wiki/spaces/") ||
- wikiUrl.includes(".jira.com/wiki/spaces/")
- ) {
- return extractSpaceFromCloudUrl(wikiUrl);
- }
- return extractSpaceFromDataCenterUrl(wikiUrl);
- } catch (e) {
- console.log("Failed to extract space from url", e);
- return null;
- }
-};
-
-const Main = () => {
- const { popup, setPopup } = usePopup();
-
- const { mutate } = useSWRConfig();
- const {
- data: connectorIndexingStatuses,
- isLoading: isConnectorIndexingStatusesLoading,
- error: connectorIndexingStatusesError,
- } = useSWR[]>(
- "/api/manage/admin/connector/indexing-status",
- errorHandlingFetcher
- );
- const {
- data: credentialsData,
- isLoading: isCredentialsLoading,
- error: credentialsError,
- refreshCredentials,
- } = usePublicCredentials();
-
- if (
- (!connectorIndexingStatuses && isConnectorIndexingStatusesLoading) ||
- (!credentialsData && isCredentialsLoading)
- ) {
- return ;
- }
-
- if (connectorIndexingStatusesError || !connectorIndexingStatuses) {
- return (
-
- );
- }
-
- if (credentialsError || !credentialsData) {
- return (
-
- );
- }
-
- const confluenceConnectorIndexingStatuses: ConnectorIndexingStatus<
- ConfluenceConfig,
- ConfluenceCredentialJson
- >[] = connectorIndexingStatuses.filter(
- (connectorIndexingStatus) =>
- connectorIndexingStatus.connector.source === "confluence"
- );
- const confluenceCredential: Credential | undefined =
- credentialsData.find(
- (credential) => credential.credential_json?.confluence_access_token
- );
-
- return (
- <>
- {popup}
-
- Step 1: Provide your access token
-
-
- {confluenceCredential ? (
- <>
-
- {/*
-
Existing Username:
-
- {confluenceCredential.credential_json?.confluence_username}
-
{" "}
-
*/}
-
Existing Access Token:
-
- {confluenceCredential.credential_json?.confluence_access_token}
-
-
-
- >
- ) : (
- <>
-
- To use the Confluence connector, first follow the guide{" "}
-
- here
- {" "}
- to generate an Access Token.
-
-
-
- formBody={
- <>
-
-
- >
- }
- validationSchema={Yup.object().shape({
- confluence_username: Yup.string().required(
- "Please enter your username on Confluence"
- ),
- confluence_access_token: Yup.string().required(
- "Please enter your Confluence access token"
- ),
- })}
- initialValues={{
- confluence_username: "",
- confluence_access_token: "",
- }}
- onSubmit={(isSuccess) => {
- if (isSuccess) {
- refreshCredentials();
- }
- }}
- />
-
- >
- )}
-
-
- Step 2: Which spaces do you want to make searchable?
-
- {confluenceCredential ? (
- <>
-
- Specify any link to a Confluence page below and click
- "Index" to Index. Based on the provided link, we will
- index either the entire page and its subpages OR the entire space.
- For example, entering{" "}
-
- https://danswer.atlassian.net/wiki/spaces/Engineering/overview
- {" "}
- and clicking the Index button will index the whole{" "}
- Engineering Confluence space, but entering
- https://danswer.atlassian.net/wiki/spaces/Engineering/pages/164331/example+page
- will index that page's children (and optionally, itself). Use
- the checkbox below to determine whether or not to index the parent
- page in addition to its children.
-
-
- {confluenceConnectorIndexingStatuses.length > 0 && (
- <>
-
- We pull the latest pages and comments from each space listed
- below every 10 minutes.
-
-
-
- connectorIndexingStatuses={
- confluenceConnectorIndexingStatuses
- }
- liveCredential={confluenceCredential}
- getCredential={(credential) => {
- return (
-
-
- {credential.credential_json.confluence_access_token}
-
-
- );
- }}
- onCredentialLink={async (connectorId) => {
- if (confluenceCredential) {
- await linkCredential(
- connectorId,
- confluenceCredential.id
- );
- mutate("/api/manage/admin/connector/indexing-status");
- }
- }}
- specialColumns={[
- {
- header: "Url",
- key: "url",
- getValue: (ccPairStatus) => (
-
- {
- ccPairStatus.connector.connector_specific_config
- .wiki_page_url
- }
-
- ),
- },
- ]}
- onUpdate={() =>
- mutate("/api/manage/admin/connector/indexing-status")
- }
- />
-
-
- >
- )}
-
- Add a New Space or Page
-
- nameBuilder={(values) =>
- `ConfluenceConnector-${values.wiki_page_url}`
- }
- ccPairNameBuilder={(values) =>
- extractSpaceFromUrl(values.wiki_page_url)
- }
- source="confluence"
- inputType="poll"
- formBody={
- <>
-
-
- >
- }
- validationSchema={Yup.object().shape({
- wiki_page_url: Yup.string().required(
- "Please enter any link to a Confluence space or Page e.g. https://danswer.atlassian.net/wiki/spaces/Engineering/overview"
- ),
- index_origin: Yup.boolean(),
- })}
- initialValues={{
- wiki_page_url: "",
- index_origin: true,
- }}
- refreshFreq={10 * 60} // 10 minutes
- credentialId={confluenceCredential.id}
- />
-
- >
- ) : (
-
- Please provide your access token in Step 1 first! Once done with that,
- you can then specify which Confluence spaces you want to make
- searchable.
-
- )}
- >
- );
-};
-
-export default function Page() {
- return (
-
-
-
-
-
-
} title="Confluence" />
-
-
-
- );
-}
diff --git a/web/src/app/admin/connectors/discourse/page.tsx b/web/src/app/admin/connectors/discourse/page.tsx
deleted file mode 100644
index 9ba840d3d92..00000000000
--- a/web/src/app/admin/connectors/discourse/page.tsx
+++ /dev/null
@@ -1,285 +0,0 @@
-"use client";
-
-import * as Yup from "yup";
-import { DiscourseIcon, TrashIcon } from "@/components/icons/icons";
-import {
- TextFormField,
- TextArrayFieldBuilder,
-} from "@/components/admin/connectors/Field";
-import { HealthCheckBanner } from "@/components/health/healthcheck";
-import { CredentialForm } from "@/components/admin/connectors/CredentialForm";
-import {
- Credential,
- ConnectorIndexingStatus,
- DiscourseConfig,
- DiscourseCredentialJson,
-} from "@/lib/types";
-import useSWR, { useSWRConfig } from "swr";
-import { errorHandlingFetcher } from "@/lib/fetcher";
-import { ErrorCallout } from "@/components/ErrorCallout";
-import { LoadingAnimation } from "@/components/Loading";
-import { adminDeleteCredential, linkCredential } from "@/lib/credential";
-import { ConnectorForm } from "@/components/admin/connectors/ConnectorForm";
-import { ConnectorsTable } from "@/components/admin/connectors/table/ConnectorsTable";
-import { usePopup } from "@/components/admin/connectors/Popup";
-import { usePublicCredentials } from "@/lib/hooks";
-import { Card, Divider, Text, Title } from "@tremor/react";
-import { AdminPageTitle } from "@/components/admin/Title";
-
-const Main = () => {
- const { popup, setPopup } = usePopup();
-
- const { mutate } = useSWRConfig();
- const {
- data: connectorIndexingStatuses,
- isLoading: isConnectorIndexingStatusesLoading,
- error: connectorIndexingStatusesError,
- } = useSWR[]>(
- "/api/manage/admin/connector/indexing-status",
- errorHandlingFetcher
- );
-
- const {
- data: credentialsData,
- isLoading: isCredentialsLoading,
- error: credentialsError,
- refreshCredentials,
- } = usePublicCredentials();
-
- if (
- (!connectorIndexingStatuses && isConnectorIndexingStatusesLoading) ||
- (!credentialsData && isCredentialsLoading)
- ) {
- return ;
- }
-
- if (connectorIndexingStatusesError || !connectorIndexingStatuses) {
- return (
-
- );
- }
-
- if (credentialsError || !credentialsData) {
- return (
-
- );
- }
-
- const discourseConnectorIndexingStatuses: ConnectorIndexingStatus<
- DiscourseConfig,
- DiscourseCredentialJson
- >[] = connectorIndexingStatuses.filter(
- (connectorIndexingStatus) =>
- connectorIndexingStatus.connector.source === "discourse"
- );
- const discourseCredential: Credential | undefined =
- credentialsData.find(
- (credential) => credential.credential_json?.discourse_api_username
- );
-
- return (
- <>
- {popup}
-
- This connector allows you to sync all your Discourse Topics into
- Danswer. More details on how to setup the Discourse connector can be
- found in{" "}
-
- this guide.
-
-
-
-
- Step 1: Provide your API Access info
-
-
- {discourseCredential ? (
- <>
-
-
Existing API Key:
-
- {discourseCredential.credential_json?.discourse_api_key}
-
-
-
- >
- ) : (
- <>
-
-
- formBody={
- <>
-
-
- >
- }
- validationSchema={Yup.object().shape({
- discourse_api_username: Yup.string().required(
- "Please enter the Username associated with the API key"
- ),
- discourse_api_key: Yup.string().required(
- "Please enter the API key"
- ),
- })}
- initialValues={{
- discourse_api_username: "",
- discourse_api_key: "",
- }}
- onSubmit={(isSuccess) => {
- if (isSuccess) {
- refreshCredentials();
- }
- }}
- />
-
- >
- )}
-
-
- Step 2: Which Categories do you want to make searchable?
-
-
- {discourseConnectorIndexingStatuses.length > 0 && (
- <>
-
- We pull Topics with new Posts every 10 minutes.
-
-
-
- connectorIndexingStatuses={discourseConnectorIndexingStatuses}
- liveCredential={discourseCredential}
- getCredential={(credential) =>
- credential.credential_json.discourse_api_username
- }
- specialColumns={[
- {
- header: "Categories",
- key: "categories",
- getValue: (ccPairStatus) =>
- ccPairStatus.connector.connector_specific_config
- .categories &&
- ccPairStatus.connector.connector_specific_config.categories
- .length > 0
- ? ccPairStatus.connector.connector_specific_config.categories.join(
- ", "
- )
- : "",
- },
- ]}
- includeName={true}
- onUpdate={() =>
- mutate("/api/manage/admin/connector/indexing-status")
- }
- onCredentialLink={async (connectorId) => {
- if (discourseCredential) {
- await linkCredential(connectorId, discourseCredential.id);
- mutate("/api/manage/admin/connector/indexing-status");
- }
- }}
- />
-
-
- >
- )}
-
- {discourseCredential ? (
- <>
-
- Create a new Discourse Connector
-
- nameBuilder={(values) =>
- values.categories
- ? `${values.base_url}-${values.categories.join("_")}`
- : `${values.base_url}-All`
- }
- source="discourse"
- inputType="poll"
- formBody={
- <>
-
- >
- }
- formBodyBuilder={TextArrayFieldBuilder({
- name: "categories",
- label: "Categories:",
- subtext:
- "Specify 0 or more Categories to index. If no Categories are specified, Topics from " +
- "all categories will be indexed.",
- })}
- validationSchema={Yup.object().shape({
- base_url: Yup.string().required(
- "Please the base URL of your Discourse site."
- ),
- categories: Yup.array().of(
- Yup.string().required("Category names must be strings")
- ),
- })}
- initialValues={{
- categories: [],
- base_url: "",
- }}
- refreshFreq={10 * 60} // 10 minutes
- credentialId={discourseCredential.id}
- />
-
- >
- ) : (
-
- Please provide your API Key Info in Step 1 first! Once done with that,
- you can then start indexing all your Discourse Topics.
-
- )}
- >
- );
-};
-
-export default function Page() {
- return (
-
-
-
-
-
-
} title="Discourse" />
-
-
-
- );
-}
diff --git a/web/src/app/admin/connectors/document360/page.tsx b/web/src/app/admin/connectors/document360/page.tsx
deleted file mode 100644
index 85653c639e3..00000000000
--- a/web/src/app/admin/connectors/document360/page.tsx
+++ /dev/null
@@ -1,277 +0,0 @@
-"use client";
-
-import * as Yup from "yup";
-import { TrashIcon, Document360Icon } from "@/components/icons/icons"; // Make sure you have a Document360 icon
-import { errorHandlingFetcher } from "@/lib/fetcher";
-import { ErrorCallout } from "@/components/ErrorCallout";
-import useSWR, { useSWRConfig } from "swr";
-import { LoadingAnimation } from "@/components/Loading";
-import { HealthCheckBanner } from "@/components/health/healthcheck";
-import {
- Document360Config,
- Document360CredentialJson,
- ConnectorIndexingStatus,
- Credential,
-} from "@/lib/types"; // Modify or create these types as required
-import { adminDeleteCredential, linkCredential } from "@/lib/credential";
-import { CredentialForm } from "@/components/admin/connectors/CredentialForm";
-import {
- TextFormField,
- TextArrayFieldBuilder,
-} from "@/components/admin/connectors/Field";
-import { ConnectorsTable } from "@/components/admin/connectors/table/ConnectorsTable";
-import { ConnectorForm } from "@/components/admin/connectors/ConnectorForm";
-import { usePublicCredentials } from "@/lib/hooks";
-import { Title, Text, Card, Divider } from "@tremor/react";
-import { AdminPageTitle } from "@/components/admin/Title";
-
-const MainSection = () => {
- const { mutate } = useSWRConfig();
- const {
- data: connectorIndexingStatuses,
- isLoading: isConnectorIndexingStatusesLoading,
- error: connectorIndexingStatusesError,
- } = useSWR[]>(
- "/api/manage/admin/connector/indexing-status",
- errorHandlingFetcher
- );
-
- const {
- data: credentialsData,
- isLoading: isCredentialsLoading,
- error: credentialsError,
- refreshCredentials,
- } = usePublicCredentials();
-
- if (
- (!connectorIndexingStatuses && isConnectorIndexingStatusesLoading) ||
- (!credentialsData && isCredentialsLoading)
- ) {
- return ;
- }
-
- if (connectorIndexingStatusesError || !connectorIndexingStatuses) {
- return (
-
- );
- }
-
- if (credentialsError || !credentialsData) {
- return (
-
- );
- }
-
- const document360ConnectorIndexingStatuses: ConnectorIndexingStatus<
- Document360Config,
- Document360CredentialJson
- >[] = connectorIndexingStatuses.filter(
- (connectorIndexingStatus) =>
- connectorIndexingStatus.connector.source === "document360"
- );
-
- const document360Credential:
- | Credential
- | undefined = credentialsData.find(
- (credential) => credential.credential_json?.document360_api_token
- );
-
- return (
- <>
-
- Step 1: Provide Credentials
-
- {document360Credential ? (
- <>
-
- Existing Document360 API Token:
-
- {document360Credential.credential_json.document360_api_token}
-
-
-
- >
- ) : (
- <>
-
- To use the Document360 connector, you must first provide the API
- token and portal ID corresponding to your Document360 setup. See
- setup guide{" "}
-
- here
- {" "}
- for more detail.
-
-
-
- formBody={
- <>
-
-
- >
- }
- validationSchema={Yup.object().shape({
- document360_api_token: Yup.string().required(
- "Please enter your Document360 API token"
- ),
- portal_id: Yup.string().required("Please enter your portal ID"),
- })}
- initialValues={{
- document360_api_token: "",
- portal_id: "",
- }}
- onSubmit={(isSuccess) => {
- if (isSuccess) {
- refreshCredentials();
- }
- }}
- />
-
- >
- )}
-
-
- Step 2: Which categories do you want to make searchable?
-
-
- {document360ConnectorIndexingStatuses.length > 0 && (
- <>
-
- We index the latest articles from each workspace listed below
- regularly.
-
-
-
- connectorIndexingStatuses={document360ConnectorIndexingStatuses}
- liveCredential={document360Credential}
- getCredential={(credential) =>
- credential.credential_json.document360_api_token
- }
- specialColumns={[
- {
- header: "Workspace",
- key: "workspace",
- getValue: (ccPairStatus) =>
- ccPairStatus.connector.connector_specific_config.workspace,
- },
- {
- header: "Categories",
- key: "categories",
- getValue: (ccPairStatus) =>
- ccPairStatus.connector.connector_specific_config
- .categories &&
- ccPairStatus.connector.connector_specific_config.categories
- .length > 0
- ? ccPairStatus.connector.connector_specific_config.categories.join(
- ", "
- )
- : "",
- },
- ]}
- onUpdate={() =>
- mutate("/api/manage/admin/connector/indexing-status")
- }
- onCredentialLink={async (connectorId) => {
- if (document360Credential) {
- await linkCredential(connectorId, document360Credential.id);
- mutate("/api/manage/admin/connector/indexing-status");
- }
- }}
- />
-
-
- >
- )}
-
- {document360Credential ? (
-
- Connect to a New Workspace
-
- nameBuilder={(values) =>
- values.categories
- ? `Document360Connector-${
- values.workspace
- }-${values.categories.join("_")}`
- : `Document360Connector-${values.workspace}`
- }
- source="document360"
- inputType="poll"
- formBody={
- <>
-
- >
- }
- formBodyBuilder={TextArrayFieldBuilder({
- name: "categories",
- label: "Categories:",
- subtext:
- "Specify 0 or more categories to index. For instance, specifying the category " +
- "'Help' will cause us to only index all content " +
- "within the 'Help' category. " +
- "If no categories are specified, all categories in your workspace will be indexed.",
- })}
- validationSchema={Yup.object().shape({
- workspace: Yup.string().required(
- "Please enter the workspace to index"
- ),
- categories: Yup.array()
- .of(Yup.string().required("Category names must be strings"))
- .required(),
- })}
- initialValues={{
- workspace: "",
- categories: [],
- }}
- refreshFreq={10 * 60} // 10 minutes
- credentialId={document360Credential.id}
- />
-
- ) : (
-
- Please provide your Document360 API token and portal ID in Step 1
- first! Once done with that, you can then specify which Document360
- categories you want to make searchable.
-
- )}
- >
- );
-};
-
-export default function Page() {
- return (
-
-
-
-
-
-
}
- title="Document360"
- />
-
-
-
- );
-}
diff --git a/web/src/app/admin/connectors/dropbox/page.tsx b/web/src/app/admin/connectors/dropbox/page.tsx
deleted file mode 100644
index a897a34072f..00000000000
--- a/web/src/app/admin/connectors/dropbox/page.tsx
+++ /dev/null
@@ -1,222 +0,0 @@
-"use client";
-
-import { AdminPageTitle } from "@/components/admin/Title";
-import { HealthCheckBanner } from "@/components/health/healthcheck";
-import { DropboxIcon } from "@/components/icons/icons";
-import { LoadingAnimation } from "@/components/Loading";
-import { ConnectorForm } from "@/components/admin/connectors/ConnectorForm";
-import { CredentialForm } from "@/components/admin/connectors/CredentialForm";
-import { TextFormField } from "@/components/admin/connectors/Field";
-import { usePopup } from "@/components/admin/connectors/Popup";
-import { ConnectorsTable } from "@/components/admin/connectors/table/ConnectorsTable";
-import { TrashIcon } from "@/components/icons/icons";
-import { adminDeleteCredential, linkCredential } from "@/lib/credential";
-import { errorHandlingFetcher } from "@/lib/fetcher";
-import { ErrorCallout } from "@/components/ErrorCallout";
-import { usePublicCredentials } from "@/lib/hooks";
-import {
- ConnectorIndexingStatus,
- Credential,
- DropboxConfig,
- DropboxCredentialJson,
-} from "@/lib/types";
-import { Card, Text, Title } from "@tremor/react";
-import useSWR, { useSWRConfig } from "swr";
-import * as Yup from "yup";
-
-const Main = () => {
- const { popup, setPopup } = usePopup();
-
- const { mutate } = useSWRConfig();
- const {
- data: connectorIndexingStatuses,
- isLoading: isConnectorIndexingStatusesLoading,
- error: connectorIndexingStatusesError,
- } = useSWR[]>(
- "/api/manage/admin/connector/indexing-status",
- errorHandlingFetcher
- );
- const {
- data: credentialsData,
- isLoading: isCredentialsLoading,
- error: credentialsError,
- refreshCredentials,
- } = usePublicCredentials();
-
- if (
- (!connectorIndexingStatuses && isConnectorIndexingStatusesLoading) ||
- (!credentialsData && isCredentialsLoading)
- ) {
- return ;
- }
-
- if (connectorIndexingStatusesError || !connectorIndexingStatuses) {
- return (
-
- );
- }
-
- if (credentialsError || !credentialsData) {
- return (
-
- );
- }
-
- const dropboxConnectorIndexingStatuses: ConnectorIndexingStatus<
- DropboxConfig,
- DropboxCredentialJson
- >[] = connectorIndexingStatuses.filter(
- (connectorIndexingStatus) =>
- connectorIndexingStatus.connector.source === "dropbox"
- );
- const dropboxCredential: Credential | undefined =
- credentialsData.find(
- (credential) => credential.credential_json?.dropbox_access_token
- );
-
- return (
- <>
- {popup}
-
- Provide your API details
-
-
- {dropboxCredential ? (
- <>
-
-
Existing API Token:
-
- {dropboxCredential.credential_json?.dropbox_access_token}
-
-
-
- >
- ) : (
- <>
-
- See the Dropbox connector{" "}
-
- setup guide
- {" "}
- on the Danswer docs to obtain a Dropbox token.
-
-
-
- formBody={
- <>
-
- >
- }
- validationSchema={Yup.object().shape({
- dropbox_access_token: Yup.string().required(
- "Please enter your Dropbox API token"
- ),
- })}
- initialValues={{
- dropbox_access_token: "",
- }}
- onSubmit={(isSuccess) => {
- if (isSuccess) {
- refreshCredentials();
- mutate("/api/manage/admin/connector/indexing-status");
- }
- }}
- />
-
- >
- )}
-
- {dropboxConnectorIndexingStatuses.length > 0 && (
- <>
-
- Dropbox indexing status
-
-
- Due to Dropbox access key design, the Dropbox connector will only
- re-index files after a new access key is provided and the indexing
- process is re-run manually. Check the docs for more information.
-
-
-
- connectorIndexingStatuses={dropboxConnectorIndexingStatuses}
- liveCredential={dropboxCredential}
- onCredentialLink={async (connectorId) => {
- if (dropboxCredential) {
- await linkCredential(connectorId, dropboxCredential.id);
- mutate("/api/manage/admin/connector/indexing-status");
- }
- }}
- onUpdate={() =>
- mutate("/api/manage/admin/connector/indexing-status")
- }
- />
-
- >
- )}
-
- {dropboxCredential && dropboxConnectorIndexingStatuses.length === 0 && (
- <>
-
- Create Connection
-
- Press connect below to start the connection to your Dropbox
- instance.
-
-
- nameBuilder={(values) => `Dropbox`}
- ccPairNameBuilder={(values) => `Dropbox`}
- source="dropbox"
- inputType="poll"
- formBody={<>>}
- validationSchema={Yup.object().shape({})}
- initialValues={{}}
- // refreshFreq={10 * 60} // disabled re-indexing
- credentialId={dropboxCredential.id}
- />
-
- >
- )}
- >
- );
-};
-
-export default function Page() {
- return (
-
-
-
-
-
} title="Dropbox" />
-
-
- );
-}
diff --git a/web/src/app/admin/connectors/file/page.tsx b/web/src/app/admin/connectors/file/page.tsx
deleted file mode 100644
index b2857f66c61..00000000000
--- a/web/src/app/admin/connectors/file/page.tsx
+++ /dev/null
@@ -1,299 +0,0 @@
-"use client";
-
-import useSWR, { useSWRConfig } from "swr";
-import * as Yup from "yup";
-
-import { FileIcon } from "@/components/icons/icons";
-import { errorHandlingFetcher } from "@/lib/fetcher";
-import { ErrorCallout } from "@/components/ErrorCallout";
-import { HealthCheckBanner } from "@/components/health/healthcheck";
-import { ConnectorIndexingStatus, FileConfig } from "@/lib/types";
-import { createCredential, linkCredential } from "@/lib/credential";
-import { useState } from "react";
-import { usePopup } from "@/components/admin/connectors/Popup";
-import { createConnector, runConnector } from "@/lib/connector";
-import { Spinner } from "@/components/Spinner";
-import { SingleUseConnectorsTable } from "@/components/admin/connectors/table/SingleUseConnectorsTable";
-import { LoadingAnimation } from "@/components/Loading";
-import { Form, Formik } from "formik";
-import {
- BooleanFormField,
- TextFormField,
-} from "@/components/admin/connectors/Field";
-import { FileUpload } from "@/components/admin/connectors/FileUpload";
-import { getNameFromPath } from "@/lib/fileUtils";
-import { Button, Card, Divider, Text } from "@tremor/react";
-import { AdminPageTitle } from "@/components/admin/Title";
-import IsPublicField from "@/components/admin/connectors/IsPublicField";
-import { usePaidEnterpriseFeaturesEnabled } from "@/components/settings/usePaidEnterpriseFeaturesEnabled";
-
-const Main = () => {
- const [selectedFiles, setSelectedFiles] = useState([]);
- const [filesAreUploading, setFilesAreUploading] = useState(false);
- const { popup, setPopup } = usePopup();
-
- const isPaidEnterpriseFeaturesEnabled = usePaidEnterpriseFeaturesEnabled();
-
- const { mutate } = useSWRConfig();
-
- const {
- data: connectorIndexingStatuses,
- isLoading: isConnectorIndexingStatusesLoading,
- } = useSWR[]>(
- "/api/manage/admin/connector/indexing-status",
- errorHandlingFetcher
- );
-
- if (!connectorIndexingStatuses && isConnectorIndexingStatusesLoading) {
- return ;
- }
-
- const fileIndexingStatuses: ConnectorIndexingStatus[] =
- connectorIndexingStatuses?.filter(
- (connectorIndexingStatus) =>
- connectorIndexingStatus.connector.source === "file"
- ) ?? [];
-
- return (
-
- {popup}
- {filesAreUploading &&
}
-
- Specify files below, click the Upload button, and the contents of
- these files will be searchable via Danswer! Currently supported file
- types include .txt, .pdf, .docx, .pptx,{" "}
- .xlsx, .csv, .md, .mdx, .conf,{" "}
- .log, .json, .tsv, .xml, .yml,{" "}
- .yaml, .eml, .epub, and finally .zip files
- (containing supported file types).
-
-
- NOTE: if the original document is accessible via a link, you can
- add a line at the very beginning of the file that looks like:
-
-
- #DANSWER_METADATA={"{"}"link": "{""}"{"}"}
-
-
{" "}
- where {""} is the link to the file. This will enable
- Danswer to link users to the original document when they click on the
- search result. More details on this can be found in the{" "}
-
- documentation.
-
-
-
-
-
- {
- const uploadCreateAndTriggerConnector = async () => {
- const formData = new FormData();
-
- selectedFiles.forEach((file) => {
- formData.append("files", file);
- });
-
- const response = await fetch(
- "/api/manage/admin/connector/file/upload",
- { method: "POST", body: formData }
- );
- const responseJson = await response.json();
- if (!response.ok) {
- setPopup({
- message: `Unable to upload files - ${responseJson.detail}`,
- type: "error",
- });
- return;
- }
-
- const filePaths = responseJson.file_paths as string[];
- const [connectorErrorMsg, connector] =
- await createConnector({
- name: "FileConnector-" + Date.now(),
- source: "file",
- input_type: "load_state",
- connector_specific_config: {
- file_locations: filePaths,
- },
- refresh_freq: null,
- prune_freq: 0,
- disabled: false,
- });
- if (connectorErrorMsg || !connector) {
- setPopup({
- message: `Unable to create connector - ${connectorErrorMsg}`,
- type: "error",
- });
- return;
- }
-
- // Since there is no "real" credential associated with a file connector
- // we create a dummy one here so that we can associate the CC Pair with a
- // user. This is needed since the user for a CC Pair is found via the credential
- // associated with it.
- const createCredentialResponse = await createCredential({
- credential_json: {},
- admin_public: true,
- });
- if (!createCredentialResponse.ok) {
- const errorMsg = await createCredentialResponse.text();
- setPopup({
- message: `Error creating credential for CC Pair - ${errorMsg}`,
- type: "error",
- });
- formikHelpers.setSubmitting(false);
- return;
- }
- const credentialId = (await createCredentialResponse.json())
- .id;
-
- const credentialResponse = await linkCredential(
- connector.id,
- credentialId,
- values.name,
- values.is_public
- );
- if (!credentialResponse.ok) {
- const credentialResponseJson =
- await credentialResponse.json();
- setPopup({
- message: `Unable to link connector to credential - ${credentialResponseJson.detail}`,
- type: "error",
- });
- return;
- }
-
- const runConnectorErrorMsg = await runConnector(
- connector.id,
- [0]
- );
- if (runConnectorErrorMsg) {
- setPopup({
- message: `Unable to run connector - ${runConnectorErrorMsg}`,
- type: "error",
- });
- return;
- }
-
- mutate("/api/manage/admin/connector/indexing-status");
- setSelectedFiles([]);
- formikHelpers.resetForm();
- setPopup({
- type: "success",
- message: "Successfully uploaded files!",
- });
- };
-
- setFilesAreUploading(true);
- try {
- await uploadCreateAndTriggerConnector();
- } catch (e) {
- console.log("Failed to index filels: ", e);
- }
- setFilesAreUploading(false);
- }}
- >
- {({ values, isSubmitting }) => (
-
- )}
-
-
-
-
-
- {fileIndexingStatuses.length > 0 && (
-
-
-
Indexed Files
-
- connectorIndexingStatuses={fileIndexingStatuses}
- specialColumns={[
- {
- header: "File Names",
- key: "file_names",
- getValue: (ccPairStatus) =>
- ccPairStatus.connector.connector_specific_config.file_locations
- .map(getNameFromPath)
- .join(", "),
- },
- ]}
- onUpdate={() =>
- mutate("/api/manage/admin/connector/indexing-status")
- }
- />
-
- )}
-
- );
-};
-
-export default function File() {
- return (
-
-
-
-
-
-
} title="File" />
-
-
-
- );
-}
diff --git a/web/src/app/admin/connectors/github/page.tsx b/web/src/app/admin/connectors/github/page.tsx
deleted file mode 100644
index 6d7e915d335..00000000000
--- a/web/src/app/admin/connectors/github/page.tsx
+++ /dev/null
@@ -1,280 +0,0 @@
-"use client";
-
-import * as Yup from "yup";
-import { GithubIcon, TrashIcon } from "@/components/icons/icons";
-import {
- BooleanFormField,
- TextFormField,
-} from "@/components/admin/connectors/Field";
-import { HealthCheckBanner } from "@/components/health/healthcheck";
-import useSWR, { useSWRConfig } from "swr";
-import { errorHandlingFetcher } from "@/lib/fetcher";
-import { ErrorCallout } from "@/components/ErrorCallout";
-import {
- GithubConfig,
- GithubCredentialJson,
- Credential,
- ConnectorIndexingStatus,
-} from "@/lib/types";
-import { ConnectorForm } from "@/components/admin/connectors/ConnectorForm";
-import { LoadingAnimation } from "@/components/Loading";
-import { CredentialForm } from "@/components/admin/connectors/CredentialForm";
-import { adminDeleteCredential, linkCredential } from "@/lib/credential";
-import { ConnectorsTable } from "@/components/admin/connectors/table/ConnectorsTable";
-import { usePublicCredentials } from "@/lib/hooks";
-import { Card, Divider, Text, Title } from "@tremor/react";
-import { AdminPageTitle } from "@/components/admin/Title";
-
-const Main = () => {
- const { mutate } = useSWRConfig();
- const {
- data: connectorIndexingStatuses,
- isLoading: isConnectorIndexingStatusesLoading,
- error: connectorIndexingStatusesError,
- } = useSWR[]>(
- "/api/manage/admin/connector/indexing-status",
- errorHandlingFetcher
- );
-
- const {
- data: credentialsData,
- isLoading: isCredentialsLoading,
- error: credentialsError,
- refreshCredentials,
- } = usePublicCredentials();
-
- if (
- (!connectorIndexingStatuses && isConnectorIndexingStatusesLoading) ||
- (!credentialsData && isCredentialsLoading)
- ) {
- return ;
- }
-
- if (connectorIndexingStatusesError || !connectorIndexingStatuses) {
- return (
-
- );
- }
-
- if (credentialsError || !credentialsData) {
- return (
-
- );
- }
-
- const githubConnectorIndexingStatuses: ConnectorIndexingStatus<
- GithubConfig,
- GithubCredentialJson
- >[] = connectorIndexingStatuses.filter(
- (connectorIndexingStatus) =>
- connectorIndexingStatus.connector.source === "github"
- );
- const githubCredential: Credential | undefined =
- credentialsData.find(
- (credential) => credential.credential_json?.github_access_token
- );
-
- return (
- <>
-
- Step 1: Provide your access token
-
- {githubCredential ? (
- <>
- {" "}
-
-
Existing Access Token:
-
- {githubCredential.credential_json.github_access_token}
-
{" "}
-
-
- >
- ) : (
- <>
-
- If you don't have an access token, read the guide{" "}
-
- here
- {" "}
- on how to get one from Github.
-
-
-
- formBody={
- <>
-
- >
- }
- validationSchema={Yup.object().shape({
- github_access_token: Yup.string().required(
- "Please enter the access token for Github"
- ),
- })}
- initialValues={{
- github_access_token: "",
- }}
- onSubmit={(isSuccess) => {
- if (isSuccess) {
- refreshCredentials();
- }
- }}
- />
-
- >
- )}
-
-
- Step 2: Which repositories do you want to make searchable?
-
-
- {githubConnectorIndexingStatuses.length > 0 && (
- <>
-
- We pull the latest Pull Requests and/or Issues from each repository
- listed below every 10 minutes.
-
-
-
- connectorIndexingStatuses={githubConnectorIndexingStatuses}
- liveCredential={githubCredential}
- getCredential={(credential) =>
- credential.credential_json.github_access_token
- }
- onCredentialLink={async (connectorId) => {
- if (githubCredential) {
- await linkCredential(connectorId, githubCredential.id);
- mutate("/api/manage/admin/connector/indexing-status");
- }
- }}
- specialColumns={[
- {
- header: "Repository",
- key: "repository",
- getValue: (ccPairStatus) => {
- const connectorConfig =
- ccPairStatus.connector.connector_specific_config;
- return `${connectorConfig.repo_owner}/${connectorConfig.repo_name}`;
- },
- },
- ]}
- onUpdate={() =>
- mutate("/api/manage/admin/connector/indexing-status")
- }
- />
-
-
- >
- )}
-
- {githubCredential ? (
-
- Connect to a New Repository
-
-
- The Github connector can index Pull Requests and Issues.
-
-
-
- nameBuilder={(values) =>
- `GithubConnector-${values.repo_owner}/${values.repo_name}`
- }
- ccPairNameBuilder={(values) =>
- `${values.repo_owner}/${values.repo_name}`
- }
- source="github"
- inputType="poll"
- formBody={
- <>
-
-
-
-
- >
- }
- validationSchema={Yup.object().shape({
- repo_owner: Yup.string().required(
- "Please enter the owner of the repository to index e.g. danswer-ai"
- ),
- repo_name: Yup.string().required(
- "Please enter the name of the repository to index e.g. danswer "
- ),
- include_prs: Yup.boolean().required(),
- include_issues: Yup.boolean().required(),
- })}
- validate={(values) => {
- if (values.include_prs || values.include_issues) {
- return {} as Record;
- }
- return {
- include_issues:
- "Please select at least one of Pull Requests or Issues",
- };
- }}
- initialValues={{
- repo_owner: "",
- repo_name: "",
- include_prs: true,
- include_issues: true,
- }}
- refreshFreq={10 * 60} // 10 minutes
- credentialId={githubCredential.id}
- />
-
- ) : (
-
- Please provide your access token in Step 1 first! Once done with that,
- you can then specify which Github repositories you want to make
- searchable.
-
- )}
- >
- );
-};
-
-export default function Page() {
- return (
-
-
-
-
-
-
}
- title="Github PRs + Issues"
- />
-
-
-
- );
-}
diff --git a/web/src/app/admin/connectors/gitlab/page.tsx b/web/src/app/admin/connectors/gitlab/page.tsx
deleted file mode 100644
index 595cd575f76..00000000000
--- a/web/src/app/admin/connectors/gitlab/page.tsx
+++ /dev/null
@@ -1,265 +0,0 @@
-"use client";
-
-import * as Yup from "yup";
-import { GitlabIcon, TrashIcon } from "@/components/icons/icons";
-import { TextFormField } from "@/components/admin/connectors/Field";
-import { HealthCheckBanner } from "@/components/health/healthcheck";
-import useSWR, { useSWRConfig } from "swr";
-import { errorHandlingFetcher } from "@/lib/fetcher";
-import { ErrorCallout } from "@/components/ErrorCallout";
-import {
- GitlabConfig,
- GitlabCredentialJson,
- Credential,
- ConnectorIndexingStatus,
-} from "@/lib/types";
-import { ConnectorForm } from "@/components/admin/connectors/ConnectorForm";
-import { LoadingAnimation } from "@/components/Loading";
-import { CredentialForm } from "@/components/admin/connectors/CredentialForm";
-import { adminDeleteCredential, linkCredential } from "@/lib/credential";
-import { ConnectorsTable } from "@/components/admin/connectors/table/ConnectorsTable";
-import { usePublicCredentials } from "@/lib/hooks";
-import { Card, Divider, Text, Title } from "@tremor/react";
-import { AdminPageTitle } from "@/components/admin/Title";
-
-const Main = () => {
- const { mutate } = useSWRConfig();
- const {
- data: connectorIndexingStatuses,
- isLoading: isConnectorIndexingStatusesLoading,
- error: connectorIndexingStatusesError,
- } = useSWR[]>(
- "/api/manage/admin/connector/indexing-status",
- errorHandlingFetcher
- );
-
- const {
- data: credentialsData,
- isLoading: isCredentialsLoading,
- error: credentialsError,
- refreshCredentials,
- } = usePublicCredentials();
-
- if (
- (!connectorIndexingStatuses && isConnectorIndexingStatusesLoading) ||
- (!credentialsData && isCredentialsLoading)
- ) {
- return ;
- }
-
- if (connectorIndexingStatusesError || !connectorIndexingStatuses) {
- return (
-
- );
- }
-
- if (credentialsError || !credentialsData) {
- return (
-
- );
- }
-
- const gitlabConnectorIndexingStatuses: ConnectorIndexingStatus<
- GitlabConfig,
- GitlabCredentialJson
- >[] = connectorIndexingStatuses.filter(
- (connectorIndexingStatus) =>
- connectorIndexingStatus.connector.source === "gitlab"
- );
- const gitlabCredential: Credential | undefined =
- credentialsData.find(
- (credential) => credential.credential_json?.gitlab_access_token
- );
-
- return (
- <>
-
- Step 1: Provide your access token
-
- {gitlabCredential ? (
- <>
- {" "}
-
-
Existing Access Token:
-
- {gitlabCredential.credential_json.gitlab_access_token}
-
{" "}
-
-
- >
- ) : (
- <>
-
- If you don't have an access token, read the guide{" "}
-
- here
- {" "}
- on how to get one from Gitlab.
-
-
-
- formBody={
- <>
-
- If you are using GitLab Cloud, keep the default value below
-
-
-
-
- >
- }
- validationSchema={Yup.object().shape({
- gitlab_url: Yup.string().default("https://gitlab.com"),
- gitlab_access_token: Yup.string().required(
- "Please enter the access token for Gitlab"
- ),
- })}
- initialValues={{
- gitlab_access_token: "",
- gitlab_url: "https://gitlab.com",
- }}
- onSubmit={(isSuccess) => {
- if (isSuccess) {
- refreshCredentials();
- }
- }}
- />
-
- >
- )}
-
-
- Step 2: Which repositories do you want to make searchable?
-
-
- {gitlabConnectorIndexingStatuses.length > 0 && (
- <>
-
- We pull the latest Pull Requests from each project listed below
- every 10 minutes.
-
-
-
- connectorIndexingStatuses={gitlabConnectorIndexingStatuses}
- liveCredential={gitlabCredential}
- getCredential={(credential) =>
- credential.credential_json.gitlab_access_token
- }
- onCredentialLink={async (connectorId) => {
- if (gitlabCredential) {
- await linkCredential(connectorId, gitlabCredential.id);
- mutate("/api/manage/admin/connector/indexing-status");
- }
- }}
- specialColumns={[
- {
- header: "Project",
- key: "project",
- getValue: (ccPairStatus) => {
- const connectorConfig =
- ccPairStatus.connector.connector_specific_config;
- return `${connectorConfig.project_owner}/${connectorConfig.project_name}`;
- },
- },
- ]}
- onUpdate={() =>
- mutate("/api/manage/admin/connector/indexing-status")
- }
- />
-
-
- >
- )}
-
- {gitlabCredential ? (
-
- Connect to a New Project
-
- nameBuilder={(values) =>
- `GitlabConnector-${values.project_owner}/${values.project_name}`
- }
- ccPairNameBuilder={(values) =>
- `${values.project_owner}/${values.project_name}`
- }
- source="gitlab"
- inputType="poll"
- formBody={
- <>
-
-
- >
- }
- validationSchema={Yup.object().shape({
- project_owner: Yup.string().required(
- "Please enter the owner of the project to index e.g. danswer-ai"
- ),
- project_name: Yup.string().required(
- "Please enter the name of the project to index e.g. danswer "
- ),
- include_mrs: Yup.boolean().required(),
- include_issues: Yup.boolean().required(),
- })}
- initialValues={{
- project_owner: "",
- project_name: "",
- include_mrs: true,
- include_issues: true,
- }}
- refreshFreq={10 * 60} // 10 minutes
- credentialId={gitlabCredential.id}
- />
-
- ) : (
-
- Please provide your access token in Step 1 first! Once done with that,
- you can then specify which Gitlab repositories you want to make
- searchable.
-
- )}
- >
- );
-};
-
-export default function Page() {
- return (
-
-
-
-
-
-
}
- title="Gitlab MRs + Issues"
- />
-
-
-
- );
-}
diff --git a/web/src/app/admin/connectors/gmail/GmailConnectorsTable.tsx b/web/src/app/admin/connectors/gmail/GmailConnectorsTable.tsx
deleted file mode 100644
index f9571204d52..00000000000
--- a/web/src/app/admin/connectors/gmail/GmailConnectorsTable.tsx
+++ /dev/null
@@ -1,127 +0,0 @@
-import { BasicTable } from "@/components/admin/connectors/BasicTable";
-import { PopupSpec } from "@/components/admin/connectors/Popup";
-import { StatusRow } from "@/components/admin/connectors/table/ConnectorsTable";
-import { deleteConnector } from "@/lib/connector";
-import {
- GmailConfig,
- ConnectorIndexingStatus,
- GmailCredentialJson,
-} from "@/lib/types";
-import { useSWRConfig } from "swr";
-import { DeleteColumn } from "@/components/admin/connectors/table/DeleteColumn";
-import {
- Table,
- TableHead,
- TableRow,
- TableHeaderCell,
- TableBody,
- TableCell,
-} from "@tremor/react";
-
-interface TableProps {
- gmailConnectorIndexingStatuses: ConnectorIndexingStatus<
- GmailConfig,
- GmailCredentialJson
- >[];
- setPopup: (popupSpec: PopupSpec | null) => void;
-}
-
-export const GmailConnectorsTable = ({
- gmailConnectorIndexingStatuses: gmailConnectorIndexingStatuses,
- setPopup,
-}: TableProps) => {
- const { mutate } = useSWRConfig();
-
- // Sorting to maintain a consistent ordering
- const sortedGmailConnectorIndexingStatuses = [
- ...gmailConnectorIndexingStatuses,
- ];
- sortedGmailConnectorIndexingStatuses.sort(
- (a, b) => a.connector.id - b.connector.id
- );
-
- return (
-
-
-
-
- Status
- Delete
-
-
-
- {sortedGmailConnectorIndexingStatuses.map(
- (connectorIndexingStatus) => {
- return (
-
-
- {
- mutate("/api/manage/admin/connector/indexing-status");
- }}
- />
-
-
-
- mutate("/api/manage/admin/connector/indexing-status")
- }
- />
-
-
- );
- }
- )}
-
-
-
- );
-
- return (
- ({
- status: (
- {
- mutate("/api/manage/admin/connector/indexing-status");
- }}
- />
- ),
- delete: (
-
- mutate("/api/manage/admin/connector/indexing-status")
- }
- />
- ),
- })
- )}
- />
- );
-};
diff --git a/web/src/app/admin/connectors/gmail/auth/callback/route.ts b/web/src/app/admin/connectors/gmail/auth/callback/route.ts
deleted file mode 100644
index 71e28f59708..00000000000
--- a/web/src/app/admin/connectors/gmail/auth/callback/route.ts
+++ /dev/null
@@ -1,34 +0,0 @@
-import { getDomain } from "@/lib/redirectSS";
-import { buildUrl } from "@/lib/utilsSS";
-import { NextRequest, NextResponse } from "next/server";
-import { cookies } from "next/headers";
-import { GMAIL_AUTH_IS_ADMIN_COOKIE_NAME } from "@/lib/constants";
-import { processCookies } from "@/lib/userSS";
-
-export const GET = async (request: NextRequest) => {
- // Wrapper around the FastAPI endpoint /connectors/gmail/callback,
- // which adds back a redirect to the Gmail admin page.
- const url = new URL(buildUrl("/manage/connector/gmail/callback"));
- url.search = request.nextUrl.search;
-
- const response = await fetch(url.toString(), {
- headers: {
- cookie: processCookies(cookies()),
- },
- });
-
- if (!response.ok) {
- console.log("Error in Gmail callback:", (await response.json()).detail);
- return NextResponse.redirect(new URL("/auth/error", getDomain(request)));
- }
-
- if (
- cookies().get(GMAIL_AUTH_IS_ADMIN_COOKIE_NAME)?.value?.toLowerCase() ===
- "true"
- ) {
- return NextResponse.redirect(
- new URL("/admin/connectors/gmail", getDomain(request))
- );
- }
- return NextResponse.redirect(new URL("/user/connectors", getDomain(request)));
-};
diff --git a/web/src/app/admin/connectors/gmail/page.tsx b/web/src/app/admin/connectors/gmail/page.tsx
deleted file mode 100644
index f0800d293e4..00000000000
--- a/web/src/app/admin/connectors/gmail/page.tsx
+++ /dev/null
@@ -1,276 +0,0 @@
-"use client";
-
-import * as Yup from "yup";
-import { GmailIcon } from "@/components/icons/icons";
-import useSWR, { useSWRConfig } from "swr";
-import { errorHandlingFetcher } from "@/lib/fetcher";
-import { ErrorCallout } from "@/components/ErrorCallout";
-import { LoadingAnimation } from "@/components/Loading";
-import { PopupSpec, usePopup } from "@/components/admin/connectors/Popup";
-import { HealthCheckBanner } from "@/components/health/healthcheck";
-import {
- ConnectorIndexingStatus,
- Credential,
- GmailCredentialJson,
- GmailServiceAccountCredentialJson,
- GmailConfig,
-} from "@/lib/types";
-import { ConnectorForm } from "@/components/admin/connectors/ConnectorForm";
-import { GmailConnectorsTable } from "./GmailConnectorsTable";
-import { gmailConnectorNameBuilder } from "./utils";
-import { GmailOAuthSection, GmailJsonUploadSection } from "./Credential";
-import { usePublicCredentials } from "@/lib/hooks";
-import { AdminPageTitle } from "@/components/admin/Title";
-import { Card, Divider, Text, Title } from "@tremor/react";
-
-interface GmailConnectorManagementProps {
- gmailPublicCredential?: Credential;
- gmailServiceAccountCredential?: Credential;
- gmailConnectorIndexingStatus: ConnectorIndexingStatus<
- GmailConfig,
- GmailCredentialJson
- > | null;
- gmailConnectorIndexingStatuses: ConnectorIndexingStatus<
- GmailConfig,
- GmailCredentialJson
- >[];
- credentialIsLinked: boolean;
- setPopup: (popupSpec: PopupSpec | null) => void;
-}
-
-const GmailConnectorManagement = ({
- gmailPublicCredential: gmailPublicCredential,
- gmailServiceAccountCredential: gmailServiceAccountCredential,
- gmailConnectorIndexingStatuses: gmailConnectorIndexingStatuses,
- setPopup,
-}: GmailConnectorManagementProps) => {
- const { mutate } = useSWRConfig();
-
- const liveCredential = gmailPublicCredential || gmailServiceAccountCredential;
- if (!liveCredential) {
- return (
-
- Please authenticate with Gmail as described in Step 2! Once done with
- that, you can then move on to enable this connector.
-
- );
- }
-
- return (
-
-
-
- {gmailConnectorIndexingStatuses.length > 0 ? (
- <>
- Checkout the{" "}
-
- status page
- {" "}
- for the latest indexing status. We fetch the latest mails from
- Gmail every
10 minutes.
- >
- ) : (
-
- Fill out the form below to create a connector. We will refresh the
- latest documents from Gmail every 10 minutes.
-
- )}
-
-
- {gmailConnectorIndexingStatuses.length > 0 && (
- <>
-
Existing Connectors:
-
-
- >
- )}
-
- {gmailConnectorIndexingStatuses.length > 0 && (
-
Add New Connector:
- )}
-
-
- nameBuilder={gmailConnectorNameBuilder}
- source="gmail"
- inputType="poll"
- formBody={null}
- validationSchema={Yup.object().shape({})}
- initialValues={{}}
- refreshFreq={10 * 60} // 10 minutes
- credentialId={liveCredential.id}
- />
-
-
- );
-};
-
-const Main = () => {
- const {
- data: appCredentialData,
- isLoading: isAppCredentialLoading,
- error: isAppCredentialError,
- } = useSWR<{ client_id: string }>(
- "/api/manage/admin/connector/gmail/app-credential",
- errorHandlingFetcher
- );
- const {
- data: serviceAccountKeyData,
- isLoading: isServiceAccountKeyLoading,
- error: isServiceAccountKeyError,
- } = useSWR<{ service_account_email: string }>(
- "/api/manage/admin/connector/gmail/service-account-key",
- errorHandlingFetcher
- );
- const {
- data: connectorIndexingStatuses,
- isLoading: isConnectorIndexingStatusesLoading,
- error: connectorIndexingStatusesError,
- } = useSWR[]>(
- "/api/manage/admin/connector/indexing-status",
- errorHandlingFetcher
- );
- const {
- data: credentialsData,
- isLoading: isCredentialsLoading,
- error: credentialsError,
- refreshCredentials,
- } = usePublicCredentials();
-
- const { popup, setPopup } = usePopup();
-
- const appCredentialSuccessfullyFetched =
- appCredentialData ||
- (isAppCredentialError && isAppCredentialError.status === 404);
- const serviceAccountKeySuccessfullyFetched =
- serviceAccountKeyData ||
- (isServiceAccountKeyError && isServiceAccountKeyError.status === 404);
-
- if (
- (!appCredentialSuccessfullyFetched && isAppCredentialLoading) ||
- (!serviceAccountKeySuccessfullyFetched && isServiceAccountKeyLoading) ||
- (!connectorIndexingStatuses && isConnectorIndexingStatusesLoading) ||
- (!credentialsData && isCredentialsLoading)
- ) {
- return (
-
-
-
- );
- }
-
- if (credentialsError || !credentialsData) {
- return (
-
-
Failed to load credentials.
-
- );
- }
-
- if (connectorIndexingStatusesError || !connectorIndexingStatuses) {
- return (
-
-
Failed to load connectors.
-
- );
- }
-
- if (
- !appCredentialSuccessfullyFetched ||
- !serviceAccountKeySuccessfullyFetched
- ) {
- return (
-
-
- Error loading Gmail app credentials. Contact an administrator.
-
-
- );
- }
-
- const gmailPublicCredential: Credential | undefined =
- credentialsData.find(
- (credential) =>
- credential.credential_json?.gmail_tokens && credential.admin_public
- );
- const gmailServiceAccountCredential:
- | Credential
- | undefined = credentialsData.find(
- (credential) => credential.credential_json?.gmail_service_account_key
- );
- const gmailConnectorIndexingStatuses: ConnectorIndexingStatus<
- GmailConfig,
- GmailCredentialJson
- >[] = connectorIndexingStatuses.filter(
- (connectorIndexingStatus) =>
- connectorIndexingStatus.connector.source === "gmail"
- );
- const gmailConnectorIndexingStatus = gmailConnectorIndexingStatuses[0];
-
- const credentialIsLinked =
- (gmailConnectorIndexingStatus !== undefined &&
- gmailPublicCredential !== undefined &&
- gmailConnectorIndexingStatus.connector.credential_ids.includes(
- gmailPublicCredential.id
- )) ||
- (gmailConnectorIndexingStatus !== undefined &&
- gmailServiceAccountCredential !== undefined &&
- gmailConnectorIndexingStatus.connector.credential_ids.includes(
- gmailServiceAccountCredential.id
- ));
-
- return (
- <>
- {popup}
-
- Step 1: Provide your Credentials
-
-
-
-
- Step 2: Authenticate with Danswer
-
- 0}
- />
-
-
- Step 3: Start Indexing!
-
-
- >
- );
-};
-
-export default function Page() {
- return (
-
-
-
-
-
-
} title="Gmail" />
-
-
-
- );
-}
diff --git a/web/src/app/admin/connectors/gmail/utils.ts b/web/src/app/admin/connectors/gmail/utils.ts
deleted file mode 100644
index e7f8a24b3f1..00000000000
--- a/web/src/app/admin/connectors/gmail/utils.ts
+++ /dev/null
@@ -1,4 +0,0 @@
-import { GmailConfig } from "@/lib/types";
-
-export const gmailConnectorNameBuilder = (values: GmailConfig) =>
- "GmailConnector";
diff --git a/web/src/app/admin/connectors/gong/page.tsx b/web/src/app/admin/connectors/gong/page.tsx
deleted file mode 100644
index 5fda45d517e..00000000000
--- a/web/src/app/admin/connectors/gong/page.tsx
+++ /dev/null
@@ -1,269 +0,0 @@
-"use client";
-
-import * as Yup from "yup";
-import { GongIcon, TrashIcon } from "@/components/icons/icons";
-import {
- TextFormField,
- TextArrayFieldBuilder,
-} from "@/components/admin/connectors/Field";
-import { HealthCheckBanner } from "@/components/health/healthcheck";
-import { CredentialForm } from "@/components/admin/connectors/CredentialForm";
-import {
- Credential,
- ConnectorIndexingStatus,
- GongConfig,
- GongCredentialJson,
-} from "@/lib/types";
-import useSWR, { useSWRConfig } from "swr";
-import { errorHandlingFetcher } from "@/lib/fetcher";
-import { ErrorCallout } from "@/components/ErrorCallout";
-import { LoadingAnimation } from "@/components/Loading";
-import { adminDeleteCredential, linkCredential } from "@/lib/credential";
-import { ConnectorForm } from "@/components/admin/connectors/ConnectorForm";
-import { ConnectorsTable } from "@/components/admin/connectors/table/ConnectorsTable";
-import { usePopup } from "@/components/admin/connectors/Popup";
-import { usePublicCredentials } from "@/lib/hooks";
-import { Card, Divider, Text, Title } from "@tremor/react";
-import { AdminPageTitle } from "@/components/admin/Title";
-
-const Main = () => {
- const { popup, setPopup } = usePopup();
-
- const { mutate } = useSWRConfig();
- const {
- data: connectorIndexingStatuses,
- isLoading: isConnectorIndexingStatusesLoading,
- error: connectorIndexingStatusesError,
- } = useSWR[]>(
- "/api/manage/admin/connector/indexing-status",
- errorHandlingFetcher
- );
-
- const {
- data: credentialsData,
- isLoading: isCredentialsLoading,
- error: credentialsError,
- refreshCredentials,
- } = usePublicCredentials();
-
- if (
- (!connectorIndexingStatuses && isConnectorIndexingStatusesLoading) ||
- (!credentialsData && isCredentialsLoading)
- ) {
- return ;
- }
-
- if (connectorIndexingStatusesError || !connectorIndexingStatuses) {
- return (
-
- );
- }
-
- if (credentialsError || !credentialsData) {
- return (
-
- );
- }
-
- const gongConnectorIndexingStatuses: ConnectorIndexingStatus<
- GongConfig,
- GongCredentialJson
- >[] = connectorIndexingStatuses.filter(
- (connectorIndexingStatus) =>
- connectorIndexingStatus.connector.source === "gong"
- );
- const gongCredential: Credential | undefined =
- credentialsData.find(
- (credential) => credential.credential_json?.gong_access_key
- );
-
- return (
- <>
- {popup}
-
- This connector allows you to sync all your Gong Transcripts into
- Danswer. More details on how to setup the Gong connector can be found in{" "}
-
- this guide.
-
-
-
-
- Step 1: Provide your API Access info
-
-
- {gongCredential ? (
- <>
-
-
Existing Access Key Secret:
-
- {gongCredential.credential_json?.gong_access_key_secret}
-
-
-
- >
- ) : (
- <>
-
-
- formBody={
- <>
-
-
- >
- }
- validationSchema={Yup.object().shape({
- gong_access_key: Yup.string().required(
- "Please enter your Gong Access Key"
- ),
- gong_access_key_secret: Yup.string().required(
- "Please enter your Gong Access Key Secret"
- ),
- })}
- initialValues={{
- gong_access_key: "",
- gong_access_key_secret: "",
- }}
- onSubmit={(isSuccess) => {
- if (isSuccess) {
- refreshCredentials();
- }
- }}
- />
-
- >
- )}
-
-
- Step 2: Which Workspaces do you want to make searchable?
-
-
- {gongConnectorIndexingStatuses.length > 0 && (
- <>
-
- We pull the latest transcript every 10 minutes.
-
-
-
- connectorIndexingStatuses={gongConnectorIndexingStatuses}
- liveCredential={gongCredential}
- getCredential={(credential) =>
- credential.credential_json.gong_access_key
- }
- specialColumns={[
- {
- header: "Workspaces",
- key: "workspaces",
- getValue: (ccPairStatus) =>
- ccPairStatus.connector.connector_specific_config
- .workspaces &&
- ccPairStatus.connector.connector_specific_config.workspaces
- .length > 0
- ? ccPairStatus.connector.connector_specific_config.workspaces.join(
- ", "
- )
- : "",
- },
- ]}
- includeName={true}
- onUpdate={() =>
- mutate("/api/manage/admin/connector/indexing-status")
- }
- onCredentialLink={async (connectorId) => {
- if (gongCredential) {
- await linkCredential(connectorId, gongCredential.id);
- mutate("/api/manage/admin/connector/indexing-status");
- }
- }}
- />
-
-
- >
- )}
-
- {gongCredential ? (
- <>
-
- Create a new Gong Connector
-
- nameBuilder={(values) =>
- values.workspaces
- ? `GongConnector-${values.workspaces.join("_")}`
- : `GongConnector-All`
- }
- source="gong"
- inputType="poll"
- formBodyBuilder={TextArrayFieldBuilder({
- name: "workspaces",
- label: "Workspaces:",
- subtext:
- "Specify 0 or more workspaces to index. Provide the workspace ID or the EXACT workspace " +
- "name from Gong. If no workspaces are specified, transcripts from all workspaces will " +
- "be indexed.",
- })}
- validationSchema={Yup.object().shape({
- workspaces: Yup.array().of(
- Yup.string().required("Workspace names must be strings")
- ),
- })}
- initialValues={{
- workspaces: [],
- }}
- refreshFreq={10 * 60} // 10 minutes
- credentialId={gongCredential.id}
- />
-
- >
- ) : (
-
- Please provide your API Access Info in Step 1 first! Once done with
- that, you can then start indexing all your Gong transcripts.
-
- )}
- >
- );
-};
-
-export default function Page() {
- return (
-
-
-
-
-
-
} title="Gong" />
-
-
-
- );
-}
diff --git a/web/src/app/admin/connectors/google-drive/ConnectorEditPopup.tsx b/web/src/app/admin/connectors/google-drive/ConnectorEditPopup.tsx
deleted file mode 100644
index 60e3bc05e02..00000000000
--- a/web/src/app/admin/connectors/google-drive/ConnectorEditPopup.tsx
+++ /dev/null
@@ -1,82 +0,0 @@
-import { UpdateConnectorForm } from "@/components/admin/connectors/ConnectorForm";
-import {
- BooleanFormField,
- TextArrayFieldBuilder,
-} from "@/components/admin/connectors/Field";
-import { XIcon } from "@/components/icons/icons";
-import { Connector, GoogleDriveConfig } from "@/lib/types";
-import * as Yup from "yup";
-import { googleDriveConnectorNameBuilder } from "./utils";
-import { Modal } from "@/components/Modal";
-import { Divider, Text } from "@tremor/react";
-
-interface Props {
- existingConnector: Connector;
- onSubmit: () => void;
-}
-
-export const ConnectorEditPopup = ({ existingConnector, onSubmit }: Props) => {
- return (
-
-
-
- Update Google Drive Connector
-
-
-
-
-
-
- Modify the selected Google Drive connector by adjusting the values
- below!
-
-
-
-
-
- nameBuilder={googleDriveConnectorNameBuilder}
- existingConnector={existingConnector}
- formBodyBuilder={(values) => (
-
- {TextArrayFieldBuilder({
- name: "folder_paths",
- label: "Folder Paths",
- })(values)}
-
-
-
-
- )}
- validationSchema={Yup.object().shape({
- folder_paths: Yup.array()
- .of(
- Yup.string().required(
- "Please specify a folder path for your google drive e.g. 'Engineering/Materials'"
- )
- )
- .required(),
- include_shared: Yup.boolean().required(),
- follow_shortcuts: Yup.boolean().required(),
- only_org_public: Yup.boolean().required(),
- })}
- onSubmit={onSubmit}
- />
-
-
- );
-};
diff --git a/web/src/app/admin/connectors/google-drive/GoogleDriveConnectorsTable.tsx b/web/src/app/admin/connectors/google-drive/GoogleDriveConnectorsTable.tsx
deleted file mode 100644
index 4da18f682d2..00000000000
--- a/web/src/app/admin/connectors/google-drive/GoogleDriveConnectorsTable.tsx
+++ /dev/null
@@ -1,300 +0,0 @@
-import { Button } from "@/components/Button";
-import { BasicTable } from "@/components/admin/connectors/BasicTable";
-import { PopupSpec } from "@/components/admin/connectors/Popup";
-import { StatusRow } from "@/components/admin/connectors/table/ConnectorsTable";
-import { EditIcon } from "@/components/icons/icons";
-import { deleteConnector } from "@/lib/connector";
-import {
- GoogleDriveConfig,
- ConnectorIndexingStatus,
- GoogleDriveCredentialJson,
-} from "@/lib/types";
-import { useSWRConfig } from "swr";
-import { useState } from "react";
-import { ConnectorEditPopup } from "./ConnectorEditPopup";
-import { DeleteColumn } from "@/components/admin/connectors/table/DeleteColumn";
-import {
- Table,
- TableHead,
- TableRow,
- TableHeaderCell,
- TableBody,
- TableCell,
-} from "@tremor/react";
-
-interface EditableColumnProps {
- connectorIndexingStatus: ConnectorIndexingStatus<
- GoogleDriveConfig,
- GoogleDriveCredentialJson
- >;
-}
-
-const EditableColumn = ({ connectorIndexingStatus }: EditableColumnProps) => {
- const { mutate } = useSWRConfig();
- const [isEditing, setIsEditing] = useState(false);
-
- return (
- <>
- {isEditing && (
- {
- setIsEditing(false);
- mutate("/api/manage/admin/connector/indexing-status");
- }}
- />
- )}
-
-
{
- setIsEditing(true);
- }}
- className="cursor-pointer"
- >
-
-
-
-
-
- >
- );
-};
-
-interface TableProps {
- googleDriveConnectorIndexingStatuses: ConnectorIndexingStatus<
- GoogleDriveConfig,
- GoogleDriveCredentialJson
- >[];
- setPopup: (popupSpec: PopupSpec | null) => void;
-}
-
-export const GoogleDriveConnectorsTable = ({
- googleDriveConnectorIndexingStatuses,
- setPopup,
-}: TableProps) => {
- const { mutate } = useSWRConfig();
-
- // Sorting to maintain a consistent ordering
- const sortedGoogleDriveConnectorIndexingStatuses = [
- ...googleDriveConnectorIndexingStatuses,
- ];
- sortedGoogleDriveConnectorIndexingStatuses.sort(
- (a, b) => a.connector.id - b.connector.id
- );
-
- return (
-
-
-
-
- Edit
- Folder Paths
- Include Shared
- Follow Shortcuts
- Only Org Public
- Status
- Delete
-
-
-
- {sortedGoogleDriveConnectorIndexingStatuses.map(
- (connectorIndexingStatus) => {
- return (
-
-
-
-
-
- {(
- connectorIndexingStatus.connector
- .connector_specific_config.folder_paths || []
- ).length > 0 ? (
-
- {(
- connectorIndexingStatus.connector
- .connector_specific_config.folder_paths || []
- ).map((path) => (
-
- - {path}
-
- ))}
-
- ) : (
- All Folders
- )}
-
-
-
- {connectorIndexingStatus.connector
- .connector_specific_config.include_shared ? (
- Yes
- ) : (
- No
- )}
-
-
-
-
- {connectorIndexingStatus.connector
- .connector_specific_config.follow_shortcuts ? (
- Yes
- ) : (
- No
- )}
-
-
-
-
- {connectorIndexingStatus.connector
- .connector_specific_config.only_org_public ? (
- Yes
- ) : (
- No
- )}
-
-
-
- {
- mutate("/api/manage/admin/connector/indexing-status");
- }}
- />
-
-
-
- mutate("/api/manage/admin/connector/indexing-status")
- }
- />
-
-
- );
- }
- )}
-
-
-
- );
-
- return (
- ({
- edit: (
-
- ),
- folder_paths:
- (
- connectorIndexingStatus.connector.connector_specific_config
- .folder_paths || []
- ).length > 0 ? (
-
- {(
- connectorIndexingStatus.connector.connector_specific_config
- .folder_paths || []
- ).map((path) => (
-
- - {path}
-
- ))}
-
- ) : (
- All Folders
- ),
- include_shared: (
-
- {connectorIndexingStatus.connector.connector_specific_config
- .include_shared ? (
- Yes
- ) : (
- No
- )}
-
- ),
- follow_shortcuts: (
-
- {connectorIndexingStatus.connector.connector_specific_config
- .follow_shortcuts ? (
- Yes
- ) : (
- No
- )}
-
- ),
- only_org_public: (
-
- {connectorIndexingStatus.connector.connector_specific_config
- .only_org_public ? (
- Yes
- ) : (
- No
- )}
-
- ),
- status: (
- {
- mutate("/api/manage/admin/connector/indexing-status");
- }}
- />
- ),
- delete: (
-
- mutate("/api/manage/admin/connector/indexing-status")
- }
- />
- ),
- })
- )}
- />
- );
-};
diff --git a/web/src/app/admin/connectors/google-drive/page.tsx b/web/src/app/admin/connectors/google-drive/page.tsx
deleted file mode 100644
index 121d8af9d0a..00000000000
--- a/web/src/app/admin/connectors/google-drive/page.tsx
+++ /dev/null
@@ -1,427 +0,0 @@
-"use client";
-
-import * as Yup from "yup";
-import { GoogleDriveIcon } from "@/components/icons/icons";
-import useSWR, { useSWRConfig } from "swr";
-import { FetchError, errorHandlingFetcher } from "@/lib/fetcher";
-import { ErrorCallout } from "@/components/ErrorCallout";
-import { LoadingAnimation } from "@/components/Loading";
-import { PopupSpec, usePopup } from "@/components/admin/connectors/Popup";
-import { HealthCheckBanner } from "@/components/health/healthcheck";
-import {
- ConnectorIndexingStatus,
- Credential,
- GoogleDriveConfig,
- GoogleDriveCredentialJson,
- GoogleDriveServiceAccountCredentialJson,
-} from "@/lib/types";
-import { linkCredential } from "@/lib/credential";
-import { ConnectorForm } from "@/components/admin/connectors/ConnectorForm";
-import {
- BooleanFormField,
- TextArrayFieldBuilder,
-} from "@/components/admin/connectors/Field";
-import { GoogleDriveConnectorsTable } from "./GoogleDriveConnectorsTable";
-import { googleDriveConnectorNameBuilder } from "./utils";
-import { DriveOAuthSection, DriveJsonUploadSection } from "./Credential";
-import { usePublicCredentials } from "@/lib/hooks";
-import { AdminPageTitle } from "@/components/admin/Title";
-import { Card, Divider, Text, Title } from "@tremor/react";
-
-interface GoogleDriveConnectorManagementProps {
- googleDrivePublicCredential?: Credential;
- googleDriveServiceAccountCredential?: Credential;
- googleDriveConnectorIndexingStatus: ConnectorIndexingStatus<
- GoogleDriveConfig,
- GoogleDriveCredentialJson
- > | null;
- googleDriveConnectorIndexingStatuses: ConnectorIndexingStatus<
- GoogleDriveConfig,
- GoogleDriveCredentialJson
- >[];
- credentialIsLinked: boolean;
- setPopup: (popupSpec: PopupSpec | null) => void;
-}
-
-const GoogleDriveConnectorManagement = ({
- googleDrivePublicCredential,
- googleDriveServiceAccountCredential,
- googleDriveConnectorIndexingStatus,
- googleDriveConnectorIndexingStatuses,
- credentialIsLinked,
- setPopup,
-}: GoogleDriveConnectorManagementProps) => {
- const { mutate } = useSWRConfig();
-
- const liveCredential =
- googleDrivePublicCredential || googleDriveServiceAccountCredential;
- if (!liveCredential) {
- return (
-
- Please authenticate with Google Drive as described in Step 2! Once done
- with that, you can then move on to enable this connector.
-
- );
- }
-
- // NOTE: if the connector has no credential linked, then it will not be
- // returned by the indexing-status API
- // if (!googleDriveConnectorIndexingStatus) {
- // return (
- // <>
- //
- // Fill out the form below to create a connector. We will refresh the
- // latest documents from Google Drive every 10 minutes.
- //
- //
- //
Add Connector
- //
- // nameBuilder={googleDriveConnectorNameBuilder}
- // source="google_drive"
- // inputType="poll"
- // formBodyBuilder={(values) => (
- //
- // {TextArrayFieldBuilder({
- // name: "folder_paths",
- // label: "Folder Paths",
- // subtext:
- // "Specify 0 or more folder paths to index! For example, specifying the path " +
- // "'Engineering/Materials' will cause us to only index all files contained " +
- // "within the 'Materials' folder within the 'Engineering' folder. " +
- // "If no folder paths are specified, we will index all documents in your drive.",
- // })(values)}
- //
- //
- // )}
- // validationSchema={Yup.object().shape({
- // folder_paths: Yup.array()
- // .of(
- // Yup.string().required(
- // "Please specify a folder path for your google drive e.g. 'Engineering/Materials'"
- // )
- // )
- // .required(),
- // include_shared: Yup.boolean().required(),
- // })}
- // initialValues={{
- // folder_paths: [],
- // }}
- // refreshFreq={10 * 60} // 10 minutes
- // onSubmit={async (isSuccess, responseJson) => {
- // if (isSuccess && responseJson) {
- // await linkCredential(
- // responseJson.id,
- // googleDrivePublicCredential.id
- // );
- // mutate("/api/manage/admin/connector/indexing-status");
- // }
- // }}
- // />
- //
- // >
- // );
- // }
-
- // If the connector has no credential, we will just hit the ^ section.
- // Leaving this in for now in case we want to change this behavior later
- // if (!credentialIsLinked) {
- // <>
- //
- // Click the button below to link your credentials! Once this is done, all
- // public documents in your Google Drive will be searchable. We will
- // refresh the latest documents every 10 minutes.
- //
- //
- // >;
- // }
-
- return (
-
-
-
- {googleDriveConnectorIndexingStatuses.length > 0 ? (
- <>
- Checkout the{" "}
-
- status page
- {" "}
- for the latest indexing status. We fetch the latest documents from
- Google Drive every
10 minutes.
- >
- ) : (
-
- Fill out the form below to create a connector. We will refresh the
- latest documents from Google Drive every 10 minutes.
-
- )}
-
-
- {googleDriveConnectorIndexingStatuses.length > 0 && (
- <>
-
Existing Connectors:
-
-
- >
- )}
-
- {googleDriveConnectorIndexingStatuses.length > 0 && (
-
Add New Connector:
- )}
-
-
- nameBuilder={googleDriveConnectorNameBuilder}
- source="google_drive"
- inputType="poll"
- formBodyBuilder={(values) => (
- <>
- {TextArrayFieldBuilder({
- name: "folder_paths",
- label: "Folder Paths",
- subtext:
- "Specify 0 or more folder paths to index! For example, specifying the path " +
- "'Engineering/Materials' will cause us to only index all files contained " +
- "within the 'Materials' folder within the 'Engineering' folder. " +
- "If no folder paths are specified, we will index all documents in your drive.",
- })(values)}
-
-
-
- >
- )}
- validationSchema={Yup.object().shape({
- folder_paths: Yup.array()
- .of(
- Yup.string().required(
- "Please specify a folder path for your google drive e.g. 'Engineering/Materials'"
- )
- )
- .required(),
- include_shared: Yup.boolean().required(),
- follow_shortcuts: Yup.boolean().required(),
- only_org_public: Yup.boolean().required(),
- })}
- initialValues={{
- folder_paths: [],
- include_shared: false,
- follow_shortcuts: false,
- only_org_public: false,
- }}
- refreshFreq={10 * 60} // 10 minutes
- credentialId={liveCredential.id}
- />
-
-
- );
-};
-
-const Main = () => {
- const {
- data: appCredentialData,
- isLoading: isAppCredentialLoading,
- error: isAppCredentialError,
- } = useSWR<{ client_id: string }, FetchError>(
- "/api/manage/admin/connector/google-drive/app-credential",
- errorHandlingFetcher
- );
- const {
- data: serviceAccountKeyData,
- isLoading: isServiceAccountKeyLoading,
- error: isServiceAccountKeyError,
- } = useSWR<{ service_account_email: string }, FetchError>(
- "/api/manage/admin/connector/google-drive/service-account-key",
- errorHandlingFetcher
- );
- const {
- data: connectorIndexingStatuses,
- isLoading: isConnectorIndexingStatusesLoading,
- error: connectorIndexingStatusesError,
- } = useSWR[], FetchError>(
- "/api/manage/admin/connector/indexing-status",
- errorHandlingFetcher
- );
- const {
- data: credentialsData,
- isLoading: isCredentialsLoading,
- error: credentialsError,
- refreshCredentials,
- } = usePublicCredentials();
-
- const { popup, setPopup } = usePopup();
-
- const appCredentialSuccessfullyFetched =
- appCredentialData ||
- (isAppCredentialError && isAppCredentialError.status === 404);
- const serviceAccountKeySuccessfullyFetched =
- serviceAccountKeyData ||
- (isServiceAccountKeyError && isServiceAccountKeyError.status === 404);
-
- if (
- (!appCredentialSuccessfullyFetched && isAppCredentialLoading) ||
- (!serviceAccountKeySuccessfullyFetched && isServiceAccountKeyLoading) ||
- (!connectorIndexingStatuses && isConnectorIndexingStatusesLoading) ||
- (!credentialsData && isCredentialsLoading)
- ) {
- return (
-
-
-
- );
- }
-
- if (credentialsError || !credentialsData) {
- return ;
- }
-
- if (connectorIndexingStatusesError || !connectorIndexingStatuses) {
- return ;
- }
-
- if (
- !appCredentialSuccessfullyFetched ||
- !serviceAccountKeySuccessfullyFetched
- ) {
- return (
-
- );
- }
-
- const googleDrivePublicCredential:
- | Credential
- | undefined = credentialsData.find(
- (credential) =>
- credential.credential_json?.google_drive_tokens && credential.admin_public
- );
- const googleDriveServiceAccountCredential:
- | Credential
- | undefined = credentialsData.find(
- (credential) => credential.credential_json?.google_drive_service_account_key
- );
- const googleDriveConnectorIndexingStatuses: ConnectorIndexingStatus<
- GoogleDriveConfig,
- GoogleDriveCredentialJson
- >[] = connectorIndexingStatuses.filter(
- (connectorIndexingStatus) =>
- connectorIndexingStatus.connector.source === "google_drive"
- );
- const googleDriveConnectorIndexingStatus =
- googleDriveConnectorIndexingStatuses[0];
-
- const credentialIsLinked =
- (googleDriveConnectorIndexingStatus !== undefined &&
- googleDrivePublicCredential !== undefined &&
- googleDriveConnectorIndexingStatus.connector.credential_ids.includes(
- googleDrivePublicCredential.id
- )) ||
- (googleDriveConnectorIndexingStatus !== undefined &&
- googleDriveServiceAccountCredential !== undefined &&
- googleDriveConnectorIndexingStatus.connector.credential_ids.includes(
- googleDriveServiceAccountCredential.id
- ));
-
- return (
- <>
- {popup}
-
- Step 1: Provide your Credentials
-
-
-
-
- Step 2: Authenticate with Danswer
-
- 0}
- />
-
-
- Step 3: Start Indexing!
-
-
- >
- );
-};
-
-export default function Page() {
- return (
-
-
-
-
-
-
}
- title="Google Drive"
- />
-
-
-
- );
-}
diff --git a/web/src/app/admin/connectors/google-drive/utils.ts b/web/src/app/admin/connectors/google-drive/utils.ts
deleted file mode 100644
index d095ec27ed5..00000000000
--- a/web/src/app/admin/connectors/google-drive/utils.ts
+++ /dev/null
@@ -1,10 +0,0 @@
-import { GoogleDriveConfig } from "@/lib/types";
-
-export const googleDriveConnectorNameBuilder = (values: GoogleDriveConfig) =>
- `GoogleDriveConnector-${
- values.folder_paths && values.folder_paths.join("_")
- }-${values.include_shared ? "shared" : "not-shared"}-${
- values.only_org_public ? "org-public" : "all"
- }-${
- values.follow_shortcuts ? "follow-shortcuts" : "do-not-follow-shortcuts"
- }`;
diff --git a/web/src/app/admin/connectors/google-sites/page.tsx b/web/src/app/admin/connectors/google-sites/page.tsx
deleted file mode 100644
index 20728633a59..00000000000
--- a/web/src/app/admin/connectors/google-sites/page.tsx
+++ /dev/null
@@ -1,249 +0,0 @@
-"use client";
-
-import useSWR, { useSWRConfig } from "swr";
-import * as Yup from "yup";
-
-import { LoadingAnimation } from "@/components/Loading";
-import { GoogleSitesIcon } from "@/components/icons/icons";
-import { errorHandlingFetcher } from "@/lib/fetcher";
-import { ErrorCallout } from "@/components/ErrorCallout";
-import { TextFormField } from "@/components/admin/connectors/Field";
-import { HealthCheckBanner } from "@/components/health/healthcheck";
-import { ConnectorIndexingStatus, GoogleSitesConfig } from "@/lib/types";
-import { Form, Formik } from "formik";
-import { useState } from "react";
-import { usePopup } from "@/components/admin/connectors/Popup";
-import { createConnector, runConnector } from "@/lib/connector";
-import { linkCredential } from "@/lib/credential";
-import { FileUpload } from "@/components/admin/connectors/FileUpload";
-import { SingleUseConnectorsTable } from "@/components/admin/connectors/table/SingleUseConnectorsTable";
-import { Spinner } from "@/components/Spinner";
-import { AdminPageTitle } from "@/components/admin/Title";
-import { Button, Card, Text, Title } from "@tremor/react";
-
-export default function GoogleSites() {
- const { mutate } = useSWRConfig();
- const [selectedFiles, setSelectedFiles] = useState([]);
- const [filesAreUploading, setFilesAreUploading] = useState(false);
- const { popup, setPopup } = usePopup();
-
- const {
- data: connectorIndexingStatuses,
- isLoading: isConnectorIndexingStatusesLoading,
- error: connectorIndexingStatusesError,
- } = useSWR[]>(
- "/api/manage/admin/connector/indexing-status",
- errorHandlingFetcher
- );
-
- const googleSitesIndexingStatuses: ConnectorIndexingStatus<
- GoogleSitesConfig,
- {}
- >[] =
- connectorIndexingStatuses?.filter(
- (connectorIndexingStatus) =>
- connectorIndexingStatus.connector.source === "google_sites"
- ) ?? [];
-
- return (
- <>
- {popup}
- {filesAreUploading && }
-
-
-
-
-
-
}
- title="Google Sites"
- />
-
-
- For an in-depth guide on how to setup this connector, check out{" "}
-
- the documentation
-
- .
-
-
-
-
Upload Files
-
-
-
{
- const uploadCreateAndTriggerConnector = async () => {
- const formData = new FormData();
-
- selectedFiles.forEach((file) => {
- formData.append("files", file);
- });
-
- const response = await fetch(
- "/api/manage/admin/connector/file/upload",
- { method: "POST", body: formData }
- );
- const responseJson = await response.json();
- if (!response.ok) {
- setPopup({
- message: `Unable to upload files - ${responseJson.detail}`,
- type: "error",
- });
- return;
- }
-
- const filePaths = responseJson.file_paths as string[];
- const [connectorErrorMsg, connector] =
- await createConnector({
- name: `GoogleSitesConnector-${values.base_url}`,
- source: "google_sites",
- input_type: "load_state",
- connector_specific_config: {
- base_url: values.base_url,
- zip_path: filePaths[0],
- },
- refresh_freq: null,
- prune_freq: 0,
- disabled: false,
- });
- if (connectorErrorMsg || !connector) {
- setPopup({
- message: `Unable to create connector - ${connectorErrorMsg}`,
- type: "error",
- });
- return;
- }
-
- const credentialResponse = await linkCredential(
- connector.id,
- 0,
- values.base_url
- );
- if (!credentialResponse.ok) {
- const credentialResponseJson =
- await credentialResponse.json();
- setPopup({
- message: `Unable to link connector to credential - ${credentialResponseJson.detail}`,
- type: "error",
- });
- return;
- }
-
- const runConnectorErrorMsg = await runConnector(
- connector.id,
- [0]
- );
- if (runConnectorErrorMsg) {
- setPopup({
- message: `Unable to run connector - ${runConnectorErrorMsg}`,
- type: "error",
- });
- return;
- }
-
- mutate("/api/manage/admin/connector/indexing-status");
- setSelectedFiles([]);
- formikHelpers.resetForm();
- setPopup({
- type: "success",
- message: "Successfully uploaded files!",
- });
- };
-
- setFilesAreUploading(true);
- try {
- await uploadCreateAndTriggerConnector();
- } catch (e) {
- console.log("Failed to index filels: ", e);
- }
- setFilesAreUploading(false);
- }}
- >
- {({ values, isSubmitting }) => (
-
- )}
-
-
-
-
-
-
- Existing Google Site Connectors
-
- {isConnectorIndexingStatusesLoading ? (
-
- ) : connectorIndexingStatusesError || !connectorIndexingStatuses ? (
-
Error loading indexing history
- ) : googleSitesIndexingStatuses.length > 0 ? (
-
- connectorIndexingStatuses={googleSitesIndexingStatuses}
- specialColumns={[
- {
- header: "Base URL",
- key: "base_url",
- getValue: (ccPairStatus) => {
- const connectorConfig =
- ccPairStatus.connector.connector_specific_config;
- return (
-
- {connectorConfig.base_url}
-
- );
- },
- },
- ]}
- onUpdate={() =>
- mutate("/api/manage/admin/connector/indexing-status")
- }
- />
- ) : (
- No indexed Google Sites found
- )}
-
- >
- );
-}
diff --git a/web/src/app/admin/connectors/google-storage/page.tsx b/web/src/app/admin/connectors/google-storage/page.tsx
deleted file mode 100644
index a836df21f6f..00000000000
--- a/web/src/app/admin/connectors/google-storage/page.tsx
+++ /dev/null
@@ -1,257 +0,0 @@
-"use client";
-
-import { AdminPageTitle } from "@/components/admin/Title";
-import { HealthCheckBanner } from "@/components/health/healthcheck";
-import { GoogleStorageIcon, TrashIcon } from "@/components/icons/icons";
-import { LoadingAnimation } from "@/components/Loading";
-import { ConnectorForm } from "@/components/admin/connectors/ConnectorForm";
-import { CredentialForm } from "@/components/admin/connectors/CredentialForm";
-import { TextFormField } from "@/components/admin/connectors/Field";
-import { usePopup } from "@/components/admin/connectors/Popup";
-import { ConnectorsTable } from "@/components/admin/connectors/table/ConnectorsTable";
-import { adminDeleteCredential, linkCredential } from "@/lib/credential";
-import { errorHandlingFetcher } from "@/lib/fetcher";
-import { ErrorCallout } from "@/components/ErrorCallout";
-import { usePublicCredentials } from "@/lib/hooks";
-import { ConnectorIndexingStatus, Credential } from "@/lib/types";
-
-import { GCSConfig, GCSCredentialJson } from "@/lib/types";
-
-import { Card, Select, SelectItem, Text, Title } from "@tremor/react";
-import useSWR, { useSWRConfig } from "swr";
-import * as Yup from "yup";
-import { useState } from "react";
-
-const GCSMain = () => {
- const { popup, setPopup } = usePopup();
- const { mutate } = useSWRConfig();
- const {
- data: connectorIndexingStatuses,
- isLoading: isConnectorIndexingStatusesLoading,
- error: connectorIndexingStatusesError,
- } = useSWR[]>(
- "/api/manage/admin/connector/indexing-status",
- errorHandlingFetcher
- );
- const {
- data: credentialsData,
- isLoading: isCredentialsLoading,
- error: credentialsError,
- refreshCredentials,
- } = usePublicCredentials();
-
- if (
- (!connectorIndexingStatuses && isConnectorIndexingStatusesLoading) ||
- (!credentialsData && isCredentialsLoading)
- ) {
- return ;
- }
-
- if (connectorIndexingStatusesError || !connectorIndexingStatuses) {
- return (
-
- );
- }
-
- if (credentialsError || !credentialsData) {
- return (
-
- );
- }
-
- const gcsConnectorIndexingStatuses: ConnectorIndexingStatus<
- GCSConfig,
- GCSCredentialJson
- >[] = connectorIndexingStatuses.filter(
- (connectorIndexingStatus) =>
- connectorIndexingStatus.connector.source === "google_cloud_storage"
- );
-
- const gcsCredential: Credential | undefined =
- credentialsData.find(
- (credential) => credential.credential_json?.project_id
- );
-
- return (
- <>
- {popup}
-
- Step 1: Provide your GCS access info
-
- {gcsCredential ? (
- <>
-
-
Existing GCS Access Key ID:
-
- {gcsCredential.credential_json.access_key_id}
-
- {", "}
-
Secret Access Key:
-
- {gcsCredential.credential_json.secret_access_key}
-
{" "}
-
-
- >
- ) : (
- <>
-
-
- -
- Provide your GCS Project ID, Client Email, and Private Key for
- authentication.
-
- -
- These credentials will be used to access your GCS buckets.
-
-
-
-
-
- formBody={
- <>
-
-
-
- >
- }
- validationSchema={Yup.object().shape({
- secret_access_key: Yup.string().required(
- "Client Email is required"
- ),
- access_key_id: Yup.string().required("Private Key is required"),
- })}
- initialValues={{
- secret_access_key: "",
- access_key_id: "",
- }}
- onSubmit={(isSuccess) => {
- if (isSuccess) {
- refreshCredentials();
- }
- }}
- />
-
- >
- )}
-
-
- Step 2: Which GCS bucket do you want to make searchable?
-
-
- {gcsConnectorIndexingStatuses.length > 0 && (
- <>
-
- GCS indexing status
-
-
- The latest changes are fetched every 10 minutes.
-
-
-
- includeName={true}
- connectorIndexingStatuses={gcsConnectorIndexingStatuses}
- liveCredential={gcsCredential}
- getCredential={(credential) => {
- return ;
- }}
- onCredentialLink={async (connectorId) => {
- if (gcsCredential) {
- await linkCredential(connectorId, gcsCredential.id);
- mutate("/api/manage/admin/connector/indexing-status");
- }
- }}
- onUpdate={() =>
- mutate("/api/manage/admin/connector/indexing-status")
- }
- />
-
- >
- )}
-
- {gcsCredential && (
- <>
-
- Create Connection
-
- Press connect below to start the connection to your GCS bucket.
-
-
- nameBuilder={(values) => `GCSConnector-${values.bucket_name}`}
- ccPairNameBuilder={(values) =>
- `GCSConnector-${values.bucket_name}`
- }
- source="google_cloud_storage"
- inputType="poll"
- formBodyBuilder={(values) => (
-
-
-
-
- )}
- validationSchema={Yup.object().shape({
- bucket_type: Yup.string()
- .oneOf(["google_cloud_storage"])
- .required("Bucket type must be google_cloud_storage"),
- bucket_name: Yup.string().required(
- "Please enter the name of the GCS bucket to index, e.g. my-gcs-bucket"
- ),
- prefix: Yup.string().default(""),
- })}
- initialValues={{
- bucket_type: "google_cloud_storage",
- bucket_name: "",
- prefix: "",
- }}
- refreshFreq={60 * 60 * 24} // 1 day
- credentialId={gcsCredential.id}
- />
-
- >
- )}
- >
- );
-};
-
-export default function Page() {
- return (
-
-
-
-
-
}
- title="Google Cloud Storage"
- />
-
-
- );
-}
diff --git a/web/src/app/admin/connectors/guru/page.tsx b/web/src/app/admin/connectors/guru/page.tsx
deleted file mode 100644
index 094bbe7c75b..00000000000
--- a/web/src/app/admin/connectors/guru/page.tsx
+++ /dev/null
@@ -1,244 +0,0 @@
-"use client";
-
-import * as Yup from "yup";
-import { GuruIcon, TrashIcon } from "@/components/icons/icons";
-import { TextFormField } from "@/components/admin/connectors/Field";
-import { HealthCheckBanner } from "@/components/health/healthcheck";
-import { CredentialForm } from "@/components/admin/connectors/CredentialForm";
-import {
- Credential,
- ConnectorIndexingStatus,
- GuruConfig,
- GuruCredentialJson,
-} from "@/lib/types";
-import useSWR, { useSWRConfig } from "swr";
-import { errorHandlingFetcher } from "@/lib/fetcher";
-import { ErrorCallout } from "@/components/ErrorCallout";
-import { LoadingAnimation } from "@/components/Loading";
-import { adminDeleteCredential, linkCredential } from "@/lib/credential";
-import { ConnectorForm } from "@/components/admin/connectors/ConnectorForm";
-import { ConnectorsTable } from "@/components/admin/connectors/table/ConnectorsTable";
-import { usePopup } from "@/components/admin/connectors/Popup";
-import { usePublicCredentials } from "@/lib/hooks";
-import { AdminPageTitle } from "@/components/admin/Title";
-import { Card, Text, Title } from "@tremor/react";
-
-const Main = () => {
- const { popup, setPopup } = usePopup();
-
- const { mutate } = useSWRConfig();
- const {
- data: connectorIndexingStatuses,
- isLoading: isConnectorIndexingStatusesLoading,
- error: connectorIndexingStatusesError,
- } = useSWR[]>(
- "/api/manage/admin/connector/indexing-status",
- errorHandlingFetcher
- );
-
- const {
- data: credentialsData,
- isLoading: isCredentialsLoading,
- isValidating: isCredentialsValidating,
- error: credentialsError,
- refreshCredentials,
- } = usePublicCredentials();
-
- if (
- isConnectorIndexingStatusesLoading ||
- isCredentialsLoading ||
- isCredentialsValidating
- ) {
- return ;
- }
-
- if (connectorIndexingStatusesError || !connectorIndexingStatuses) {
- return (
-
- );
- }
-
- if (credentialsError || !credentialsData) {
- return (
-
- );
- }
-
- const guruConnectorIndexingStatuses: ConnectorIndexingStatus<
- GuruConfig,
- GuruCredentialJson
- >[] = connectorIndexingStatuses.filter(
- (connectorIndexingStatus) =>
- connectorIndexingStatus.connector.source === "guru"
- );
- const guruCredential: Credential | undefined =
- credentialsData.find((credential) => credential.credential_json?.guru_user);
-
- return (
- <>
- {popup}
-
- This connector allows you to sync all your Guru Cards into Danswer.
-
-
-
- Step 1: Provide your Credentials
-
-
- {guruCredential ? (
- <>
-
- Existing Access Token:
-
- {guruCredential.credential_json?.guru_user_token}
-
-
-
- >
- ) : (
- <>
-
- To use the Guru connector, first follow the guide{" "}
-
- here
- {" "}
- to generate a User Token.
-
-
-
- formBody={
- <>
-
-
- >
- }
- validationSchema={Yup.object().shape({
- guru_user: Yup.string().required(
- "Please enter your Guru username"
- ),
- guru_user_token: Yup.string().required(
- "Please enter your Guru access token"
- ),
- })}
- initialValues={{
- guru_user: "",
- guru_user_token: "",
- }}
- onSubmit={(isSuccess) => {
- if (isSuccess) {
- refreshCredentials();
- }
- }}
- />
-
- >
- )}
-
-
- Step 2: Start indexing!
-
- {guruCredential ? (
- !guruConnectorIndexingStatuses.length ? (
- <>
-
- Click the button below to start indexing! We will pull the latest
- features, components, and products from Guru every 10{" "}
- minutes.
-
-
-
- nameBuilder={() => "GuruConnector"}
- ccPairNameBuilder={() => "Guru"}
- source="guru"
- inputType="poll"
- formBody={null}
- validationSchema={Yup.object().shape({})}
- initialValues={{}}
- refreshFreq={10 * 60} // 10 minutes
- credentialId={guruCredential.id}
- />
-
- >
- ) : (
- <>
-
- Guru connector is setup! We are pulling the latest cards from Guru
- every 10 minutes.
-
-
- connectorIndexingStatuses={guruConnectorIndexingStatuses}
- liveCredential={guruCredential}
- getCredential={(credential) => {
- return (
-
-
{credential.credential_json.guru_user}
-
- );
- }}
- onCredentialLink={async (connectorId) => {
- if (guruCredential) {
- await linkCredential(connectorId, guruCredential.id);
- mutate("/api/manage/admin/connector/indexing-status");
- }
- }}
- onUpdate={() =>
- mutate("/api/manage/admin/connector/indexing-status")
- }
- />
- >
- )
- ) : (
- <>
-
- Please provide your access token in Step 1 first! Once done with
- that, you can then start indexing all your Guru cards.
-
- >
- )}
- >
- );
-};
-
-export default function Page() {
- return (
-
-
-
-
-
-
} title="Guru" />
-
-
-
- );
-}
diff --git a/web/src/app/admin/connectors/hubspot/page.tsx b/web/src/app/admin/connectors/hubspot/page.tsx
deleted file mode 100644
index 199c027b6e7..00000000000
--- a/web/src/app/admin/connectors/hubspot/page.tsx
+++ /dev/null
@@ -1,232 +0,0 @@
-"use client";
-
-import * as Yup from "yup";
-import { HubSpotIcon, TrashIcon } from "@/components/icons/icons";
-import { TextFormField } from "@/components/admin/connectors/Field";
-import { HealthCheckBanner } from "@/components/health/healthcheck";
-import { CredentialForm } from "@/components/admin/connectors/CredentialForm";
-import {
- Credential,
- ConnectorIndexingStatus,
- HubSpotConfig,
- HubSpotCredentialJson,
-} from "@/lib/types";
-import useSWR, { useSWRConfig } from "swr";
-import { errorHandlingFetcher } from "@/lib/fetcher";
-import { ErrorCallout } from "@/components/ErrorCallout";
-import { LoadingAnimation } from "@/components/Loading";
-import { adminDeleteCredential, linkCredential } from "@/lib/credential";
-import { ConnectorForm } from "@/components/admin/connectors/ConnectorForm";
-import { ConnectorsTable } from "@/components/admin/connectors/table/ConnectorsTable";
-import { usePopup } from "@/components/admin/connectors/Popup";
-import { usePublicCredentials } from "@/lib/hooks";
-import { AdminPageTitle } from "@/components/admin/Title";
-import { Card, Text, Title } from "@tremor/react";
-
-const Main = () => {
- const { popup, setPopup } = usePopup();
-
- const { mutate } = useSWRConfig();
- const {
- data: connectorIndexingStatuses,
- isLoading: isConnectorIndexingStatusesLoading,
- error: connectorIndexingStatusesError,
- } = useSWR[]>(
- "/api/manage/admin/connector/indexing-status",
- errorHandlingFetcher
- );
-
- const {
- data: credentialsData,
- isLoading: isCredentialsLoading,
- isValidating: isCredentialsValidating,
- error: credentialsError,
- refreshCredentials,
- } = usePublicCredentials();
-
- if (
- isConnectorIndexingStatusesLoading ||
- isCredentialsLoading ||
- isCredentialsValidating
- ) {
- return ;
- }
-
- if (connectorIndexingStatusesError || !connectorIndexingStatuses) {
- return (
-
- );
- }
-
- if (credentialsError || !credentialsData) {
- return (
-
- );
- }
-
- const hubSpotConnectorIndexingStatuses: ConnectorIndexingStatus<
- HubSpotConfig,
- HubSpotCredentialJson
- >[] = connectorIndexingStatuses.filter(
- (connectorIndexingStatus) =>
- connectorIndexingStatus.connector.source === "hubspot"
- );
- const hubSpotCredential: Credential =
- credentialsData.filter(
- (credential) => credential.credential_json?.hubspot_access_token
- )[0];
-
- return (
- <>
- {popup}
-
- This connector allows you to sync all your HubSpot Tickets into Danswer.
-
-
-
- Step 1: Provide your Credentials
-
-
- {hubSpotCredential ? (
- <>
-
- Existing Access Token:
-
- {hubSpotCredential.credential_json?.hubspot_access_token}
-
-
-
- >
- ) : (
- <>
-
- To use the HubSpot connector, provide the HubSpot Access Token.
-
-
-
- formBody={
- <>
-
- >
- }
- validationSchema={Yup.object().shape({
- hubspot_access_token: Yup.string().required(
- "Please enter your HubSpot Access Token"
- ),
- })}
- initialValues={{
- hubspot_access_token: "",
- }}
- onSubmit={(isSuccess) => {
- if (isSuccess) {
- refreshCredentials();
- }
- }}
- />
-
- >
- )}
-
-
- Step 2: Start indexing!
-
- {hubSpotCredential ? (
- !hubSpotConnectorIndexingStatuses.length ? (
- <>
-
- Click the button below to start indexing! We will pull the latest
- tickets from HubSpot every 10 minutes.
-
-
-
- nameBuilder={() => "HubSpotConnector"}
- ccPairNameBuilder={() => "HubSpotConnector"}
- source="hubspot"
- inputType="poll"
- formBody={null}
- validationSchema={Yup.object().shape({})}
- initialValues={{}}
- refreshFreq={10 * 60} // 10 minutes
- credentialId={hubSpotCredential.id}
- />
-
- >
- ) : (
- <>
-
- HubSpot connector is setup! We are pulling the latest tickets from
- HubSpot every 10 minutes.
-
-
- connectorIndexingStatuses={hubSpotConnectorIndexingStatuses}
- liveCredential={hubSpotCredential}
- getCredential={(credential) => {
- return (
-
-
{credential.credential_json.hubspot_access_token}
-
- );
- }}
- onCredentialLink={async (connectorId) => {
- if (hubSpotCredential) {
- await linkCredential(connectorId, hubSpotCredential.id);
- mutate("/api/manage/admin/connector/indexing-status");
- }
- }}
- onUpdate={() =>
- mutate("/api/manage/admin/connector/indexing-status")
- }
- />
- >
- )
- ) : (
- <>
-
- Please provide your access token in Step 1 first! Once done with
- that, you can then start indexing all your HubSpot tickets.
-
- >
- )}
- >
- );
-};
-
-export default function Page() {
- return (
-
-
-
-
-
-
} title="HubSpot" />
-
-
-
- );
-}
diff --git a/web/src/app/admin/connectors/jira/page.tsx b/web/src/app/admin/connectors/jira/page.tsx
deleted file mode 100644
index f960348e6da..00000000000
--- a/web/src/app/admin/connectors/jira/page.tsx
+++ /dev/null
@@ -1,374 +0,0 @@
-"use client";
-
-import * as Yup from "yup";
-import { JiraIcon, TrashIcon } from "@/components/icons/icons";
-import {
- TextFormField,
- TextArrayFieldBuilder,
-} from "@/components/admin/connectors/Field";
-import { HealthCheckBanner } from "@/components/health/healthcheck";
-import { CredentialForm } from "@/components/admin/connectors/CredentialForm";
-import {
- JiraConfig,
- JiraCredentialJson,
- JiraServerCredentialJson,
- ConnectorIndexingStatus,
-} from "@/lib/types";
-import useSWR, { useSWRConfig } from "swr";
-import { errorHandlingFetcher } from "@/lib/fetcher";
-import { ErrorCallout } from "@/components/ErrorCallout";
-import { LoadingAnimation } from "@/components/Loading";
-import { adminDeleteCredential, linkCredential } from "@/lib/credential";
-import { ConnectorForm } from "@/components/admin/connectors/ConnectorForm";
-import { ConnectorsTable } from "@/components/admin/connectors/table/ConnectorsTable";
-import { usePopup } from "@/components/admin/connectors/Popup";
-import { usePublicCredentials } from "@/lib/hooks";
-import { AdminPageTitle } from "@/components/admin/Title";
-import { Card, Divider, Text, Title } from "@tremor/react";
-
-// Copied from the `extract_jira_project` function
-const extractJiraProject = (url: string): string | null => {
- const parsedUrl = new URL(url);
- const splitPath = parsedUrl.pathname.split("/");
- const projectPos = splitPath.indexOf("projects");
- if (projectPos !== -1 && splitPath.length > projectPos + 1) {
- const jiraProject = splitPath[projectPos + 1];
- return jiraProject;
- }
- return null;
-};
-
-const Main = () => {
- const { popup, setPopup } = usePopup();
-
- const { mutate } = useSWRConfig();
- const {
- data: connectorIndexingStatuses,
- isLoading: isConnectorIndexingStatusesLoading,
- error: connectorIndexingStatusesError,
- } = useSWR[]>(
- "/api/manage/admin/connector/indexing-status",
- errorHandlingFetcher
- );
- const {
- data: credentialsData,
- isLoading: isCredentialsLoading,
- error: credentialsError,
- isValidating: isCredentialsValidating,
- refreshCredentials,
- } = usePublicCredentials();
-
- if (
- isConnectorIndexingStatusesLoading ||
- isCredentialsLoading ||
- isCredentialsValidating
- ) {
- return ;
- }
-
- if (connectorIndexingStatusesError || !connectorIndexingStatuses) {
- return (
-
- );
- }
-
- if (credentialsError || !credentialsData) {
- return (
-
- );
- }
-
- const jiraConnectorIndexingStatuses: ConnectorIndexingStatus<
- JiraConfig,
- JiraCredentialJson | JiraServerCredentialJson
- >[] = connectorIndexingStatuses.filter(
- (connectorIndexingStatus) =>
- connectorIndexingStatus.connector.source === "jira"
- );
- const jiraCredential = credentialsData.filter(
- (credential) => credential.credential_json?.jira_api_token
- )[0];
-
- return (
- <>
- {popup}
-
- Step 1: Provide your Credentials
-
-
- {jiraCredential ? (
- <>
-
-
Existing Access Token:
-
- {jiraCredential.credential_json?.jira_api_token}
-
-
-
- >
- ) : (
- <>
-
- To use the Jira connector, first follow the guide{" "}
-
- here
- {" "}
- to generate an Access Token (for cloud) or Personal Access Token
- (for server). Submit only one form.
-
- Cloud
-
-
- formBody={
- <>
-
-
- >
- }
- validationSchema={Yup.object().shape({
- jira_user_email: Yup.string().required(
- "Please enter your username on Jira"
- ),
- jira_api_token: Yup.string().required(
- "Please enter your Jira access token"
- ),
- })}
- initialValues={{
- jira_user_email: "",
- jira_api_token: "",
- }}
- onSubmit={(isSuccess) => {
- if (isSuccess) {
- refreshCredentials();
- }
- }}
- />
-
- Server
-
-
- formBody={
- <>
-
- >
- }
- validationSchema={Yup.object().shape({
- jira_api_token: Yup.string().required(
- "Please enter your Jira personal access token"
- ),
- })}
- initialValues={{
- jira_api_token: "",
- }}
- onSubmit={(isSuccess) => {
- if (isSuccess) {
- refreshCredentials();
- }
- }}
- />
-
- >
- )}
-
- {/* TODO: make this periodic */}
-
- Step 2: Which spaces do you want to make searchable?
-
- {jiraCredential ? (
- <>
- {" "}
-
- Specify any link to a Jira page below and click "Index" to
- Index. Based on the provided link, we will index the ENTIRE PROJECT,
- not just the specified page. For example, entering{" "}
-
- https://danswer.atlassian.net/jira/software/projects/DAN/boards/1
- {" "}
- and clicking the Index button will index the whole DAN Jira
- project.
-
- {jiraConnectorIndexingStatuses.length > 0 && (
- <>
-
- We pull the latest pages and comments from each space listed
- below every 10 minutes.
-
-
-
- connectorIndexingStatuses={jiraConnectorIndexingStatuses}
- liveCredential={jiraCredential}
- getCredential={(credential) => {
- return (
-
-
{credential.credential_json.jira_api_token}
-
- );
- }}
- onCredentialLink={async (connectorId) => {
- if (jiraCredential) {
- await linkCredential(connectorId, jiraCredential.id);
- mutate("/api/manage/admin/connector/indexing-status");
- }
- }}
- specialColumns={[
- {
- header: "Url",
- key: "url",
- getValue: (ccPairStatus) => {
- const connectorConfig =
- ccPairStatus.connector.connector_specific_config;
- return (
-
- {connectorConfig.jira_project_url}
-
- );
- },
- },
- {
- header: "Disable comments from users",
- key: "comment_email_blacklist",
- getValue: (ccPairStatus) => {
- const connectorConfig =
- ccPairStatus.connector.connector_specific_config;
- return connectorConfig.comment_email_blacklist &&
- connectorConfig.comment_email_blacklist.length > 0
- ? connectorConfig.comment_email_blacklist.join(", ")
- : "";
- },
- },
- ]}
- onUpdate={() =>
- mutate("/api/manage/admin/connector/indexing-status")
- }
- />
-
-
- >
- )}
-
- Add a New Project
-
- nameBuilder={(values) =>
- `JiraConnector-${values.jira_project_url}`
- }
- ccPairNameBuilder={(values) =>
- extractJiraProject(values.jira_project_url)
- }
- credentialId={jiraCredential.id}
- source="jira"
- inputType="poll"
- formBody={
- <>
-
- >
- }
- formBodyBuilder={(values) => {
- return (
- <>
-
- {TextArrayFieldBuilder({
- name: "comment_email_blacklist",
- label: "Disable comments from users:",
- subtext: `
- This is generally useful to ignore certain bots. Add user emails which comments should NOT be indexed.`,
- })(values)}
- >
- );
- }}
- validationSchema={Yup.object().shape({
- jira_project_url: Yup.string().required(
- "Please enter any link to your jira project e.g. https://danswer.atlassian.net/jira/software/projects/DAN/boards/1"
- ),
- comment_email_blacklist: Yup.array()
- .of(Yup.string().required("Emails names must be strings"))
- .required(),
- })}
- initialValues={{
- jira_project_url: "",
- comment_email_blacklist: [],
- }}
- refreshFreq={10 * 60} // 10 minutes
- />
-
- >
- ) : (
- <>
-
- Please provide your access token in Step 1 first! Once done with
- that, you can then specify which Jira projects you want to make
- searchable.
-
- >
- )}
- >
- );
-};
-
-export default function Page() {
- return (
-
-
-
-
-
-
} title="Jira" />
-
-
-
- );
-}
diff --git a/web/src/app/admin/connectors/linear/page.tsx b/web/src/app/admin/connectors/linear/page.tsx
deleted file mode 100644
index 6af018729dc..00000000000
--- a/web/src/app/admin/connectors/linear/page.tsx
+++ /dev/null
@@ -1,236 +0,0 @@
-"use client";
-
-import * as Yup from "yup";
-import { LinearIcon, TrashIcon } from "@/components/icons/icons";
-import { TextFormField } from "@/components/admin/connectors/Field";
-import { HealthCheckBanner } from "@/components/health/healthcheck";
-import { CredentialForm } from "@/components/admin/connectors/CredentialForm";
-import {
- Credential,
- ConnectorIndexingStatus,
- LinearCredentialJson,
-} from "@/lib/types";
-import useSWR, { useSWRConfig } from "swr";
-import { errorHandlingFetcher } from "@/lib/fetcher";
-import { ErrorCallout } from "@/components/ErrorCallout";
-import { LoadingAnimation } from "@/components/Loading";
-import { adminDeleteCredential, linkCredential } from "@/lib/credential";
-import { ConnectorForm } from "@/components/admin/connectors/ConnectorForm";
-import { ConnectorsTable } from "@/components/admin/connectors/table/ConnectorsTable";
-import { usePopup } from "@/components/admin/connectors/Popup";
-import { usePublicCredentials } from "@/lib/hooks";
-import { Card, Text, Title } from "@tremor/react";
-import { AdminPageTitle } from "@/components/admin/Title";
-
-const Main = () => {
- const { popup, setPopup } = usePopup();
-
- const { mutate } = useSWRConfig();
- const {
- data: connectorIndexingStatuses,
- isLoading: isConnectorIndexingStatusesLoading,
- error: connectorIndexingStatusesError,
- } = useSWR[]>(
- "/api/manage/admin/connector/indexing-status",
- errorHandlingFetcher
- );
- const {
- data: credentialsData,
- isLoading: isCredentialsLoading,
- error: credentialsError,
- refreshCredentials,
- } = usePublicCredentials();
-
- if (
- (!connectorIndexingStatuses && isConnectorIndexingStatusesLoading) ||
- (!credentialsData && isCredentialsLoading)
- ) {
- return ;
- }
-
- if (connectorIndexingStatusesError || !connectorIndexingStatuses) {
- return (
-
- );
- }
-
- if (credentialsError || !credentialsData) {
- return (
-
- );
- }
-
- const linearConnectorIndexingStatuses: ConnectorIndexingStatus<
- {},
- LinearCredentialJson
- >[] = connectorIndexingStatuses.filter(
- (connectorIndexingStatus) =>
- connectorIndexingStatus.connector.source === "linear"
- );
- const linearCredential: Credential | undefined =
- credentialsData.find(
- (credential) => credential.credential_json?.linear_api_key
- );
-
- return (
- <>
- {popup}
-
- Step 1: Provide your Credentials
-
-
- {linearCredential ? (
- <>
-
- Existing API Key:
-
- {linearCredential.credential_json?.linear_api_key}
-
-
-
- >
- ) : (
- <>
-
- To use the Linear connector, first follow the guide{" "}
-
- here
- {" "}
- to generate an API Key.
-
-
-
- formBody={
- <>
-
- >
- }
- validationSchema={Yup.object().shape({
- linear_api_key: Yup.string().required(
- "Please enter your Linear API Key!"
- ),
- })}
- initialValues={{
- linear_api_key: "",
- }}
- onSubmit={(isSuccess) => {
- if (isSuccess) {
- refreshCredentials();
- }
- }}
- />
-
- >
- )}
-
-
- Step 2: Start indexing
-
- {linearCredential ? (
- <>
- {linearConnectorIndexingStatuses.length > 0 ? (
- <>
-
- We pull the latest issues and comments every{" "}
- 10 minutes.
-
-
-
- connectorIndexingStatuses={linearConnectorIndexingStatuses}
- liveCredential={linearCredential}
- getCredential={(credential) => {
- return (
-
-
{credential.credential_json.linear_api_key}
-
- );
- }}
- onCredentialLink={async (connectorId) => {
- if (linearCredential) {
- await linkCredential(connectorId, linearCredential.id);
- mutate("/api/manage/admin/connector/indexing-status");
- }
- }}
- onUpdate={() =>
- mutate("/api/manage/admin/connector/indexing-status")
- }
- />
-
- >
- ) : (
-
- Create Connector
-
- Press connect below to start the connection Linear. We pull the
- latest issues and comments every 10{" "}
- minutes.
-
-
- nameBuilder={() => "LinearConnector"}
- ccPairNameBuilder={() => "Linear"}
- source="linear"
- inputType="poll"
- formBody={<>>}
- validationSchema={Yup.object().shape({})}
- initialValues={{}}
- refreshFreq={10 * 60} // 10 minutes
- credentialId={linearCredential.id}
- />
-
- )}
- >
- ) : (
- <>
-
- Please provide your access token in Step 1 first! Once done with
- that, you can then start indexing Linear.
-
- >
- )}
- >
- );
-};
-
-export default function Page() {
- return (
-
-
-
-
-
-
} title="Linear" />
-
-
-
- );
-}
diff --git a/web/src/app/admin/connectors/loopio/page.tsx b/web/src/app/admin/connectors/loopio/page.tsx
deleted file mode 100644
index 920d15b8246..00000000000
--- a/web/src/app/admin/connectors/loopio/page.tsx
+++ /dev/null
@@ -1,263 +0,0 @@
-"use client";
-
-import * as Yup from "yup";
-import { LoopioIcon, TrashIcon } from "@/components/icons/icons";
-import { TextFormField } from "@/components/admin/connectors/Field";
-import { HealthCheckBanner } from "@/components/health/healthcheck";
-import { CredentialForm } from "@/components/admin/connectors/CredentialForm";
-import {
- Credential,
- ConnectorIndexingStatus,
- LoopioConfig,
- LoopioCredentialJson,
-} from "@/lib/types";
-import useSWR, { useSWRConfig } from "swr";
-import { errorHandlingFetcher } from "@/lib/fetcher";
-import { ErrorCallout } from "@/components/ErrorCallout";
-import { LoadingAnimation } from "@/components/Loading";
-import { adminDeleteCredential, linkCredential } from "@/lib/credential";
-import { ConnectorForm } from "@/components/admin/connectors/ConnectorForm";
-import { ConnectorsTable } from "@/components/admin/connectors/table/ConnectorsTable";
-import { usePopup } from "@/components/admin/connectors/Popup";
-import { usePublicCredentials } from "@/lib/hooks";
-
-const Main = () => {
- const { popup, setPopup } = usePopup();
-
- const { mutate } = useSWRConfig();
- const {
- data: connectorIndexingStatuses,
- isLoading: isConnectorIndexingStatusesLoading,
- error: connectorIndexingStatusesError,
- } = useSWR[]>(
- "/api/manage/admin/connector/indexing-status",
- errorHandlingFetcher
- );
-
- const {
- data: credentialsData,
- isLoading: isCredentialsLoading,
- isValidating: isCredentialsValidating,
- error: credentialsError,
- refreshCredentials,
- } = usePublicCredentials();
-
- if (
- isConnectorIndexingStatusesLoading ||
- isCredentialsLoading ||
- isCredentialsValidating
- ) {
- return ;
- }
-
- if (connectorIndexingStatusesError || !connectorIndexingStatuses) {
- return (
-
- );
- }
-
- if (credentialsError || !credentialsData) {
- return (
-
- );
- }
-
- const loopioConnectorIndexingStatuses: ConnectorIndexingStatus<
- LoopioConfig,
- LoopioCredentialJson
- >[] = connectorIndexingStatuses.filter(
- (connectorIndexingStatus) =>
- connectorIndexingStatus.connector.source === "loopio"
- );
- const loopioCredential: Credential | undefined =
- credentialsData.find(
- (credential) => credential.credential_json?.loopio_client_id
- );
-
- return (
- <>
- {popup}
-
- This connector allows you to sync your Loopio Library Entries into
- Danswer
-
-
-
- Step 1: Provide your API Access info
-
-
- {loopioCredential ? (
- <>
-
-
Existing App Key Secret:
-
- {loopioCredential.credential_json?.loopio_client_token}
-
-
-
- >
- ) : (
- <>
-
-
- formBody={
- <>
-
-
-
- >
- }
- validationSchema={Yup.object().shape({
- loopio_subdomain: Yup.string().required(
- "Please enter your Loopio Account subdomain"
- ),
- loopio_client_id: Yup.string().required(
- "Please enter your Loopio App Key ID"
- ),
- loopio_client_token: Yup.string().required(
- "Please enter your Loopio App Key Secret"
- ),
- })}
- initialValues={{
- loopio_subdomain: "",
- loopio_client_id: "",
- loopio_client_token: "",
- }}
- onSubmit={(isSuccess) => {
- if (isSuccess) {
- refreshCredentials();
- }
- }}
- />
-
- >
- )}
-
-
- Step 2: Which Stack do you want to make searchable?
-
-
- Leave this blank if you want to index all stacks.
-
-
- {loopioConnectorIndexingStatuses.length > 0 && (
- <>
-
- We pull the latest library entries every 24 hours.
-
-
-
- connectorIndexingStatuses={loopioConnectorIndexingStatuses}
- liveCredential={loopioCredential}
- getCredential={(credential) =>
- credential.credential_json.loopio_client_id
- }
- specialColumns={[
- {
- header: "Stack",
- key: "loopio_stack_name",
- getValue: (ccPairStatus) =>
- ccPairStatus.connector.connector_specific_config
- .loopio_stack_name || "All stacks",
- },
- ]}
- includeName={true}
- onUpdate={() =>
- mutate("/api/manage/admin/connector/indexing-status")
- }
- onCredentialLink={async (connectorId) => {
- if (loopioCredential) {
- await linkCredential(connectorId, loopioCredential.id);
- mutate("/api/manage/admin/connector/indexing-status");
- }
- }}
- />
-
- >
- )}
-
- {loopioCredential ? (
- <>
-
-
Create a new Loopio Connector
-
- nameBuilder={(values) =>
- values?.loopio_stack_name
- ? `LoopioConnector-${values.loopio_stack_name}-Stack`
- : `LoopioConnector-AllStacks`
- }
- source="loopio"
- inputType="poll"
- formBody={
- <>
-
- >
- }
- validationSchema={Yup.object().shape({
- loopio_stack_name: Yup.string(),
- })}
- initialValues={{
- loopio_stack_name: "",
- }}
- refreshFreq={60 * 60 * 24} // 24 hours
- credentialId={loopioCredential.id}
- />
-
- >
- ) : (
-
- Please provide your API Access Info in Step 1 first! Once done with
- that, you can start indexing your Loopio library.
-
- )}
- >
- );
-};
-
-export default function Page() {
- return (
-
- );
-}
diff --git a/web/src/app/admin/connectors/mediawiki/page.tsx b/web/src/app/admin/connectors/mediawiki/page.tsx
deleted file mode 100644
index e0c17a6e72d..00000000000
--- a/web/src/app/admin/connectors/mediawiki/page.tsx
+++ /dev/null
@@ -1,219 +0,0 @@
-"use client";
-
-import * as Yup from "yup";
-import { MediaWikiIcon, TrashIcon } from "@/components/icons/icons";
-import {
- TextArrayField,
- TextArrayFieldBuilder,
- TextFormField,
-} from "@/components/admin/connectors/Field";
-import { HealthCheckBanner } from "@/components/health/healthcheck";
-import { CredentialForm } from "@/components/admin/connectors/CredentialForm";
-import {
- MediaWikiCredentialJson,
- MediaWikiConfig,
- ConnectorIndexingStatus,
- Credential,
-} from "@/lib/types";
-import useSWR, { useSWRConfig } from "swr";
-import { errorHandlingFetcher } from "@/lib/fetcher";
-import { ErrorCallout } from "@/components/ErrorCallout";
-import { LoadingAnimation } from "@/components/Loading";
-import { adminDeleteCredential, linkCredential } from "@/lib/credential";
-import { ConnectorForm } from "@/components/admin/connectors/ConnectorForm";
-import { ConnectorsTable } from "@/components/admin/connectors/table/ConnectorsTable";
-import { usePopup } from "@/components/admin/connectors/Popup";
-import { usePublicCredentials } from "@/lib/hooks";
-import { AdminPageTitle } from "@/components/admin/Title";
-import { Card, Text, Title } from "@tremor/react";
-
-const Main = () => {
- const { popup, setPopup } = usePopup();
-
- const { mutate } = useSWRConfig();
- const {
- data: connectorIndexingStatuses,
- isLoading: isConnectorIndexingStatusesLoading,
- error: connectorIndexingStatusesError,
- } = useSWR[]>(
- "/api/manage/admin/connector/indexing-status",
- errorHandlingFetcher
- );
- const {
- data: credentialsData,
- isLoading: isCredentialsLoading,
- error: credentialsError,
- refreshCredentials,
- } = usePublicCredentials();
-
- if (
- (!connectorIndexingStatuses && isConnectorIndexingStatusesLoading) ||
- (!credentialsData && isCredentialsLoading)
- ) {
- return ;
- }
-
- if (connectorIndexingStatusesError || !connectorIndexingStatuses) {
- return (
-
- );
- }
-
- if (credentialsError || !credentialsData) {
- return (
-
- );
- }
-
- const mediawikiConnectorIndexingStatuses: ConnectorIndexingStatus<
- MediaWikiConfig,
- MediaWikiCredentialJson
- >[] = connectorIndexingStatuses.filter(
- (connectorIndexingStatus) =>
- connectorIndexingStatus.connector.source === "mediawiki"
- );
- const mediawikiCredential: Credential | undefined =
- credentialsData.find((credential) => true);
-
- return (
- <>
- {popup}
- {mediawikiConnectorIndexingStatuses.length > 0 && (
- <>
-
- MediaWiki indexing status
-
-
- The latest page, chapter, book and shelf changes are fetched every
- 10 minutes.
-
-
-
- connectorIndexingStatuses={mediawikiConnectorIndexingStatuses}
- liveCredential={mediawikiCredential}
- getCredential={(credential) => {
- return ;
- }}
- onCredentialLink={async (connectorId) => {
- if (mediawikiCredential) {
- await linkCredential(connectorId, mediawikiCredential.id);
- mutate("/api/manage/admin/connector/indexing-status");
- }
- }}
- onUpdate={() =>
- mutate("/api/manage/admin/connector/indexing-status")
- }
- />
-
- >
- )}
-
- {mediawikiCredential && (
- <>
-
- Create Connection
-
- Press connect below to start the connection to your MediaWiki
- instance.
-
-
- nameBuilder={(values) =>
- `MediaWikiConnector-${values.connector_name}`
- }
- ccPairNameBuilder={(values) =>
- `MediaWikiConnector-${values.connector_name}`
- }
- source="mediawiki"
- inputType="poll"
- formBodyBuilder={(values) => (
-
-
-
-
- {TextArrayFieldBuilder({
- name: "pages",
- label: "Pages to index:",
- subtext:
- "Specify 0 or more names of pages to index. Only specify the name of the page, not its url.",
- })(values)}
- {TextArrayFieldBuilder({
- name: "categories",
- label: "Categories to index:",
- subtext:
- "Specify 0 or more names of categories to index. For most MediaWiki sites, these are pages" +
- " with a name of the form 'Category: XYZ', that are lists of other pages/categories. Only" +
- " specify the name of the category, not its url.",
- })(values)}
-
-
- )}
- validationSchema={Yup.object().shape({
- connector_name: Yup.string().required(
- "Please enter a name for your MediaWiki connector."
- ),
- hostname: Yup.string().required(
- "Please enter the base URL for your MediaWiki site"
- ),
- language_code: Yup.string().default("en"),
- categories: Yup.array().of(
- Yup.string().required(
- "Please enter categories to index from your MediaWiki site"
- )
- ),
- pages: Yup.array().of(
- Yup.string().required(
- "Please enter pages to index from your MediaWiki site"
- )
- ),
- recurse_depth: Yup.number().required(
- "Please enter the recursion depth for your MediaWiki site."
- ),
- })}
- initialValues={{
- connector_name: "",
- hostname: "",
- language_code: "en",
- categories: [],
- pages: [],
- recurse_depth: 0,
- }}
- refreshFreq={10 * 60} // 10 minutes
- credentialId={mediawikiCredential.id}
- />
-
- >
- )}
- >
- );
-};
-
-export default function Page() {
- return (
-
-
-
-
-
-
} title="MediaWiki" />
-
-
-
- );
-}
diff --git a/web/src/app/admin/connectors/notion/page.tsx b/web/src/app/admin/connectors/notion/page.tsx
deleted file mode 100644
index aa205dce0ee..00000000000
--- a/web/src/app/admin/connectors/notion/page.tsx
+++ /dev/null
@@ -1,272 +0,0 @@
-"use client";
-
-import * as Yup from "yup";
-import { NotionIcon, TrashIcon } from "@/components/icons/icons";
-import { TextFormField } from "@/components/admin/connectors/Field";
-import { HealthCheckBanner } from "@/components/health/healthcheck";
-import { CredentialForm } from "@/components/admin/connectors/CredentialForm";
-import {
- NotionCredentialJson,
- NotionConfig,
- Credential,
- ConnectorIndexingStatus,
-} from "@/lib/types";
-import useSWR, { useSWRConfig } from "swr";
-import { errorHandlingFetcher } from "@/lib/fetcher";
-import { ErrorCallout } from "@/components/ErrorCallout";
-import { LoadingAnimation } from "@/components/Loading";
-import { adminDeleteCredential, linkCredential } from "@/lib/credential";
-import { ConnectorForm } from "@/components/admin/connectors/ConnectorForm";
-import { ConnectorsTable } from "@/components/admin/connectors/table/ConnectorsTable";
-import { usePopup } from "@/components/admin/connectors/Popup";
-import { usePublicCredentials } from "@/lib/hooks";
-import { AdminPageTitle } from "@/components/admin/Title";
-import { Card, Divider, Text, Title } from "@tremor/react";
-
-const Main = () => {
- const { popup, setPopup } = usePopup();
-
- const { mutate } = useSWRConfig();
- const {
- data: connectorIndexingStatuses,
- isLoading: isConnectorIndexingStatusesLoading,
- error: connectorIndexingStatusesError,
- } = useSWR[]>(
- "/api/manage/admin/connector/indexing-status",
- errorHandlingFetcher
- );
- const {
- data: credentialsData,
- isLoading: isCredentialsLoading,
- error: credentialsError,
- refreshCredentials,
- } = usePublicCredentials();
-
- if (
- (!connectorIndexingStatuses && isConnectorIndexingStatusesLoading) ||
- (!credentialsData && isCredentialsLoading)
- ) {
- return ;
- }
-
- if (connectorIndexingStatusesError || !connectorIndexingStatuses) {
- return (
-
- );
- }
-
- if (credentialsError || !credentialsData) {
- return (
-
- );
- }
-
- const notionConnectorIndexingStatuses: ConnectorIndexingStatus<
- NotionConfig,
- NotionCredentialJson
- >[] = connectorIndexingStatuses.filter(
- (connectorIndexingStatus) =>
- connectorIndexingStatus.connector.source === "notion"
- );
- const notionCredential: Credential | undefined =
- credentialsData.find(
- (credential) => credential.credential_json?.notion_integration_token
- );
-
- return (
- <>
- {popup}
-
- Step 1: Provide your authorization details
-
-
- {notionCredential ? (
- <>
-
-
Existing Integration Token:
-
- {notionCredential.credential_json?.notion_integration_token}
-
-
-
- >
- ) : (
- <>
-
- To get started you'll need to create an internal integration in
- Notion for Danswer. Follow the instructions in the
-
- Notion Developer Documentation
-
- on the Notion website, to create a new integration. Once
- you've created an integration, copy the integration secret
- token and paste it below. Follow the remaining instructions on the
- Notion docs to allow Danswer to read Notion Databases and Pages
- using the new integration.
-
-
-
- formBody={
-
- }
- validationSchema={Yup.object().shape({
- notion_integration_token: Yup.string().required(
- "Please enter the Notion Integration token for the Danswer integration."
- ),
- })}
- initialValues={{
- notion_integration_token: "",
- }}
- onSubmit={(isSuccess) => {
- if (isSuccess) {
- refreshCredentials();
- mutate("/api/manage/admin/connector/indexing-status");
- }
- }}
- />
-
- >
- )}
-
-
- Step 2: Manage Connectors
-
- {notionConnectorIndexingStatuses.length > 0 && (
- <>
-
- The latest page updates are fetched from Notion every 10 minutes.
-
-
-
- connectorIndexingStatuses={notionConnectorIndexingStatuses}
- specialColumns={[
- {
- header: "Root Page ID",
- key: "root_page_id",
- getValue: (ccPairStatus) =>
- ccPairStatus.connector.connector_specific_config
- .root_page_id || "-",
- },
- ]}
- liveCredential={notionCredential}
- getCredential={(credential) => {
- return (
-
-
{credential.credential_json.notion_integration_token}
-
- );
- }}
- onCredentialLink={async (connectorId) => {
- if (notionCredential) {
- await linkCredential(connectorId, notionCredential.id);
- mutate("/api/manage/admin/connector/indexing-status");
- }
- }}
- onUpdate={() =>
- mutate("/api/manage/admin/connector/indexing-status")
- }
- />
-
-
- >
- )}
-
- {notionCredential && (
- <>
-
- Create New Connection
-
- Press connect below to start the connection to Notion.
-
-
- nameBuilder={(values) =>
- values.root_page_id
- ? `NotionConnector-${values.root_page_id}`
- : "NotionConnector"
- }
- ccPairNameBuilder={(values) =>
- values.root_page_id ? `Notion-${values.root_page_id}` : "Notion"
- }
- source="notion"
- inputType="poll"
- formBody={
- <>
-
- >
- }
- validationSchema={Yup.object().shape({
- root_page_id: Yup.string(),
- })}
- initialValues={{
- root_page_id: "",
- }}
- refreshFreq={10 * 60} // 10 minutes
- credentialId={notionCredential.id}
- />
-
- >
- )}
-
- {!notionCredential && (
- <>
-
- Please provide your integration details in Step 1 first! Once done
- with that, you'll be able to start the connection then see
- indexing status.
-
- >
- )}
- >
- );
-};
-
-export default function Page() {
- return (
-
-
-
-
-
-
} title="Notion" />
-
-
-
- );
-}
diff --git a/web/src/app/admin/connectors/oracle-storage/page.tsx b/web/src/app/admin/connectors/oracle-storage/page.tsx
deleted file mode 100644
index 34847a4b9a1..00000000000
--- a/web/src/app/admin/connectors/oracle-storage/page.tsx
+++ /dev/null
@@ -1,272 +0,0 @@
-"use client";
-
-import { AdminPageTitle } from "@/components/admin/Title";
-import { HealthCheckBanner } from "@/components/health/healthcheck";
-import { OCIStorageIcon, TrashIcon } from "@/components/icons/icons";
-import { LoadingAnimation } from "@/components/Loading";
-import { ConnectorForm } from "@/components/admin/connectors/ConnectorForm";
-import { CredentialForm } from "@/components/admin/connectors/CredentialForm";
-import { TextFormField } from "@/components/admin/connectors/Field";
-import { usePopup } from "@/components/admin/connectors/Popup";
-import { ConnectorsTable } from "@/components/admin/connectors/table/ConnectorsTable";
-import { adminDeleteCredential, linkCredential } from "@/lib/credential";
-import { errorHandlingFetcher } from "@/lib/fetcher";
-import { ErrorCallout } from "@/components/ErrorCallout";
-import { usePublicCredentials } from "@/lib/hooks";
-
-import {
- ConnectorIndexingStatus,
- Credential,
- OCIConfig,
- OCICredentialJson,
- R2Config,
- R2CredentialJson,
-} from "@/lib/types";
-import { Card, Select, SelectItem, Text, Title } from "@tremor/react";
-import useSWR, { useSWRConfig } from "swr";
-import * as Yup from "yup";
-import { useState } from "react";
-
-const OCIMain = () => {
- const { popup, setPopup } = usePopup();
-
- const { mutate } = useSWRConfig();
- const {
- data: connectorIndexingStatuses,
- isLoading: isConnectorIndexingStatusesLoading,
- error: connectorIndexingStatusesError,
- } = useSWR[]>(
- "/api/manage/admin/connector/indexing-status",
- errorHandlingFetcher
- );
- const {
- data: credentialsData,
- isLoading: isCredentialsLoading,
- error: credentialsError,
- refreshCredentials,
- } = usePublicCredentials();
-
- if (
- (!connectorIndexingStatuses && isConnectorIndexingStatusesLoading) ||
- (!credentialsData && isCredentialsLoading)
- ) {
- return ;
- }
-
- if (connectorIndexingStatusesError || !connectorIndexingStatuses) {
- return (
-
- );
- }
-
- if (credentialsError || !credentialsData) {
- return (
-
- );
- }
-
- const ociConnectorIndexingStatuses: ConnectorIndexingStatus<
- OCIConfig,
- OCICredentialJson
- >[] = connectorIndexingStatuses.filter(
- (connectorIndexingStatus) =>
- connectorIndexingStatus.connector.source === "oci_storage"
- );
-
- const ociCredential: Credential | undefined =
- credentialsData.find((credential) => credential.credential_json?.namespace);
-
- return (
- <>
- {popup}
-
- Step 1: Provide your access info
-
- {ociCredential ? (
- <>
- {" "}
-
-
Existing OCI Access Key ID:
-
- {ociCredential.credential_json.access_key_id}
-
- {", "}
-
Namespace:
-
- {ociCredential.credential_json.namespace}
-
{" "}
-
-
- >
- ) : (
- <>
-
-
- -
- Provide your OCI Access Key ID, Secret Access Key, Namespace,
- and Region for authentication.
-
- -
- These credentials will be used to access your OCI buckets.
-
-
-
-
-
- formBody={
- <>
-
-
-
-
- >
- }
- validationSchema={Yup.object().shape({
- access_key_id: Yup.string().required(
- "OCI Access Key ID is required"
- ),
- secret_access_key: Yup.string().required(
- "OCI Secret Access Key is required"
- ),
- namespace: Yup.string().required("Namespace is required"),
- region: Yup.string().required("Region is required"),
- })}
- initialValues={{
- access_key_id: "",
- secret_access_key: "",
- namespace: "",
- region: "",
- }}
- onSubmit={(isSuccess) => {
- if (isSuccess) {
- refreshCredentials();
- }
- }}
- />
-
- >
- )}
-
-
- Step 2: Which OCI bucket do you want to make searchable?
-
-
- {ociConnectorIndexingStatuses.length > 0 && (
- <>
-
- OCI indexing status
-
-
- The latest changes are fetched every 10 minutes.
-
-
-
- includeName={true}
- connectorIndexingStatuses={ociConnectorIndexingStatuses}
- liveCredential={ociCredential}
- getCredential={(credential) => {
- return ;
- }}
- onCredentialLink={async (connectorId) => {
- if (ociCredential) {
- await linkCredential(connectorId, ociCredential.id);
- mutate("/api/manage/admin/connector/indexing-status");
- }
- }}
- onUpdate={() =>
- mutate("/api/manage/admin/connector/indexing-status")
- }
- />
-
- >
- )}
-
- {ociCredential && (
- <>
-
- Create Connection
-
- Press connect below to start the connection to your OCI bucket.
-
-
- nameBuilder={(values) => `OCIConnector-${values.bucket_name}`}
- ccPairNameBuilder={(values) =>
- `OCIConnector-${values.bucket_name}`
- }
- source="oci_storage"
- inputType="poll"
- formBodyBuilder={(values) => (
-
-
-
-
- )}
- validationSchema={Yup.object().shape({
- bucket_type: Yup.string()
- .oneOf(["oci_storage"])
- .required("Bucket type must be oci_storage"),
- bucket_name: Yup.string().required(
- "Please enter the name of the OCI bucket to index, e.g. my-test-bucket"
- ),
- prefix: Yup.string().default(""),
- })}
- initialValues={{
- bucket_type: "oci_storage",
- bucket_name: "",
- prefix: "",
- }}
- refreshFreq={60 * 60 * 24} // 1 day
- credentialId={ociCredential.id}
- />
-
- >
- )}
- >
- );
-};
-
-export default function Page() {
- return (
-
-
-
-
-
}
- title="Oracle Cloud Infrastructure"
- />
-
-
- );
-}
diff --git a/web/src/app/admin/connectors/productboard/page.tsx b/web/src/app/admin/connectors/productboard/page.tsx
deleted file mode 100644
index 1694baa8ccb..00000000000
--- a/web/src/app/admin/connectors/productboard/page.tsx
+++ /dev/null
@@ -1,254 +0,0 @@
-"use client";
-
-import * as Yup from "yup";
-import { ProductboardIcon, TrashIcon } from "@/components/icons/icons";
-import { TextFormField } from "@/components/admin/connectors/Field";
-import { HealthCheckBanner } from "@/components/health/healthcheck";
-import { CredentialForm } from "@/components/admin/connectors/CredentialForm";
-import {
- ProductboardConfig,
- ConnectorIndexingStatus,
- ProductboardCredentialJson,
- Credential,
-} from "@/lib/types";
-import useSWR, { useSWRConfig } from "swr";
-import { errorHandlingFetcher } from "@/lib/fetcher";
-import { ErrorCallout } from "@/components/ErrorCallout";
-import { LoadingAnimation } from "@/components/Loading";
-import { adminDeleteCredential, linkCredential } from "@/lib/credential";
-import { ConnectorForm } from "@/components/admin/connectors/ConnectorForm";
-import { ConnectorsTable } from "@/components/admin/connectors/table/ConnectorsTable";
-import { usePopup } from "@/components/admin/connectors/Popup";
-import { usePublicCredentials } from "@/lib/hooks";
-import { Card, Text, Title } from "@tremor/react";
-import { AdminPageTitle } from "@/components/admin/Title";
-
-const Main = () => {
- const { popup, setPopup } = usePopup();
-
- const { mutate } = useSWRConfig();
- const {
- data: connectorIndexingStatuses,
- isLoading: isConnectorIndexingStatusesLoading,
- error: connectorIndexingStatusesError,
- } = useSWR[]>(
- "/api/manage/admin/connector/indexing-status",
- errorHandlingFetcher
- );
- const {
- data: credentialsData,
- isLoading: isCredentialsLoading,
- error: credentialsError,
- isValidating: isCredentialsValidating,
- refreshCredentials,
- } = usePublicCredentials();
-
- if (
- isConnectorIndexingStatusesLoading ||
- isCredentialsLoading ||
- isCredentialsValidating
- ) {
- return ;
- }
-
- if (connectorIndexingStatusesError || !connectorIndexingStatuses) {
- return (
-
- );
- }
-
- if (credentialsError || !credentialsData) {
- return (
-
- );
- }
-
- const productboardConnectorIndexingStatuses: ConnectorIndexingStatus<
- ProductboardConfig,
- ProductboardCredentialJson
- >[] = connectorIndexingStatuses.filter(
- (connectorIndexingStatus) =>
- connectorIndexingStatus.connector.source === "productboard"
- );
- const productboardCredential:
- | Credential
- | undefined = credentialsData.find(
- (credential) => credential.credential_json?.productboard_access_token
- );
-
- return (
- <>
- {popup}
-
- This connector allows you to sync all your Features,{" "}
- Components, Products, and Objectives from
- Productboard into Danswer. At this time, the Productboard APIs does not
- support pulling in Releases or Notes.
-
-
-
- Step 1: Provide your Credentials
-
-
- {productboardCredential ? (
- <>
-
- Existing Access Token:
-
- {
- productboardCredential.credential_json
- ?.productboard_access_token
- }
-
-
-
- >
- ) : (
- <>
-
- To use the Productboard connector, first follow the guide{" "}
-
- here
- {" "}
- to generate an Access Token.
-
-
-
- formBody={
- <>
-
- >
- }
- validationSchema={Yup.object().shape({
- productboard_access_token: Yup.string().required(
- "Please enter your Productboard access token"
- ),
- })}
- initialValues={{
- productboard_access_token: "",
- }}
- onSubmit={(isSuccess) => {
- if (isSuccess) {
- refreshCredentials();
- }
- }}
- />
-
- >
- )}
-
-
- Step 2: Start indexing!
-
- {productboardCredential ? (
- !productboardConnectorIndexingStatuses.length ? (
- <>
-
- Click the button below to start indexing! We will pull the latest
- features, components, and products from Productboard every{" "}
- 10 minutes.
-
-
-
- nameBuilder={() => "ProductboardConnector"}
- ccPairNameBuilder={() => "Productboard"}
- source="productboard"
- inputType="poll"
- formBody={null}
- validationSchema={Yup.object().shape({})}
- initialValues={{}}
- refreshFreq={10 * 60} // 10 minutes
- credentialId={productboardCredential.id}
- />
-
- >
- ) : (
- <>
-
- Productboard connector is setup! We are pulling the latest
- features, components, and products from Productboard every{" "}
- 10 minutes.
-
-
- connectorIndexingStatuses={productboardConnectorIndexingStatuses}
- liveCredential={productboardCredential}
- getCredential={(credential) => {
- return (
-
-
- {credential.credential_json.productboard_access_token}
-
-
- );
- }}
- onCredentialLink={async (connectorId) => {
- if (productboardCredential) {
- await linkCredential(connectorId, productboardCredential.id);
- mutate("/api/manage/admin/connector/indexing-status");
- }
- }}
- onUpdate={() =>
- mutate("/api/manage/admin/connector/indexing-status")
- }
- />
- >
- )
- ) : (
- <>
-
- Please provide your access token in Step 1 first! Once done with
- that, you can then start indexing all your Productboard features,
- components, and products.
-
- >
- )}
- >
- );
-};
-
-export default function Page() {
- return (
-
-
-
-
-
-
}
- title="Productboard"
- />
-
-
-
- );
-}
diff --git a/web/src/app/admin/connectors/r2/page.tsx b/web/src/app/admin/connectors/r2/page.tsx
deleted file mode 100644
index 372660acc4f..00000000000
--- a/web/src/app/admin/connectors/r2/page.tsx
+++ /dev/null
@@ -1,265 +0,0 @@
-"use client";
-
-import { AdminPageTitle } from "@/components/admin/Title";
-import { HealthCheckBanner } from "@/components/health/healthcheck";
-import { R2Icon, S3Icon, TrashIcon } from "@/components/icons/icons";
-import { LoadingAnimation } from "@/components/Loading";
-import { ConnectorForm } from "@/components/admin/connectors/ConnectorForm";
-import { CredentialForm } from "@/components/admin/connectors/CredentialForm";
-import { TextFormField } from "@/components/admin/connectors/Field";
-import { usePopup } from "@/components/admin/connectors/Popup";
-import { ConnectorsTable } from "@/components/admin/connectors/table/ConnectorsTable";
-import { adminDeleteCredential, linkCredential } from "@/lib/credential";
-import { errorHandlingFetcher } from "@/lib/fetcher";
-import { ErrorCallout } from "@/components/ErrorCallout";
-import { usePublicCredentials } from "@/lib/hooks";
-import {
- ConnectorIndexingStatus,
- Credential,
- R2Config,
- R2CredentialJson,
-} from "@/lib/types";
-import { Card, Select, SelectItem, Text, Title } from "@tremor/react";
-import useSWR, { useSWRConfig } from "swr";
-import * as Yup from "yup";
-import { useState } from "react";
-
-const R2Main = () => {
- const { popup, setPopup } = usePopup();
-
- const { mutate } = useSWRConfig();
- const {
- data: connectorIndexingStatuses,
- isLoading: isConnectorIndexingStatusesLoading,
- error: connectorIndexingStatusesError,
- } = useSWR[]>(
- "/api/manage/admin/connector/indexing-status",
- errorHandlingFetcher
- );
- const {
- data: credentialsData,
- isLoading: isCredentialsLoading,
- error: credentialsError,
- refreshCredentials,
- } = usePublicCredentials();
-
- if (
- (!connectorIndexingStatuses && isConnectorIndexingStatusesLoading) ||
- (!credentialsData && isCredentialsLoading)
- ) {
- return ;
- }
-
- if (connectorIndexingStatusesError || !connectorIndexingStatuses) {
- return (
-
- );
- }
-
- if (credentialsError || !credentialsData) {
- return (
-
- );
- }
-
- const r2ConnectorIndexingStatuses: ConnectorIndexingStatus<
- R2Config,
- R2CredentialJson
- >[] = connectorIndexingStatuses.filter(
- (connectorIndexingStatus) =>
- connectorIndexingStatus.connector.source === "r2"
- );
-
- const r2Credential: Credential | undefined =
- credentialsData.find(
- (credential) => credential.credential_json?.account_id
- );
-
- return (
- <>
- {popup}
-
- Step 1: Provide your access info
-
- {r2Credential ? (
- <>
- {" "}
-
-
Existing R2 Access Key ID:
-
- {r2Credential.credential_json.r2_access_key_id}
-
- {", "}
-
Account ID:
-
- {r2Credential.credential_json.account_id}
-
{" "}
-
-
- >
- ) : (
- <>
-
-
- -
- Provide your R2 Access Key ID, Secret Access Key, and Account ID
- for authentication.
-
- - These credentials will be used to access your R2 buckets.
-
-
-
-
- formBody={
- <>
-
-
-
- >
- }
- validationSchema={Yup.object().shape({
- r2_access_key_id: Yup.string().required(
- "R2 Access Key ID is required"
- ),
- r2_secret_access_key: Yup.string().required(
- "R2 Secret Access Key is required"
- ),
- account_id: Yup.string().required("Account ID is required"),
- })}
- initialValues={{
- r2_access_key_id: "",
- r2_secret_access_key: "",
- account_id: "",
- }}
- onSubmit={(isSuccess) => {
- if (isSuccess) {
- refreshCredentials();
- }
- }}
- />
-
- >
- )}
-
-
- Step 2: Which R2 bucket do you want to make searchable?
-
-
- {r2ConnectorIndexingStatuses.length > 0 && (
- <>
-
- R2 indexing status
-
-
- The latest changes are fetched every 10 minutes.
-
-
-
- includeName={true}
- connectorIndexingStatuses={r2ConnectorIndexingStatuses}
- liveCredential={r2Credential}
- getCredential={(credential) => {
- return ;
- }}
- onCredentialLink={async (connectorId) => {
- if (r2Credential) {
- await linkCredential(connectorId, r2Credential.id);
- mutate("/api/manage/admin/connector/indexing-status");
- }
- }}
- onUpdate={() =>
- mutate("/api/manage/admin/connector/indexing-status")
- }
- />
-
- >
- )}
-
- {r2Credential && (
- <>
-
- Create Connection
-
- Press connect below to start the connection to your R2 bucket.
-
-
- nameBuilder={(values) => `R2Connector-${values.bucket_name}`}
- ccPairNameBuilder={(values) =>
- `R2Connector-${values.bucket_name}`
- }
- source="r2"
- inputType="poll"
- formBodyBuilder={(values) => (
-
-
-
-
- )}
- validationSchema={Yup.object().shape({
- bucket_type: Yup.string()
- .oneOf(["r2"])
- .required("Bucket type must be r2"),
- bucket_name: Yup.string().required(
- "Please enter the name of the r2 bucket to index, e.g. my-test-bucket"
- ),
- prefix: Yup.string().default(""),
- })}
- initialValues={{
- bucket_type: "r2",
- bucket_name: "",
- prefix: "",
- }}
- refreshFreq={60 * 60 * 24} // 1 day
- credentialId={r2Credential.id}
- />
-
- >
- )}
- >
- );
-};
-
-export default function Page() {
- const [selectedStorage, setSelectedStorage] = useState("s3");
-
- return (
-
-
-
-
-
} title="R2 Storage" />
-
-
- );
-}
diff --git a/web/src/app/admin/connectors/request-tracker/page.tsx b/web/src/app/admin/connectors/request-tracker/page.tsx
deleted file mode 100644
index 147dd1ae2e6..00000000000
--- a/web/src/app/admin/connectors/request-tracker/page.tsx
+++ /dev/null
@@ -1,256 +0,0 @@
-"use client";
-
-import * as Yup from "yup";
-import { TrashIcon, RequestTrackerIcon } from "@/components/icons/icons"; // Make sure you have a Document360 icon
-import { errorHandlingFetcher } from "@/lib/fetcher";
-import { ErrorCallout } from "@/components/ErrorCallout";
-import useSWR, { useSWRConfig } from "swr";
-import { LoadingAnimation } from "@/components/Loading";
-import { HealthCheckBanner } from "@/components/health/healthcheck";
-import {
- RequestTrackerConfig,
- RequestTrackerCredentialJson,
- ConnectorIndexingStatus,
- Credential,
-} from "@/lib/types"; // Modify or create these types as required
-import { adminDeleteCredential, linkCredential } from "@/lib/credential";
-import { CredentialForm } from "@/components/admin/connectors/CredentialForm";
-import {
- TextFormField,
- TextArrayFieldBuilder,
-} from "@/components/admin/connectors/Field";
-import { ConnectorsTable } from "@/components/admin/connectors/table/ConnectorsTable";
-import { ConnectorForm } from "@/components/admin/connectors/ConnectorForm";
-import { usePublicCredentials } from "@/lib/hooks";
-import { AdminPageTitle } from "@/components/admin/Title";
-import { Card, Text, Title } from "@tremor/react";
-
-const MainSection = () => {
- const { mutate } = useSWRConfig();
- const {
- data: connectorIndexingStatuses,
- isLoading: isConnectorIndexingStatusesLoading,
- error: connectorIndexingStatusesError,
- } = useSWR[]>(
- "/api/manage/admin/connector/indexing-status",
- errorHandlingFetcher
- );
-
- const {
- data: credentialsData,
- isLoading: isCredentialsLoading,
- error: credentialsError,
- refreshCredentials,
- } = usePublicCredentials();
-
- if (
- (!connectorIndexingStatuses && isConnectorIndexingStatusesLoading) ||
- (!credentialsData && isCredentialsLoading)
- ) {
- return ;
- }
-
- if (connectorIndexingStatusesError || !connectorIndexingStatuses) {
- return (
-
- );
- }
-
- if (credentialsError || !credentialsData) {
- return (
-
- );
- }
-
- const requestTrackerConnectorIndexingStatuses: ConnectorIndexingStatus<
- RequestTrackerConfig,
- RequestTrackerCredentialJson
- >[] = connectorIndexingStatuses.filter(
- (connectorIndexingStatus) =>
- connectorIndexingStatus.connector.source === "requesttracker"
- );
-
- const requestTrackerCredential:
- | Credential
- | undefined = credentialsData.find(
- (credential) => credential.credential_json?.requesttracker_username
- );
-
- return (
- <>
-
- Step 1: Provide Request Tracker credentials
-
- {requestTrackerCredential ? (
- <>
-
- Existing Request Tracker username:
-
- {requestTrackerCredential.credential_json.requesttracker_username}
-
-
-
- >
- ) : (
- <>
-
- To use the Request Tracker connector, provide a Request Tracker
- username, password, and base url.
-
-
- This connector currently supports{" "}
-
- Request Tracker REST API 1.0
-
- ,{" "}
- not the latest REST API 2.0 introduced in Request Tracker 5.0
- .
-
-
-
- formBody={
- <>
-
-
-
- >
- }
- validationSchema={Yup.object().shape({
- requesttracker_username: Yup.string().required(
- "Please enter your Request Tracker username"
- ),
- requesttracker_password: Yup.string().required(
- "Please enter your Request Tracker password"
- ),
- requesttracker_base_url: Yup.string()
- .url()
- .required(
- "Please enter the base url of your RT installation"
- ),
- })}
- initialValues={{
- requesttracker_username: "",
- requesttracker_password: "",
- requesttracker_base_url: "",
- }}
- onSubmit={(isSuccess) => {
- if (isSuccess) {
- refreshCredentials();
- }
- }}
- />
-
- >
- )}
-
-
- Step 2: Manage Request Tracker Connector
-
-
- {requestTrackerConnectorIndexingStatuses.length > 0 && (
- <>
-
- We index the most recently updated tickets from each Request Tracker
- instance listed below regularly.
-
-
- The initial poll at this time retrieves tickets updated in the past
- hour. All subsequent polls execute every ten minutes. This should be
- configurable in the future.
-
-
-
- connectorIndexingStatuses={
- requestTrackerConnectorIndexingStatuses
- }
- liveCredential={requestTrackerCredential}
- getCredential={(credential) =>
- credential.credential_json.requesttracker_base_url
- }
- onUpdate={() =>
- mutate("/api/manage/admin/connector/indexing-status")
- }
- onCredentialLink={async (connectorId) => {
- if (requestTrackerCredential) {
- await linkCredential(
- connectorId,
- requestTrackerCredential.id
- );
- mutate("/api/manage/admin/connector/indexing-status");
- }
- }}
- />
-
- >
- )}
-
- {requestTrackerCredential &&
- requestTrackerConnectorIndexingStatuses.length === 0 ? (
-
-
- nameBuilder={(values) =>
- `RequestTracker-${requestTrackerCredential.credential_json.requesttracker_base_url}`
- }
- ccPairNameBuilder={(values) =>
- `Request Tracker ${requestTrackerCredential.credential_json.requesttracker_base_url}`
- }
- source="requesttracker"
- inputType="poll"
- validationSchema={Yup.object().shape({})}
- formBody={<>>}
- initialValues={{}}
- credentialId={requestTrackerCredential.id}
- refreshFreq={10 * 60} // 10 minutes
- />
-
- ) : (
- <>>
- )}
- >
- );
-};
-
-export default function Page() {
- return (
-
-
-
-
-
-
}
- title="Request Tracker"
- />
-
-
-
- );
-}
diff --git a/web/src/app/admin/connectors/s3/page.tsx b/web/src/app/admin/connectors/s3/page.tsx
deleted file mode 100644
index 81064a70bf1..00000000000
--- a/web/src/app/admin/connectors/s3/page.tsx
+++ /dev/null
@@ -1,258 +0,0 @@
-"use client";
-
-import { AdminPageTitle } from "@/components/admin/Title";
-import { HealthCheckBanner } from "@/components/health/healthcheck";
-import { S3Icon, TrashIcon } from "@/components/icons/icons";
-import { LoadingAnimation } from "@/components/Loading";
-import { ConnectorForm } from "@/components/admin/connectors/ConnectorForm";
-import { CredentialForm } from "@/components/admin/connectors/CredentialForm";
-import { TextFormField } from "@/components/admin/connectors/Field";
-import { usePopup } from "@/components/admin/connectors/Popup";
-import { ConnectorsTable } from "@/components/admin/connectors/table/ConnectorsTable";
-import { adminDeleteCredential, linkCredential } from "@/lib/credential";
-import { errorHandlingFetcher } from "@/lib/fetcher";
-import { ErrorCallout } from "@/components/ErrorCallout";
-import { usePublicCredentials } from "@/lib/hooks";
-import {
- ConnectorIndexingStatus,
- Credential,
- S3Config,
- S3CredentialJson,
-} from "@/lib/types";
-import { Card, Text, Title } from "@tremor/react";
-import useSWR, { useSWRConfig } from "swr";
-import * as Yup from "yup";
-import { useState } from "react";
-
-const S3Main = () => {
- const { popup, setPopup } = usePopup();
-
- const { mutate } = useSWRConfig();
- const {
- data: connectorIndexingStatuses,
- isLoading: isConnectorIndexingStatusesLoading,
- error: connectorIndexingStatusesError,
- } = useSWR[]>(
- "/api/manage/admin/connector/indexing-status",
- errorHandlingFetcher
- );
- const {
- data: credentialsData,
- isLoading: isCredentialsLoading,
- error: credentialsError,
- refreshCredentials,
- } = usePublicCredentials();
-
- if (
- (!connectorIndexingStatuses && isConnectorIndexingStatusesLoading) ||
- (!credentialsData && isCredentialsLoading)
- ) {
- return ;
- }
-
- if (connectorIndexingStatusesError || !connectorIndexingStatuses) {
- return (
-
- );
- }
-
- if (credentialsError || !credentialsData) {
- return (
-
- );
- }
-
- const s3ConnectorIndexingStatuses: ConnectorIndexingStatus<
- S3Config,
- S3CredentialJson
- >[] = connectorIndexingStatuses.filter(
- (connectorIndexingStatus) =>
- connectorIndexingStatus.connector.source === "s3"
- );
-
- const s3Credential: Credential | undefined =
- credentialsData.find(
- (credential) => credential.credential_json?.aws_access_key_id
- );
-
- return (
- <>
- {popup}
-
- Step 1: Provide your access info
-
- {s3Credential ? (
- <>
- {" "}
-
-
Existing AWS Access Key ID:
-
- {s3Credential.credential_json.aws_access_key_id}
-
-
-
- >
- ) : (
- <>
-
-
- -
- If AWS Access Key ID and AWS Secret Access Key are provided,
- they will be used for authenticating the connector.
-
- - Otherwise, the Profile Name will be used (if provided).
- -
- If no credentials are provided, then the connector will try to
- authenticate with any default AWS credentials available.
-
-
-
-
-
- formBody={
- <>
-
-
- >
- }
- validationSchema={Yup.object().shape({
- aws_access_key_id: Yup.string().default(""),
- aws_secret_access_key: Yup.string().default(""),
- })}
- initialValues={{
- aws_access_key_id: "",
- aws_secret_access_key: "",
- }}
- onSubmit={(isSuccess) => {
- if (isSuccess) {
- refreshCredentials();
- }
- }}
- />
-
- >
- )}
-
-
- Step 2: Which S3 bucket do you want to make searchable?
-
-
- {s3ConnectorIndexingStatuses.length > 0 && (
- <>
-
- S3 indexing status
-
-
- The latest changes are fetched every 10 minutes.
-
-
-
- includeName={true}
- connectorIndexingStatuses={s3ConnectorIndexingStatuses}
- liveCredential={s3Credential}
- getCredential={(credential) => {
- return ;
- }}
- onCredentialLink={async (connectorId) => {
- if (s3Credential) {
- await linkCredential(connectorId, s3Credential.id);
- mutate("/api/manage/admin/connector/indexing-status");
- }
- }}
- onUpdate={() =>
- mutate("/api/manage/admin/connector/indexing-status")
- }
- />
-
- >
- )}
-
- {s3Credential && (
- <>
-
- Create Connection
-
- Press connect below to start the connection to your S3 bucket.
-
-
- nameBuilder={(values) => `S3Connector-${values.bucket_name}`}
- ccPairNameBuilder={(values) =>
- `S3Connector-${values.bucket_name}`
- }
- source="s3"
- inputType="poll"
- formBodyBuilder={(values) => (
-
-
-
-
- )}
- validationSchema={Yup.object().shape({
- bucket_type: Yup.string()
- .oneOf(["s3"])
- .required("Bucket type must be s3"),
- bucket_name: Yup.string().required(
- "Please enter the name of the s3 bucket to index, e.g. my-test-bucket"
- ),
- prefix: Yup.string().default(""),
- })}
- initialValues={{
- bucket_type: "s3",
- bucket_name: "",
- prefix: "",
- }}
- refreshFreq={60 * 60 * 24} // 1 day
- credentialId={s3Credential.id}
- />
-
- >
- )}
- >
- );
-};
-
-export default function Page() {
- const [selectedStorage, setSelectedStorage] = useState("s3");
-
- return (
-
-
-
-
-
} title="S3 Storage" />
-
-
-
- );
-}
diff --git a/web/src/app/admin/connectors/salesforce/page.tsx b/web/src/app/admin/connectors/salesforce/page.tsx
deleted file mode 100644
index 8771b14f94b..00000000000
--- a/web/src/app/admin/connectors/salesforce/page.tsx
+++ /dev/null
@@ -1,290 +0,0 @@
-"use client";
-
-import * as Yup from "yup";
-import { TrashIcon, SalesforceIcon } from "@/components/icons/icons"; // Make sure you have a Document360 icon
-import { errorHandlingFetcher as fetcher } from "@/lib/fetcher";
-import useSWR, { useSWRConfig } from "swr";
-import { LoadingAnimation } from "@/components/Loading";
-import { HealthCheckBanner } from "@/components/health/healthcheck";
-import {
- SalesforceConfig,
- SalesforceCredentialJson,
- ConnectorIndexingStatus,
- Credential,
-} from "@/lib/types"; // Modify or create these types as required
-import { adminDeleteCredential, linkCredential } from "@/lib/credential";
-import { CredentialForm } from "@/components/admin/connectors/CredentialForm";
-import {
- TextFormField,
- TextArrayFieldBuilder,
-} from "@/components/admin/connectors/Field";
-import { ConnectorsTable } from "@/components/admin/connectors/table/ConnectorsTable";
-import { ConnectorForm } from "@/components/admin/connectors/ConnectorForm";
-import { usePublicCredentials } from "@/lib/hooks";
-import { AdminPageTitle } from "@/components/admin/Title";
-import { Card, Text, Title } from "@tremor/react";
-
-const MainSection = () => {
- const { mutate } = useSWRConfig();
- const {
- data: connectorIndexingStatuses,
- isLoading: isConnectorIndexingStatusesLoading,
- error: isConnectorIndexingStatusesError,
- } = useSWR[]>(
- "/api/manage/admin/connector/indexing-status",
- fetcher
- );
-
- const {
- data: credentialsData,
- isLoading: isCredentialsLoading,
- error: isCredentialsError,
- refreshCredentials,
- } = usePublicCredentials();
-
- if (
- (!connectorIndexingStatuses && isConnectorIndexingStatusesLoading) ||
- (!credentialsData && isCredentialsLoading)
- ) {
- return ;
- }
-
- if (isConnectorIndexingStatusesError || !connectorIndexingStatuses) {
- return Failed to load connectors
;
- }
-
- if (isCredentialsError || !credentialsData) {
- return Failed to load credentials
;
- }
-
- const SalesforceConnectorIndexingStatuses: ConnectorIndexingStatus<
- SalesforceConfig,
- SalesforceCredentialJson
- >[] = connectorIndexingStatuses.filter(
- (connectorIndexingStatus) =>
- connectorIndexingStatus.connector.source === "salesforce"
- );
-
- const SalesforceCredential: Credential | undefined =
- credentialsData.find(
- (credential) => credential.credential_json?.sf_username
- );
-
- return (
- <>
-
- The Salesforce connector allows you to index and search through your
- Salesforce data. Once setup, all indicated Salesforce data will be will
- be queryable within Danswer.
-
-
-
- Step 1: Provide Salesforce credentials
-
- {SalesforceCredential ? (
- <>
-
- Existing SalesForce Username:
-
- {SalesforceCredential.credential_json.sf_username}
-
-
-
- >
- ) : (
- <>
-
- As a first step, please provide the Salesforce admin account's
- username, password, and Salesforce security token. You can follow
- the guide{" "}
-
- here
- {" "}
- to create get your Salesforce Security Token.
-
-
-
- formBody={
- <>
-
-
-
- >
- }
- validationSchema={Yup.object().shape({
- sf_username: Yup.string().required(
- "Please enter your Salesforce username"
- ),
- sf_password: Yup.string().required(
- "Please enter your Salesforce password"
- ),
- sf_security_token: Yup.string().required(
- "Please enter your Salesforce security token"
- ),
- })}
- initialValues={{
- sf_username: "",
- sf_password: "",
- sf_security_token: "",
- }}
- onSubmit={(isSuccess) => {
- if (isSuccess) {
- refreshCredentials();
- }
- }}
- />
-
- >
- )}
-
-
- Step 2: Manage Salesforce Connector
-
-
- {SalesforceConnectorIndexingStatuses.length > 0 && (
- <>
-
- The latest state of your Salesforce objects are fetched every 10
- minutes.
-
-
-
- connectorIndexingStatuses={SalesforceConnectorIndexingStatuses}
- liveCredential={SalesforceCredential}
- getCredential={(credential) =>
- credential.credential_json.sf_security_token
- }
- onUpdate={() =>
- mutate("/api/manage/admin/connector/indexing-status")
- }
- onCredentialLink={async (connectorId) => {
- if (SalesforceCredential) {
- await linkCredential(connectorId, SalesforceCredential.id);
- mutate("/api/manage/admin/connector/indexing-status");
- }
- }}
- specialColumns={[
- {
- header: "Connectors",
- key: "connectors",
- getValue: (ccPairStatus) => {
- const connectorConfig =
- ccPairStatus.connector.connector_specific_config;
- return `${connectorConfig.requested_objects}`;
- },
- },
- ]}
- includeName
- />
-
- >
- )}
-
- {SalesforceCredential ? (
-
-
- nameBuilder={(values) =>
- values.requested_objects && values.requested_objects.length > 0
- ? `Salesforce-${values.requested_objects.join("-")}`
- : "Salesforce"
- }
- ccPairNameBuilder={(values) =>
- values.requested_objects && values.requested_objects.length > 0
- ? `Salesforce-${values.requested_objects.join("-")}`
- : "Salesforce"
- }
- source="salesforce"
- inputType="poll"
- // formBody={<>>}
- formBodyBuilder={TextArrayFieldBuilder({
- name: "requested_objects",
- label: "Specify Salesforce objects to organize by:",
- subtext: (
- <>
-
- Specify the Salesforce object types you want us to index.{" "}
-
-
- Click
-
- {" "}
- here{" "}
-
- for an example of how Danswer uses the objects.
-
- If unsure, don't specify any objects and Danswer will
- default to indexing by 'Account'.
-
-
- Hint: Use the singular form of the object name (e.g.,
- 'Opportunity' instead of 'Opportunities').
- >
- ),
- })}
- validationSchema={Yup.object().shape({
- requested_objects: Yup.array()
- .of(
- Yup.string().required(
- "Salesforce object names must be strings"
- )
- )
- .required(),
- })}
- initialValues={{
- requested_objects: [],
- }}
- credentialId={SalesforceCredential.id}
- refreshFreq={10 * 60} // 10 minutes
- />
-
- ) : (
-
- Please provide all Salesforce info in Step 1 first! Once you're
- done with that, you can then specify which Salesforce objects you want
- to make searchable.
-
- )}
- >
- );
-};
-
-export default function Page() {
- return (
-
-
-
-
-
-
} title="Salesforce" />
-
-
-
- );
-}
diff --git a/web/src/app/admin/connectors/sharepoint/page.tsx b/web/src/app/admin/connectors/sharepoint/page.tsx
deleted file mode 100644
index bf970415472..00000000000
--- a/web/src/app/admin/connectors/sharepoint/page.tsx
+++ /dev/null
@@ -1,294 +0,0 @@
-"use client";
-
-import * as Yup from "yup";
-import { TrashIcon, SharepointIcon } from "@/components/icons/icons"; // Make sure you have a Document360 icon
-import { errorHandlingFetcher } from "@/lib/fetcher";
-import { ErrorCallout } from "@/components/ErrorCallout";
-import useSWR, { useSWRConfig } from "swr";
-import { LoadingAnimation } from "@/components/Loading";
-import { HealthCheckBanner } from "@/components/health/healthcheck";
-import {
- SharepointConfig,
- SharepointCredentialJson,
- ConnectorIndexingStatus,
- Credential,
-} from "@/lib/types"; // Modify or create these types as required
-import { adminDeleteCredential, linkCredential } from "@/lib/credential";
-import { CredentialForm } from "@/components/admin/connectors/CredentialForm";
-import {
- TextFormField,
- TextArrayFieldBuilder,
-} from "@/components/admin/connectors/Field";
-import { ConnectorsTable } from "@/components/admin/connectors/table/ConnectorsTable";
-import { ConnectorForm } from "@/components/admin/connectors/ConnectorForm";
-import { usePublicCredentials } from "@/lib/hooks";
-import { AdminPageTitle } from "@/components/admin/Title";
-import { Card, Text, Title } from "@tremor/react";
-
-const MainSection = () => {
- const { mutate } = useSWRConfig();
- const {
- data: connectorIndexingStatuses,
- isLoading: isConnectorIndexingStatusesLoading,
- error: connectorIndexingStatusesError,
- } = useSWR[]>(
- "/api/manage/admin/connector/indexing-status",
- errorHandlingFetcher
- );
-
- const {
- data: credentialsData,
- isLoading: isCredentialsLoading,
- error: credentialsError,
- refreshCredentials,
- } = usePublicCredentials();
-
- if (
- (!connectorIndexingStatuses && isConnectorIndexingStatusesLoading) ||
- (!credentialsData && isCredentialsLoading)
- ) {
- return ;
- }
-
- if (connectorIndexingStatusesError || !connectorIndexingStatuses) {
- return (
-
- );
- }
-
- if (credentialsError || !credentialsData) {
- return (
-
- );
- }
-
- const sharepointConnectorIndexingStatuses: ConnectorIndexingStatus<
- SharepointConfig,
- SharepointCredentialJson
- >[] = connectorIndexingStatuses.filter(
- (connectorIndexingStatus) =>
- connectorIndexingStatus.connector.source === "sharepoint"
- );
-
- const sharepointCredential: Credential | undefined =
- credentialsData.find(
- (credential) => credential.credential_json?.sp_client_id
- );
-
- return (
- <>
-
- The Sharepoint connector allows you to index and search through your
- Sharepoint files. Once setup, your Word documents, Excel files,
- PowerPoint presentations, OneNote notebooks, PDFs, and uploaded files
- will be queryable within Danswer.
-
-
-
- Step 1: Provide Sharepoint credentials
-
- {sharepointCredential ? (
- <>
-
- Existing Azure AD Client ID:
-
- {sharepointCredential.credential_json.sp_client_id}
-
-
-
- >
- ) : (
- <>
-
- As a first step, please provide Application (client) ID, Directory
- (tenant) ID, and Client Secret. You can follow the guide{" "}
-
- here
- {" "}
- to create an Azure AD application and obtain these values.
-
-
-
- formBody={
- <>
-
-
-
- >
- }
- validationSchema={Yup.object().shape({
- sp_client_id: Yup.string().required(
- "Please enter your Application (client) ID"
- ),
- sp_directory_id: Yup.string().required(
- "Please enter your Directory (tenant) ID"
- ),
- sp_client_secret: Yup.string().required(
- "Please enter your Client Secret"
- ),
- })}
- initialValues={{
- sp_client_id: "",
- sp_directory_id: "",
- sp_client_secret: "",
- }}
- onSubmit={(isSuccess) => {
- if (isSuccess) {
- refreshCredentials();
- }
- }}
- />
-
- >
- )}
-
-
- Step 2: Manage Sharepoint Connector
-
-
- {sharepointConnectorIndexingStatuses.length > 0 && (
- <>
-
- The latest state of your Word documents, Excel files, PowerPoint
- presentations, OneNote notebooks, PDFs, and uploaded files are
- fetched every 10 minutes.
-
-
-
- connectorIndexingStatuses={sharepointConnectorIndexingStatuses}
- liveCredential={sharepointCredential}
- getCredential={(credential) =>
- credential.credential_json.sp_directory_id
- }
- onUpdate={() =>
- mutate("/api/manage/admin/connector/indexing-status")
- }
- onCredentialLink={async (connectorId) => {
- if (sharepointCredential) {
- await linkCredential(connectorId, sharepointCredential.id);
- mutate("/api/manage/admin/connector/indexing-status");
- }
- }}
- specialColumns={[
- {
- header: "Connectors",
- key: "connectors",
- getValue: (ccPairStatus) => {
- const connectorConfig =
- ccPairStatus.connector.connector_specific_config;
- return `${connectorConfig.sites}`;
- },
- },
- ]}
- includeName
- />
-
- >
- )}
-
- {sharepointCredential ? (
-
-
- nameBuilder={(values) =>
- values.sites && values.sites.length > 0
- ? `Sharepoint-${values.sites.join("-")}`
- : "Sharepoint"
- }
- ccPairNameBuilder={(values) =>
- values.sites && values.sites.length > 0
- ? `Sharepoint-${values.sites.join("-")}`
- : "Sharepoint"
- }
- source="sharepoint"
- inputType="poll"
- // formBody={<>>}
- formBodyBuilder={TextArrayFieldBuilder({
- name: "sites",
- label: "Sites:",
- subtext: (
- <>
-
-
- -
- • If no sites are specified, all sites in your
- organization will be indexed (Sites.Read.All permission
- required).
-
- -
- • Specifying
- 'https://danswerai.sharepoint.com/sites/support'
- for example will only index documents within this site.
-
- -
- • Specifying
- 'https://danswerai.sharepoint.com/sites/support/subfolder'
- for example will only index documents within this folder.
-
-
- >
- ),
- })}
- validationSchema={Yup.object().shape({
- sites: Yup.array()
- .of(Yup.string().required("Site names must be strings"))
- .required(),
- })}
- initialValues={{
- sites: [],
- }}
- credentialId={sharepointCredential.id}
- refreshFreq={10 * 60} // 10 minutes
- />
-
- ) : (
-
- Please provide all Azure info in Step 1 first! Once you're done
- with that, you can then specify which Sharepoint sites you want to
- make searchable.
-
- )}
- >
- );
-};
-
-export default function Page() {
- return (
-
-
-
-
-
-
} title="Sharepoint" />
-
-
-
- );
-}
diff --git a/web/src/app/admin/connectors/slab/page.tsx b/web/src/app/admin/connectors/slab/page.tsx
deleted file mode 100644
index 11dcd799e46..00000000000
--- a/web/src/app/admin/connectors/slab/page.tsx
+++ /dev/null
@@ -1,282 +0,0 @@
-"use client";
-
-import * as Yup from "yup";
-import { SlabIcon, TrashIcon } from "@/components/icons/icons";
-import { TextFormField } from "@/components/admin/connectors/Field";
-import { HealthCheckBanner } from "@/components/health/healthcheck";
-import { CredentialForm } from "@/components/admin/connectors/CredentialForm";
-import {
- ConnectorIndexingStatus,
- SlabCredentialJson,
- SlabConfig,
- Credential,
-} from "@/lib/types";
-import useSWR, { useSWRConfig } from "swr";
-import { errorHandlingFetcher } from "@/lib/fetcher";
-import { ErrorCallout } from "@/components/ErrorCallout";
-import { LoadingAnimation } from "@/components/Loading";
-import { adminDeleteCredential, linkCredential } from "@/lib/credential";
-import { ConnectorForm } from "@/components/admin/connectors/ConnectorForm";
-import { ConnectorsTable } from "@/components/admin/connectors/table/ConnectorsTable";
-import { usePopup } from "@/components/admin/connectors/Popup";
-import { usePublicCredentials } from "@/lib/hooks";
-import { Card, Text, Title } from "@tremor/react";
-import { AdminPageTitle } from "@/components/admin/Title";
-
-const Main = () => {
- const { popup, setPopup } = usePopup();
-
- const { mutate } = useSWRConfig();
- const {
- data: connectorIndexingStatuses,
- isLoading: isConnectorIndexingStatusesLoading,
- error: connectorIndexingStatusesError,
- } = useSWR[]>(
- "/api/manage/admin/connector/indexing-status",
- errorHandlingFetcher
- );
- const {
- data: credentialsData,
- isLoading: isCredentialsLoading,
- error: credentialsError,
- isValidating: isCredentialsValidating,
- refreshCredentials,
- } = usePublicCredentials();
-
- if (
- isConnectorIndexingStatusesLoading ||
- isCredentialsLoading ||
- isCredentialsValidating
- ) {
- return ;
- }
-
- if (connectorIndexingStatusesError || !connectorIndexingStatuses) {
- return (
-
- );
- }
-
- if (credentialsError || !credentialsData) {
- return (
-
- );
- }
-
- const slabConnectorIndexingStatuses: ConnectorIndexingStatus<
- SlabConfig,
- SlabCredentialJson
- >[] = connectorIndexingStatuses.filter(
- (connectorIndexingStatus) =>
- connectorIndexingStatus.connector.source === "slab"
- );
- const slabCredential: Credential | undefined =
- credentialsData.find(
- (credential) => credential.credential_json?.slab_bot_token
- );
-
- return (
- <>
- {popup}
-
- Step 1: Provide your Credentials
-
-
- {slabCredential ? (
- <>
-
- Existing Slab Bot Token:
-
- {slabCredential.credential_json?.slab_bot_token}
-
-
-
- >
- ) : (
- <>
-
- To use the Slab connector, first follow the guide{" "}
-
- here
- {" "}
- to generate a Slab Bot Token.
-
-
-
- formBody={
- <>
-
- >
- }
- validationSchema={Yup.object().shape({
- slab_bot_token: Yup.string().required(
- "Please enter your Slab Bot Token"
- ),
- })}
- initialValues={{
- slab_bot_token: "",
- }}
- onSubmit={(isSuccess) => {
- if (isSuccess) {
- refreshCredentials();
- }
- }}
- />
-
- >
- )}
-
-
- Step 2: What's the base URL for your Slab team?
-
- {slabCredential ? (
- <>
- {slabConnectorIndexingStatuses.length > 0 ? (
- <>
-
- We are pulling the latest documents from{" "}
-
- {
- slabConnectorIndexingStatuses[0].connector
- .connector_specific_config.base_url
- }
- {" "}
- every 10 minutes.
-
-
- connectorIndexingStatuses={slabConnectorIndexingStatuses}
- liveCredential={slabCredential}
- getCredential={(credential) => {
- return (
-
-
{credential.credential_json.slab_bot_token}
-
- );
- }}
- onCredentialLink={async (connectorId) => {
- if (slabCredential) {
- await linkCredential(connectorId, slabCredential.id);
- mutate("/api/manage/admin/connector/indexing-status");
- }
- }}
- specialColumns={[
- {
- header: "Url",
- key: "url",
- getValue: (ccPairStatus) => {
- const connectorConfig =
- ccPairStatus.connector.connector_specific_config;
- return (
-
- {connectorConfig.base_url}
-
- );
- },
- },
- ]}
- onUpdate={() =>
- mutate("/api/manage/admin/connector/indexing-status")
- }
- />
- >
- ) : (
- <>
-
- Specify the base URL for your Slab team below. This will look
- something like:{" "}
-
- https://danswer.slab.com/
-
-
-
- Add a New Space
-
- nameBuilder={(values) => `SlabConnector-${values.base_url}`}
- ccPairNameBuilder={(values) => values.base_url}
- source="slab"
- inputType="poll"
- formBody={
- <>
-
- >
- }
- validationSchema={Yup.object().shape({
- base_url: Yup.string().required(
- "Please enter the base URL for your team e.g. https://danswer.slab.com/"
- ),
- })}
- initialValues={{
- base_url: "",
- }}
- refreshFreq={10 * 60} // 10 minutes
- credentialId={slabCredential.id}
- />
-
- >
- )}
- >
- ) : (
- <>
-
- Please provide your access token in Step 1 first! Once done with
- that, you can then specify the URL for your Slab team and get
- started with indexing.
-
- >
- )}
- >
- );
-};
-
-export default function Page() {
- return (
-
-
-
-
-
-
} title="Slab" />
-
-
-
- );
-}
diff --git a/web/src/app/admin/connectors/slack/page.tsx b/web/src/app/admin/connectors/slack/page.tsx
deleted file mode 100644
index 9352b0d776d..00000000000
--- a/web/src/app/admin/connectors/slack/page.tsx
+++ /dev/null
@@ -1,296 +0,0 @@
-"use client";
-
-import * as Yup from "yup";
-import { SlackIcon, TrashIcon } from "@/components/icons/icons";
-import { errorHandlingFetcher } from "@/lib/fetcher";
-import { ErrorCallout } from "@/components/ErrorCallout";
-import useSWR, { useSWRConfig } from "swr";
-import { LoadingAnimation } from "@/components/Loading";
-import { HealthCheckBanner } from "@/components/health/healthcheck";
-import {
- SlackConfig,
- SlackCredentialJson,
- ConnectorIndexingStatus,
- Credential,
-} from "@/lib/types";
-import { adminDeleteCredential, linkCredential } from "@/lib/credential";
-import { CredentialForm } from "@/components/admin/connectors/CredentialForm";
-import {
- TextFormField,
- TextArrayFieldBuilder,
- BooleanFormField,
- TextArrayField,
-} from "@/components/admin/connectors/Field";
-import { ConnectorsTable } from "@/components/admin/connectors/table/ConnectorsTable";
-import { ConnectorForm } from "@/components/admin/connectors/ConnectorForm";
-import { usePublicCredentials } from "@/lib/hooks";
-import { Button, Card, Divider, Text, Title } from "@tremor/react";
-import { AdminPageTitle } from "@/components/admin/Title";
-
-const MainSection = () => {
- const { mutate } = useSWRConfig();
- const {
- data: connectorIndexingStatuses,
- isLoading: isConnectorIndexingStatusesLoading,
- error: connectorIndexingStatusesError,
- } = useSWR[]>(
- "/api/manage/admin/connector/indexing-status",
- errorHandlingFetcher
- );
-
- const {
- data: credentialsData,
- isLoading: isCredentialsLoading,
- error: credentialsError,
- refreshCredentials,
- } = usePublicCredentials();
-
- if (
- (!connectorIndexingStatuses && isConnectorIndexingStatusesLoading) ||
- (!credentialsData && isCredentialsLoading)
- ) {
- return ;
- }
-
- if (connectorIndexingStatusesError || !connectorIndexingStatuses) {
- return (
-
- );
- }
-
- if (credentialsError || !credentialsData) {
- return (
-
- );
- }
-
- const slackConnectorIndexingStatuses: ConnectorIndexingStatus<
- SlackConfig,
- SlackCredentialJson
- >[] = connectorIndexingStatuses.filter(
- (connectorIndexingStatus) =>
- connectorIndexingStatus.connector.source === "slack"
- );
- const slackCredential: Credential | undefined =
- credentialsData.find(
- (credential) => credential.credential_json?.slack_bot_token
- );
-
- return (
- <>
-
- Step 1: Provide Credentials
-
- {slackCredential ? (
- <>
-
- Existing Slack Bot Token:
-
- {slackCredential.credential_json.slack_bot_token}
-
-
-
- >
- ) : (
- <>
-
- To use the Slack connector, you must first provide a Slack bot token
- corresponding to the Slack App set up in your workspace. For more
- details on setting up the Danswer Slack App, see the{" "}
-
- docs
-
- .
-
-
-
- formBody={
- <>
-
- >
- }
- validationSchema={Yup.object().shape({
- slack_bot_token: Yup.string().required(
- "Please enter your Slack bot token"
- ),
- })}
- initialValues={{
- slack_bot_token: "",
- }}
- onSubmit={(isSuccess) => {
- if (isSuccess) {
- refreshCredentials();
- }
- }}
- />
-
- >
- )}
-
-
- Step 2: Which channels do you want to make searchable?
-
-
- {slackConnectorIndexingStatuses.length > 0 && (
- <>
-
- We pull the latest messages from each workspace listed below every{" "}
- 10 minutes.
-
-
-
- connectorIndexingStatuses={slackConnectorIndexingStatuses}
- liveCredential={slackCredential}
- getCredential={(credential) =>
- credential.credential_json.slack_bot_token
- }
- specialColumns={[
- {
- header: "Workspace",
- key: "workspace",
- getValue: (ccPairStatus) =>
- ccPairStatus.connector.connector_specific_config.workspace,
- },
- {
- header: "Channels",
- key: "channels",
- getValue: (ccPairStatus) => {
- const connectorConfig =
- ccPairStatus.connector.connector_specific_config;
- return connectorConfig.channels &&
- connectorConfig.channels.length > 0
- ? connectorConfig.channels.join(", ")
- : "";
- },
- },
- ]}
- onUpdate={() =>
- mutate("/api/manage/admin/connector/indexing-status")
- }
- onCredentialLink={async (connectorId) => {
- if (slackCredential) {
- await linkCredential(connectorId, slackCredential.id);
- mutate("/api/manage/admin/connector/indexing-status");
- }
- }}
- />
-
-
- >
- )}
-
- {slackCredential ? (
-
- Connect to a New Workspace
-
- nameBuilder={(values) =>
- values.channels
- ? `SlackConnector-${values.workspace}-${values.channels.join(
- "_"
- )}`
- : `SlackConnector-${values.workspace}`
- }
- source="slack"
- inputType="poll"
- formBody={
- <>
-
- >
- }
- formBodyBuilder={(values) => {
- return (
- <>
-
- {TextArrayFieldBuilder({
- name: "channels",
- label: "Channels:",
- subtext: `
- Specify 0 or more channels to index. For example, specifying the channel
- "support" will cause us to only index all content within the "#support" channel.
- If no channels are specified, all channels in your workspace will be indexed.`,
- })(values)}
-
- If enabled, we will treat the "channels"
- specified above as regular expressions. A channel's
- messages will be pulled in by the connector if the name
- of the channel fully matches any of the specified
- regular expressions.
-
- For example, specifying .*-support.* as a
- "channel" will cause the connector to include
- any channels with "-support" in the name.
-
- }
- />
- >
- );
- }}
- validationSchema={Yup.object().shape({
- workspace: Yup.string().required(
- "Please enter the workspace to index"
- ),
- channels: Yup.array()
- .of(Yup.string().required("Channel names must be strings"))
- .required(),
- channel_regex_enabled: Yup.boolean().required(),
- })}
- initialValues={{
- workspace: "",
- channels: [],
- channel_regex_enabled: false,
- }}
- refreshFreq={10 * 60} // 10 minutes
- credentialId={slackCredential.id}
- />
-
- ) : (
-
- Please provide your slack bot token in Step 1 first! Once done with
- that, you can then specify which Slack channels you want to make
- searchable.
-
- )}
- >
- );
-};
-
-export default function Page() {
- return (
-
-
-
-
-
-
} title="Slack" />
-
-
-
- );
-}
diff --git a/web/src/app/admin/connectors/teams/page.tsx b/web/src/app/admin/connectors/teams/page.tsx
deleted file mode 100644
index 530d430abb6..00000000000
--- a/web/src/app/admin/connectors/teams/page.tsx
+++ /dev/null
@@ -1,275 +0,0 @@
-"use client";
-
-import * as Yup from "yup";
-import { TrashIcon, TeamsIcon } from "@/components/icons/icons"; // Make sure you have a Document360 icon
-import { errorHandlingFetcher } from "@/lib/fetcher";
-import useSWR, { useSWRConfig } from "swr";
-import { LoadingAnimation } from "@/components/Loading";
-import { HealthCheckBanner } from "@/components/health/healthcheck";
-import {
- TeamsConfig,
- TeamsCredentialJson,
- ConnectorIndexingStatus,
- Credential,
-} from "@/lib/types"; // Modify or create these types as required
-import { adminDeleteCredential, linkCredential } from "@/lib/credential";
-import { CredentialForm } from "@/components/admin/connectors/CredentialForm";
-import {
- TextFormField,
- TextArrayFieldBuilder,
-} from "@/components/admin/connectors/Field";
-import { ConnectorsTable } from "@/components/admin/connectors/table/ConnectorsTable";
-import { ConnectorForm } from "@/components/admin/connectors/ConnectorForm";
-import { usePublicCredentials } from "@/lib/hooks";
-import { AdminPageTitle } from "@/components/admin/Title";
-import { Card, Text, Title } from "@tremor/react";
-import { ErrorCallout } from "@/components/ErrorCallout";
-
-const MainSection = () => {
- const { mutate } = useSWRConfig();
- const {
- data: connectorIndexingStatuses,
- isLoading: isConnectorIndexingStatusesLoading,
- error: connectorIndexingStatusesError,
- } = useSWR
[]>(
- "/api/manage/admin/connector/indexing-status",
- errorHandlingFetcher
- );
-
- const {
- data: credentialsData,
- isLoading: isCredentialsLoading,
- error: credentialsError,
- refreshCredentials,
- } = usePublicCredentials();
-
- if (
- (!connectorIndexingStatuses && isConnectorIndexingStatusesLoading) ||
- (!credentialsData && isCredentialsLoading)
- ) {
- return ;
- }
-
- if (connectorIndexingStatusesError || !connectorIndexingStatuses) {
- return (
-
- );
- }
-
- if (credentialsError || !credentialsData) {
- return (
-
- );
- }
-
- const teamsConnectorIndexingStatuses: ConnectorIndexingStatus<
- TeamsConfig,
- TeamsCredentialJson
- >[] = connectorIndexingStatuses.filter(
- (connectorIndexingStatus) =>
- connectorIndexingStatus.connector.source === "teams"
- );
-
- const teamsCredential: Credential | undefined =
- credentialsData.find(
- (credential) => credential.credential_json?.teams_client_id
- );
-
- return (
- <>
-
- The Teams connector allows you to index and search through your Teams
- channels. Once setup, all messages from the channels contained in the
- specified teams will be queryable within Danswer.
-
-
-
- Step 1: Provide Teams credentials
-
- {teamsCredential ? (
- <>
-
- Existing Azure AD Client ID:
-
- {teamsCredential.credential_json.teams_client_id}
-
-
-
- >
- ) : (
- <>
-
- As a first step, please provide Application (client) ID, Directory
- (tenant) ID, and Client Secret. You can follow the guide{" "}
-
- here
- {" "}
- to create an Azure AD application and obtain these values.
-
-
-
- formBody={
- <>
-
-
-
- >
- }
- validationSchema={Yup.object().shape({
- teams_client_id: Yup.string().required(
- "Please enter your Application (client) ID"
- ),
- teams_directory_id: Yup.string().required(
- "Please enter your Directory (tenant) ID"
- ),
- teams_client_secret: Yup.string().required(
- "Please enter your Client Secret"
- ),
- })}
- initialValues={{
- teams_client_id: "",
- teams_directory_id: "",
- teams_client_secret: "",
- }}
- onSubmit={(isSuccess) => {
- if (isSuccess) {
- refreshCredentials();
- }
- }}
- />
-
- >
- )}
-
-
- Step 2: Manage Teams Connector
-
-
- {teamsConnectorIndexingStatuses.length > 0 && (
- <>
-
- The latest messages from the specified teams are fetched every 10
- minutes.
-
-
-
- connectorIndexingStatuses={teamsConnectorIndexingStatuses}
- liveCredential={teamsCredential}
- getCredential={(credential) =>
- credential.credential_json.teams_directory_id
- }
- onUpdate={() =>
- mutate("/api/manage/admin/connector/indexing-status")
- }
- onCredentialLink={async (connectorId) => {
- if (teamsCredential) {
- await linkCredential(connectorId, teamsCredential.id);
- mutate("/api/manage/admin/connector/indexing-status");
- }
- }}
- specialColumns={[
- {
- header: "Connectors",
- key: "connectors",
- getValue: (ccPairStatus) => {
- const connectorConfig =
- ccPairStatus.connector.connector_specific_config;
- return `${connectorConfig.teams}`;
- },
- },
- ]}
- includeName
- />
-
- >
- )}
-
- {teamsCredential ? (
-
-
- nameBuilder={(values) =>
- values.teams && values.teams.length > 0
- ? `Teams-${values.teams.join("-")}`
- : "Teams"
- }
- ccPairNameBuilder={(values) =>
- values.teams && values.teams.length > 0
- ? `Teams-${values.teams.join("-")}`
- : "Teams"
- }
- source="teams"
- inputType="poll"
- // formBody={<>>}
- formBodyBuilder={TextArrayFieldBuilder({
- name: "teams",
- label: "Teams:",
- subtext:
- "Specify 0 or more Teams to index. " +
- "For example, specifying the Team 'Support' for the 'danswerai' Org will cause " +
- "us to only index messages sent in channels belonging to the 'Support' Team. " +
- "If no Teams are specified, all Teams in your organization will be indexed.",
- })}
- validationSchema={Yup.object().shape({
- teams: Yup.array()
- .of(Yup.string().required("Team names must be strings"))
- .required(),
- })}
- initialValues={{
- teams: [],
- }}
- credentialId={teamsCredential.id}
- refreshFreq={10 * 60} // 10 minutes
- />
-
- ) : (
-
- Please provide all Azure info in Step 1 first! Once you're done
- with that, you can then specify which teams you want to make
- searchable.
-
- )}
- >
- );
-};
-
-export default function Page() {
- return (
-
-
-
-
-
-
} title="Teams" />
-
-
-
- );
-}
diff --git a/web/src/app/admin/connectors/web/page.tsx b/web/src/app/admin/connectors/web/page.tsx
deleted file mode 100644
index 410d187920e..00000000000
--- a/web/src/app/admin/connectors/web/page.tsx
+++ /dev/null
@@ -1,184 +0,0 @@
-"use client";
-
-import useSWR, { useSWRConfig } from "swr";
-import * as Yup from "yup";
-
-import { LoadingAnimation } from "@/components/Loading";
-import {
- GlobeIcon,
- GearIcon,
- ArrowSquareOutIcon,
-} from "@/components/icons/icons";
-import { errorHandlingFetcher } from "@/lib/fetcher";
-import { ErrorCallout } from "@/components/ErrorCallout";
-import {
- SelectorFormField,
- TextFormField,
-} from "@/components/admin/connectors/Field";
-import { HealthCheckBanner } from "@/components/health/healthcheck";
-import { ConnectorIndexingStatus, WebConfig } from "@/lib/types";
-import { ConnectorsTable } from "@/components/admin/connectors/table/ConnectorsTable";
-import { ConnectorForm } from "@/components/admin/connectors/ConnectorForm";
-import { AdminPageTitle } from "@/components/admin/Title";
-import { Card, Title } from "@tremor/react";
-
-const SCRAPE_TYPE_TO_PRETTY_NAME = {
- recursive: "Recursive",
- single: "Single Page",
- sitemap: "Sitemap",
-};
-
-export default function Web() {
- const { mutate } = useSWRConfig();
-
- const {
- data: connectorIndexingStatuses,
- isLoading: isConnectorIndexingStatusesLoading,
- error: connectorIndexingStatusesError,
- } = useSWR[]>(
- "/api/manage/admin/connector/indexing-status",
- errorHandlingFetcher
- );
-
- const webIndexingStatuses: ConnectorIndexingStatus[] =
- connectorIndexingStatuses?.filter(
- (connectorIndexingStatus) =>
- connectorIndexingStatus.connector.source === "web"
- ) ?? [];
-
- return (
-
-
-
-
-
-
} title="Web" />
-
-
- Step 1: Specify which websites to index
-
-
- We re-fetch the latest state of the website once a day.
-
-
-
- nameBuilder={(values) => `WebConnector-${values.base_url}`}
- ccPairNameBuilder={(values) => values.base_url}
- // Since there is no "real" credential associated with a web connector
- // we create a dummy one here so that we can associate the CC Pair with a
- // user. This is needed since the user for a CC Pair is found via the credential
- // associated with it.
- shouldCreateEmptyCredentialForConnector={true}
- source="web"
- inputType="load_state"
- formBody={
- <>
-
-
-
-
- >
- }
- validationSchema={Yup.object().shape({
- base_url: Yup.string().required(
- "Please enter the website URL to scrape e.g. https://docs.danswer.dev/"
- ),
- web_connector_type: Yup.string()
- .oneOf(["recursive", "single", "sitemap"])
- .optional(),
- })}
- initialValues={{
- base_url: "",
- web_connector_type: undefined,
- }}
- refreshFreq={60 * 60 * 24} // 1 day
- pruneFreq={0} // Don't prune
- />
-
-
-
- Already Indexed Websites
-
- {isConnectorIndexingStatusesLoading ? (
-
- ) : connectorIndexingStatusesError || !connectorIndexingStatuses ? (
-
Error loading indexing history
- ) : webIndexingStatuses.length > 0 ? (
-
- connectorIndexingStatuses={webIndexingStatuses}
- specialColumns={[
- {
- header: "Base URL",
- key: "base_url",
- getValue: (
- ccPairStatus: ConnectorIndexingStatus
- ) => {
- const connectorConfig =
- ccPairStatus.connector.connector_specific_config;
- return (
-
- );
- },
- },
- {
- header: "Scrape Method",
- key: "web_connector_type",
- getValue: (ccPairStatus) => {
- const connectorConfig =
- ccPairStatus.connector.connector_specific_config;
- return connectorConfig.web_connector_type
- ? SCRAPE_TYPE_TO_PRETTY_NAME[
- connectorConfig.web_connector_type
- ]
- : "Recursive";
- },
- },
- ]}
- onUpdate={() => mutate("/api/manage/admin/connector/indexing-status")}
- />
- ) : (
- No indexed websites found
- )}
-
- );
-}
diff --git a/web/src/app/admin/connectors/wikipedia/page.tsx b/web/src/app/admin/connectors/wikipedia/page.tsx
deleted file mode 100644
index f410b209a21..00000000000
--- a/web/src/app/admin/connectors/wikipedia/page.tsx
+++ /dev/null
@@ -1,214 +0,0 @@
-"use client";
-
-import * as Yup from "yup";
-import { WikipediaIcon, TrashIcon } from "@/components/icons/icons";
-import {
- TextArrayField,
- TextArrayFieldBuilder,
- TextFormField,
-} from "@/components/admin/connectors/Field";
-import { HealthCheckBanner } from "@/components/health/healthcheck";
-import { CredentialForm } from "@/components/admin/connectors/CredentialForm";
-import {
- WikipediaCredentialJson,
- WikipediaConfig,
- ConnectorIndexingStatus,
- Credential,
-} from "@/lib/types";
-import useSWR, { useSWRConfig } from "swr";
-import { errorHandlingFetcher } from "@/lib/fetcher";
-import { ErrorCallout } from "@/components/ErrorCallout";
-import { LoadingAnimation } from "@/components/Loading";
-import { adminDeleteCredential, linkCredential } from "@/lib/credential";
-import { ConnectorForm } from "@/components/admin/connectors/ConnectorForm";
-import { ConnectorsTable } from "@/components/admin/connectors/table/ConnectorsTable";
-import { usePopup } from "@/components/admin/connectors/Popup";
-import { usePublicCredentials } from "@/lib/hooks";
-import { AdminPageTitle } from "@/components/admin/Title";
-import { Card, Text, Title } from "@tremor/react";
-
-const Main = () => {
- const { popup, setPopup } = usePopup();
-
- const { mutate } = useSWRConfig();
- const {
- data: connectorIndexingStatuses,
- isLoading: isConnectorIndexingStatusesLoading,
- error: connectorIndexingStatusesError,
- } = useSWR[]>(
- "/api/manage/admin/connector/indexing-status",
- errorHandlingFetcher
- );
- const {
- data: credentialsData,
- isLoading: isCredentialsLoading,
- error: credentialsError,
- refreshCredentials,
- } = usePublicCredentials();
-
- if (
- (!connectorIndexingStatuses && isConnectorIndexingStatusesLoading) ||
- (!credentialsData && isCredentialsLoading)
- ) {
- return ;
- }
-
- if (connectorIndexingStatusesError || !connectorIndexingStatuses) {
- return (
-
- );
- }
-
- if (credentialsError || !credentialsData) {
- return (
-
- );
- }
-
- const wikipediaConnectorIndexingStatuses: ConnectorIndexingStatus<
- WikipediaConfig,
- WikipediaCredentialJson
- >[] = connectorIndexingStatuses.filter(
- (connectorIndexingStatus) =>
- connectorIndexingStatus.connector.source === "wikipedia"
- );
- const wikipediaCredential: Credential | undefined =
- credentialsData.find((credential) => true);
-
- return (
- <>
- {popup}
- {wikipediaConnectorIndexingStatuses.length > 0 && (
- <>
-
- Wikipedia indexing status
-
-
- The latest page, chapter, book and shelf changes are fetched every
- 10 minutes.
-
-
-
- connectorIndexingStatuses={wikipediaConnectorIndexingStatuses}
- liveCredential={wikipediaCredential}
- getCredential={(credential) => {
- return ;
- }}
- onCredentialLink={async (connectorId) => {
- if (wikipediaCredential) {
- await linkCredential(connectorId, wikipediaCredential.id);
- mutate("/api/manage/admin/connector/indexing-status");
- }
- }}
- onUpdate={() =>
- mutate("/api/manage/admin/connector/indexing-status")
- }
- />
-
- >
- )}
-
- {wikipediaCredential && (
- <>
-
- Create Connection
-
- Press connect below to start the connection to your Wikipedia
- instance.
-
-
- nameBuilder={(values) =>
- `WikipediaConnector-${values.connector_name}`
- }
- ccPairNameBuilder={(values) =>
- `WikipediaConnector-${values.connector_name}`
- }
- source="wikipedia"
- inputType="poll"
- formBodyBuilder={(values) => (
-
-
-
- {TextArrayFieldBuilder({
- name: "pages",
- label: "Pages to index:",
- subtext:
- "Specify 0 or more names of pages to index. Only specify the name of the page, not its url.",
- })(values)}
- {TextArrayFieldBuilder({
- name: "categories",
- label: "Categories to index:",
- subtext:
- "Specify 0 or more names of categories to index. These are pages" +
- " with a name of the form 'Category: XYZ', that are lists of other pages/categories. Only" +
- " specify the name of the category, not its url.",
- })(values)}
-
-
- )}
- validationSchema={Yup.object().shape({
- connector_name: Yup.string().required(
- "Please enter a name for your Wikipedia connector."
- ),
- language_code: Yup.string().default("en"),
- categories: Yup.array().of(
- Yup.string().required(
- "Please enter categories to index from your Wikipedia site"
- )
- ),
- pages: Yup.array().of(
- Yup.string().required(
- "Please enter pages to index from your Wikipedia site"
- )
- ),
- recurse_depth: Yup.number().required(
- "Please enter the recursion depth for your Wikipedia site."
- ),
- })}
- initialValues={{
- connector_name: "",
- language_code: "en",
- categories: [],
- pages: [],
- recurse_depth: 0,
- }}
- refreshFreq={10 * 60} // 10 minutes
- credentialId={wikipediaCredential.id}
- />
-
- >
- )}
- >
- );
-};
-
-export default function Page() {
- return (
-
-
-
-
-
-
} title="Wikipedia" />
-
-
-
- );
-}
diff --git a/web/src/app/admin/connectors/zendesk/page.tsx b/web/src/app/admin/connectors/zendesk/page.tsx
deleted file mode 100644
index dac1fe76e49..00000000000
--- a/web/src/app/admin/connectors/zendesk/page.tsx
+++ /dev/null
@@ -1,254 +0,0 @@
-"use client";
-
-import * as Yup from "yup";
-import { TrashIcon, ZendeskIcon } from "@/components/icons/icons";
-import { TextFormField } from "@/components/admin/connectors/Field";
-import { HealthCheckBanner } from "@/components/health/healthcheck";
-import { CredentialForm } from "@/components/admin/connectors/CredentialForm";
-import {
- ZendeskCredentialJson,
- ZendeskConfig,
- ConnectorIndexingStatus,
- Credential,
-} from "@/lib/types";
-import useSWR, { useSWRConfig } from "swr";
-import { errorHandlingFetcher } from "@/lib/fetcher";
-import { ErrorCallout } from "@/components/ErrorCallout";
-import { LoadingAnimation } from "@/components/Loading";
-import { adminDeleteCredential, linkCredential } from "@/lib/credential";
-import { ConnectorForm } from "@/components/admin/connectors/ConnectorForm";
-import { ConnectorsTable } from "@/components/admin/connectors/table/ConnectorsTable";
-import { usePopup } from "@/components/admin/connectors/Popup";
-import { usePublicCredentials } from "@/lib/hooks";
-import { AdminPageTitle } from "@/components/admin/Title";
-import { Card, Divider, Text, Title } from "@tremor/react";
-
-const Main = () => {
- const { popup, setPopup } = usePopup();
-
- const { mutate } = useSWRConfig();
- const {
- data: connectorIndexingStatuses,
- isLoading: isConnectorIndexingStatusesLoading,
- error: connectorIndexingStatusesError,
- } = useSWR[]>(
- "/api/manage/admin/connector/indexing-status",
- errorHandlingFetcher
- );
- const {
- data: credentialsData,
- isLoading: isCredentialsLoading,
- error: credentialsError,
- refreshCredentials,
- } = usePublicCredentials();
-
- if (
- (!connectorIndexingStatuses && isConnectorIndexingStatusesLoading) ||
- (!credentialsData && isCredentialsLoading)
- ) {
- return ;
- }
-
- if (connectorIndexingStatusesError || !connectorIndexingStatuses) {
- return (
-
- );
- }
-
- if (credentialsError || !credentialsData) {
- return (
-
- );
- }
-
- const zendeskConnectorIndexingStatuses: ConnectorIndexingStatus<
- ZendeskConfig,
- ZendeskCredentialJson
- >[] = connectorIndexingStatuses.filter(
- (connectorIndexingStatus) =>
- connectorIndexingStatus.connector.source === "zendesk"
- );
- const zendeskCredential: Credential | undefined =
- credentialsData.find(
- (credential) => credential.credential_json?.zendesk_email
- );
-
- return (
- <>
- {popup}
-
- Provide your API details
-
-
- {zendeskCredential ? (
- <>
-
-
Existing API Token:
-
- {zendeskCredential.credential_json?.zendesk_token}
-
-
-
- >
- ) : (
- <>
-
- To get started you'll need API token details for your Zendesk
- instance. You can generate this by access the Admin Center of your
- instance (e.g. https://<subdomain>.zendesk.com/admin/).
- Proceed to the "Apps and Integrations" section and
- "Zendesk API" page. Add a new API token and provide it
- with a name. You will also need to provide the e-mail address of a
- user that the system will impersonate. This is of little consequence
- as we are only performing read actions.
-
-
-
- formBody={
- <>
-
-
-
- >
- }
- validationSchema={Yup.object().shape({
- zendesk_subdomain: Yup.string().required(
- "Please enter the subdomain for your Zendesk instance"
- ),
- zendesk_email: Yup.string().required(
- "Please enter your user email to user with the token"
- ),
- zendesk_token: Yup.string().required(
- "Please enter your Zendesk API token"
- ),
- })}
- initialValues={{
- zendesk_subdomain: "",
- zendesk_email: "",
- zendesk_token: "",
- }}
- onSubmit={(isSuccess) => {
- if (isSuccess) {
- refreshCredentials();
- mutate("/api/manage/admin/connector/indexing-status");
- }
- }}
- />
-
- >
- )}
-
- {zendeskConnectorIndexingStatuses.length > 0 && (
- <>
-
- Zendesk indexing status
-
-
- The latest article changes are fetched every 10 minutes.
-
-
-
- connectorIndexingStatuses={zendeskConnectorIndexingStatuses}
- liveCredential={zendeskCredential}
- getCredential={(credential) => {
- return (
-
-
{credential.credential_json.zendesk_token}
-
- );
- }}
- onCredentialLink={async (connectorId) => {
- if (zendeskCredential) {
- await linkCredential(connectorId, zendeskCredential.id);
- mutate("/api/manage/admin/connector/indexing-status");
- }
- }}
- onUpdate={() =>
- mutate("/api/manage/admin/connector/indexing-status")
- }
- />
-
- >
- )}
-
- {zendeskCredential && zendeskConnectorIndexingStatuses.length === 0 && (
- <>
-
- Create Connection
-
- Press connect below to start the connection to your Zendesk
- instance.
-
-
- nameBuilder={(values) => `ZendeskConnector`}
- ccPairNameBuilder={(values) => `ZendeskConnector`}
- source="zendesk"
- inputType="poll"
- formBody={<>>}
- validationSchema={Yup.object().shape({})}
- initialValues={{}}
- refreshFreq={10 * 60} // 10 minutes
- credentialId={zendeskCredential.id}
- />
-
- >
- )}
-
- {!zendeskCredential && (
- <>
-
- Please provide your API details in Step 1 first! Once done with
- that, you'll be able to start the connection then see indexing
- status.
-
- >
- )}
- >
- );
-};
-
-export default function Page() {
- return (
-
-
-
-
-
-
} title="Zendesk" />
-
-
-
- );
-}
diff --git a/web/src/app/admin/connectors/zulip/page.tsx b/web/src/app/admin/connectors/zulip/page.tsx
deleted file mode 100644
index 66a35df30a5..00000000000
--- a/web/src/app/admin/connectors/zulip/page.tsx
+++ /dev/null
@@ -1,248 +0,0 @@
-"use client";
-
-import * as Yup from "yup";
-import { ZulipIcon, TrashIcon } from "@/components/icons/icons";
-import { errorHandlingFetcher } from "@/lib/fetcher";
-import { ErrorCallout } from "@/components/ErrorCallout";
-import useSWR, { useSWRConfig } from "swr";
-import { LoadingAnimation } from "@/components/Loading";
-import { HealthCheckBanner } from "@/components/health/healthcheck";
-import {
- ZulipConfig,
- Credential,
- ZulipCredentialJson,
- ConnectorIndexingStatus,
-} from "@/lib/types";
-import { adminDeleteCredential, linkCredential } from "@/lib/credential";
-import { CredentialForm } from "@/components/admin/connectors/CredentialForm";
-import { TextFormField } from "@/components/admin/connectors/Field";
-import { ConnectorsTable } from "@/components/admin/connectors/table/ConnectorsTable";
-import { ConnectorForm } from "@/components/admin/connectors/ConnectorForm";
-import { usePublicCredentials } from "@/lib/hooks";
-import { Card, Divider, Text, Title } from "@tremor/react";
-import { AdminPageTitle } from "@/components/admin/Title";
-
-const MainSection = () => {
- const { mutate } = useSWRConfig();
- const {
- data: connectorIndexingStatuses,
- isLoading: isConnectorIndexingStatusesLoading,
- error: connectorIndexingStatusesError,
- } = useSWR[]>(
- "/api/manage/admin/connector/indexing-status",
- errorHandlingFetcher
- );
-
- const {
- data: credentialsData,
- isLoading: isCredentialsLoading,
- error: credentialsError,
- refreshCredentials,
- } = usePublicCredentials();
-
- if (
- (!connectorIndexingStatuses && isConnectorIndexingStatusesLoading) ||
- (!credentialsData && isCredentialsLoading)
- ) {
- return ;
- }
-
- if (connectorIndexingStatusesError || !connectorIndexingStatuses) {
- return (
-
- );
- }
-
- if (credentialsError || !credentialsData) {
- return (
-
- );
- }
-
- const zulipConnectorIndexingStatuses: ConnectorIndexingStatus<
- ZulipConfig,
- ZulipCredentialJson
- >[] = connectorIndexingStatuses.filter(
- (connectorIndexingStatus) =>
- connectorIndexingStatus.connector.source === "zulip"
- );
- const zulipCredential: Credential | undefined =
- credentialsData.filter(
- (credential) => credential.credential_json?.zuliprc_content
- )[0];
-
- return (
- <>
-
- Step 1: Provide Credentials
-
- {zulipCredential ? (
- <>
-
- Existing zuliprc file content:
-
- {zulipCredential.credential_json.zuliprc_content}
- {" "}
-
-
- >
- ) : (
- <>
-
- To use the Zulip connector, you must first provide content of the
- zuliprc config file. For more details on setting up the Danswer
- Zulip connector, see the{" "}
-
- docs
-
- .
-
-
-
- formBody={
- <>
-
- >
- }
- validationSchema={Yup.object().shape({
- zuliprc_content: Yup.string().required(
- "Please enter content of the zuliprc file"
- ),
- })}
- initialValues={{
- zuliprc_content: "",
- }}
- onSubmit={(isSuccess) => {
- if (isSuccess) {
- refreshCredentials();
- }
- }}
- />
-
- >
- )}
-
-
- Step 2: Which workspaces do you want to make searchable?
-
-
- {zulipCredential ? (
- <>
- {zulipConnectorIndexingStatuses.length > 0 && (
- <>
-
- We pull the latest messages from each workspace listed below
- every 10 minutes.
-
-
-
- credential.credential_json.zuliprc_content
- }
- specialColumns={[
- {
- header: "Realm name",
- key: "realm_name",
- getValue: (ccPairStatus) =>
- ccPairStatus.connector.connector_specific_config
- .realm_name,
- },
- {
- header: "Realm url",
- key: "realm_url",
- getValue: (ccPairStatus) =>
- ccPairStatus.connector.connector_specific_config
- .realm_url,
- },
- ]}
- onUpdate={() =>
- mutate("/api/manage/admin/connector/indexing-status")
- }
- onCredentialLink={async (connectorId) => {
- if (zulipCredential) {
- await linkCredential(connectorId, zulipCredential.id);
- mutate("/api/manage/admin/connector/indexing-status");
- }
- }}
- />
-
-
- >
- )}
-
-
- Connect to a New Realm
-
- nameBuilder={(values) => `ZulipConnector-${values.realm_name}`}
- ccPairNameBuilder={(values) => values.realm_name}
- source="zulip"
- inputType="poll"
- credentialId={zulipCredential.id}
- formBody={
- <>
-
-
- >
- }
- validationSchema={Yup.object().shape({
- realm_name: Yup.string().required(
- "Please enter the realm name"
- ),
- realm_url: Yup.string().required("Please enter the realm url"),
- })}
- initialValues={{
- realm_name: "",
- realm_url: "",
- }}
- refreshFreq={10 * 60} // 10 minutes
- />
-
- >
- ) : (
-
- Please provide your Zulip credentials in Step 1 first! Once done with
- that, you can then specify which Zulip realms you want to make
- searchable.
-
- )}
- >
- );
-};
-
-export default function Page() {
- return (
-
-
-
-
-
-
} title="Zulip" />
-
-
-
- );
-}
diff --git a/web/src/app/admin/documents/explorer/Explorer.tsx b/web/src/app/admin/documents/explorer/Explorer.tsx
index 315df323e8b..a773c222484 100644
--- a/web/src/app/admin/documents/explorer/Explorer.tsx
+++ b/web/src/app/admin/documents/explorer/Explorer.tsx
@@ -15,8 +15,9 @@ import { HorizontalFilters } from "@/components/search/filtering/Filters";
import { useFilters } from "@/lib/hooks";
import { buildFilters } from "@/lib/search/utils";
import { DocumentUpdatedAtBadge } from "@/components/search/DocumentUpdatedAtBadge";
-import { Connector, DocumentSet } from "@/lib/types";
+import { DocumentSet } from "@/lib/types";
import { SourceIcon } from "@/components/SourceIcon";
+import { Connector } from "@/lib/connectors/connectors";
const DocumentDisplay = ({
document,
@@ -173,7 +174,11 @@ export function Explorer({
setQuery(event.target.value);
}}
onKeyDown={(event) => {
- if (event.key === "Enter" && !event.shiftKey) {
+ if (
+ event.key === "Enter" &&
+ !event.shiftKey &&
+ !(event.nativeEvent as any).isComposing
+ ) {
onSearch(query);
event.preventDefault();
}
diff --git a/web/src/app/admin/documents/sets/DocumentSetCreationForm.tsx b/web/src/app/admin/documents/sets/DocumentSetCreationForm.tsx
index 89a73bf3c5d..814af4e2863 100644
--- a/web/src/app/admin/documents/sets/DocumentSetCreationForm.tsx
+++ b/web/src/app/admin/documents/sets/DocumentSetCreationForm.tsx
@@ -3,16 +3,24 @@
import { ArrayHelpers, FieldArray, Form, Formik } from "formik";
import * as Yup from "yup";
import { PopupSpec } from "@/components/admin/connectors/Popup";
-import { createDocumentSet, updateDocumentSet } from "./lib";
-import { ConnectorIndexingStatus, DocumentSet, UserGroup } from "@/lib/types";
import {
- BooleanFormField,
- TextFormField,
-} from "@/components/admin/connectors/Field";
+ createDocumentSet,
+ updateDocumentSet,
+ DocumentSetCreationRequest,
+} from "./lib";
+import {
+ ConnectorIndexingStatus,
+ DocumentSet,
+ UserGroup,
+ UserRole,
+} from "@/lib/types";
+import { TextFormField } from "@/components/admin/connectors/Field";
import { ConnectorTitle } from "@/components/admin/connectors/ConnectorTitle";
-import { Button, Divider, Text } from "@tremor/react";
-import { FiUsers } from "react-icons/fi";
+import { Button, Divider } from "@tremor/react";
import { usePaidEnterpriseFeaturesEnabled } from "@/components/settings/usePaidEnterpriseFeaturesEnabled";
+import { IsPublicGroupSelector } from "@/components/IsPublicGroupSelector";
+import React, { useEffect, useState } from "react";
+import { useUser } from "@/components/user/UserProvider";
interface SetCreationPopupProps {
ccPairs: ConnectorIndexingStatus[];
@@ -30,27 +38,29 @@ export const DocumentSetCreationForm = ({
existingDocumentSet,
}: SetCreationPopupProps) => {
const isPaidEnterpriseFeaturesEnabled = usePaidEnterpriseFeaturesEnabled();
-
const isUpdate = existingDocumentSet !== undefined;
+ const [localCcPairs, setLocalCcPairs] = useState(ccPairs);
+ const { user } = useUser();
+
+ useEffect(() => {
+ if (existingDocumentSet?.is_public) {
+ return;
+ }
+ }, [existingDocumentSet?.is_public]);
return (
-
initialValues={{
- name: existingDocumentSet ? existingDocumentSet.name : "",
- description: existingDocumentSet
- ? existingDocumentSet.description
- : "",
- cc_pair_ids: existingDocumentSet
- ? existingDocumentSet.cc_pair_descriptors.map(
- (ccPairDescriptor) => {
- return ccPairDescriptor.id;
- }
- )
- : ([] as number[]),
- is_public: existingDocumentSet ? existingDocumentSet.is_public : true,
- users: existingDocumentSet ? existingDocumentSet.users : [],
- groups: existingDocumentSet ? existingDocumentSet.groups : [],
+ name: existingDocumentSet?.name ?? "",
+ description: existingDocumentSet?.description ?? "",
+ cc_pair_ids:
+ existingDocumentSet?.cc_pair_descriptors.map(
+ (ccPairDescriptor) => ccPairDescriptor.id
+ ) ?? [],
+ is_public: existingDocumentSet?.is_public ?? true,
+ users: existingDocumentSet?.users ?? [],
+ groups: existingDocumentSet?.groups ?? [],
}}
validationSchema={Yup.object().shape({
name: Yup.string().required("Please enter a name for the set"),
@@ -74,6 +84,7 @@ export const DocumentSetCreationForm = ({
response = await updateDocumentSet({
id: existingDocumentSet.id,
...processedValues,
+ users: processedValues.users,
});
} else {
response = await createDocumentSet(processedValues);
@@ -98,129 +109,90 @@ export const DocumentSetCreationForm = ({
}
}}
>
- {({ isSubmitting, values }) => (
-