mirror of
https://github.com/danswer-ai/danswer.git
synced 2025-09-19 20:24:32 +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 { 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 { useState } from "react";
|
||||||
import { PageSelector } from "@/components/PageSelector";
|
import { PageSelector } from "@/components/PageSelector";
|
||||||
import { DocumentBoostStatus } from "@/lib/types";
|
import { DocumentBoostStatus } from "@/lib/types";
|
||||||
import { updateBoost, updateHiddenStatus } from "../lib";
|
import { updateHiddenStatus } from "../lib";
|
||||||
import { CheckmarkIcon, EditIcon } from "@/components/icons/icons";
|
|
||||||
import { numToDisplay } from "./constants";
|
import { numToDisplay } from "./constants";
|
||||||
import { FiEye, FiEyeOff } from "react-icons/fi";
|
import { FiEye, FiEyeOff } from "react-icons/fi";
|
||||||
import { getErrorMsg } from "@/lib/fetchUtils";
|
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 useSWR from "swr";
|
||||||
|
|
||||||
import { BasicTable } from "@/components/admin/connectors/BasicTable";
|
|
||||||
import { LoadingAnimation } from "@/components/Loading";
|
import { LoadingAnimation } from "@/components/Loading";
|
||||||
import { timeAgo } from "@/lib/time";
|
import { NotebookIcon } from "@/components/icons/icons";
|
||||||
import {
|
|
||||||
NotebookIcon,
|
|
||||||
QuestionIcon,
|
|
||||||
XSquareIcon,
|
|
||||||
} from "@/components/icons/icons";
|
|
||||||
import { fetcher } from "@/lib/fetcher";
|
import { fetcher } from "@/lib/fetcher";
|
||||||
import { getSourceMetadata } from "@/components/source";
|
|
||||||
import { CheckCircle } from "@phosphor-icons/react";
|
|
||||||
import { HealthCheckBanner } from "@/components/health/healthcheck";
|
import { HealthCheckBanner } from "@/components/health/healthcheck";
|
||||||
import { ConnectorIndexingStatus } from "@/lib/types";
|
import { ConnectorIndexingStatus } from "@/lib/types";
|
||||||
import { useState } from "react";
|
import { CCPairIndexingStatusTable } from "./CCPairIndexingStatusTable";
|
||||||
import { getDocsProcessedPerMinute } from "@/lib/indexAttempt";
|
import { Divider } from "@tremor/react";
|
||||||
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>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
function Main() {
|
function Main() {
|
||||||
const {
|
const {
|
||||||
@@ -54,7 +18,7 @@ function Main() {
|
|||||||
} = useSWR<ConnectorIndexingStatus<any, any>[]>(
|
} = useSWR<ConnectorIndexingStatus<any, any>[]>(
|
||||||
"/api/manage/admin/connector/indexing-status",
|
"/api/manage/admin/connector/indexing-status",
|
||||||
fetcher,
|
fetcher,
|
||||||
{ refreshInterval: 30000 } // 30 seconds
|
{ refreshInterval: 10000 } // 10 seconds
|
||||||
);
|
);
|
||||||
|
|
||||||
if (indexAttemptIsLoading) {
|
if (indexAttemptIsLoading) {
|
||||||
@@ -77,132 +41,20 @@ function Main() {
|
|||||||
});
|
});
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<BasicTable
|
<CCPairIndexingStatusTable ccPairsIndexingStatuses={indexAttemptData} />
|
||||||
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>
|
|
||||||
// ),
|
|
||||||
};
|
|
||||||
})}
|
|
||||||
/>
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function Status() {
|
export default function Status() {
|
||||||
return (
|
return (
|
||||||
<div className="mx-auto container">
|
<div className="mx-auto container dark">
|
||||||
<div className="mb-4">
|
<div className="mb-4">
|
||||||
<HealthCheckBanner />
|
<HealthCheckBanner />
|
||||||
</div>
|
</div>
|
||||||
<div className="border-solid border-gray-600 border-b pb-2 mb-4 flex">
|
<h1 className="text-3xl font-bold flex gap-x-2 mb-2">
|
||||||
<NotebookIcon size={32} />
|
<NotebookIcon size={32} /> Indexing Status
|
||||||
<h1 className="text-3xl font-bold pl-2">Indexing Status</h1>
|
</h1>
|
||||||
</div>
|
<Divider />
|
||||||
<Main />
|
<Main />
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
@@ -21,28 +21,28 @@ export const HoverPopup = ({
|
|||||||
popupDirectionClass = "top-0 left-0 transform translate-x-[-110%]";
|
popupDirectionClass = "top-0 left-0 transform translate-x-[-110%]";
|
||||||
break;
|
break;
|
||||||
case "bottom":
|
case "bottom":
|
||||||
popupDirectionClass = "top-0 left-0 mt-8";
|
popupDirectionClass = "top-0 left-0 mt-6 pt-2";
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
className="relative flex"
|
className="relative flex z-30"
|
||||||
onMouseEnter={() => {
|
onMouseEnter={() => {
|
||||||
setHovered(true);
|
setHovered(true);
|
||||||
console.log("HIII");
|
|
||||||
}}
|
}}
|
||||||
onMouseLeave={() => setHovered(false)}
|
onMouseLeave={() => setHovered(false)}
|
||||||
>
|
>
|
||||||
{hovered && (
|
{hovered && (
|
||||||
<div
|
<div className={`absolute ${popupDirectionClass}`}>
|
||||||
className={
|
<div
|
||||||
`absolute bg-gray-700 px-3 py-2 rounded shadow-lg z-30 ` +
|
className={
|
||||||
(classNameModifications || "") +
|
`bg-gray-800 px-3 py-2 rounded shadow-lg z-30 ` +
|
||||||
` ${popupDirectionClass}`
|
(classNameModifications || "")
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
{popupContent}
|
{popupContent}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
{mainContent}
|
{mainContent}
|
||||||
|
@@ -8,34 +8,57 @@ import {
|
|||||||
FiClock,
|
FiClock,
|
||||||
FiPauseCircle,
|
FiPauseCircle,
|
||||||
} from "react-icons/fi";
|
} from "react-icons/fi";
|
||||||
|
import { HoverPopup } from "./HoverPopup";
|
||||||
|
|
||||||
export function IndexAttemptStatus({
|
export function IndexAttemptStatus({
|
||||||
status,
|
status,
|
||||||
|
errorMsg,
|
||||||
size = "md",
|
size = "md",
|
||||||
}: {
|
}: {
|
||||||
status: ValidStatuses;
|
status: ValidStatuses;
|
||||||
|
errorMsg?: string | null;
|
||||||
size?: "xs" | "sm" | "md" | "lg";
|
size?: "xs" | "sm" | "md" | "lg";
|
||||||
}) {
|
}) {
|
||||||
let badge;
|
let badge;
|
||||||
|
|
||||||
if (status === "failed") {
|
if (status === "failed") {
|
||||||
badge = (
|
const icon = (
|
||||||
<Badge size={size} color="red" icon={FiAlertTriangle}>
|
<Badge size={size} color="red" icon={FiAlertTriangle}>
|
||||||
Failed
|
Failed
|
||||||
</Badge>
|
</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") {
|
} else if (status === "success") {
|
||||||
badge = (
|
badge = (
|
||||||
<Badge size={size} color="green" icon={FiCheckCircle}>
|
<Badge size={size} color="green" icon={FiCheckCircle}>
|
||||||
Succeeded
|
Succeeded
|
||||||
</Badge>
|
</Badge>
|
||||||
);
|
);
|
||||||
} else if (status === "in_progress" || status === "not_started") {
|
} else if (status === "in_progress") {
|
||||||
badge = (
|
badge = (
|
||||||
<Badge size={size} color="fuchsia" icon={FiClock}>
|
<Badge size={size} color="fuchsia" icon={FiClock}>
|
||||||
In Progress
|
In Progress
|
||||||
</Badge>
|
</Badge>
|
||||||
);
|
);
|
||||||
|
} else if (status === "not_started") {
|
||||||
|
badge = (
|
||||||
|
<Badge size={size} color="fuchsia" icon={FiClock}>
|
||||||
|
Scheduled
|
||||||
|
</Badge>
|
||||||
|
);
|
||||||
} else {
|
} else {
|
||||||
badge = (
|
badge = (
|
||||||
<Badge size={size} color="yellow" icon={FiClock}>
|
<Badge size={size} color="yellow" icon={FiClock}>
|
||||||
|
Reference in New Issue
Block a user