mirror of
https://github.com/danswer-ai/danswer.git
synced 2025-09-20 13:05:49 +02:00
EE Connector Deletion Bugfix + Refactor (#2042)
--------- Co-authored-by: Weves <chrisweaver101@gmail.com>
This commit is contained in:
committed by
GitHub
parent
79523f2e0a
commit
c7e5b11c63
@@ -1,7 +1,7 @@
|
||||
"use client";
|
||||
|
||||
import { Button } from "@tremor/react";
|
||||
import { CCPairFullInfo } from "./types";
|
||||
import { CCPairFullInfo, ConnectorCredentialPairStatus } from "./types";
|
||||
import { usePopup } from "@/components/admin/connectors/Popup";
|
||||
import { FiTrash } from "react-icons/fi";
|
||||
import { deleteCCPair } from "@/lib/documentDeletion";
|
||||
@@ -16,7 +16,7 @@ export function DeletionButton({ ccPair }: { ccPair: CCPairFullInfo }) {
|
||||
ccPair?.latest_deletion_attempt?.status === "STARTED";
|
||||
|
||||
let tooltip: string;
|
||||
if (ccPair.connector.disabled) {
|
||||
if (ccPair.status !== ConnectorCredentialPairStatus.ACTIVE) {
|
||||
if (isDeleting) {
|
||||
tooltip = "This connector is currently being deleted";
|
||||
} else {
|
||||
@@ -41,7 +41,9 @@ export function DeletionButton({ ccPair }: { ccPair: CCPairFullInfo }) {
|
||||
)
|
||||
}
|
||||
icon={FiTrash}
|
||||
disabled={!ccPair.connector.disabled || isDeleting}
|
||||
disabled={
|
||||
ccPair.status === ConnectorCredentialPairStatus.ACTIVE || isDeleting
|
||||
}
|
||||
tooltip={tooltip}
|
||||
>
|
||||
Delete
|
||||
|
@@ -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 ? (
|
||||
<Button
|
||||
color="green"
|
||||
size="xs"
|
||||
onClick={() =>
|
||||
disableConnector(ccPair.connector, setPopup, () =>
|
||||
mutate(buildCCPairInfoUrl(ccPair.id))
|
||||
setCCPairStatus(
|
||||
ccPair.id,
|
||||
ConnectorCredentialPairStatus.ACTIVE,
|
||||
setPopup,
|
||||
() => mutate(buildCCPairInfoUrl(ccPair.id))
|
||||
)
|
||||
}
|
||||
tooltip="Click to start indexing again!"
|
||||
@@ -35,8 +38,11 @@ export function ModifyStatusButtonCluster({
|
||||
color="red"
|
||||
size="xs"
|
||||
onClick={() =>
|
||||
disableConnector(ccPair.connector, setPopup, () =>
|
||||
mutate(buildCCPairInfoUrl(ccPair.id))
|
||||
setCCPairStatus(
|
||||
ccPair.id,
|
||||
ConnectorCredentialPairStatus.PAUSED,
|
||||
setPopup,
|
||||
() => mutate(buildCCPairInfoUrl(ccPair.id))
|
||||
)
|
||||
}
|
||||
tooltip={
|
||||
|
@@ -1,6 +1,6 @@
|
||||
"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";
|
||||
@@ -92,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
|
||||
@@ -112,9 +112,6 @@ function Main({ ccPairId }: { ccPairId: number }) {
|
||||
setEditableName(ccPair.name);
|
||||
setIsEditing(true);
|
||||
};
|
||||
const deleting =
|
||||
ccPair.latest_deletion_attempt?.status == "PENDING" ||
|
||||
ccPair.latest_deletion_attempt?.status == "STARTED";
|
||||
|
||||
const resetEditing = () => {
|
||||
setIsEditing(false);
|
||||
@@ -169,16 +166,18 @@ function Main({ ccPairId }: { ccPairId: number }) {
|
||||
ccPairId={ccPair.id}
|
||||
connectorId={ccPair.connector.id}
|
||||
credentialId={ccPair.credential.id}
|
||||
isDisabled={ccPair.connector.disabled}
|
||||
isDisabled={
|
||||
ccPair.status === ConnectorCredentialPairStatus.PAUSED
|
||||
}
|
||||
isDeleting={isDeleting}
|
||||
/>
|
||||
)}
|
||||
{!deleting && <ModifyStatusButtonCluster ccPair={ccPair} />}
|
||||
{!isDeleting && <ModifyStatusButtonCluster ccPair={ccPair} />}
|
||||
</div>
|
||||
</div>
|
||||
<CCPairStatus
|
||||
status={lastIndexAttempt?.status || "not_started"}
|
||||
disabled={ccPair.connector.disabled}
|
||||
disabled={ccPair.status === ConnectorCredentialPairStatus.PAUSED}
|
||||
isDeleting={isDeleting}
|
||||
/>
|
||||
<div className="text-sm mt-1">
|
||||
|
@@ -2,9 +2,16 @@ 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<any>;
|
||||
credential: Credential<any>;
|
||||
|
@@ -189,7 +189,6 @@ export default function AddConnector({
|
||||
refresh_freq: refreshFreq * 60 || null,
|
||||
prune_freq: pruneFreq * 60 || null,
|
||||
indexing_start: indexingStart,
|
||||
disabled: false,
|
||||
},
|
||||
undefined,
|
||||
credentialActivated ? false : true,
|
||||
|
@@ -43,7 +43,6 @@ export const submitFiles = async (
|
||||
refresh_freq: null,
|
||||
prune_freq: null,
|
||||
indexing_start: null,
|
||||
disabled: false,
|
||||
});
|
||||
if (connectorErrorMsg || !connector) {
|
||||
setPopup({
|
||||
|
@@ -44,7 +44,6 @@ export const submitGoogleSite = async (
|
||||
refresh_freq: advancedConfig.refreshFreq,
|
||||
prune_freq: advancedConfig.pruneFreq,
|
||||
indexing_start: advancedConfig.indexingStart,
|
||||
disabled: false,
|
||||
});
|
||||
if (connectorErrorMsg || !connector) {
|
||||
setPopup({
|
||||
|
@@ -32,6 +32,7 @@ import { Warning } from "@phosphor-icons/react";
|
||||
import Cookies from "js-cookie";
|
||||
import { TOGGLED_CONNECTORS_COOKIE_NAME } from "@/lib/constants";
|
||||
import { usePaidEnterpriseFeaturesEnabled } from "@/components/settings/usePaidEnterpriseFeaturesEnabled";
|
||||
import { ConnectorCredentialPairStatus } from "../../connector/[ccPairId]/types";
|
||||
|
||||
const columnWidths = {
|
||||
first: "20%",
|
||||
@@ -146,20 +147,25 @@ function ConnectorRow({
|
||||
};
|
||||
|
||||
const getActivityBadge = () => {
|
||||
if (ccPairsIndexingStatus.connector.disabled) {
|
||||
if (ccPairsIndexingStatus.deletion_attempt) {
|
||||
return (
|
||||
<Badge
|
||||
color="red"
|
||||
className="w-fit px-2 py-1 rounded-full border border-red-500"
|
||||
>
|
||||
<div className="flex text-xs items-center gap-x-1">
|
||||
<div className="w-3 h-3 rounded-full bg-red-500"></div>
|
||||
Deleting
|
||||
</div>
|
||||
</Badge>
|
||||
);
|
||||
}
|
||||
if (
|
||||
ccPairsIndexingStatus.cc_pair_status ===
|
||||
ConnectorCredentialPairStatus.DELETING
|
||||
) {
|
||||
return (
|
||||
<Badge
|
||||
color="red"
|
||||
className="w-fit px-2 py-1 rounded-full border border-red-500"
|
||||
>
|
||||
<div className="flex text-xs items-center gap-x-1">
|
||||
<div className="w-3 h-3 rounded-full bg-red-500"></div>
|
||||
Deleting
|
||||
</div>
|
||||
</Badge>
|
||||
);
|
||||
} else if (
|
||||
ccPairsIndexingStatus.cc_pair_status ===
|
||||
ConnectorCredentialPairStatus.PAUSED
|
||||
) {
|
||||
return (
|
||||
<Badge
|
||||
color="yellow"
|
||||
@@ -172,6 +178,8 @@ function ConnectorRow({
|
||||
</Badge>
|
||||
);
|
||||
}
|
||||
|
||||
// ACTIVE case
|
||||
switch (ccPairsIndexingStatus.last_status) {
|
||||
case "in_progress":
|
||||
return (
|
||||
@@ -302,7 +310,10 @@ export function CCPairIndexingStatusTable({
|
||||
const statuses = grouped[source];
|
||||
summaries[source] = {
|
||||
count: statuses.length,
|
||||
active: statuses.filter((status) => !status.connector.disabled).length,
|
||||
active: statuses.filter(
|
||||
(status) =>
|
||||
status.cc_pair_status === ConnectorCredentialPairStatus.ACTIVE
|
||||
).length,
|
||||
public: statuses.filter((status) => status.public_doc).length,
|
||||
totalDocsIndexed: statuses.reduce(
|
||||
(sum, status) => sum + status.docs_indexed,
|
||||
@@ -362,6 +373,7 @@ export function CCPairIndexingStatusTable({
|
||||
ccPairsIndexingStatus={{
|
||||
cc_pair_id: 1,
|
||||
name: "Sample File Connector",
|
||||
cc_pair_status: ConnectorCredentialPairStatus.ACTIVE,
|
||||
last_status: "success",
|
||||
connector: {
|
||||
name: "Sample File Connector",
|
||||
@@ -373,7 +385,6 @@ export function CCPairIndexingStatusTable({
|
||||
refresh_freq: 86400,
|
||||
prune_freq: null,
|
||||
indexing_start: new Date("2023-07-01T12:00:00Z"),
|
||||
disabled: false,
|
||||
id: 1,
|
||||
credential_ids: [],
|
||||
time_created: "2023-07-01T12:00:00Z",
|
||||
|
@@ -194,7 +194,6 @@ export function ConnectorForm<T extends Yup.AnyObject>({
|
||||
connector_specific_config: connectorConfig,
|
||||
refresh_freq: refreshFreq || 0,
|
||||
prune_freq: pruneFreq ?? null,
|
||||
disabled: false,
|
||||
indexing_start: indexingStart || null,
|
||||
});
|
||||
|
||||
@@ -343,7 +342,6 @@ export function UpdateConnectorForm<T extends Yup.AnyObject>({
|
||||
connector_specific_config: values,
|
||||
refresh_freq: existingConnector.refresh_freq,
|
||||
prune_freq: existingConnector.prune_freq,
|
||||
disabled: false,
|
||||
indexing_start: existingConnector.indexing_start,
|
||||
},
|
||||
existingConnector.id
|
||||
|
@@ -1,251 +0,0 @@
|
||||
import { ConnectorIndexingStatus } from "@/lib/types";
|
||||
|
||||
import { PopupSpec, usePopup } from "@/components/admin/connectors/Popup";
|
||||
import { useState } from "react";
|
||||
import { LinkBreakIcon, LinkIcon } from "@/components/icons/icons";
|
||||
import { disableConnector } from "@/lib/connector";
|
||||
import { AttachCredentialButtonForTable } from "@/components/admin/connectors/buttons/AttachCredentialButtonForTable";
|
||||
import { DeleteColumn } from "./DeleteColumn";
|
||||
import {
|
||||
Table,
|
||||
TableHead,
|
||||
TableRow,
|
||||
TableHeaderCell,
|
||||
TableBody,
|
||||
TableCell,
|
||||
} from "@tremor/react";
|
||||
import { FiCheck, FiXCircle } from "react-icons/fi";
|
||||
import { Credential } from "@/lib/connectors/credentials";
|
||||
|
||||
interface StatusRowProps<ConnectorConfigType, ConnectorCredentialType> {
|
||||
connectorIndexingStatus: ConnectorIndexingStatus<
|
||||
ConnectorConfigType,
|
||||
ConnectorCredentialType
|
||||
>;
|
||||
hasCredentialsIssue: boolean;
|
||||
setPopup: (popupSpec: PopupSpec | null) => void;
|
||||
onUpdate: () => void;
|
||||
}
|
||||
|
||||
export function StatusRow<ConnectorConfigType, ConnectorCredentialType>({
|
||||
connectorIndexingStatus,
|
||||
hasCredentialsIssue,
|
||||
setPopup,
|
||||
onUpdate,
|
||||
}: StatusRowProps<ConnectorConfigType, ConnectorCredentialType>) {
|
||||
const [statusHovered, setStatusHovered] = useState<boolean>(false);
|
||||
const connector = connectorIndexingStatus.connector;
|
||||
|
||||
let shouldDisplayDisabledToggle = !hasCredentialsIssue;
|
||||
let statusDisplay;
|
||||
switch (connectorIndexingStatus.last_status) {
|
||||
case "failed":
|
||||
statusDisplay = <div className="text-error">Failed</div>;
|
||||
break;
|
||||
default:
|
||||
statusDisplay = <div className="text-success flex">Enabled!</div>;
|
||||
}
|
||||
if (connector.disabled) {
|
||||
const deletionAttempt = connectorIndexingStatus.deletion_attempt;
|
||||
if (!deletionAttempt || deletionAttempt.status === "FAILURE") {
|
||||
statusDisplay = <div className="text-error">Paused</div>;
|
||||
} else {
|
||||
statusDisplay = <div className="text-error">Deleting...</div>;
|
||||
shouldDisplayDisabledToggle = false;
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="flex">
|
||||
{statusDisplay}
|
||||
{shouldDisplayDisabledToggle && (
|
||||
<div
|
||||
className="cursor-pointer ml-1 my-auto relative"
|
||||
onMouseEnter={() => setStatusHovered(true)}
|
||||
onMouseLeave={() => setStatusHovered(false)}
|
||||
onClick={() => disableConnector(connector, setPopup, onUpdate)}
|
||||
>
|
||||
{statusHovered && (
|
||||
<div className="flex flex-nowrap absolute top-0 left-0 ml-8 bg-background border border-border px-3 py-2 rounded shadow-lg">
|
||||
{connector.disabled ? "Enable!" : "Pause!"}
|
||||
</div>
|
||||
)}
|
||||
{connector.disabled ? (
|
||||
<LinkIcon className="my-auto flex flex-shrink-0 text-error" />
|
||||
) : (
|
||||
<LinkBreakIcon
|
||||
className={`my-auto flex flex-shrink-0 ${
|
||||
connectorIndexingStatus.last_status === "failed"
|
||||
? "text-error"
|
||||
: "text-success"
|
||||
}`}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export interface ColumnSpecification<
|
||||
ConnectorConfigType,
|
||||
ConnectorCredentialType,
|
||||
> {
|
||||
header: string;
|
||||
key: string;
|
||||
getValue: (
|
||||
ccPairStatus: ConnectorIndexingStatus<
|
||||
ConnectorConfigType,
|
||||
ConnectorCredentialType
|
||||
>
|
||||
) => JSX.Element | string | undefined;
|
||||
}
|
||||
|
||||
export interface ConnectorsTableProps<
|
||||
ConnectorConfigType,
|
||||
ConnectorCredentialType,
|
||||
> {
|
||||
connectorIndexingStatuses: ConnectorIndexingStatus<
|
||||
ConnectorConfigType,
|
||||
ConnectorCredentialType
|
||||
>[];
|
||||
liveCredential?: Credential<ConnectorCredentialType> | null;
|
||||
getCredential?: (
|
||||
credential: Credential<ConnectorCredentialType>
|
||||
) => JSX.Element | string;
|
||||
onUpdate: () => void;
|
||||
onCredentialLink?: (connectorId: number) => void;
|
||||
specialColumns?: ColumnSpecification<
|
||||
ConnectorConfigType,
|
||||
ConnectorCredentialType
|
||||
>[];
|
||||
includeName?: boolean;
|
||||
}
|
||||
|
||||
export function ConnectorsTable<ConnectorConfigType, ConnectorCredentialType>({
|
||||
connectorIndexingStatuses,
|
||||
liveCredential,
|
||||
getCredential,
|
||||
specialColumns,
|
||||
onUpdate,
|
||||
onCredentialLink,
|
||||
includeName = false,
|
||||
}: ConnectorsTableProps<ConnectorConfigType, ConnectorCredentialType>) {
|
||||
const { popup, setPopup } = usePopup();
|
||||
|
||||
const connectorIncludesCredential =
|
||||
getCredential !== undefined && onCredentialLink !== undefined;
|
||||
|
||||
const columns = [
|
||||
...(includeName ? [{ header: "Name", key: "name" }] : []),
|
||||
...(specialColumns ?? []),
|
||||
{
|
||||
header: "Status",
|
||||
key: "status",
|
||||
},
|
||||
{
|
||||
header: "Is Public",
|
||||
key: "is_public",
|
||||
},
|
||||
];
|
||||
if (connectorIncludesCredential) {
|
||||
columns.push({
|
||||
header: "Credential",
|
||||
key: "credential",
|
||||
});
|
||||
}
|
||||
columns.push({
|
||||
header: "Remove",
|
||||
key: "remove",
|
||||
});
|
||||
|
||||
return (
|
||||
<div>
|
||||
{popup}
|
||||
|
||||
<Table className="overflow-visible">
|
||||
<TableHead>
|
||||
<TableRow>
|
||||
{includeName && <TableHeaderCell>Name</TableHeaderCell>}
|
||||
{specialColumns?.map(({ header }) => (
|
||||
<TableHeaderCell key={header}>{header}</TableHeaderCell>
|
||||
))}
|
||||
<TableHeaderCell>Status</TableHeaderCell>
|
||||
<TableHeaderCell>Is Public</TableHeaderCell>
|
||||
{connectorIncludesCredential && (
|
||||
<TableHeaderCell>Credential</TableHeaderCell>
|
||||
)}
|
||||
<TableHeaderCell>Remove</TableHeaderCell>
|
||||
</TableRow>
|
||||
</TableHead>
|
||||
<TableBody>
|
||||
{connectorIndexingStatuses.map((connectorIndexingStatus) => {
|
||||
const connector = connectorIndexingStatus.connector;
|
||||
// const credential = connectorIndexingStatus.credential;
|
||||
const hasValidCredentials =
|
||||
liveCredential &&
|
||||
connector.credential_ids.includes(liveCredential.id);
|
||||
const credentialDisplay = connectorIncludesCredential ? (
|
||||
hasValidCredentials ? (
|
||||
<div className="max-w-sm truncate">
|
||||
{getCredential(liveCredential)}
|
||||
</div>
|
||||
) : liveCredential ? (
|
||||
<AttachCredentialButtonForTable
|
||||
onClick={() => onCredentialLink(connector.id)}
|
||||
/>
|
||||
) : (
|
||||
<p className="text-red-700">N/A</p>
|
||||
)
|
||||
) : (
|
||||
"-"
|
||||
);
|
||||
return (
|
||||
<TableRow key={connectorIndexingStatus.cc_pair_id}>
|
||||
{includeName && (
|
||||
<TableCell className="whitespace-normal break-all">
|
||||
<p className="text font-medium">
|
||||
{connectorIndexingStatus.name}
|
||||
</p>
|
||||
</TableCell>
|
||||
)}
|
||||
{specialColumns?.map(({ key, getValue }) => (
|
||||
<TableCell key={key}>
|
||||
{getValue(connectorIndexingStatus)}
|
||||
</TableCell>
|
||||
))}
|
||||
<TableCell>
|
||||
<StatusRow
|
||||
connectorIndexingStatus={connectorIndexingStatus}
|
||||
hasCredentialsIssue={
|
||||
!hasValidCredentials && connectorIncludesCredential
|
||||
}
|
||||
setPopup={setPopup}
|
||||
onUpdate={onUpdate}
|
||||
/>
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
{connectorIndexingStatus.public_doc ? (
|
||||
<FiCheck className="my-auto text-success" size="18" />
|
||||
) : (
|
||||
<FiXCircle className="my-auto text-error" />
|
||||
)}
|
||||
</TableCell>
|
||||
{connectorIncludesCredential && (
|
||||
<TableCell>{credentialDisplay}</TableCell>
|
||||
)}
|
||||
<TableCell>
|
||||
<DeleteColumn
|
||||
connectorIndexingStatus={connectorIndexingStatus}
|
||||
setPopup={setPopup}
|
||||
onUpdate={onUpdate}
|
||||
/>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
);
|
||||
})}
|
||||
</TableBody>
|
||||
</Table>
|
||||
</div>
|
||||
);
|
||||
}
|
@@ -1,60 +0,0 @@
|
||||
import { InfoIcon, TrashIcon } from "@/components/icons/icons";
|
||||
import {
|
||||
deleteCCPair,
|
||||
scheduleDeletionJobForConnector,
|
||||
} from "@/lib/documentDeletion";
|
||||
import { ConnectorIndexingStatus } from "@/lib/types";
|
||||
import { PopupSpec } from "../Popup";
|
||||
import { useState } from "react";
|
||||
import { DeleteButton } from "@/components/DeleteButton";
|
||||
|
||||
interface Props<ConnectorConfigType, ConnectorCredentialType> {
|
||||
connectorIndexingStatus: ConnectorIndexingStatus<
|
||||
ConnectorConfigType,
|
||||
ConnectorCredentialType
|
||||
>;
|
||||
setPopup: (popupSpec: PopupSpec | null) => void;
|
||||
onUpdate: () => void;
|
||||
}
|
||||
|
||||
export function DeleteColumn<ConnectorConfigType, ConnectorCredentialType>({
|
||||
connectorIndexingStatus,
|
||||
setPopup,
|
||||
onUpdate,
|
||||
}: Props<ConnectorConfigType, ConnectorCredentialType>) {
|
||||
const [deleteHovered, setDeleteHovered] = useState<boolean>(false);
|
||||
|
||||
const connector = connectorIndexingStatus.connector;
|
||||
const credential = connectorIndexingStatus.credential;
|
||||
|
||||
return (
|
||||
<div
|
||||
className="relative"
|
||||
onMouseEnter={() => setDeleteHovered(true)}
|
||||
onMouseLeave={() => setDeleteHovered(false)}
|
||||
>
|
||||
{connectorIndexingStatus.is_deletable ? (
|
||||
<div className="cursor-pointer mx-auto flex">
|
||||
<DeleteButton
|
||||
onClick={() =>
|
||||
deleteCCPair(connector.id, credential.id, setPopup, onUpdate)
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
) : (
|
||||
<div>
|
||||
{deleteHovered && (
|
||||
<div className="w-64 z-30 whitespace-normal flex absolute mt-8 top-0 left-0 bg-background border border-border px-3 py-2 rounded shadow-lg text-xs">
|
||||
<InfoIcon className="flex flex-shrink-0 mr-2" />
|
||||
In order to delete a connector it must be disabled and have no
|
||||
ongoing / planned index jobs.
|
||||
</div>
|
||||
)}
|
||||
<div className="flex mx-auto text-xs">
|
||||
<DeleteButton disabled />
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
@@ -1,174 +0,0 @@
|
||||
import { DeletionAttemptSnapshot, ValidStatuses } from "@/lib/types";
|
||||
import { usePopup } from "@/components/admin/connectors/Popup";
|
||||
import { updateConnector } from "@/lib/connector";
|
||||
import { AttachCredentialButtonForTable } from "@/components/admin/connectors/buttons/AttachCredentialButtonForTable";
|
||||
import { scheduleDeletionJobForConnector } from "@/lib/documentDeletion";
|
||||
import { ConnectorsTableProps } from "./ConnectorsTable";
|
||||
import {
|
||||
Table,
|
||||
TableHead,
|
||||
TableRow,
|
||||
TableHeaderCell,
|
||||
TableBody,
|
||||
TableCell,
|
||||
} from "@tremor/react";
|
||||
import { DeleteButton } from "@/components/DeleteButton";
|
||||
|
||||
const SingleUseConnectorStatus = ({
|
||||
indexingStatus,
|
||||
deletionAttempt,
|
||||
}: {
|
||||
indexingStatus: ValidStatuses | null;
|
||||
deletionAttempt: DeletionAttemptSnapshot | null;
|
||||
}) => {
|
||||
if (
|
||||
deletionAttempt &&
|
||||
(deletionAttempt.status === "PENDING" ||
|
||||
deletionAttempt.status === "STARTED")
|
||||
) {
|
||||
return <div className="text-error">Deleting...</div>;
|
||||
}
|
||||
|
||||
if (!indexingStatus || indexingStatus === "not_started") {
|
||||
return <div>Not Started</div>;
|
||||
}
|
||||
|
||||
if (indexingStatus === "in_progress") {
|
||||
return <div>In Progress</div>;
|
||||
}
|
||||
|
||||
if (indexingStatus === "success") {
|
||||
return <div className="text-success">Success!</div>;
|
||||
}
|
||||
|
||||
return <div className="text-error">Failed</div>;
|
||||
};
|
||||
|
||||
export function SingleUseConnectorsTable<
|
||||
ConnectorConfigType,
|
||||
ConnectorCredentialType,
|
||||
>({
|
||||
connectorIndexingStatuses,
|
||||
liveCredential,
|
||||
getCredential,
|
||||
specialColumns,
|
||||
onUpdate,
|
||||
onCredentialLink,
|
||||
includeName = false,
|
||||
}: ConnectorsTableProps<ConnectorConfigType, ConnectorCredentialType>) {
|
||||
const { popup, setPopup } = usePopup();
|
||||
|
||||
const connectorIncludesCredential =
|
||||
getCredential !== undefined && onCredentialLink !== undefined;
|
||||
|
||||
return (
|
||||
<div>
|
||||
{popup}
|
||||
|
||||
<Table className="overflow-visible">
|
||||
<TableHead>
|
||||
<TableRow>
|
||||
{includeName && <TableHeaderCell>Name</TableHeaderCell>}
|
||||
{specialColumns?.map(({ header }) => (
|
||||
<TableHeaderCell key={header}>{header}</TableHeaderCell>
|
||||
))}
|
||||
<TableHeaderCell>Status</TableHeaderCell>
|
||||
{connectorIncludesCredential && (
|
||||
<TableHeaderCell>Credential</TableHeaderCell>
|
||||
)}
|
||||
<TableHeaderCell>Remove</TableHeaderCell>
|
||||
</TableRow>
|
||||
</TableHead>
|
||||
<TableBody>
|
||||
{connectorIndexingStatuses.map((connectorIndexingStatus) => {
|
||||
const connector = connectorIndexingStatus.connector;
|
||||
// const credential = connectorIndexingStatus.credential;
|
||||
const hasValidCredentials =
|
||||
liveCredential &&
|
||||
connector.credential_ids.includes(liveCredential.id);
|
||||
const credentialDisplay = connectorIncludesCredential ? (
|
||||
hasValidCredentials ? (
|
||||
<div className="max-w-sm truncate">
|
||||
{getCredential(liveCredential)}
|
||||
</div>
|
||||
) : liveCredential ? (
|
||||
<AttachCredentialButtonForTable
|
||||
onClick={() => onCredentialLink(connector.id)}
|
||||
/>
|
||||
) : (
|
||||
<p className="text-red-700">N/A</p>
|
||||
)
|
||||
) : (
|
||||
"-"
|
||||
);
|
||||
return (
|
||||
<TableRow key={connectorIndexingStatus.cc_pair_id}>
|
||||
{includeName && (
|
||||
<TableCell className="whitespace-normal break-all">
|
||||
<p className="text font-medium">
|
||||
{connectorIndexingStatus.name}
|
||||
</p>
|
||||
</TableCell>
|
||||
)}
|
||||
{specialColumns?.map(({ key, getValue }) => (
|
||||
<TableCell className="max-w-sm" key={key}>
|
||||
<div className="break-words whitespace-normal">
|
||||
{getValue(connectorIndexingStatus)}
|
||||
</div>
|
||||
</TableCell>
|
||||
))}
|
||||
<TableCell>
|
||||
<SingleUseConnectorStatus
|
||||
indexingStatus={connectorIndexingStatus.last_status}
|
||||
deletionAttempt={connectorIndexingStatus.deletion_attempt}
|
||||
/>
|
||||
</TableCell>
|
||||
{connectorIncludesCredential && (
|
||||
<TableCell>{credentialDisplay}</TableCell>
|
||||
)}
|
||||
<TableCell>
|
||||
<div
|
||||
className="cursor-pointer mx-auto flex"
|
||||
onClick={async () => {
|
||||
// for one-time, just disable the connector at deletion time
|
||||
// this is required before deletion can happen
|
||||
await updateConnector({
|
||||
...connector,
|
||||
disabled: !connector.disabled,
|
||||
});
|
||||
|
||||
const deletionScheduleError =
|
||||
await scheduleDeletionJobForConnector(
|
||||
connector.id,
|
||||
connectorIndexingStatus.credential.id
|
||||
);
|
||||
if (deletionScheduleError) {
|
||||
setPopup({
|
||||
message:
|
||||
"Failed to schedule deletion of connector - " +
|
||||
deletionScheduleError,
|
||||
type: "error",
|
||||
});
|
||||
} else {
|
||||
setPopup({
|
||||
message: "Scheduled deletion of connector!",
|
||||
type: "success",
|
||||
});
|
||||
}
|
||||
setTimeout(() => {
|
||||
setPopup(null);
|
||||
}, 4000);
|
||||
onUpdate();
|
||||
}}
|
||||
>
|
||||
<DeleteButton />
|
||||
</div>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
);
|
||||
})}
|
||||
</TableBody>
|
||||
</Table>
|
||||
</div>
|
||||
);
|
||||
}
|
49
web/src/lib/ccPair.ts
Normal file
49
web/src/lib/ccPair.ts
Normal file
@@ -0,0 +1,49 @@
|
||||
import { ConnectorCredentialPairStatus } from "@/app/admin/connector/[ccPairId]/types";
|
||||
import { PopupSpec } from "@/components/admin/connectors/Popup";
|
||||
|
||||
export async function setCCPairStatus(
|
||||
ccPairId: number,
|
||||
ccPairStatus: ConnectorCredentialPairStatus,
|
||||
setPopup?: (popupSpec: PopupSpec | null) => void,
|
||||
onUpdate?: () => void
|
||||
) {
|
||||
try {
|
||||
const response = await fetch(
|
||||
`/api/manage/admin/cc-pair/${ccPairId}/status`,
|
||||
{
|
||||
method: "PUT",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
body: JSON.stringify({ status: ccPairStatus }),
|
||||
}
|
||||
);
|
||||
|
||||
if (!response.ok) {
|
||||
const errorMessage = (await response.json()).detail;
|
||||
setPopup &&
|
||||
setPopup({
|
||||
message: "Failed to update connector status - " + errorMessage,
|
||||
type: "error",
|
||||
});
|
||||
}
|
||||
|
||||
setPopup &&
|
||||
setPopup({
|
||||
message:
|
||||
ccPairStatus === ConnectorCredentialPairStatus.ACTIVE
|
||||
? "Enabled connector!"
|
||||
: "Paused connector!",
|
||||
type: "success",
|
||||
});
|
||||
|
||||
onUpdate && onUpdate();
|
||||
} catch (error) {
|
||||
console.error("Error updating CC pair status:", error);
|
||||
setPopup &&
|
||||
setPopup({
|
||||
message: "Failed to update connector status",
|
||||
type: "error",
|
||||
});
|
||||
}
|
||||
}
|
@@ -52,26 +52,6 @@ export async function updateConnector<T>(
|
||||
return await response.json();
|
||||
}
|
||||
|
||||
export async function disableConnector(
|
||||
connector: Connector<any>,
|
||||
setPopup: (popupSpec: PopupSpec | null) => void,
|
||||
onUpdate: () => void
|
||||
) {
|
||||
updateConnector({
|
||||
...connector,
|
||||
disabled: !connector.disabled,
|
||||
}).then(() => {
|
||||
setPopup({
|
||||
message: connector.disabled ? "Enabled connector!" : "Paused connector!",
|
||||
type: "success",
|
||||
});
|
||||
setTimeout(() => {
|
||||
setPopup(null);
|
||||
}, 4000);
|
||||
onUpdate && onUpdate();
|
||||
});
|
||||
}
|
||||
|
||||
export async function deleteConnector(
|
||||
connectorId: number
|
||||
): Promise<string | null> {
|
||||
|
@@ -774,7 +774,6 @@ export interface ConnectorBase<T> {
|
||||
refresh_freq: number | null;
|
||||
prune_freq: number | null;
|
||||
indexing_start: Date | null;
|
||||
disabled: boolean;
|
||||
}
|
||||
|
||||
export interface Connector<T> extends ConnectorBase<T> {
|
||||
|
@@ -1,6 +1,7 @@
|
||||
import { Persona } from "@/app/admin/assistants/interfaces";
|
||||
import { Credential } from "./connectors/credentials";
|
||||
import { Connector } from "./connectors/connectors";
|
||||
import { ConnectorCredentialPairStatus } from "@/app/admin/connector/[ccPairId]/types";
|
||||
|
||||
export interface UserPreferences {
|
||||
chosen_assistants: number[] | null;
|
||||
@@ -67,6 +68,7 @@ export interface ConnectorIndexingStatus<
|
||||
> {
|
||||
cc_pair_id: number;
|
||||
name: string | null;
|
||||
cc_pair_status: ConnectorCredentialPairStatus;
|
||||
connector: Connector<ConnectorConfigType>;
|
||||
credential: Credential<ConnectorCredentialType>;
|
||||
public_doc: boolean;
|
||||
|
Reference in New Issue
Block a user