diff --git a/ui/app/api/mirrors/alerts/route.ts b/ui/app/api/mirrors/alerts/route.ts index 1870ef89af..ecb9891cbd 100644 --- a/ui/app/api/mirrors/alerts/route.ts +++ b/ui/app/api/mirrors/alerts/route.ts @@ -4,13 +4,18 @@ export const dynamic = 'force-dynamic'; export async function POST(request: Request) { const { flowName } = await request.json(); - const errs = await prisma.flow_errors.findMany({ + const errCount = await prisma.flow_errors.count({ where: { flow_name: flowName, + error_type: 'error', + ack: false, }, }); - - return new Response( - JSON.stringify(errs, (_, v) => (typeof v === 'bigint' ? v.toString() : v)) - ); + let mirrorStatus: 'healthy' | 'failed'; + if (errCount > 0) { + mirrorStatus = 'failed'; + } else { + mirrorStatus = 'healthy'; + } + return new Response(JSON.stringify(mirrorStatus)); } diff --git a/ui/app/dto/MirrorsDTO.ts b/ui/app/dto/MirrorsDTO.ts index 4a76200fd4..977f7f353c 100644 --- a/ui/app/dto/MirrorsDTO.ts +++ b/ui/app/dto/MirrorsDTO.ts @@ -28,3 +28,12 @@ export type SyncStatusRow = { endTime: Date | null; numRows: number; }; + +export type AlertErr = { + id: bigint; + flow_name: string; + error_message: string; + error_type: string; + error_timestamp: Date; + ack: boolean; +}; diff --git a/ui/app/mirrors/errors/[mirrorName]/page.tsx b/ui/app/mirrors/errors/[mirrorName]/page.tsx new file mode 100644 index 0000000000..75e8c91d2b --- /dev/null +++ b/ui/app/mirrors/errors/[mirrorName]/page.tsx @@ -0,0 +1,75 @@ +import { AlertErr } from '@/app/dto/MirrorsDTO'; +import prisma from '@/app/utils/prisma'; +import TimeLabel from '@/components/TimeComponent'; +import { Label } from '@/lib/Label'; +import { Table, TableCell, TableRow } from '@/lib/Table'; + +type MirrorErrorProps = { + params: { mirrorName: string }; +}; + +const MirrorError = async ({ params: { mirrorName } }: MirrorErrorProps) => { + const mirrorErrors: AlertErr[] = await prisma.flow_errors.findMany({ + where: { + flow_name: mirrorName, + error_type: 'error', + }, + distinct: ['error_message'], + }); + + return ( +
+ +
+
+ + +
+ + Type + Message + + + + + } + > + {mirrorErrors.map((mirrorError) => ( + + + {mirrorError.error_type.toUpperCase()} + + + {mirrorError.error_message} + + + + + + ))} +
+
+
+
+ ); +}; + +export default MirrorError; diff --git a/ui/app/mirrors/mirror-error.tsx b/ui/app/mirrors/mirror-error.tsx deleted file mode 100644 index 950ef6d500..0000000000 --- a/ui/app/mirrors/mirror-error.tsx +++ /dev/null @@ -1,74 +0,0 @@ -'use client'; - -import { Icon } from "@/lib/Icon"; -import { Prisma } from "@prisma/client"; -import React, { useState, useEffect } from "react"; -import * as Popover from '@radix-ui/react-popover'; -import { ProgressCircle } from "@/lib/ProgressCircle"; -import styled, { css } from 'styled-components'; - - -// const NoErrorMirror = styled.div` -// color: ${({ theme }) => theme.colors.positive.fill.normal}; -// `; - -// const ErroredMirror = styled.div` -// color: ${({ theme }) => theme.colors.destructive.fill.normal}; -// `; - - -export const ErrorModal = ({ flowErrors }: { flowErrors: Prisma.flow_errorsSelect[] }) => { - return ( - - ); -}; - - -export const MirrorError = ({ flowName }: { flowName: string }) => { - const [flowErrors, setFlowErrors] = useState(); - const [isLoading, setIsLoading] = useState(true); - const [error, setError] = useState(null); - - useEffect(() => { - const fetchData = async () => { - setIsLoading(true); - try { - const response = await fetch(`/api/mirrors/alerts`, { - method: 'POST', - headers: { - 'Content-Type': 'application/json', - }, - body: JSON.stringify({ flowName }), - }); - - if (!response.ok) { - throw new Error('Network response was not ok'); - } - - const data = await response.json(); - setFlowErrors(data.errors); - } catch (err: any) { - setError(err.message); - } finally { - setIsLoading(false); - } - }; - - fetchData(); - }, [flowName]); - - if (isLoading) { - return
; - } - - if (error) { - console.log(error); - return
; - } - - if (!flowErrors || flowErrors.length === 0) { - return ; - } - - return ; -}; diff --git a/ui/app/mirrors/mirror-status.tsx b/ui/app/mirrors/mirror-status.tsx new file mode 100644 index 0000000000..2a68b7b3d2 --- /dev/null +++ b/ui/app/mirrors/mirror-status.tsx @@ -0,0 +1,83 @@ +'use client'; + +import { Button } from '@/lib/Button'; +import { Icon } from '@/lib/Icon'; +import { Label } from '@/lib/Label'; +import { ProgressCircle } from '@/lib/ProgressCircle'; +import Link from 'next/link'; +import { useRouter } from 'next/navigation'; +import { useEffect, useState } from 'react'; +export const ErrorModal = ({ flowName }: { flowName: string }) => { + const router = useRouter(); + return ( + + + + ); +}; + +export const MirrorError = ({ flowName }: { flowName: string }) => { + const [flowStatus, setFlowStatus] = useState(); + const [isLoading, setIsLoading] = useState(true); + const [error, setError] = useState(null); + + useEffect(() => { + const fetchData = async () => { + setIsLoading(true); + try { + const response = await fetch(`/api/mirrors/alerts`, { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify({ flowName }), + }); + + if (!response.ok) { + throw new Error('Network response was not ok'); + } + + const flowStatus = await response.json(); + setFlowStatus(flowStatus); + } catch (err: any) { + setError(err.message); + } finally { + setIsLoading(false); + } + }; + + fetchData(); + }, [flowName]); + + if (isLoading) { + return ( +
+ +
+ ); + } + + if (error) { + return ( +
+ +
+ ); + } + + if (flowStatus == 'healthy') { + return ; + } + + return ; +}; diff --git a/ui/app/mirrors/tables.tsx b/ui/app/mirrors/tables.tsx index 133427ff43..6c1289befc 100644 --- a/ui/app/mirrors/tables.tsx +++ b/ui/app/mirrors/tables.tsx @@ -5,10 +5,9 @@ import TimeLabel from '@/components/TimeComponent'; import { Label } from '@/lib/Label'; import { SearchField } from '@/lib/SearchField'; import { Table, TableCell, TableRow } from '@/lib/Table'; -import { Tab } from '@tremor/react'; import Link from 'next/link'; import { useMemo, useState } from 'react'; -import { MirrorError } from './mirror-error'; +import { MirrorError } from './mirror-status'; export function CDCFlows({ cdcFlows }: { cdcFlows: any }) { const [searchQuery, setSearchQuery] = useState(''); @@ -45,15 +44,26 @@ export function CDCFlows({ cdcFlows }: { cdcFlows: any }) { }} header={ - {['Name', 'Source', 'Destination', 'Start Time', 'Errors', ''].map( - (heading, index) => ( - - - - ) - )} + {[ + 'Name', + 'Source', + 'Destination', + 'Start Time', + 'Status', + '', + ].map((heading, index) => ( + + + + ))} } >