mirror of
https://github.com/danswer-ai/danswer.git
synced 2025-09-18 19:43:26 +02:00
Re-style cc pair status table
This commit is contained in:
@@ -1,10 +1,9 @@
|
||||
import { BasicTable } from "@/components/admin/connectors/BasicTable";
|
||||
import { PopupSpec, usePopup } from "@/components/admin/connectors/Popup";
|
||||
import { usePopup } from "@/components/admin/connectors/Popup";
|
||||
import { useState } from "react";
|
||||
import { PageSelector } from "@/components/PageSelector";
|
||||
import { DocumentBoostStatus } from "@/lib/types";
|
||||
import { updateBoost, updateHiddenStatus } from "../lib";
|
||||
import { CheckmarkIcon, EditIcon } from "@/components/icons/icons";
|
||||
import { updateHiddenStatus } from "../lib";
|
||||
import { numToDisplay } from "./constants";
|
||||
import { FiEye, FiEyeOff } from "react-icons/fi";
|
||||
import { getErrorMsg } from "@/lib/fetchUtils";
|
||||
|
146
web/src/app/admin/indexing/status/CCPairIndexingStatusTable.tsx
Normal file
146
web/src/app/admin/indexing/status/CCPairIndexingStatusTable.tsx
Normal file
@@ -0,0 +1,146 @@
|
||||
"use client";
|
||||
|
||||
import {
|
||||
Table,
|
||||
TableHead,
|
||||
TableRow,
|
||||
TableHeaderCell,
|
||||
TableBody,
|
||||
TableCell,
|
||||
} from "@tremor/react";
|
||||
import { CCPairStatus, IndexAttemptStatus } from "@/components/Status";
|
||||
import { useState } from "react";
|
||||
import { PageSelector } from "@/components/PageSelector";
|
||||
import { timeAgo } from "@/lib/time";
|
||||
import { ConnectorIndexingStatus } from "@/lib/types";
|
||||
import { ConnectorTitle } from "@/components/admin/connectors/ConnectorTitle";
|
||||
import { getDocsProcessedPerMinute } from "@/lib/indexAttempt";
|
||||
import Link from "next/link";
|
||||
|
||||
const NUM_IN_PAGE = 20;
|
||||
|
||||
function CCPairIndexingStatusDisplay({
|
||||
ccPairsIndexingStatus,
|
||||
}: {
|
||||
ccPairsIndexingStatus: ConnectorIndexingStatus<any, any>;
|
||||
}) {
|
||||
if (ccPairsIndexingStatus.connector.disabled) {
|
||||
return (
|
||||
<CCPairStatus
|
||||
status="not_started"
|
||||
disabled={true}
|
||||
isDeleting={ccPairsIndexingStatus?.deletion_attempt !== null}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
const docsPerMinute = getDocsProcessedPerMinute(
|
||||
ccPairsIndexingStatus.latest_index_attempt
|
||||
)?.toFixed(2);
|
||||
return (
|
||||
<>
|
||||
<IndexAttemptStatus
|
||||
status={ccPairsIndexingStatus.last_status || "not_started"}
|
||||
errorMsg={ccPairsIndexingStatus?.latest_index_attempt?.error_msg}
|
||||
size="xs"
|
||||
/>
|
||||
{ccPairsIndexingStatus?.latest_index_attempt?.num_docs_indexed &&
|
||||
ccPairsIndexingStatus?.latest_index_attempt?.status === "in_progress" ? (
|
||||
<div className="text-xs mt-0.5">
|
||||
<div>
|
||||
<i>Current Run:</i>{" "}
|
||||
{ccPairsIndexingStatus.latest_index_attempt.num_docs_indexed} docs
|
||||
indexed
|
||||
</div>
|
||||
<div>
|
||||
<i>Speed:</i>{" "}
|
||||
{docsPerMinute ? (
|
||||
<>{docsPerMinute} docs / min</>
|
||||
) : (
|
||||
"calculating rate..."
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
) : null}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
export function CCPairIndexingStatusTable({
|
||||
ccPairsIndexingStatuses,
|
||||
}: {
|
||||
ccPairsIndexingStatuses: ConnectorIndexingStatus<any, any>[];
|
||||
}) {
|
||||
const [page, setPage] = useState(1);
|
||||
|
||||
return (
|
||||
<div className="dark">
|
||||
<Table>
|
||||
<TableHead>
|
||||
<TableRow>
|
||||
<TableHeaderCell>Connector</TableHeaderCell>
|
||||
<TableHeaderCell>Status</TableHeaderCell>
|
||||
<TableHeaderCell>Last Indexed</TableHeaderCell>
|
||||
<TableHeaderCell>Docs Indexed</TableHeaderCell>
|
||||
</TableRow>
|
||||
</TableHead>
|
||||
<TableBody>
|
||||
{ccPairsIndexingStatuses
|
||||
.slice(NUM_IN_PAGE * (page - 1), NUM_IN_PAGE * page)
|
||||
.map((ccPairsIndexingStatus) => {
|
||||
return (
|
||||
<TableRow
|
||||
key={ccPairsIndexingStatus.cc_pair_id}
|
||||
className="hover:bg-gradient-to-r hover:from-gray-800 hover:to-indigo-950 cursor-pointer relative"
|
||||
>
|
||||
<TableCell className="whitespace-normal">
|
||||
<ConnectorTitle
|
||||
connector={ccPairsIndexingStatus.connector}
|
||||
ccPairId={ccPairsIndexingStatus.cc_pair_id}
|
||||
ccPairName={ccPairsIndexingStatus.name}
|
||||
/>
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
<CCPairIndexingStatusDisplay
|
||||
ccPairsIndexingStatus={ccPairsIndexingStatus}
|
||||
/>
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
{timeAgo(ccPairsIndexingStatus?.last_success) || "-"}
|
||||
</TableCell>
|
||||
<TableCell>{ccPairsIndexingStatus.docs_indexed}</TableCell>
|
||||
{/* Wrapping in <td> to avoid console warnings */}
|
||||
<td className="w-0 p-0">
|
||||
<Link
|
||||
href={`/admin/connector/${ccPairsIndexingStatus.cc_pair_id}`}
|
||||
className="absolute w-full h-full left-0"
|
||||
></Link>
|
||||
</td>
|
||||
</TableRow>
|
||||
);
|
||||
})}
|
||||
</TableBody>
|
||||
</Table>
|
||||
{ccPairsIndexingStatuses.length > NUM_IN_PAGE && (
|
||||
<div className="mt-3 flex">
|
||||
<div className="mx-auto">
|
||||
<PageSelector
|
||||
totalPages={Math.ceil(
|
||||
ccPairsIndexingStatuses.length / NUM_IN_PAGE
|
||||
)}
|
||||
currentPage={page}
|
||||
onPageChange={(newPage) => {
|
||||
setPage(newPage);
|
||||
window.scrollTo({
|
||||
top: 0,
|
||||
left: 0,
|
||||
behavior: "smooth",
|
||||
});
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
@@ -2,49 +2,13 @@
|
||||
|
||||
import useSWR from "swr";
|
||||
|
||||
import { BasicTable } from "@/components/admin/connectors/BasicTable";
|
||||
import { LoadingAnimation } from "@/components/Loading";
|
||||
import { timeAgo } from "@/lib/time";
|
||||
import {
|
||||
NotebookIcon,
|
||||
QuestionIcon,
|
||||
XSquareIcon,
|
||||
} from "@/components/icons/icons";
|
||||
import { NotebookIcon } from "@/components/icons/icons";
|
||||
import { fetcher } from "@/lib/fetcher";
|
||||
import { getSourceMetadata } from "@/components/source";
|
||||
import { CheckCircle } from "@phosphor-icons/react";
|
||||
import { HealthCheckBanner } from "@/components/health/healthcheck";
|
||||
import { ConnectorIndexingStatus } from "@/lib/types";
|
||||
import { useState } from "react";
|
||||
import { getDocsProcessedPerMinute } from "@/lib/indexAttempt";
|
||||
import { ConnectorTitle } from "@/components/admin/connectors/ConnectorTitle";
|
||||
|
||||
const ErrorDisplay = ({ message }: { message: string }) => {
|
||||
const [isHovered, setIsHovered] = useState(false);
|
||||
return (
|
||||
<div
|
||||
onMouseEnter={() => {
|
||||
setIsHovered(true);
|
||||
}}
|
||||
onMouseLeave={() => setIsHovered(false)}
|
||||
className="relative"
|
||||
>
|
||||
{isHovered && (
|
||||
<div className="absolute pt-8 top-0 left-0 z-10">
|
||||
<div className="bg-gray-700 px-3 pb-3 pt-2 rounded shadow-lg text-xs">
|
||||
<div className="text-sm text-red-600 mb-1 flex">Error Message:</div>
|
||||
|
||||
{message}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
<div className="text-red-600 flex cursor-default">
|
||||
<QuestionIcon className="my-auto mr-1" size={18} />
|
||||
Error
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
import { CCPairIndexingStatusTable } from "./CCPairIndexingStatusTable";
|
||||
import { Divider } from "@tremor/react";
|
||||
|
||||
function Main() {
|
||||
const {
|
||||
@@ -54,7 +18,7 @@ function Main() {
|
||||
} = useSWR<ConnectorIndexingStatus<any, any>[]>(
|
||||
"/api/manage/admin/connector/indexing-status",
|
||||
fetcher,
|
||||
{ refreshInterval: 30000 } // 30 seconds
|
||||
{ refreshInterval: 10000 } // 10 seconds
|
||||
);
|
||||
|
||||
if (indexAttemptIsLoading) {
|
||||
@@ -77,132 +41,20 @@ function Main() {
|
||||
});
|
||||
|
||||
return (
|
||||
<BasicTable
|
||||
columns={[
|
||||
{ header: "Connector", key: "connector" },
|
||||
{ header: "Status", key: "status" },
|
||||
{ header: "Last Indexed", key: "indexed_at" },
|
||||
{ header: "Docs Indexed", key: "docs_indexed" },
|
||||
// { header: "Re-Index", key: "reindex" },
|
||||
]}
|
||||
data={indexAttemptData.map((connectorIndexingStatus) => {
|
||||
const sourceMetadata = getSourceMetadata(
|
||||
connectorIndexingStatus.connector.source
|
||||
);
|
||||
let statusDisplay = (
|
||||
<div className="text-gray-400">Initializing...</div>
|
||||
);
|
||||
if (connectorIndexingStatus.connector.disabled) {
|
||||
statusDisplay = (
|
||||
<div className="text-red-600 flex">
|
||||
<XSquareIcon className="my-auto mr-1" size={18} />
|
||||
Disabled
|
||||
</div>
|
||||
);
|
||||
} else if (connectorIndexingStatus.last_status === "success") {
|
||||
statusDisplay = (
|
||||
<div className="text-green-600 flex">
|
||||
<CheckCircle className="my-auto mr-1" size="18" />
|
||||
Enabled
|
||||
</div>
|
||||
);
|
||||
} else if (connectorIndexingStatus.last_status === "failed") {
|
||||
statusDisplay = (
|
||||
<ErrorDisplay message={connectorIndexingStatus.error_msg} />
|
||||
);
|
||||
} else if (connectorIndexingStatus.last_status === "not_started") {
|
||||
statusDisplay = <div className="text-gray-400">Scheduled</div>;
|
||||
} else if (connectorIndexingStatus.last_status === "in_progress") {
|
||||
const docsPerMinute = getDocsProcessedPerMinute(
|
||||
connectorIndexingStatus.latest_index_attempt
|
||||
)?.toFixed(2);
|
||||
statusDisplay = (
|
||||
<div className="text-gray-400">
|
||||
In Progress{" "}
|
||||
{connectorIndexingStatus?.latest_index_attempt
|
||||
?.num_docs_indexed ? (
|
||||
<div className="text-xs mt-0.5">
|
||||
<div>
|
||||
<i>Current Run:</i>{" "}
|
||||
{
|
||||
connectorIndexingStatus.latest_index_attempt
|
||||
.num_docs_indexed
|
||||
}{" "}
|
||||
docs indexed
|
||||
</div>
|
||||
<div>
|
||||
<i>Speed:</i>{" "}
|
||||
{docsPerMinute ? (
|
||||
<>~{docsPerMinute} docs / min</>
|
||||
) : (
|
||||
"calculating rate..."
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
) : null}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
return {
|
||||
indexed_at: timeAgo(connectorIndexingStatus?.last_success) || "-",
|
||||
docs_indexed: connectorIndexingStatus?.docs_indexed
|
||||
? `${connectorIndexingStatus?.docs_indexed} documents`
|
||||
: "-",
|
||||
connector: (
|
||||
<ConnectorTitle
|
||||
ccPairName={connectorIndexingStatus.name}
|
||||
ccPairId={connectorIndexingStatus.cc_pair_id}
|
||||
connector={connectorIndexingStatus.connector}
|
||||
isPublic={connectorIndexingStatus.public_doc}
|
||||
owner={connectorIndexingStatus.owner}
|
||||
/>
|
||||
),
|
||||
status: statusDisplay,
|
||||
// TODO: add the below back in after this is supported in the backend
|
||||
// reindex: (
|
||||
// <button
|
||||
// className={
|
||||
// "group relative " +
|
||||
// "py-1 px-2 border border-transparent text-sm " +
|
||||
// "font-medium rounded-md text-white bg-red-800 " +
|
||||
// "hover:bg-red-900 focus:outline-none focus:ring-2 " +
|
||||
// "focus:ring-offset-2 focus:ring-red-500 mx-auto"
|
||||
// }
|
||||
// onClick={async () => {
|
||||
// const { message, isSuccess } = await submitIndexRequest(
|
||||
// connectorIndexingStatus.connector.source,
|
||||
// connectorIndexingStatus.connector
|
||||
// .connector_specific_config
|
||||
// );
|
||||
// setPopup({
|
||||
// message,
|
||||
// type: isSuccess ? "success" : "error",
|
||||
// });
|
||||
// setTimeout(() => {
|
||||
// setPopup(null);
|
||||
// }, 4000);
|
||||
// mutate("/api/manage/admin/connector/index-attempt");
|
||||
// }}
|
||||
// >
|
||||
// Index
|
||||
// </button>
|
||||
// ),
|
||||
};
|
||||
})}
|
||||
/>
|
||||
<CCPairIndexingStatusTable ccPairsIndexingStatuses={indexAttemptData} />
|
||||
);
|
||||
}
|
||||
|
||||
export default function Status() {
|
||||
return (
|
||||
<div className="mx-auto container">
|
||||
<div className="mx-auto container dark">
|
||||
<div className="mb-4">
|
||||
<HealthCheckBanner />
|
||||
</div>
|
||||
<div className="border-solid border-gray-600 border-b pb-2 mb-4 flex">
|
||||
<NotebookIcon size={32} />
|
||||
<h1 className="text-3xl font-bold pl-2">Indexing Status</h1>
|
||||
</div>
|
||||
<h1 className="text-3xl font-bold flex gap-x-2 mb-2">
|
||||
<NotebookIcon size={32} /> Indexing Status
|
||||
</h1>
|
||||
<Divider />
|
||||
<Main />
|
||||
</div>
|
||||
);
|
||||
|
@@ -21,28 +21,28 @@ export const HoverPopup = ({
|
||||
popupDirectionClass = "top-0 left-0 transform translate-x-[-110%]";
|
||||
break;
|
||||
case "bottom":
|
||||
popupDirectionClass = "top-0 left-0 mt-8";
|
||||
popupDirectionClass = "top-0 left-0 mt-6 pt-2";
|
||||
break;
|
||||
}
|
||||
|
||||
return (
|
||||
<div
|
||||
className="relative flex"
|
||||
className="relative flex z-30"
|
||||
onMouseEnter={() => {
|
||||
setHovered(true);
|
||||
console.log("HIII");
|
||||
}}
|
||||
onMouseLeave={() => setHovered(false)}
|
||||
>
|
||||
{hovered && (
|
||||
<div
|
||||
className={
|
||||
`absolute bg-gray-700 px-3 py-2 rounded shadow-lg z-30 ` +
|
||||
(classNameModifications || "") +
|
||||
` ${popupDirectionClass}`
|
||||
}
|
||||
>
|
||||
{popupContent}
|
||||
<div className={`absolute ${popupDirectionClass}`}>
|
||||
<div
|
||||
className={
|
||||
`bg-gray-800 px-3 py-2 rounded shadow-lg z-30 ` +
|
||||
(classNameModifications || "")
|
||||
}
|
||||
>
|
||||
{popupContent}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
{mainContent}
|
||||
|
@@ -8,34 +8,57 @@ import {
|
||||
FiClock,
|
||||
FiPauseCircle,
|
||||
} from "react-icons/fi";
|
||||
import { HoverPopup } from "./HoverPopup";
|
||||
|
||||
export function IndexAttemptStatus({
|
||||
status,
|
||||
errorMsg,
|
||||
size = "md",
|
||||
}: {
|
||||
status: ValidStatuses;
|
||||
errorMsg?: string | null;
|
||||
size?: "xs" | "sm" | "md" | "lg";
|
||||
}) {
|
||||
let badge;
|
||||
|
||||
if (status === "failed") {
|
||||
badge = (
|
||||
const icon = (
|
||||
<Badge size={size} color="red" icon={FiAlertTriangle}>
|
||||
Failed
|
||||
</Badge>
|
||||
);
|
||||
if (errorMsg) {
|
||||
badge = (
|
||||
<HoverPopup
|
||||
mainContent={<div className="cursor-pointer">{icon}</div>}
|
||||
popupContent={
|
||||
<div className="flex flex-wrap whitespace-normal w-64">
|
||||
{errorMsg}
|
||||
</div>
|
||||
}
|
||||
/>
|
||||
);
|
||||
} else {
|
||||
badge = icon;
|
||||
}
|
||||
} else if (status === "success") {
|
||||
badge = (
|
||||
<Badge size={size} color="green" icon={FiCheckCircle}>
|
||||
Succeeded
|
||||
</Badge>
|
||||
);
|
||||
} else if (status === "in_progress" || status === "not_started") {
|
||||
} else if (status === "in_progress") {
|
||||
badge = (
|
||||
<Badge size={size} color="fuchsia" icon={FiClock}>
|
||||
In Progress
|
||||
</Badge>
|
||||
);
|
||||
} else if (status === "not_started") {
|
||||
badge = (
|
||||
<Badge size={size} color="fuchsia" icon={FiClock}>
|
||||
Scheduled
|
||||
</Badge>
|
||||
);
|
||||
} else {
|
||||
badge = (
|
||||
<Badge size={size} color="yellow" icon={FiClock}>
|
||||
|
Reference in New Issue
Block a user