diff --git a/ui/api/messages/actions.ts b/ui/api/messages/actions.ts index fda16d881..2f2c02499 100644 --- a/ui/api/messages/actions.ts +++ b/ui/api/messages/actions.ts @@ -24,7 +24,7 @@ export async function getTopicMessages( | undefined; maxValueLength: number | undefined; }, -): Promise { +): Promise<{ messages: Message[]; ts: Date }> { const sp = new URLSearchParams( filterUndefinedFromObj({ "fields[records]": @@ -55,7 +55,7 @@ export async function getTopicMessages( }); const rawData = await res.json(); log.trace({ rawData }, "Received messages"); - return MessageApiResponse.parse(rawData).data; + return { messages: MessageApiResponse.parse(rawData).data, ts: new Date() }; } export async function getTopicMessage( @@ -67,7 +67,7 @@ export async function getTopicMessage( }, ): Promise { log.info({ kafkaId, topicId, params }, "getTopicMessage"); - const messages = await getTopicMessages(kafkaId, topicId, { + const { messages } = await getTopicMessages(kafkaId, topicId, { pageSize: 1, partition: params.partition, filter: { diff --git a/ui/app/[locale]/kafka/[kafkaId]/@header/topics/[topicId]/TopicHeader.tsx b/ui/app/[locale]/kafka/[kafkaId]/@header/topics/[topicId]/TopicHeader.tsx index 104f396ac..e61c9671a 100644 --- a/ui/app/[locale]/kafka/[kafkaId]/@header/topics/[topicId]/TopicHeader.tsx +++ b/ui/app/[locale]/kafka/[kafkaId]/@header/topics/[topicId]/TopicHeader.tsx @@ -13,7 +13,7 @@ import { } from "@/libs/patternfly/react-core"; import { Skeleton } from "@patternfly/react-core"; import { notFound } from "next/navigation"; -import { Suspense } from "react"; +import { ReactNode, Suspense } from "react"; export const fetchCache = "force-cache"; @@ -22,6 +22,7 @@ export function TopicHeader({ }: { params: KafkaTopicParams; }) { + const portal =
; return ( } + actions={[portal]} /> } > - + ); } async function ConnectedTopicHeader({ params: { kafkaId, topicId }, + portal, }: { params: KafkaTopicParams; + portal: ReactNode; }) { const cluster = await getKafkaCluster(kafkaId); if (!cluster) { @@ -135,6 +139,7 @@ async function ConnectedTopicHeader({ } + actions={[portal]} /> ); } diff --git a/ui/app/[locale]/kafka/[kafkaId]/topics/[topicId]/messages/ConnectedMessagesTable.tsx b/ui/app/[locale]/kafka/[kafkaId]/topics/[topicId]/messages/ConnectedMessagesTable.tsx index 1fcae2448..a13a2056a 100644 --- a/ui/app/[locale]/kafka/[kafkaId]/topics/[topicId]/messages/ConnectedMessagesTable.tsx +++ b/ui/app/[locale]/kafka/[kafkaId]/topics/[topicId]/messages/ConnectedMessagesTable.tsx @@ -1,18 +1,21 @@ "use client"; import { Message } from "@/api/messages/schema"; -import { RefreshInterval } from "@/app/[locale]/kafka/[kafkaId]/topics/[topicId]/messages/_components/RefreshSelector"; +import { RefreshInterval, RefreshSelector } from "@/components/RefreshSelector"; import { useFilterParams } from "@/utils/useFilterParams"; -import { useEffect, useState, useTransition } from "react"; +import { useEffect, useLayoutEffect, useState, useTransition } from "react"; +import { createPortal } from "react-dom"; import { MessagesTable } from "./_components/MessagesTable"; export function ConnectedMessagesTable({ messages, + lastRefresh, selectedMessage, partitions, params, }: { messages: Message[]; + lastRefresh: Date | undefined; selectedMessage: Message | undefined; partitions: number; params: { @@ -24,8 +27,10 @@ export function ConnectedMessagesTable({ limit: number; }; }) { - const [refreshInterval, setRefreshInterval] = useState(); + const [automaticRefresh, setAutomaticRefresh] = useState(false); + const [refreshInterval, setRefreshInterval] = useState(1); const [isPending, startTransition] = useTransition(); + const [isAutorefreshing, startAutorefreshTransition] = useTransition(); const updateUrl = useFilterParams(params); function setPartition(partition: number | undefined) { @@ -116,37 +121,95 @@ export function ConnectedMessagesTable({ useEffect(() => { let interval: ReturnType; - if (refreshInterval) { + if (automaticRefresh) { interval = setInterval(async () => { - updateUrl({ ...params, _ts: Date.now() }); + if (!isAutorefreshing) { + startAutorefreshTransition(() => + updateUrl({ ...params, _ts: Date.now() }), + ); + } }, refreshInterval * 1000); } return () => clearInterval(interval); - }, [params, updateUrl, refreshInterval]); + }, [params, updateUrl, automaticRefresh, refreshInterval, isAutorefreshing]); return ( - + <> + + + ); } + +function ConnectedRefreshSelector({ + isRefreshing, + isLive, + refreshInterval, + lastRefresh, + onRefresh, + onRefreshInterval, + onToggleLive, +}: { + isRefreshing: boolean; + isLive: boolean; + refreshInterval: RefreshInterval; + lastRefresh: Date | undefined; + onRefresh: () => void; + onRefreshInterval: (interval: RefreshInterval) => void; + onToggleLive: (enable: boolean) => void; +}) { + const [container, setContainer] = useState(); + + function seekContainer() { + const el = document.getElementById("topic-header-portal"); + if (el) { + setContainer(el); + } else { + setTimeout(seekContainer, 100); + } + } + + useLayoutEffect(seekContainer, []); // we want to run this just once + + return container + ? createPortal( + , + container, + ) + : null; +} diff --git a/ui/app/[locale]/kafka/[kafkaId]/topics/[topicId]/messages/_components/MessagesTable.tsx b/ui/app/[locale]/kafka/[kafkaId]/topics/[topicId]/messages/_components/MessagesTable.tsx index 5196bbb2d..059f90c6e 100644 --- a/ui/app/[locale]/kafka/[kafkaId]/topics/[topicId]/messages/_components/MessagesTable.tsx +++ b/ui/app/[locale]/kafka/[kafkaId]/topics/[topicId]/messages/_components/MessagesTable.tsx @@ -8,10 +8,6 @@ import { import { FilterGroup } from "@/app/[locale]/kafka/[kafkaId]/topics/[topicId]/messages/_components/FilterGroup"; import { NoResultsEmptyState } from "@/app/[locale]/kafka/[kafkaId]/topics/[topicId]/messages/_components/NoResultsEmptyState"; import { PartitionSelector } from "@/app/[locale]/kafka/[kafkaId]/topics/[topicId]/messages/_components/PartitionSelector"; -import { - RefreshInterval, - RefreshSelector, -} from "@/app/[locale]/kafka/[kafkaId]/topics/[topicId]/messages/_components/RefreshSelector"; import { DateTime } from "@/components/DateTime"; import { Number } from "@/components/Number"; import { ResponsiveTable } from "@/components/table"; @@ -56,7 +52,6 @@ const defaultColumns = ["offset", "timestampUTC", "key", "value"]; export type MessageBrowserProps = { isRefreshing: boolean; - refreshInterval: RefreshInterval; selectedMessage: Message | undefined; lastUpdated: Date | undefined; messages: Message[]; @@ -72,8 +67,6 @@ export type MessageBrowserProps = { onEpochChange: (value: number | undefined) => void; onLatest: () => void; onLimitChange: (value: number) => void; - onRefresh: () => void; - onRefreshInterval: (interval: RefreshInterval) => void; onSelectMessage: (message: Message) => void; onDeselectMessage: () => void; }; @@ -88,15 +81,12 @@ export function MessagesTable({ filterOffset, filterEpoch, filterTimestamp, - refreshInterval, onPartitionChange, onOffsetChange, onTimestampChange, onEpochChange, onLatest, onLimitChange, - onRefresh, - onRefreshInterval, onSelectMessage, onDeselectMessage, }: MessageBrowserProps) { @@ -141,7 +131,6 @@ export function MessagesTable({ setShowColumnsManagement(true)} /> @@ -319,15 +306,12 @@ export function MessagesTableToolbar({ partition, partitions, limit, - refreshInterval, onEpochChange, onTimestampChange, onOffsetChange, onPartitionChange, onLimitChange, onLatest, - onRefresh, - onRefreshInterval, onColumnManagement, }: Pick< MessageBrowserProps, @@ -338,15 +322,12 @@ export function MessagesTableToolbar({ | "partition" | "partitions" | "limit" - | "refreshInterval" | "onEpochChange" | "onTimestampChange" | "onOffsetChange" | "onPartitionChange" | "onLimitChange" | "onLatest" - | "onRefresh" - | "onRefreshInterval" > & { onColumnManagement: () => void; }) { @@ -420,17 +401,6 @@ export function MessagesTableToolbar({ /> - {/* icon buttons */} - - - - -