From 3f09ef8e07336d22c099f90b01984678bd134608 Mon Sep 17 00:00:00 2001 From: Yash Mehrotra Date: Wed, 1 Apr 2026 10:52:16 +0530 Subject: [PATCH 1/6] chore: live tail mode for config changes --- src/api/query-hooks/useConfigChangesHooks.ts | 11 ++- src/api/services/configs.ts | 5 + src/api/types/configs.ts | 1 + .../ConfigChangesFilters.tsx | 3 + .../FilterBar/ConfigRelatedChangesFilters.tsx | 5 +- src/pages/config/ConfigChangesPage.tsx | 96 +++++++++++++++++-- .../details/ConfigDetailsChangesPage.tsx | 89 +++++++++++++++-- 7 files changed, 193 insertions(+), 17 deletions(-) diff --git a/src/api/query-hooks/useConfigChangesHooks.ts b/src/api/query-hooks/useConfigChangesHooks.ts index 65e6586da..a0b45d776 100644 --- a/src/api/query-hooks/useConfigChangesHooks.ts +++ b/src/api/query-hooks/useConfigChangesHooks.ts @@ -39,9 +39,11 @@ function useConfigChangesTagsFilter(paramPrefix?: string) { export function useGetAllConfigsChangesQuery( { paramPrefix, + from_inserted_at, ...queryOptions }: UseQueryOptions & { paramPrefix?: string; + from_inserted_at?: string; } = { enabled: true, keepPreviousData: true @@ -75,6 +77,7 @@ export function useGetAllConfigsChangesQuery( severity, from, to, + from_inserted_at, configTypes, configType, sortBy: sortBy[0]?.id, @@ -93,7 +96,12 @@ export function useGetAllConfigsChangesQuery( } export function useGetConfigChangesByIDQuery( - queryOptions: UseQueryOptions = { + { + from_inserted_at, + ...queryOptions + }: UseQueryOptions & { + from_inserted_at?: string; + } = { enabled: true, keepPreviousData: true } @@ -143,6 +151,7 @@ export function useGetConfigChangesByIDQuery( severity, from, to, + from_inserted_at, configTypes, sortBy: sortBy[0]?.id, sortOrder: sortBy[0]?.desc ? "desc" : "asc", diff --git a/src/api/services/configs.ts b/src/api/services/configs.ts index 98d0fafab..8648c265f 100644 --- a/src/api/services/configs.ts +++ b/src/api/services/configs.ts @@ -230,6 +230,7 @@ export type GetConfigsRelatedChangesParams = { severity?: string; from?: string; to?: string; + from_inserted_at?: string; configTypes?: string; configType?: string; pageSize?: number | string; @@ -252,6 +253,7 @@ export async function getConfigsChanges({ severity, from, to, + from_inserted_at, configTypes, configType, pageIndex, @@ -302,6 +304,9 @@ export async function getConfigsChanges({ if (to) { requestData.set("to", to); } + if (from_inserted_at) { + requestData.set("from_inserted_at", from_inserted_at); + } if (severity) { requestData.set("severity", severity); } diff --git a/src/api/types/configs.ts b/src/api/types/configs.ts index a0cfc4cb7..eeb3215ad 100644 --- a/src/api/types/configs.ts +++ b/src/api/types/configs.ts @@ -23,6 +23,7 @@ export interface ConfigChange extends CreatedAt { tags?: Record; first_observed?: string; count?: number; + inserted_at?: string; } export interface Change { diff --git a/src/components/Configs/Changes/ConfigChangesFilters/ConfigChangesFilters.tsx b/src/components/Configs/Changes/ConfigChangesFilters/ConfigChangesFilters.tsx index 9b0b05566..ee0e455c1 100644 --- a/src/components/Configs/Changes/ConfigChangesFilters/ConfigChangesFilters.tsx +++ b/src/components/Configs/Changes/ConfigChangesFilters/ConfigChangesFilters.tsx @@ -67,11 +67,13 @@ export function FilterBadge({ filters, paramKey }: FilterBadgeProps) { type ConfigChangeFiltersProps = React.HTMLProps & { paramsToReset?: string[]; + extra?: React.ReactNode; }; export function ConfigChangeFilters({ className, paramsToReset = [], + extra, ...props }: ConfigChangeFiltersProps) { const [params] = useSearchParams(); @@ -100,6 +102,7 @@ export function ConfigChangeFilters({ + {extra}
diff --git a/src/components/Configs/Changes/ConfigsRelatedChanges/FilterBar/ConfigRelatedChangesFilters.tsx b/src/components/Configs/Changes/ConfigsRelatedChanges/FilterBar/ConfigRelatedChangesFilters.tsx index 990e16375..eb16137ce 100644 --- a/src/components/Configs/Changes/ConfigsRelatedChanges/FilterBar/ConfigRelatedChangesFilters.tsx +++ b/src/components/Configs/Changes/ConfigsRelatedChanges/FilterBar/ConfigRelatedChangesFilters.tsx @@ -13,11 +13,13 @@ import ConfigTypesTristateDropdown from "../../ConfigChangesFilters/ConfigTypesT type ConfigChangeFiltersProps = { className?: string; paramsToReset?: string[]; + extra?: React.ReactNode; }; export function ConfigRelatedChangesFilters({ className, - paramsToReset = [] + paramsToReset = [], + extra }: ConfigChangeFiltersProps) { const arbitraryFilters = useConfigChangesArbitraryFilters(); @@ -35,6 +37,7 @@ export function ConfigRelatedChangesFilters({ + {extra}
diff --git a/src/pages/config/ConfigChangesPage.tsx b/src/pages/config/ConfigChangesPage.tsx index 5a614b9d0..29aa52b0d 100644 --- a/src/pages/config/ConfigChangesPage.tsx +++ b/src/pages/config/ConfigChangesPage.tsx @@ -1,4 +1,5 @@ import { useGetAllConfigsChangesQuery } from "@flanksource-ui/api/query-hooks/useConfigChangesHooks"; +import { ConfigChange } from "@flanksource-ui/api/types/configs"; import { ConfigChangeTable } from "@flanksource-ui/components/Configs/Changes/ConfigChangeTable"; import { ConfigChangeFilters } from "@flanksource-ui/components/Configs/Changes/ConfigChangesFilters/ConfigChangesFilters"; import ConfigPageTabs from "@flanksource-ui/components/Configs/ConfigPageTabs"; @@ -12,9 +13,21 @@ import { import { Head } from "@flanksource-ui/ui/Head"; import { SearchLayout } from "@flanksource-ui/ui/Layout/SearchLayout"; import { refreshButtonClickedTrigger } from "@flanksource-ui/ui/SlidingSideBar/SlidingSideBar"; +import { Toggle } from "@flanksource-ui/ui/FormControls/Toggle"; import { useAtom } from "jotai"; +import { useEffect, useState } from "react"; import { useSearchParams } from "react-router-dom"; +function getNewestInsertedAt(changes: ConfigChange[]): string | undefined { + return changes.reduce( + (latest, c) => + c.inserted_at && (!latest || c.inserted_at > latest) + ? c.inserted_at + : latest, + undefined as string | undefined + ); +} + export function ConfigChangesPage() { const [, setRefreshButtonClickedTrigger] = useAtom( refreshButtonClickedTrigger @@ -31,22 +44,80 @@ export function ConfigChangesPage() { const pageSize = params.get("pageSize") ?? "200"; + const [liveTail, setLiveTail] = useState(false); + const [tailCursor, setTailCursor] = useState(undefined); + const [tailedChanges, setTailedChanges] = useState([]); + const { data, isLoading, error, isRefetching, refetch } = useGetAllConfigsChangesQuery({ keepPreviousData: true }); - const changes = (data?.changes ?? []).map((changes) => ({ - ...changes, + // Initialize cursor from base data when live tail is turned on + useEffect(() => { + if (liveTail && data?.changes?.length && !tailCursor) { + setTailCursor(getNewestInsertedAt(data.changes)); + } + }, [liveTail, data, tailCursor]); + + // Reset when live tail is turned off + useEffect(() => { + if (!liveTail) { + setTailedChanges([]); + setTailCursor(undefined); + } + }, [liveTail]); + + const { data: pollData } = useGetAllConfigsChangesQuery({ + from_inserted_at: tailCursor, + keepPreviousData: false, + enabled: liveTail && !!tailCursor, + refetchInterval: liveTail ? 5000 : false + }); + + // Accumulate new items from poll and advance cursor + useEffect(() => { + if (!pollData?.changes?.length) return; + + const incoming = pollData.changes; + const newest = getNewestInsertedAt(incoming); + if (newest) { + setTailCursor((prev) => (!prev || newest > prev ? newest : prev)); + } + + setTailedChanges((prev) => { + const existingIds = new Set(prev.map((c) => c.id)); + const deduped = incoming.filter((c) => !existingIds.has(c.id)); + if (deduped.length === 0) return prev; + return [...deduped, ...prev]; + }); + }, [pollData]); + + const baseChanges = (data?.changes ?? []).map((change) => ({ + ...change, + config: { + id: change.config_id!, + type: change.type!, + name: change.name!, + deleted_at: change.deleted_at + } + })); + + const tailedWithConfig = tailedChanges.map((change) => ({ + ...change, config: { - id: changes.config_id!, - type: changes.type!, - name: changes.name!, - deleted_at: changes.deleted_at + id: change.config_id!, + type: change.type!, + name: change.name!, + deleted_at: change.deleted_at } })); - const totalChanges = data?.total ?? 0; + const baseIds = new Set(baseChanges.map((c) => c.id)); + const uniqueTailed = tailedWithConfig.filter((c) => !baseIds.has(c.id)); + const changes = [...uniqueTailed, ...baseChanges]; + + const totalChanges = (data?.total ?? 0) + uniqueTailed.length; const totalChangesPages = Math.ceil(totalChanges / parseInt(pageSize)); const errorMessage = @@ -99,7 +170,16 @@ export function ConfigChangesPage() { ) : ( <> - + + } + /> + c.inserted_at && (!latest || c.inserted_at > latest) + ? c.inserted_at + : latest, + undefined as string | undefined + ); +} + export function ConfigDetailsChangesPage() { const { id } = useParams(); @@ -15,21 +28,78 @@ export function ConfigDetailsChangesPage() { const pageSize = params.get("pageSize") ?? "200"; + const [liveTail, setLiveTail] = useState(false); + const [tailCursor, setTailCursor] = useState(undefined); + const [tailedChanges, setTailedChanges] = useState([]); + const { data, isLoading, error, refetch } = useGetConfigChangesByIDQuery({ keepPreviousData: true, enabled: !!id }); - const changes = (data?.changes ?? []).map((changes) => ({ - ...changes, + // Initialize cursor from base data when live tail is turned on + useEffect(() => { + if (liveTail && data?.changes?.length && !tailCursor) { + setTailCursor(getNewestInsertedAt(data.changes)); + } + }, [liveTail, data, tailCursor]); + + // Reset when live tail is turned off + useEffect(() => { + if (!liveTail) { + setTailedChanges([]); + setTailCursor(undefined); + } + }, [liveTail]); + + const { data: pollData } = useGetConfigChangesByIDQuery({ + from_inserted_at: tailCursor, + keepPreviousData: false, + enabled: liveTail && !!id && !!tailCursor, + refetchInterval: liveTail ? 5000 : false + }); + + // Accumulate new items from poll and advance cursor + useEffect(() => { + if (!pollData?.changes?.length) return; + + const incoming = pollData.changes; + const newest = getNewestInsertedAt(incoming); + if (newest) { + setTailCursor((prev) => (!prev || newest > prev ? newest : prev)); + } + + setTailedChanges((prev) => { + const existingIds = new Set(prev.map((c) => c.id)); + const deduped = incoming.filter((c) => !existingIds.has(c.id)); + if (deduped.length === 0) return prev; + return [...deduped, ...prev]; + }); + }, [pollData]); + + const baseChanges = (data?.changes ?? []).map((change) => ({ + ...change, config: { - id: changes.config_id!, - type: changes.type!, - name: changes.name! + id: change.config_id!, + type: change.type!, + name: change.name! } })); - const totalChanges = data?.total ?? 0; + const tailedWithConfig = tailedChanges.map((change) => ({ + ...change, + config: { + id: change.config_id!, + type: change.type!, + name: change.name! + } + })); + + const baseIds = new Set(baseChanges.map((c) => c.id)); + const uniqueTailed = tailedWithConfig.filter((c) => !baseIds.has(c.id)); + const changes = [...uniqueTailed, ...baseChanges]; + + const totalChanges = (data?.total ?? 0) + uniqueTailed.length; const totalChangesPages = Math.ceil(totalChanges / parseInt(pageSize)); if (error) { @@ -50,7 +120,12 @@ export function ConfigDetailsChangesPage() { >
- + + } + />
Date: Fri, 10 Apr 2026 18:51:24 +0530 Subject: [PATCH 2/6] chore: changes --- src/api/query-hooks/useConfigChangesHooks.ts | 8 ++++---- src/pages/config/ConfigChangesPage.tsx | 3 ++- src/pages/config/details/ConfigDetailsChangesPage.tsx | 2 +- 3 files changed, 7 insertions(+), 6 deletions(-) diff --git a/src/api/query-hooks/useConfigChangesHooks.ts b/src/api/query-hooks/useConfigChangesHooks.ts index a0b45d776..c434775da 100644 --- a/src/api/query-hooks/useConfigChangesHooks.ts +++ b/src/api/query-hooks/useConfigChangesHooks.ts @@ -75,8 +75,8 @@ export function useGetAllConfigsChangesQuery( include_deleted_configs: showChangesFromDeletedConfigs, changeType, severity, - from, - to, + from: from_inserted_at ? undefined : from, + to: from_inserted_at ? undefined : to, from_inserted_at, configTypes, configType, @@ -149,8 +149,8 @@ export function useGetConfigChangesByIDQuery( include_deleted_configs: showChangesFromDeletedConfigs, changeType: change_type, severity, - from, - to, + from: from_inserted_at ? undefined : from, + to: from_inserted_at ? undefined : to, from_inserted_at, configTypes, sortBy: sortBy[0]?.id, diff --git a/src/pages/config/ConfigChangesPage.tsx b/src/pages/config/ConfigChangesPage.tsx index 29aa52b0d..30629ab30 100644 --- a/src/pages/config/ConfigChangesPage.tsx +++ b/src/pages/config/ConfigChangesPage.tsx @@ -50,7 +50,8 @@ export function ConfigChangesPage() { const { data, isLoading, error, isRefetching, refetch } = useGetAllConfigsChangesQuery({ - keepPreviousData: true + keepPreviousData: true, + enabled: !liveTail }); // Initialize cursor from base data when live tail is turned on diff --git a/src/pages/config/details/ConfigDetailsChangesPage.tsx b/src/pages/config/details/ConfigDetailsChangesPage.tsx index 24d06c6e5..8ef236e3e 100644 --- a/src/pages/config/details/ConfigDetailsChangesPage.tsx +++ b/src/pages/config/details/ConfigDetailsChangesPage.tsx @@ -34,7 +34,7 @@ export function ConfigDetailsChangesPage() { const { data, isLoading, error, refetch } = useGetConfigChangesByIDQuery({ keepPreviousData: true, - enabled: !!id + enabled: !!id && !liveTail }); // Initialize cursor from base data when live tail is turned on From fca1ba88f011a2ed923505808840cb1f8c132242 Mon Sep 17 00:00:00 2001 From: Yash Mehrotra Date: Sat, 11 Apr 2026 11:43:47 +0530 Subject: [PATCH 3/6] chore: update merge logic --- src/api/query-hooks/useConfigChangesHooks.ts | 18 +++++------ src/api/services/configs.ts | 5 --- src/pages/config/ConfigChangesPage.tsx | 32 ++++++++++--------- .../details/ConfigDetailsChangesPage.tsx | 31 ++++++++++-------- 4 files changed, 42 insertions(+), 44 deletions(-) diff --git a/src/api/query-hooks/useConfigChangesHooks.ts b/src/api/query-hooks/useConfigChangesHooks.ts index c434775da..879d5eb76 100644 --- a/src/api/query-hooks/useConfigChangesHooks.ts +++ b/src/api/query-hooks/useConfigChangesHooks.ts @@ -39,11 +39,11 @@ function useConfigChangesTagsFilter(paramPrefix?: string) { export function useGetAllConfigsChangesQuery( { paramPrefix, - from_inserted_at, + cursor, ...queryOptions }: UseQueryOptions & { paramPrefix?: string; - from_inserted_at?: string; + cursor?: string; } = { enabled: true, keepPreviousData: true @@ -75,9 +75,8 @@ export function useGetAllConfigsChangesQuery( include_deleted_configs: showChangesFromDeletedConfigs, changeType, severity, - from: from_inserted_at ? undefined : from, - to: from_inserted_at ? undefined : to, - from_inserted_at, + from: cursor ?? from, + to: cursor ? undefined : to, configTypes, configType, sortBy: sortBy[0]?.id, @@ -97,10 +96,10 @@ export function useGetAllConfigsChangesQuery( export function useGetConfigChangesByIDQuery( { - from_inserted_at, + cursor, ...queryOptions }: UseQueryOptions & { - from_inserted_at?: string; + cursor?: string; } = { enabled: true, keepPreviousData: true @@ -149,9 +148,8 @@ export function useGetConfigChangesByIDQuery( include_deleted_configs: showChangesFromDeletedConfigs, changeType: change_type, severity, - from: from_inserted_at ? undefined : from, - to: from_inserted_at ? undefined : to, - from_inserted_at, + from: cursor ?? from, + to: cursor ? undefined : to, configTypes, sortBy: sortBy[0]?.id, sortOrder: sortBy[0]?.desc ? "desc" : "asc", diff --git a/src/api/services/configs.ts b/src/api/services/configs.ts index 8648c265f..98d0fafab 100644 --- a/src/api/services/configs.ts +++ b/src/api/services/configs.ts @@ -230,7 +230,6 @@ export type GetConfigsRelatedChangesParams = { severity?: string; from?: string; to?: string; - from_inserted_at?: string; configTypes?: string; configType?: string; pageSize?: number | string; @@ -253,7 +252,6 @@ export async function getConfigsChanges({ severity, from, to, - from_inserted_at, configTypes, configType, pageIndex, @@ -304,9 +302,6 @@ export async function getConfigsChanges({ if (to) { requestData.set("to", to); } - if (from_inserted_at) { - requestData.set("from_inserted_at", from_inserted_at); - } if (severity) { requestData.set("severity", severity); } diff --git a/src/pages/config/ConfigChangesPage.tsx b/src/pages/config/ConfigChangesPage.tsx index 30629ab30..ecf0efba0 100644 --- a/src/pages/config/ConfigChangesPage.tsx +++ b/src/pages/config/ConfigChangesPage.tsx @@ -18,11 +18,11 @@ import { useAtom } from "jotai"; import { useEffect, useState } from "react"; import { useSearchParams } from "react-router-dom"; -function getNewestInsertedAt(changes: ConfigChange[]): string | undefined { +function getNewestCreatedAt(changes: ConfigChange[]): string | undefined { return changes.reduce( (latest, c) => - c.inserted_at && (!latest || c.inserted_at > latest) - ? c.inserted_at + c.created_at && (!latest || c.created_at > latest) + ? c.created_at : latest, undefined as string | undefined ); @@ -50,14 +50,13 @@ export function ConfigChangesPage() { const { data, isLoading, error, isRefetching, refetch } = useGetAllConfigsChangesQuery({ - keepPreviousData: true, - enabled: !liveTail + keepPreviousData: true }); // Initialize cursor from base data when live tail is turned on useEffect(() => { if (liveTail && data?.changes?.length && !tailCursor) { - setTailCursor(getNewestInsertedAt(data.changes)); + setTailCursor(getNewestCreatedAt(data.changes)); } }, [liveTail, data, tailCursor]); @@ -70,7 +69,7 @@ export function ConfigChangesPage() { }, [liveTail]); const { data: pollData } = useGetAllConfigsChangesQuery({ - from_inserted_at: tailCursor, + cursor: tailCursor, keepPreviousData: false, enabled: liveTail && !!tailCursor, refetchInterval: liveTail ? 5000 : false @@ -81,16 +80,15 @@ export function ConfigChangesPage() { if (!pollData?.changes?.length) return; const incoming = pollData.changes; - const newest = getNewestInsertedAt(incoming); + const newest = getNewestCreatedAt(incoming); if (newest) { setTailCursor((prev) => (!prev || newest > prev ? newest : prev)); } setTailedChanges((prev) => { - const existingIds = new Set(prev.map((c) => c.id)); - const deduped = incoming.filter((c) => !existingIds.has(c.id)); - if (deduped.length === 0) return prev; - return [...deduped, ...prev]; + const incomingIds = new Set(incoming.map((c) => c.id)); + const filtered = prev.filter((c) => !incomingIds.has(c.id)); + return [...incoming, ...filtered]; }); }, [pollData]); @@ -114,11 +112,15 @@ export function ConfigChangesPage() { } })); + const tailedIds = new Set(tailedWithConfig.map((c) => c.id)); + const baseWithoutTailed = baseChanges.filter((c) => !tailedIds.has(c.id)); const baseIds = new Set(baseChanges.map((c) => c.id)); - const uniqueTailed = tailedWithConfig.filter((c) => !baseIds.has(c.id)); - const changes = [...uniqueTailed, ...baseChanges]; + const newTailedCount = tailedWithConfig.filter( + (c) => !baseIds.has(c.id) + ).length; + const changes = [...tailedWithConfig, ...baseWithoutTailed]; - const totalChanges = (data?.total ?? 0) + uniqueTailed.length; + const totalChanges = (data?.total ?? 0) + newTailedCount; const totalChangesPages = Math.ceil(totalChanges / parseInt(pageSize)); const errorMessage = diff --git a/src/pages/config/details/ConfigDetailsChangesPage.tsx b/src/pages/config/details/ConfigDetailsChangesPage.tsx index 8ef236e3e..306110ed6 100644 --- a/src/pages/config/details/ConfigDetailsChangesPage.tsx +++ b/src/pages/config/details/ConfigDetailsChangesPage.tsx @@ -8,11 +8,11 @@ import { Toggle } from "@flanksource-ui/ui/FormControls/Toggle"; import { useEffect, useState } from "react"; import { useParams, useSearchParams } from "react-router-dom"; -function getNewestInsertedAt(changes: ConfigChange[]): string | undefined { +function getNewestCreatedAt(changes: ConfigChange[]): string | undefined { return changes.reduce( (latest, c) => - c.inserted_at && (!latest || c.inserted_at > latest) - ? c.inserted_at + c.created_at && (!latest || c.created_at > latest) + ? c.created_at : latest, undefined as string | undefined ); @@ -34,13 +34,13 @@ export function ConfigDetailsChangesPage() { const { data, isLoading, error, refetch } = useGetConfigChangesByIDQuery({ keepPreviousData: true, - enabled: !!id && !liveTail + enabled: !!id }); // Initialize cursor from base data when live tail is turned on useEffect(() => { if (liveTail && data?.changes?.length && !tailCursor) { - setTailCursor(getNewestInsertedAt(data.changes)); + setTailCursor(getNewestCreatedAt(data.changes)); } }, [liveTail, data, tailCursor]); @@ -53,7 +53,7 @@ export function ConfigDetailsChangesPage() { }, [liveTail]); const { data: pollData } = useGetConfigChangesByIDQuery({ - from_inserted_at: tailCursor, + cursor: tailCursor, keepPreviousData: false, enabled: liveTail && !!id && !!tailCursor, refetchInterval: liveTail ? 5000 : false @@ -64,16 +64,15 @@ export function ConfigDetailsChangesPage() { if (!pollData?.changes?.length) return; const incoming = pollData.changes; - const newest = getNewestInsertedAt(incoming); + const newest = getNewestCreatedAt(incoming); if (newest) { setTailCursor((prev) => (!prev || newest > prev ? newest : prev)); } setTailedChanges((prev) => { - const existingIds = new Set(prev.map((c) => c.id)); - const deduped = incoming.filter((c) => !existingIds.has(c.id)); - if (deduped.length === 0) return prev; - return [...deduped, ...prev]; + const incomingIds = new Set(incoming.map((c) => c.id)); + const filtered = prev.filter((c) => !incomingIds.has(c.id)); + return [...incoming, ...filtered]; }); }, [pollData]); @@ -95,11 +94,15 @@ export function ConfigDetailsChangesPage() { } })); + const tailedIds = new Set(tailedWithConfig.map((c) => c.id)); + const baseWithoutTailed = baseChanges.filter((c) => !tailedIds.has(c.id)); const baseIds = new Set(baseChanges.map((c) => c.id)); - const uniqueTailed = tailedWithConfig.filter((c) => !baseIds.has(c.id)); - const changes = [...uniqueTailed, ...baseChanges]; + const newTailedCount = tailedWithConfig.filter( + (c) => !baseIds.has(c.id) + ).length; + const changes = [...tailedWithConfig, ...baseWithoutTailed]; - const totalChanges = (data?.total ?? 0) + uniqueTailed.length; + const totalChanges = (data?.total ?? 0) + newTailedCount; const totalChangesPages = Math.ceil(totalChanges / parseInt(pageSize)); if (error) { From 1f07c39a47c893363dd661d036c5717aedc20a70 Mon Sep 17 00:00:00 2001 From: Yash Mehrotra Date: Sat, 11 Apr 2026 16:23:26 +0530 Subject: [PATCH 4/6] chore: fix build error --- src/pages/config/ConfigChangesPage.tsx | 15 ++++++++------- .../config/details/ConfigDetailsChangesPage.tsx | 15 ++++++++------- 2 files changed, 16 insertions(+), 14 deletions(-) diff --git a/src/pages/config/ConfigChangesPage.tsx b/src/pages/config/ConfigChangesPage.tsx index ecf0efba0..1a7230ed8 100644 --- a/src/pages/config/ConfigChangesPage.tsx +++ b/src/pages/config/ConfigChangesPage.tsx @@ -19,13 +19,14 @@ import { useEffect, useState } from "react"; import { useSearchParams } from "react-router-dom"; function getNewestCreatedAt(changes: ConfigChange[]): string | undefined { - return changes.reduce( - (latest, c) => - c.created_at && (!latest || c.created_at > latest) - ? c.created_at - : latest, - undefined as string | undefined - ); + let latest: string | undefined; + for (const c of changes) { + const ts = typeof c.created_at === "string" ? c.created_at : undefined; + if (ts && (!latest || ts > latest)) { + latest = ts; + } + } + return latest; } export function ConfigChangesPage() { diff --git a/src/pages/config/details/ConfigDetailsChangesPage.tsx b/src/pages/config/details/ConfigDetailsChangesPage.tsx index 306110ed6..1e25dba17 100644 --- a/src/pages/config/details/ConfigDetailsChangesPage.tsx +++ b/src/pages/config/details/ConfigDetailsChangesPage.tsx @@ -9,13 +9,14 @@ import { useEffect, useState } from "react"; import { useParams, useSearchParams } from "react-router-dom"; function getNewestCreatedAt(changes: ConfigChange[]): string | undefined { - return changes.reduce( - (latest, c) => - c.created_at && (!latest || c.created_at > latest) - ? c.created_at - : latest, - undefined as string | undefined - ); + let latest: string | undefined; + for (const c of changes) { + const ts = typeof c.created_at === "string" ? c.created_at : undefined; + if (ts && (!latest || ts > latest)) { + latest = ts; + } + } + return latest; } export function ConfigDetailsChangesPage() { From 5f6b33caeb10a7e3f4f563a1bec044ea9e044051 Mon Sep 17 00:00:00 2001 From: Yash Mehrotra Date: Tue, 14 Apr 2026 09:37:09 +0530 Subject: [PATCH 5/6] chore: use from_inserted_at for live mode --- src/api/query-hooks/useConfigChangesHooks.ts | 18 ++++++++++-------- src/api/services/configs.ts | 5 +++++ src/pages/config/ConfigChangesPage.tsx | 10 +++++----- .../details/ConfigDetailsChangesPage.tsx | 10 +++++----- 4 files changed, 25 insertions(+), 18 deletions(-) diff --git a/src/api/query-hooks/useConfigChangesHooks.ts b/src/api/query-hooks/useConfigChangesHooks.ts index 879d5eb76..c434775da 100644 --- a/src/api/query-hooks/useConfigChangesHooks.ts +++ b/src/api/query-hooks/useConfigChangesHooks.ts @@ -39,11 +39,11 @@ function useConfigChangesTagsFilter(paramPrefix?: string) { export function useGetAllConfigsChangesQuery( { paramPrefix, - cursor, + from_inserted_at, ...queryOptions }: UseQueryOptions & { paramPrefix?: string; - cursor?: string; + from_inserted_at?: string; } = { enabled: true, keepPreviousData: true @@ -75,8 +75,9 @@ export function useGetAllConfigsChangesQuery( include_deleted_configs: showChangesFromDeletedConfigs, changeType, severity, - from: cursor ?? from, - to: cursor ? undefined : to, + from: from_inserted_at ? undefined : from, + to: from_inserted_at ? undefined : to, + from_inserted_at, configTypes, configType, sortBy: sortBy[0]?.id, @@ -96,10 +97,10 @@ export function useGetAllConfigsChangesQuery( export function useGetConfigChangesByIDQuery( { - cursor, + from_inserted_at, ...queryOptions }: UseQueryOptions & { - cursor?: string; + from_inserted_at?: string; } = { enabled: true, keepPreviousData: true @@ -148,8 +149,9 @@ export function useGetConfigChangesByIDQuery( include_deleted_configs: showChangesFromDeletedConfigs, changeType: change_type, severity, - from: cursor ?? from, - to: cursor ? undefined : to, + from: from_inserted_at ? undefined : from, + to: from_inserted_at ? undefined : to, + from_inserted_at, configTypes, sortBy: sortBy[0]?.id, sortOrder: sortBy[0]?.desc ? "desc" : "asc", diff --git a/src/api/services/configs.ts b/src/api/services/configs.ts index 98d0fafab..8648c265f 100644 --- a/src/api/services/configs.ts +++ b/src/api/services/configs.ts @@ -230,6 +230,7 @@ export type GetConfigsRelatedChangesParams = { severity?: string; from?: string; to?: string; + from_inserted_at?: string; configTypes?: string; configType?: string; pageSize?: number | string; @@ -252,6 +253,7 @@ export async function getConfigsChanges({ severity, from, to, + from_inserted_at, configTypes, configType, pageIndex, @@ -302,6 +304,9 @@ export async function getConfigsChanges({ if (to) { requestData.set("to", to); } + if (from_inserted_at) { + requestData.set("from_inserted_at", from_inserted_at); + } if (severity) { requestData.set("severity", severity); } diff --git a/src/pages/config/ConfigChangesPage.tsx b/src/pages/config/ConfigChangesPage.tsx index 1a7230ed8..09613ab6f 100644 --- a/src/pages/config/ConfigChangesPage.tsx +++ b/src/pages/config/ConfigChangesPage.tsx @@ -18,10 +18,10 @@ import { useAtom } from "jotai"; import { useEffect, useState } from "react"; import { useSearchParams } from "react-router-dom"; -function getNewestCreatedAt(changes: ConfigChange[]): string | undefined { +function getNewestInsertedAt(changes: ConfigChange[]): string | undefined { let latest: string | undefined; for (const c of changes) { - const ts = typeof c.created_at === "string" ? c.created_at : undefined; + const ts = typeof c.inserted_at === "string" ? c.inserted_at : undefined; if (ts && (!latest || ts > latest)) { latest = ts; } @@ -57,7 +57,7 @@ export function ConfigChangesPage() { // Initialize cursor from base data when live tail is turned on useEffect(() => { if (liveTail && data?.changes?.length && !tailCursor) { - setTailCursor(getNewestCreatedAt(data.changes)); + setTailCursor(getNewestInsertedAt(data.changes)); } }, [liveTail, data, tailCursor]); @@ -70,7 +70,7 @@ export function ConfigChangesPage() { }, [liveTail]); const { data: pollData } = useGetAllConfigsChangesQuery({ - cursor: tailCursor, + from_inserted_at: tailCursor, keepPreviousData: false, enabled: liveTail && !!tailCursor, refetchInterval: liveTail ? 5000 : false @@ -81,7 +81,7 @@ export function ConfigChangesPage() { if (!pollData?.changes?.length) return; const incoming = pollData.changes; - const newest = getNewestCreatedAt(incoming); + const newest = getNewestInsertedAt(incoming); if (newest) { setTailCursor((prev) => (!prev || newest > prev ? newest : prev)); } diff --git a/src/pages/config/details/ConfigDetailsChangesPage.tsx b/src/pages/config/details/ConfigDetailsChangesPage.tsx index 1e25dba17..0cd74f44a 100644 --- a/src/pages/config/details/ConfigDetailsChangesPage.tsx +++ b/src/pages/config/details/ConfigDetailsChangesPage.tsx @@ -8,10 +8,10 @@ import { Toggle } from "@flanksource-ui/ui/FormControls/Toggle"; import { useEffect, useState } from "react"; import { useParams, useSearchParams } from "react-router-dom"; -function getNewestCreatedAt(changes: ConfigChange[]): string | undefined { +function getNewestInsertedAt(changes: ConfigChange[]): string | undefined { let latest: string | undefined; for (const c of changes) { - const ts = typeof c.created_at === "string" ? c.created_at : undefined; + const ts = typeof c.inserted_at === "string" ? c.inserted_at : undefined; if (ts && (!latest || ts > latest)) { latest = ts; } @@ -41,7 +41,7 @@ export function ConfigDetailsChangesPage() { // Initialize cursor from base data when live tail is turned on useEffect(() => { if (liveTail && data?.changes?.length && !tailCursor) { - setTailCursor(getNewestCreatedAt(data.changes)); + setTailCursor(getNewestInsertedAt(data.changes)); } }, [liveTail, data, tailCursor]); @@ -54,7 +54,7 @@ export function ConfigDetailsChangesPage() { }, [liveTail]); const { data: pollData } = useGetConfigChangesByIDQuery({ - cursor: tailCursor, + from_inserted_at: tailCursor, keepPreviousData: false, enabled: liveTail && !!id && !!tailCursor, refetchInterval: liveTail ? 5000 : false @@ -65,7 +65,7 @@ export function ConfigDetailsChangesPage() { if (!pollData?.changes?.length) return; const incoming = pollData.changes; - const newest = getNewestCreatedAt(incoming); + const newest = getNewestInsertedAt(incoming); if (newest) { setTailCursor((prev) => (!prev || newest > prev ? newest : prev)); } From 0a82fdfc2cb89039913bd2885af38b0ec19026ad Mon Sep 17 00:00:00 2001 From: Yash Mehrotra Date: Tue, 14 Apr 2026 11:47:13 +0530 Subject: [PATCH 6/6] chore: refetch when live mode is turned off --- src/pages/config/ConfigChangesPage.tsx | 3 ++- src/pages/config/details/ConfigDetailsChangesPage.tsx | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/src/pages/config/ConfigChangesPage.tsx b/src/pages/config/ConfigChangesPage.tsx index 09613ab6f..32ac2fd27 100644 --- a/src/pages/config/ConfigChangesPage.tsx +++ b/src/pages/config/ConfigChangesPage.tsx @@ -66,8 +66,9 @@ export function ConfigChangesPage() { if (!liveTail) { setTailedChanges([]); setTailCursor(undefined); + refetch(); } - }, [liveTail]); + }, [liveTail, refetch]); const { data: pollData } = useGetAllConfigsChangesQuery({ from_inserted_at: tailCursor, diff --git a/src/pages/config/details/ConfigDetailsChangesPage.tsx b/src/pages/config/details/ConfigDetailsChangesPage.tsx index 0cd74f44a..28aef9e85 100644 --- a/src/pages/config/details/ConfigDetailsChangesPage.tsx +++ b/src/pages/config/details/ConfigDetailsChangesPage.tsx @@ -50,8 +50,9 @@ export function ConfigDetailsChangesPage() { if (!liveTail) { setTailedChanges([]); setTailCursor(undefined); + refetch(); } - }, [liveTail]); + }, [liveTail, refetch]); const { data: pollData } = useGetConfigChangesByIDQuery({ from_inserted_at: tailCursor,