('tab1');
+
let snapshot = <>>;
if (cdc.snapshotStatus) {
snapshot = ;
}
- return <>{snapshot}>;
+
+ return (
+
+
+
+ Details
+
+
+ Sync Status
+
+
+ Initial Copy
+
+
+
+
+
+
+ {syncStatusChild}
+
+
+ {snapshot}
+
+
+ );
}
diff --git a/ui/app/mirrors/edit/[mirrorId]/cdcDetails.tsx b/ui/app/mirrors/edit/[mirrorId]/cdcDetails.tsx
new file mode 100644
index 0000000000..bd20ad1851
--- /dev/null
+++ b/ui/app/mirrors/edit/[mirrorId]/cdcDetails.tsx
@@ -0,0 +1,35 @@
+import { FlowConnectionConfigs } from '@/grpc_generated/flow';
+
+type CDCDetailsProps = {
+ config: FlowConnectionConfigs | undefined;
+};
+
+export default function CDCDetails({ config }: CDCDetailsProps) {
+ if (!config) {
+ return No configuration provided
;
+ }
+
+ return (
+
+
CDC Details
+
+
+
+
+ Source |
+ {config.source?.name || '-'} |
+
+
+ Destination |
+ {config.destination?.name || '-'} |
+
+
+ Flow Job Name |
+ {config.flowJobName} |
+
+
+
+
+
+ );
+}
diff --git a/ui/app/mirrors/edit/[mirrorId]/page.tsx b/ui/app/mirrors/edit/[mirrorId]/page.tsx
index 6a7eff8637..6e8c9c013c 100644
--- a/ui/app/mirrors/edit/[mirrorId]/page.tsx
+++ b/ui/app/mirrors/edit/[mirrorId]/page.tsx
@@ -1,57 +1,55 @@
-'use client';
-
import { MirrorStatusResponse } from '@/grpc_generated/route';
import { Header } from '@/lib/Header';
import { LayoutMain } from '@/lib/Layout';
-import { ProgressCircle } from '@/lib/ProgressCircle';
-import useSWR from 'swr';
+import { GetFlowHttpAddressFromEnv } from '@/rpc/http';
+import { Suspense } from 'react';
import { CDCMirror } from './cdc';
+import SyncStatus from './syncStatus';
type EditMirrorProps = {
params: { mirrorId: string };
};
-async function fetcher([url, mirrorId]: [string, string]) {
- return fetch(url, {
- method: 'POST',
- headers: {
- 'Content-Type': 'application/json',
- },
- body: JSON.stringify({
- flowJobName: mirrorId,
- }),
- })
- .then((res) => {
- if (!res.ok) throw new Error('Error fetching mirror status');
- return res.json();
- })
- .then((res: MirrorStatusResponse) => res);
+function getMirrorStatusUrl(mirrorId: string) {
+ let base = GetFlowHttpAddressFromEnv();
+ return `${base}/v1/mirrors/${mirrorId}`;
}
-export default function EditMirror({ params: { mirrorId } }: EditMirrorProps) {
- const {
- data: mirrorStatus,
- error,
- isValidating,
- } = useSWR(() => [`/api/mirrors/status`, mirrorId], fetcher);
-
- if (isValidating) {
- return ;
- }
+async function getMirrorStatus(mirrorId: string) {
+ const url = getMirrorStatusUrl(mirrorId);
+ const resp = await fetch(url);
+ const json = await resp.json();
+ return json;
+}
- if (error) {
- console.error('Error fetching mirror status:', error);
- return Error occurred!
;
- }
+function Loading() {
+ return Loading...
;
+}
+export default async function EditMirror({
+ params: { mirrorId },
+}: EditMirrorProps) {
+ const mirrorStatus: MirrorStatusResponse = await getMirrorStatus(mirrorId);
if (!mirrorStatus) {
return No mirror status found!
;
}
+ let syncStatusChild = <>>;
+ if (mirrorStatus.cdcStatus) {
+ syncStatusChild = ;
+ }
+
return (
- {mirrorStatus.cdcStatus && }
+ }>
+ {mirrorStatus.cdcStatus && (
+
+ )}
+
);
}
diff --git a/ui/app/mirrors/edit/[mirrorId]/syncStatus.tsx b/ui/app/mirrors/edit/[mirrorId]/syncStatus.tsx
new file mode 100644
index 0000000000..80cb35701c
--- /dev/null
+++ b/ui/app/mirrors/edit/[mirrorId]/syncStatus.tsx
@@ -0,0 +1,33 @@
+import prisma from '@/app/utils/prisma';
+import { SyncStatusTable } from './syncStatusTable';
+
+type SyncStatusProps = {
+ flowJobName: string | undefined;
+};
+
+export default async function SyncStatus({ flowJobName }: SyncStatusProps) {
+ if (!flowJobName) {
+ return Flow job name not provided!
;
+ }
+
+ const syncs = await prisma.cdc_batches.findMany({
+ where: {
+ flow_name: flowJobName,
+ start_time: {
+ not: undefined,
+ },
+ },
+ orderBy: {
+ start_time: 'desc',
+ },
+ });
+
+ const rows = syncs.map((sync) => ({
+ batchId: sync.id,
+ startTime: sync.start_time,
+ endTime: sync.end_time,
+ numRows: sync.rows_in_batch,
+ }));
+
+ return ;
+}
diff --git a/ui/app/mirrors/edit/[mirrorId]/syncStatusTable.tsx b/ui/app/mirrors/edit/[mirrorId]/syncStatusTable.tsx
new file mode 100644
index 0000000000..64c319a4f9
--- /dev/null
+++ b/ui/app/mirrors/edit/[mirrorId]/syncStatusTable.tsx
@@ -0,0 +1,120 @@
+'use client';
+
+import { Button } from '@/lib/Button';
+import { Checkbox } from '@/lib/Checkbox';
+import { Icon } from '@/lib/Icon';
+import { Label } from '@/lib/Label';
+import { ProgressCircle } from '@/lib/ProgressCircle';
+import { SearchField } from '@/lib/SearchField';
+import { Table, TableCell, TableRow } from '@/lib/Table';
+import moment from 'moment';
+import { useState } from 'react';
+
+type SyncStatusRow = {
+ batchId: number;
+ startTime: Date;
+ endTime: Date | null;
+ numRows: number;
+};
+
+type SyncStatusTableProps = {
+ rows: SyncStatusRow[];
+};
+
+function TimeWithDurationOrRunning({
+ startTime,
+ endTime,
+}: {
+ startTime: Date;
+ endTime: Date | null;
+}) {
+ if (endTime) {
+ return (
+
+ );
+ } else {
+ return (
+
+ );
+ }
+}
+
+const ROWS_PER_PAGE = 10;
+
+export const SyncStatusTable = ({ rows }: SyncStatusTableProps) => {
+ const [currentPage, setCurrentPage] = useState(1);
+ const totalPages = Math.ceil(rows.length / ROWS_PER_PAGE);
+
+ const startRow = (currentPage - 1) * ROWS_PER_PAGE;
+ const endRow = startRow + ROWS_PER_PAGE;
+
+ const displayedRows = rows.slice(startRow, endRow);
+
+ const handlePrevPage = () => {
+ if (currentPage > 1) setCurrentPage(currentPage - 1);
+ };
+
+ const handleNextPage = () => {
+ if (currentPage < totalPages) setCurrentPage(currentPage + 1);
+ };
+
+ return (
+ Initial Copy}
+ toolbar={{
+ left: (
+ <>
+
+
+
+
+ >
+ ),
+ right: ,
+ }}
+ header={
+
+
+
+
+ Batch ID
+ Start Time
+ End Time (Duation)
+ Num Rows Synced
+
+ }
+ >
+ {displayedRows.map((row, index) => (
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {row.numRows}
+
+ ))}
+
+ );
+};
diff --git a/ui/lib/Table/Table.styles.ts b/ui/lib/Table/Table.styles.ts
index 8b9375656f..09958cbf8d 100644
--- a/ui/lib/Table/Table.styles.ts
+++ b/ui/lib/Table/Table.styles.ts
@@ -19,7 +19,9 @@ export const StyledTable = styled.table`
export const StyledTableBody = styled.tbody``;
-export const StyledTableHeader = styled.thead``;
+export const StyledTableHeader = styled.thead`
+ text-align: left;
+`;
export const ToolbarWrapper = styled.div`
display: flex;
diff --git a/ui/package.json b/ui/package.json
index 72fa05ce48..c22337cf24 100644
--- a/ui/package.json
+++ b/ui/package.json
@@ -25,6 +25,7 @@
"@radix-ui/react-radio-group": "^1.1.3",
"@radix-ui/react-select": "^1.2.2",
"@radix-ui/react-switch": "^1.0.3",
+ "@radix-ui/react-tabs": "^1.0.4",
"@radix-ui/react-toast": "^1.1.4",
"@radix-ui/react-toggle": "^1.0.3",
"@radix-ui/react-toggle-group": "^1.0.4",
diff --git a/ui/yarn.lock b/ui/yarn.lock
index 7e366ef2d0..e7347d113c 100644
--- a/ui/yarn.lock
+++ b/ui/yarn.lock
@@ -2119,6 +2119,21 @@
"@radix-ui/react-use-previous" "1.0.1"
"@radix-ui/react-use-size" "1.0.1"
+"@radix-ui/react-tabs@^1.0.4":
+ version "1.0.4"
+ resolved "https://registry.yarnpkg.com/@radix-ui/react-tabs/-/react-tabs-1.0.4.tgz#993608eec55a5d1deddd446fa9978d2bc1053da2"
+ integrity sha512-egZfYY/+wRNCflXNHx+dePvnz9FbmssDTJBtgRfDY7e8SE5oIo3Py2eCB1ckAbh1Q7cQ/6yJZThJ++sgbxibog==
+ dependencies:
+ "@babel/runtime" "^7.13.10"
+ "@radix-ui/primitive" "1.0.1"
+ "@radix-ui/react-context" "1.0.1"
+ "@radix-ui/react-direction" "1.0.1"
+ "@radix-ui/react-id" "1.0.1"
+ "@radix-ui/react-presence" "1.0.1"
+ "@radix-ui/react-primitive" "1.0.3"
+ "@radix-ui/react-roving-focus" "1.0.4"
+ "@radix-ui/react-use-controllable-state" "1.0.1"
+
"@radix-ui/react-toast@^1.1.4":
version "1.1.5"
resolved "https://registry.npmjs.org/@radix-ui/react-toast/-/react-toast-1.1.5.tgz"