diff --git a/ui/app/peers/[peerName]/datatables.tsx b/ui/app/peers/[peerName]/datatables.tsx deleted file mode 100644 index f86f7c5536..0000000000 --- a/ui/app/peers/[peerName]/datatables.tsx +++ /dev/null @@ -1,170 +0,0 @@ -import { CopyButton } from '@/components/CopyButton'; -import TimeLabel from '@/components/TimeComponent'; -import { SlotInfo, StatInfo } from '@/grpc_generated/route'; -import { Label } from '@/lib/Label'; -import { Table, TableCell, TableRow } from '@/lib/Table'; -import { DurationDisplay, SlotNameDisplay } from './helpers'; - -export const SlotTable = ({ data }: { data: SlotInfo[] }) => { - return ( -
- -
- - {[ - 'Slot Name', - 'Active', - 'Redo LSN', - 'Restart LSN', - 'Lag (In MB)', - ].map((heading, index) => ( - - - - ))} - - } - > - {data.map(({ slotName, active, redoLSN, restartLSN, lagInMb }) => { - return ( - - - - - - - - - - - - - - - - - - ); - })} -
-
-
- ); -}; - -export const StatTable = ({ data }: { data: StatInfo[] }) => { - return ( -
- -
- - {[ - 'PID', - 'Duration', - 'Wait Event', - 'Wait Event Type', - 'Start Time', - 'Query', - ].map((heading, id) => ( - - - - ))} - - } - > - {data.map((stat) => ( - - - - - - - - - - - - - - - - - -
- {stat.query} - -
-
-
- ))} -
-
-
- ); -}; diff --git a/ui/app/peers/[peerName]/page.tsx b/ui/app/peers/[peerName]/page.tsx index 3a6eae42a7..4e88c5c3ee 100644 --- a/ui/app/peers/[peerName]/page.tsx +++ b/ui/app/peers/[peerName]/page.tsx @@ -1,15 +1,36 @@ +import { CopyButton } from '@/components/CopyButton'; import ReloadButton from '@/components/ReloadButton'; +import { PostgresConfig } from '@/grpc_generated/peers'; import { PeerSlotResponse, PeerStatResponse } from '@/grpc_generated/route'; import { Label } from '@/lib/Label'; import { GetFlowHttpAddressFromEnv } from '@/rpc/http'; import Link from 'next/link'; -import { SlotTable, StatTable } from './datatables'; +import prisma from '../../utils/prisma'; +import SlotTable from './slottable'; +import StatTable from './stattable'; +import { connStringStyle } from './style'; export const dynamic = 'force-dynamic'; type DataConfigProps = { params: { peerName: string }; }; +async function fetchConnectionString(peerName: string) { + const config = await prisma.peers.findUnique({ + select: { + options: true, + }, + where: { + name: peerName, + }, + }); + if (config) { + const pgConfig = PostgresConfig.decode(config.options); + return `postgresql://${pgConfig.user}:${pgConfig.password}@${pgConfig.host}:${pgConfig.port}`; + } + return ''; +} + const PeerData = async ({ params: { peerName } }: DataConfigProps) => { const getSlotData = async () => { const flowServiceAddr = GetFlowHttpAddressFromEnv(); @@ -50,6 +71,7 @@ const PeerData = async ({ params: { peerName } }: DataConfigProps) => { const slots = await getSlotData(); const stats = await getStatData(); + const connectionString = await fetchConnectionString(peerName); return (
@@ -57,6 +79,13 @@ const PeerData = async ({ params: { peerName } }: DataConfigProps) => {
{peerName}
+
+ +
+ {connectionString} + +
+
{slots && stats ? (
{ + return ( +
+ +
+ + {[ + 'Slot Name', + 'Active', + 'Redo LSN', + 'Restart LSN', + 'Lag (In MB)', + ].map((heading, index) => ( + + + + ))} + + } + > + {data.map(({ slotName, active, redoLSN, restartLSN, lagInMb }) => { + return ( + + + + + + + + + + + + + + + + + + ); + })} +
+
+
+ ); +}; + +export default SlotTable; diff --git a/ui/app/peers/[peerName]/stattable.tsx b/ui/app/peers/[peerName]/stattable.tsx new file mode 100644 index 0000000000..a3a3e80fe3 --- /dev/null +++ b/ui/app/peers/[peerName]/stattable.tsx @@ -0,0 +1,110 @@ +'use client'; +import { CopyButton } from '@/components/CopyButton'; +import TimeLabel from '@/components/TimeComponent'; +import { StatInfo } from '@/grpc_generated/route'; +import { Label } from '@/lib/Label'; +import { SearchField } from '@/lib/SearchField'; +import { Table, TableCell, TableRow } from '@/lib/Table'; +import { useMemo, useState } from 'react'; +import { DurationDisplay } from './helpers'; +import { tableStyle } from './style'; + +const StatTable = ({ data }: { data: StatInfo[] }) => { + const [search, setSearch] = useState(''); + const filteredData = useMemo(() => { + return data.filter((stat) => { + return stat.query.toLowerCase().includes(search.toLowerCase()); + }); + }, [data, search]); + + return ( +
+ +
+ + {[ + 'PID', + 'Duration', + 'Wait Event', + 'Wait Event Type', + 'Start Time', + 'Query', + ].map((heading, id) => ( + + + + ))} + + } + toolbar={{ + left: <>, + right: ( + ) => + setSearch(e.target.value) + } + /> + ), + }} + > + {filteredData.map((stat) => ( + + + + + + + + + + + + + + + + + +
+ {stat.query} + +
+
+
+ ))} +
+
+
+ ); +}; + +export default StatTable; diff --git a/ui/app/peers/[peerName]/style.ts b/ui/app/peers/[peerName]/style.ts new file mode 100644 index 0000000000..1afdbb86b0 --- /dev/null +++ b/ui/app/peers/[peerName]/style.ts @@ -0,0 +1,22 @@ +import { CSSProperties } from 'styled-components'; + +export const tableStyle = { + maxHeight: '100%', + overflow: 'scroll', + padding: '0.5rem', + borderRadius: '0.5rem', + border: '1px solid rgba(0,0,0,0.1)', +}; + +export const connStringStyle: CSSProperties = { + backgroundColor: 'white', + display: 'flex', + width: 'fit-content', + alignItems: 'center', + padding: '0.5rem', + border: '1px solid rgba(0,0,0,0.1)', + borderRadius: '0.5rem', + marginTop: '0.5rem', + fontFamily: 'monospace', + whiteSpace: 'pre-wrap', +}; diff --git a/ui/components/CopyButton.tsx b/ui/components/CopyButton.tsx index bb1334564f..860dd574a2 100644 --- a/ui/components/CopyButton.tsx +++ b/ui/components/CopyButton.tsx @@ -13,7 +13,10 @@ export const CopyButton = ({ text }: { text: string }) => {