From 1a05a988a90d25454b1fa69a0e73142a975bf853 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 27 Apr 2026 14:09:52 +0000 Subject: [PATCH 01/19] Bump @eslint/js from 9.39.4 to 10.0.1 Bumps [@eslint/js](https://github.com/eslint/eslint/tree/HEAD/packages/js) from 9.39.4 to 10.0.1. - [Release notes](https://github.com/eslint/eslint/releases) - [Commits](https://github.com/eslint/eslint/commits/v10.0.1/packages/js) --- updated-dependencies: - dependency-name: "@eslint/js" dependency-version: 10.0.1 dependency-type: direct:development update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] --- package-lock.json | 18 +++++++++++++----- package.json | 2 +- 2 files changed, 14 insertions(+), 6 deletions(-) diff --git a/package-lock.json b/package-lock.json index c307454..144e636 100644 --- a/package-lock.json +++ b/package-lock.json @@ -82,7 +82,7 @@ "zustand": "^5.0.12" }, "devDependencies": { - "@eslint/js": "^9.39.1", + "@eslint/js": "^10.0.1", "@types/node": "^25.0.3", "@types/react": "^19.2.14", "@types/react-dom": "^19.2.2", @@ -452,16 +452,24 @@ } }, "node_modules/@eslint/js": { - "version": "9.39.4", - "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.39.4.tgz", - "integrity": "sha512-nE7DEIchvtiFTwBw4Lfbu59PG+kCofhjsKaCWzxTpt4lfRjRMqG6uMBzKXuEcyXhOHoUp9riAm7/aWYGhXZ9cw==", + "version": "10.0.1", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-10.0.1.tgz", + "integrity": "sha512-zeR9k5pd4gxjZ0abRoIaxdc7I3nDktoXZk2qOv9gCNWx3mVwEn32VRhyLaRsDiJjTs0xq/T8mfPtyuXu7GWBcA==", "dev": true, "license": "MIT", "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + "node": "^20.19.0 || ^22.13.0 || >=24" }, "funding": { "url": "https://eslint.org/donate" + }, + "peerDependencies": { + "eslint": "^10.0.0" + }, + "peerDependenciesMeta": { + "eslint": { + "optional": true + } } }, "node_modules/@eslint/object-schema": { diff --git a/package.json b/package.json index 9628b37..413b5a0 100644 --- a/package.json +++ b/package.json @@ -84,7 +84,7 @@ "zustand": "^5.0.12" }, "devDependencies": { - "@eslint/js": "^9.39.1", + "@eslint/js": "^10.0.1", "@types/node": "^25.0.3", "@types/react": "^19.2.14", "@types/react-dom": "^19.2.2", From 0db532ff4b5a5b38e6f5c3db9ea5bcadcefd6cce Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 4 May 2026 14:50:47 +0000 Subject: [PATCH 02/19] Bump lucide-react from 0.563.0 to 1.14.0 Bumps [lucide-react](https://github.com/lucide-icons/lucide/tree/HEAD/packages/lucide-react) from 0.563.0 to 1.14.0. - [Release notes](https://github.com/lucide-icons/lucide/releases) - [Commits](https://github.com/lucide-icons/lucide/commits/1.14.0/packages/lucide-react) --- updated-dependencies: - dependency-name: lucide-react dependency-version: 1.14.0 dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] --- package-lock.json | 8 ++++---- package.json | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/package-lock.json b/package-lock.json index 144e636..ce22f79 100644 --- a/package-lock.json +++ b/package-lock.json @@ -56,7 +56,7 @@ "i18next": "^26.0.4", "i18next-browser-languagedetector": "^8.2.1", "input-otp": "^1.4.2", - "lucide-react": "^0.563.0", + "lucide-react": "^1.14.0", "motion": "^12.34.0", "next-themes": "^0.4.6", "radix-ui": "^1.4.3", @@ -6773,9 +6773,9 @@ } }, "node_modules/lucide-react": { - "version": "0.563.0", - "resolved": "https://registry.npmjs.org/lucide-react/-/lucide-react-0.563.0.tgz", - "integrity": "sha512-8dXPB2GI4dI8jV4MgUDGBeLdGk8ekfqVZ0BdLcrRzocGgG75ltNEmWS+gE7uokKF/0oSUuczNDT+g9hFJ23FkA==", + "version": "1.14.0", + "resolved": "https://registry.npmjs.org/lucide-react/-/lucide-react-1.14.0.tgz", + "integrity": "sha512-+1mdWcfSJVUsaTIjN9zoezmUhfXo5l0vP7ekBMPo3jcS/aIkxHnXqAPsByszMZx/Y8oQBRJxJx5xg+RH3urzxA==", "license": "ISC", "peerDependencies": { "react": "^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0" diff --git a/package.json b/package.json index 413b5a0..b526437 100644 --- a/package.json +++ b/package.json @@ -58,7 +58,7 @@ "i18next": "^26.0.4", "i18next-browser-languagedetector": "^8.2.1", "input-otp": "^1.4.2", - "lucide-react": "^0.563.0", + "lucide-react": "^1.14.0", "motion": "^12.34.0", "next-themes": "^0.4.6", "radix-ui": "^1.4.3", From 29ccbf40c060a693b50d863aea76825964950616 Mon Sep 17 00:00:00 2001 From: mcz Date: Tue, 5 May 2026 10:45:52 +0200 Subject: [PATCH 03/19] Virtualize overview news list to fix lag with thousands of entries --- package-lock.json | 28 ++++++++++++ package.json | 1 + src/pages/panel/overview/news.tsx | 75 ++++++++++++++++++++----------- 3 files changed, 79 insertions(+), 25 deletions(-) diff --git a/package-lock.json b/package-lock.json index 144e636..f61a7ad 100644 --- a/package-lock.json +++ b/package-lock.json @@ -44,6 +44,7 @@ "@tailwindcss/vite": "^4.1.17", "@tanstack/react-query": "^5.90.21", "@tanstack/react-table": "^8.21.3", + "@tanstack/react-virtual": "^3.13.24", "ace-builds": "^1.43.6", "ansi_up": "^6.0.6", "axios": "^1.15.0", @@ -3643,6 +3644,23 @@ "react-dom": ">=16.8" } }, + "node_modules/@tanstack/react-virtual": { + "version": "3.13.24", + "resolved": "https://registry.npmjs.org/@tanstack/react-virtual/-/react-virtual-3.13.24.tgz", + "integrity": "sha512-aIJvz5OSkhNIhZIpYivrxrPTKYsjW9Uzy+sP/mx0S3sev2HyvPb7xmjbYvokzEpfgYHy/HjzJ2zFAETuUfgCpg==", + "license": "MIT", + "dependencies": { + "@tanstack/virtual-core": "3.14.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/tannerlinsley" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", + "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" + } + }, "node_modules/@tanstack/table-core": { "version": "8.21.3", "resolved": "https://registry.npmjs.org/@tanstack/table-core/-/table-core-8.21.3.tgz", @@ -3656,6 +3674,16 @@ "url": "https://github.com/sponsors/tannerlinsley" } }, + "node_modules/@tanstack/virtual-core": { + "version": "3.14.0", + "resolved": "https://registry.npmjs.org/@tanstack/virtual-core/-/virtual-core-3.14.0.tgz", + "integrity": "sha512-JLANqGy/D6k4Ujmh8Tr25lGimuOXNiaVyXaCAZS0W+1390sADdGnyUdSWNIfd49gebtIxGMij4IktRVzrdr12Q==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/tannerlinsley" + } + }, "node_modules/@tybys/wasm-util": { "version": "0.10.1", "resolved": "https://registry.npmjs.org/@tybys/wasm-util/-/wasm-util-0.10.1.tgz", diff --git a/package.json b/package.json index 413b5a0..91d4fed 100644 --- a/package.json +++ b/package.json @@ -46,6 +46,7 @@ "@tailwindcss/vite": "^4.1.17", "@tanstack/react-query": "^5.90.21", "@tanstack/react-table": "^8.21.3", + "@tanstack/react-virtual": "^3.13.24", "ace-builds": "^1.43.6", "ansi_up": "^6.0.6", "axios": "^1.15.0", diff --git a/src/pages/panel/overview/news.tsx b/src/pages/panel/overview/news.tsx index df5cb23..79ba836 100644 --- a/src/pages/panel/overview/news.tsx +++ b/src/pages/panel/overview/news.tsx @@ -1,7 +1,8 @@ "use client" -import {startTransition, useEffect, useState} from "react"; +import {startTransition, useEffect, useRef, useState} from "react"; import {useTranslation} from "react-i18next"; +import {useVirtualizer} from "@tanstack/react-virtual"; import { Card, CardContent, @@ -36,6 +37,7 @@ const getNotificationIconAndColor = (type: Notification['type']) => { export default function OverviewNewsBox() { const {t} = useTranslation(); const [notifications, setNotifications] = useState([]); + const scrollRef = useRef(null); useEffect(() => { const fetchNotifications = async () => { @@ -52,6 +54,13 @@ export default function OverviewNewsBox() { return () => clearInterval(interval); }, []); + const virtualizer = useVirtualizer({ + count: notifications.length, + getScrollElement: () => scrollRef.current, + estimateSize: () => 56, + overscan: 5, + }); + return ( @@ -63,31 +72,47 @@ export default function OverviewNewsBox() { {notifications.length > 0 ? (
+
+
+ {virtualizer.getVirtualItems().map(virtualItem => { + const notification = notifications[virtualItem.index]; + const { icon, color } = getNotificationIconAndColor(notification.type); + const age = Date.now() - notification.created_at; -
- {notifications.map(notification => { - const { icon, color } = getNotificationIconAndColor(notification.type); - const age = Date.now() - notification.created_at; - - return ( -
-
- {icon} -
-
-

- {notification.title} -

-

- {notification.description} -

-
-
- + return ( +
+
+
+ {icon} +
+
+

+ {notification.title} +

+

+ {notification.description} +

+
+
+ +
+
-
- ); - })} + ); + })} +
Date: Tue, 5 May 2026 11:59:31 +0200 Subject: [PATCH 05/19] Guard NotificationSheet against undefined notifications response --- src/layouts/panel/header/notification/sheet.tsx | 2 +- src/layouts/panel/header/resource-add-dialog.tsx | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/src/layouts/panel/header/notification/sheet.tsx b/src/layouts/panel/header/notification/sheet.tsx index 711b3cf..7952743 100644 --- a/src/layouts/panel/header/notification/sheet.tsx +++ b/src/layouts/panel/header/notification/sheet.tsx @@ -27,7 +27,7 @@ export default function NotificationSheet({cluster}: {cluster: boolean}) { : NotificationService.listAll(); result.then(({ notifications }) => { - setNotifications(notifications); + setNotifications(notifications ?? []); }); }; diff --git a/src/layouts/panel/header/resource-add-dialog.tsx b/src/layouts/panel/header/resource-add-dialog.tsx index 7395296..7400d9f 100644 --- a/src/layouts/panel/header/resource-add-dialog.tsx +++ b/src/layouts/panel/header/resource-add-dialog.tsx @@ -11,7 +11,8 @@ import {Spinner} from "@/components/ui/spinner.tsx"; import * as ReactAce from "react-ace"; import "ace-builds/src-noconflict/mode-yaml"; -const AceEditor = ((ReactAce as unknown as {default?: unknown}).default ?? ReactAce) as typeof import("react-ace").default; +const ReactAceModule = ReactAce as unknown as { default?: { default?: unknown } & unknown }; +const AceEditor = (ReactAceModule.default?.default ?? ReactAceModule.default ?? ReactAce) as typeof import("react-ace").default; import "ace-builds/src-noconflict/theme-tomorrow"; import "ace-builds/src-noconflict/theme-tomorrow_night"; import {useTheme} from "next-themes"; From d07d5a59701a85818d303b1a03f507ee8eee5ff6 Mon Sep 17 00:00:00 2001 From: mcz Date: Tue, 5 May 2026 12:21:13 +0200 Subject: [PATCH 06/19] Virtualize notification sheet to fix lag with thousands of entries --- .../panel/header/notification/sheet.tsx | 70 +++++++++++++------ 1 file changed, 47 insertions(+), 23 deletions(-) diff --git a/src/layouts/panel/header/notification/sheet.tsx b/src/layouts/panel/header/notification/sheet.tsx index 7952743..63c2b03 100644 --- a/src/layouts/panel/header/notification/sheet.tsx +++ b/src/layouts/panel/header/notification/sheet.tsx @@ -10,6 +10,7 @@ import {AvatarFallback} from "@radix-ui/react-avatar"; import {Bell} from "lucide-react"; import {Badge} from "@/components/ui/badge.tsx"; import * as React from "react"; +import {useVirtualizer} from "@tanstack/react-virtual"; import NotificationService, { type Notification } from "@/api/services/notification-service.ts"; @@ -19,6 +20,7 @@ import NotificationAlert from "@/layouts/panel/header/notification/alert.tsx"; export default function NotificationSheet({cluster}: {cluster: boolean}) { const {t} = useTranslation(); const [notifications, setNotifications] = React.useState([]); + const scrollRef = React.useRef(null); React.useEffect(() => { const fetchNotifications = () => { @@ -36,6 +38,21 @@ export default function NotificationSheet({cluster}: {cluster: boolean}) { return () => clearInterval(interval); }, [cluster]); + const virtualizer = useVirtualizer({ + count: notifications.length, + getScrollElement: () => scrollRef.current, + estimateSize: () => 120, + overscan: 5, + }); + + const handleSeen = React.useCallback((id: string) => { + setNotifications(prev => + prev.map(n => + n.id === id ? { ...n, state: "SEEN" } : n + ) + ); + }, []); + const unseenCount = notifications.filter(n => n.state !== "SEEN").length; return ( @@ -62,29 +79,36 @@ export default function NotificationSheet({cluster}: {cluster: boolean}) { {t("panel.header.notifications.title")} -
-
    - {notifications.length === 0 && ( -

    - {t("panel.header.notifications.empty")} -

    - )} - - {notifications.map(notification => ( - - setNotifications(prev => - prev.map(n => - n.id === id ? { ...n, state: "SEEN" } : n - ) - ) - } - /> - ))} -
+
+ {notifications.length === 0 ? ( +

+ {t("panel.header.notifications.empty")} +

+ ) : ( +
+ {virtualizer.getVirtualItems().map(virtualItem => { + const notification = notifications[virtualItem.index]; + return ( +
+ +
+ ); + })} +
+ )}
void; + clearAuthenticationToken: () => void; + setInformation: (info: UserInformation) => void; + clearInformation: () => void; + }; +}; +type RefreshStore = { + refresh_token?: string; actions: { - setUserToken: (token: UserToken) => void; - clearUserToken: () => void; - setUserInformation: (token: UserInformation) => void; - clearUserInformation: () => void; + setRefreshToken: (token?: string) => void; + clearRefreshToken: () => void; }; }; -const useUserStore = create()( +const useAuthStore = create()( persist( (set) => ({ - token: {}, + authentication_token: undefined, information: {}, - actions: { - setUserToken: (token) => { - set({ token }); - }, - clearUserToken() { - set({ token: {} }); - }, - - setUserInformation: (info) => { - set({ information: info }); - }, - clearUserInformation() { - set({ information: {} }); - }, + setAuthenticationToken: (token) => set({authentication_token: token}), + clearAuthenticationToken: () => set({authentication_token: undefined}), + setInformation: (info) => set({information: info}), + clearInformation: () => set({information: {}}), }, }), { name: "user", - storage: createJSONStorage(() => cookiesStorage), - partialize: (state) => ({ - token: state.token, - information: state.information, + storage: createJSONStorage(() => cookieStorage(AUTH_COOKIE_EXPIRY)), + partialize: (s) => ({ + authentication_token: s.authentication_token, + information: s.information, }), }, ), ); -export const useUserToken = () => useUserStore((state) => state.token); -export const useUserInformation = () => - useUserStore((state) => state.information); -export const useUserActions = () => useUserStore((state) => state.actions); +const useRefreshStore = create()( + persist( + (set) => ({ + refresh_token: undefined, + actions: { + setRefreshToken: (token) => set({refresh_token: token}), + clearRefreshToken: () => set({refresh_token: undefined}), + }, + }), + { + name: "user-refresh", + storage: createJSONStorage(() => cookieStorage(REFRESH_COOKIE_EXPIRY)), + partialize: (s) => ({refresh_token: s.refresh_token}), + }, + ), +); + +export const useUserToken = (): UserToken => { + const authentication_token = useAuthStore((s) => s.authentication_token); + const refresh_token = useRefreshStore((s) => s.refresh_token); + return {authentication_token, refresh_token}; +}; + +export const useUserInformation = () => useAuthStore((s) => s.information); + +export const useUserActions = () => { + const authActions = useAuthStore((s) => s.actions); + const refreshActions = useRefreshStore((s) => s.actions); + return useMemo( + () => ({ + setUserToken: (token: UserToken) => { + authActions.setAuthenticationToken(token.authentication_token); + refreshActions.setRefreshToken(token.refresh_token); + }, + clearUserToken: () => { + authActions.clearAuthenticationToken(); + refreshActions.clearRefreshToken(); + }, + setUserInformation: authActions.setInformation, + clearUserInformation: authActions.clearInformation, + }), + [authActions, refreshActions], + ); +}; + +export const useHasUserHydrated = () => { + const auth = useAuthStore.persist.hasHydrated(); + const refresh = useRefreshStore.persist.hasHydrated(); + return auth && refresh; +}; + +const userStore = { + getState: () => { + const auth = useAuthStore.getState(); + const refresh = useRefreshStore.getState(); + return { + token: { + authentication_token: auth.authentication_token, + refresh_token: refresh.refresh_token, + } as UserToken, + information: auth.information, + actions: { + setUserToken: (token: UserToken) => { + auth.actions.setAuthenticationToken(token.authentication_token); + refresh.actions.setRefreshToken(token.refresh_token); + }, + clearUserToken: () => { + auth.actions.clearAuthenticationToken(); + refresh.actions.clearRefreshToken(); + }, + }, + }; + }, +}; export const useLogin = () => { const { t } = useTranslation(); @@ -115,4 +186,4 @@ export const useLogout = () => { }; }; -export default useUserStore; \ No newline at end of file +export default userStore; From 89008f3947f679625700e3843c71fea3252a4bd1 Mon Sep 17 00:00:00 2001 From: mcz Date: Tue, 5 May 2026 14:59:21 +0200 Subject: [PATCH 11/19] Wait for both auth and refresh stores to hydrate before guarding --- src/routes/components/login-auth-guard.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/routes/components/login-auth-guard.tsx b/src/routes/components/login-auth-guard.tsx index 9968ec6..c6dc3d5 100644 --- a/src/routes/components/login-auth-guard.tsx +++ b/src/routes/components/login-auth-guard.tsx @@ -1,6 +1,6 @@ import {useEffect} from "react"; import {useRouter} from "../hooks"; -import useUserStore, {useUserToken} from "@/store/user-store"; +import {useHasUserHydrated, useUserToken} from "@/store/user-store"; type Props = { children: React.ReactNode; @@ -9,7 +9,7 @@ export default function LoginAuthGuard({children}: Props) { const router = useRouter(); const {authentication_token} = useUserToken(); - const hasHydrated = useUserStore.persist.hasHydrated(); + const hasHydrated = useHasUserHydrated(); useEffect(() => { if (hasHydrated && !authentication_token) { From 87ebf2eb45f472a906e8340666d9405568c4efde Mon Sep 17 00:00:00 2001 From: mcz Date: Tue, 5 May 2026 15:14:10 +0200 Subject: [PATCH 12/19] Replace bare locale calls with the nested keys --- src/pages/panel/overview/workload.tsx | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/pages/panel/overview/workload.tsx b/src/pages/panel/overview/workload.tsx index 89f3469..c55e114 100644 --- a/src/pages/panel/overview/workload.tsx +++ b/src/pages/panel/overview/workload.tsx @@ -155,21 +155,21 @@ export default function OverviewWorkloadBox({nodes}: { nodes: Node[] }) {
Date: Tue, 5 May 2026 19:01:16 +0200 Subject: [PATCH 13/19] Limit initial pod log fetch to last hour --- src/pages/panel/pod/log/index.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pages/panel/pod/log/index.tsx b/src/pages/panel/pod/log/index.tsx index 4d4bdd3..47bf2d6 100644 --- a/src/pages/panel/pod/log/index.tsx +++ b/src/pages/panel/pod/log/index.tsx @@ -31,7 +31,7 @@ export default function PodLogsPage() { const logResponse: PodLogResponse = await podService.log({ namespace: pod.namespace, pod: pod.name, - since_seconds: firstRunRef.current ? -1 : 2, + since_seconds: firstRunRef.current ? 3600 : 2, }); if (!logResponse.success || !isMounted) { return; From d1a52aea03855203cd7b363b2699aa590828a0c5 Mon Sep 17 00:00:00 2001 From: mcz Date: Tue, 5 May 2026 19:43:45 +0200 Subject: [PATCH 14/19] Centralize frontend poll interval and bump from 3s to 5s --- src/hooks/use-cron-job.ts | 3 ++- src/hooks/use-daemon-set.ts | 3 ++- src/hooks/use-deployment.ts | 3 ++- src/hooks/use-node.ts | 3 ++- src/hooks/use-pod.ts | 3 ++- src/hooks/use-replica-set.ts | 3 ++- src/hooks/use-stateful-set.ts | 3 ++- src/layouts/panel/header/notification/sheet.tsx | 3 ++- src/lib/constants.ts | 1 + src/pages/panel/cluster/index.tsx | 3 ++- src/pages/panel/cron-jobs/index.tsx | 3 ++- src/pages/panel/daemon-sets/index.tsx | 3 ++- src/pages/panel/deployments/index.tsx | 3 ++- src/pages/panel/events/index.tsx | 3 ++- src/pages/panel/namespaces/index.tsx | 3 ++- src/pages/panel/nodes/index.tsx | 3 ++- src/pages/panel/overview/activity.tsx | 3 ++- src/pages/panel/overview/index.tsx | 3 ++- src/pages/panel/overview/news.tsx | 3 ++- src/pages/panel/pods/list.tsx | 3 ++- src/pages/panel/replica-sets/index.tsx | 3 ++- src/pages/panel/services/index.tsx | 3 ++- src/pages/panel/stateful-sets/index.tsx | 3 ++- 23 files changed, 45 insertions(+), 22 deletions(-) create mode 100644 src/lib/constants.ts diff --git a/src/hooks/use-cron-job.ts b/src/hooks/use-cron-job.ts index 1c37d0c..99ea219 100644 --- a/src/hooks/use-cron-job.ts +++ b/src/hooks/use-cron-job.ts @@ -1,3 +1,4 @@ +import {POLL_INTERVAL_MS} from "@/lib/constants.ts"; import {useSearchParams} from "react-router-dom"; import {useEffect, useState} from "react"; import cronJobService, {type CronJob} from "@/api/services/cron-job-service.ts"; @@ -26,7 +27,7 @@ export default function useCronJob() { } loadCronJob(); - const interval = window.setInterval(loadCronJob, 3000); + const interval = window.setInterval(loadCronJob, POLL_INTERVAL_MS); return () => { isMounted = false; clearInterval(interval); }; }, [searchParams]); diff --git a/src/hooks/use-daemon-set.ts b/src/hooks/use-daemon-set.ts index 329968c..7d30fee 100644 --- a/src/hooks/use-daemon-set.ts +++ b/src/hooks/use-daemon-set.ts @@ -1,3 +1,4 @@ +import {POLL_INTERVAL_MS} from "@/lib/constants.ts"; import {useSearchParams} from "react-router-dom"; import {useEffect, useState} from "react"; import daemonSetService, {type DaemonSet} from "@/api/services/daemon-set-service.ts"; @@ -26,7 +27,7 @@ export default function useDaemonSet() { } loadDaemonSet(); - const interval = window.setInterval(loadDaemonSet, 3000); + const interval = window.setInterval(loadDaemonSet, POLL_INTERVAL_MS); return () => { isMounted = false; clearInterval(interval); }; }, [searchParams]); diff --git a/src/hooks/use-deployment.ts b/src/hooks/use-deployment.ts index ccf5813..3feaaac 100644 --- a/src/hooks/use-deployment.ts +++ b/src/hooks/use-deployment.ts @@ -1,3 +1,4 @@ +import {POLL_INTERVAL_MS} from "@/lib/constants.ts"; import {useSearchParams} from "react-router-dom"; import {useEffect, useState} from "react"; import deploymentService, {type Deployment} from "@/api/services/deployment-service.ts"; @@ -26,7 +27,7 @@ export default function useDeployment() { } loadDeployment(); - const interval = window.setInterval(loadDeployment, 3000); + const interval = window.setInterval(loadDeployment, POLL_INTERVAL_MS); return () => { isMounted = false; clearInterval(interval); }; }, [searchParams]); diff --git a/src/hooks/use-node.ts b/src/hooks/use-node.ts index dc13521..acd27bd 100644 --- a/src/hooks/use-node.ts +++ b/src/hooks/use-node.ts @@ -1,3 +1,4 @@ +import {POLL_INTERVAL_MS} from "@/lib/constants.ts"; import {useSearchParams} from "react-router-dom"; import {useEffect, useState} from "react"; import nodeService, {type Node} from "@/api/services/node-service.ts"; @@ -25,7 +26,7 @@ export default function useNode() { } loadNode(); - const interval = window.setInterval(loadNode, 3000); + const interval = window.setInterval(loadNode, POLL_INTERVAL_MS); return () => { isMounted = false; clearInterval(interval); }; }, [searchParams]); diff --git a/src/hooks/use-pod.ts b/src/hooks/use-pod.ts index b68518a..164599c 100644 --- a/src/hooks/use-pod.ts +++ b/src/hooks/use-pod.ts @@ -1,3 +1,4 @@ +import {POLL_INTERVAL_MS} from "@/lib/constants.ts"; import {useSearchParams} from "react-router-dom"; import {useEffect, useState} from "react"; import podService, {type Pod} from "@/api/services/pod-service.ts"; @@ -26,7 +27,7 @@ export default function usePod() { } loadPod(); - const interval = window.setInterval(loadPod, 3000); + const interval = window.setInterval(loadPod, POLL_INTERVAL_MS); return () => { isMounted = false; clearInterval(interval); }; }, [searchParams]); diff --git a/src/hooks/use-replica-set.ts b/src/hooks/use-replica-set.ts index 9e51cf8..d2c4022 100644 --- a/src/hooks/use-replica-set.ts +++ b/src/hooks/use-replica-set.ts @@ -1,3 +1,4 @@ +import {POLL_INTERVAL_MS} from "@/lib/constants.ts"; import {useSearchParams} from "react-router-dom"; import {useEffect, useState} from "react"; import replicaSetService, {type ReplicaSet} from "@/api/services/replica-set-service.ts"; @@ -26,7 +27,7 @@ export default function useReplicaSet() { } loadReplicaSet(); - const interval = window.setInterval(loadReplicaSet, 3000); + const interval = window.setInterval(loadReplicaSet, POLL_INTERVAL_MS); return () => { isMounted = false; clearInterval(interval); }; }, [searchParams]); diff --git a/src/hooks/use-stateful-set.ts b/src/hooks/use-stateful-set.ts index 98cdf54..85c7f65 100644 --- a/src/hooks/use-stateful-set.ts +++ b/src/hooks/use-stateful-set.ts @@ -1,3 +1,4 @@ +import {POLL_INTERVAL_MS} from "@/lib/constants.ts"; import {useSearchParams} from "react-router-dom"; import {useEffect, useState} from "react"; import statefulSetService, {type StatefulSet} from "@/api/services/stateful-set-service.ts"; @@ -26,7 +27,7 @@ export default function useStatefulSet() { } loadStatefulSet(); - const interval = window.setInterval(loadStatefulSet, 3000); + const interval = window.setInterval(loadStatefulSet, POLL_INTERVAL_MS); return () => { isMounted = false; clearInterval(interval); }; }, [searchParams]); diff --git a/src/layouts/panel/header/notification/sheet.tsx b/src/layouts/panel/header/notification/sheet.tsx index 63c2b03..25af910 100644 --- a/src/layouts/panel/header/notification/sheet.tsx +++ b/src/layouts/panel/header/notification/sheet.tsx @@ -1,3 +1,4 @@ +import {POLL_INTERVAL_MS} from "@/lib/constants.ts"; import { Sheet, SheetContent, @@ -34,7 +35,7 @@ export default function NotificationSheet({cluster}: {cluster: boolean}) { }; fetchNotifications(); - const interval = setInterval(fetchNotifications, 3000); + const interval = setInterval(fetchNotifications, POLL_INTERVAL_MS); return () => clearInterval(interval); }, [cluster]); diff --git a/src/lib/constants.ts b/src/lib/constants.ts new file mode 100644 index 0000000..ec18bcf --- /dev/null +++ b/src/lib/constants.ts @@ -0,0 +1 @@ +export const POLL_INTERVAL_MS = 5000; diff --git a/src/pages/panel/cluster/index.tsx b/src/pages/panel/cluster/index.tsx index fcee759..67fb664 100644 --- a/src/pages/panel/cluster/index.tsx +++ b/src/pages/panel/cluster/index.tsx @@ -1,3 +1,4 @@ +import {POLL_INTERVAL_MS} from "@/lib/constants.ts"; import {AppHeader} from "@/layouts/panel/header"; import {SidebarProvider} from "@/components/ui/sidebar.tsx"; import React, {useEffect, useState} from "react"; @@ -95,7 +96,7 @@ export default function ClusterPage() { }) ); setClusters(updatedClusters); - }, 3000); + }, POLL_INTERVAL_MS); return () => clearInterval(interval); }, [clusters]); diff --git a/src/pages/panel/cron-jobs/index.tsx b/src/pages/panel/cron-jobs/index.tsx index c7f9b70..ed781a8 100644 --- a/src/pages/panel/cron-jobs/index.tsx +++ b/src/pages/panel/cron-jobs/index.tsx @@ -1,3 +1,4 @@ +import {POLL_INTERVAL_MS} from "@/lib/constants.ts"; import {useEffect, useState} from "react"; import {DataTable} from "@/components/table"; import PanelPage from "@/layouts/panel"; @@ -88,7 +89,7 @@ export default function CronJobsPage() { loadCronJobs(); loadNamespaces(); - const interval = window.setInterval(loadCronJobs, 3000); + const interval = window.setInterval(loadCronJobs, POLL_INTERVAL_MS); return () => { clearInterval(interval); }; diff --git a/src/pages/panel/daemon-sets/index.tsx b/src/pages/panel/daemon-sets/index.tsx index 73fbe22..c497ad9 100644 --- a/src/pages/panel/daemon-sets/index.tsx +++ b/src/pages/panel/daemon-sets/index.tsx @@ -1,3 +1,4 @@ +import {POLL_INTERVAL_MS} from "@/lib/constants.ts"; import {useEffect, useState} from "react"; import {DataTable} from "@/components/table"; import PanelPage from "@/layouts/panel"; @@ -85,7 +86,7 @@ export default function DaemonSetsPage() { loadDaemonSets(); loadNamespaces(); - const interval = window.setInterval(loadDaemonSets, 3000); + const interval = window.setInterval(loadDaemonSets, POLL_INTERVAL_MS); return () => { clearInterval(interval); }; diff --git a/src/pages/panel/deployments/index.tsx b/src/pages/panel/deployments/index.tsx index 1f08031..eda9878 100644 --- a/src/pages/panel/deployments/index.tsx +++ b/src/pages/panel/deployments/index.tsx @@ -1,3 +1,4 @@ +import {POLL_INTERVAL_MS} from "@/lib/constants.ts"; import {useEffect, useState} from "react"; import {DataTable} from "@/components/table"; import PanelPage from "@/layouts/panel"; @@ -85,7 +86,7 @@ export default function DeploymentsPage() { loadDeployments(); loadNamespaces(); - const interval = window.setInterval(loadDeployments, 3000); + const interval = window.setInterval(loadDeployments, POLL_INTERVAL_MS); return () => { clearInterval(interval); }; diff --git a/src/pages/panel/events/index.tsx b/src/pages/panel/events/index.tsx index 656936b..49d7344 100644 --- a/src/pages/panel/events/index.tsx +++ b/src/pages/panel/events/index.tsx @@ -1,3 +1,4 @@ +import {POLL_INTERVAL_MS} from "@/lib/constants.ts"; import PanelPage from "@/layouts/panel" import {useEffect, useState} from "react"; import EventService, {type Event} from "@/api/services/event-service.ts"; @@ -107,7 +108,7 @@ export default function EventsPage() { } loadEvents(); - const interval = setInterval(loadEvents, 3000); + const interval = setInterval(loadEvents, POLL_INTERVAL_MS); return () => clearInterval(interval); }, [startDate, endDate, limit, selectedValues, search]); diff --git a/src/pages/panel/namespaces/index.tsx b/src/pages/panel/namespaces/index.tsx index d1899a0..b40e0eb 100644 --- a/src/pages/panel/namespaces/index.tsx +++ b/src/pages/panel/namespaces/index.tsx @@ -1,3 +1,4 @@ +import {POLL_INTERVAL_MS} from "@/lib/constants.ts"; import {useEffect, useState} from "react"; import {DataTable} from "@/components/table"; import PanelPage from "@/layouts/panel"; @@ -55,7 +56,7 @@ export default function NamespacesPage() { }; useEffect(() => { loadNamespaces(); - const interval = window.setInterval(loadNamespaces, 3000); + const interval = window.setInterval(loadNamespaces, POLL_INTERVAL_MS); return () => clearInterval(interval); }, []); if (!isLoading && namespaces.length === 0) { diff --git a/src/pages/panel/nodes/index.tsx b/src/pages/panel/nodes/index.tsx index b1d2937..52e0116 100644 --- a/src/pages/panel/nodes/index.tsx +++ b/src/pages/panel/nodes/index.tsx @@ -1,3 +1,4 @@ +import {POLL_INTERVAL_MS} from "@/lib/constants.ts"; import {useEffect, useState} from "react"; import {DataTable} from "@/components/table"; import PanelPage from "@/layouts/panel"; @@ -81,7 +82,7 @@ export default function NodesPage() { } } loadNodes(); - const interval = window.setInterval(loadNodes, 3000); + const interval = window.setInterval(loadNodes, POLL_INTERVAL_MS); return () => { clearInterval(interval); }; diff --git a/src/pages/panel/overview/activity.tsx b/src/pages/panel/overview/activity.tsx index 2dbea5b..d200420 100644 --- a/src/pages/panel/overview/activity.tsx +++ b/src/pages/panel/overview/activity.tsx @@ -1,5 +1,6 @@ "use client"; +import {POLL_INTERVAL_MS} from "@/lib/constants.ts"; import {startTransition, useEffect, useState} from "react"; import { useTranslation } from "react-i18next"; import { Bar, BarChart, CartesianGrid, Rectangle, XAxis } from "recharts"; @@ -142,7 +143,7 @@ export default function OverviewActivityBox() { loadEvents(); - const interval = setInterval(loadEvents, 3000); + const interval = setInterval(loadEvents, POLL_INTERVAL_MS); return () => clearInterval(interval); }, []); diff --git a/src/pages/panel/overview/index.tsx b/src/pages/panel/overview/index.tsx index f8e6c0c..ab3c56f 100644 --- a/src/pages/panel/overview/index.tsx +++ b/src/pages/panel/overview/index.tsx @@ -1,3 +1,4 @@ +import {POLL_INTERVAL_MS} from "@/lib/constants.ts"; import PanelPage from "@/layouts/panel" import OverviewStatusBox from "@/pages/panel/overview/status.tsx"; import OverviewWorkloadBox from "@/pages/panel/overview/workload.tsx"; @@ -21,7 +22,7 @@ export default function OverviewPage() { } } loadNodes(); - const interval = window.setInterval(loadNodes, 3000); + const interval = window.setInterval(loadNodes, POLL_INTERVAL_MS); return () => { clearInterval(interval); }; diff --git a/src/pages/panel/overview/news.tsx b/src/pages/panel/overview/news.tsx index 79ba836..9015cb5 100644 --- a/src/pages/panel/overview/news.tsx +++ b/src/pages/panel/overview/news.tsx @@ -1,5 +1,6 @@ "use client" +import {POLL_INTERVAL_MS} from "@/lib/constants.ts"; import {startTransition, useEffect, useRef, useState} from "react"; import {useTranslation} from "react-i18next"; import {useVirtualizer} from "@tanstack/react-virtual"; @@ -50,7 +51,7 @@ export default function OverviewNewsBox() { }; fetchNotifications(); - const interval = setInterval(fetchNotifications, 3000); + const interval = setInterval(fetchNotifications, POLL_INTERVAL_MS); return () => clearInterval(interval); }, []); diff --git a/src/pages/panel/pods/list.tsx b/src/pages/panel/pods/list.tsx index ba5d71e..1dc29fd 100644 --- a/src/pages/panel/pods/list.tsx +++ b/src/pages/panel/pods/list.tsx @@ -1,3 +1,4 @@ +import {POLL_INTERVAL_MS} from "@/lib/constants.ts"; import {Box, Trash2} from "lucide-react"; import {DataTable} from "@/components/table"; import {useEffect, useState} from "react"; @@ -67,7 +68,7 @@ export default function PodsList( loadPods(); loadNamespaces(); - const interval = window.setInterval(loadPods, 3000); + const interval = window.setInterval(loadPods, POLL_INTERVAL_MS); return () => { clearInterval(interval); }; diff --git a/src/pages/panel/replica-sets/index.tsx b/src/pages/panel/replica-sets/index.tsx index 3d7547b..9d0954e 100644 --- a/src/pages/panel/replica-sets/index.tsx +++ b/src/pages/panel/replica-sets/index.tsx @@ -1,3 +1,4 @@ +import {POLL_INTERVAL_MS} from "@/lib/constants.ts"; import {useEffect, useState} from "react"; import {DataTable} from "@/components/table"; import PanelPage from "@/layouts/panel"; @@ -90,7 +91,7 @@ export default function ReplicaSetsPage() { loadReplicaSets(); loadNamespaces(); - const interval = window.setInterval(loadReplicaSets, 3000); + const interval = window.setInterval(loadReplicaSets, POLL_INTERVAL_MS); return () => { clearInterval(interval); }; diff --git a/src/pages/panel/services/index.tsx b/src/pages/panel/services/index.tsx index 7e70412..2e02266 100644 --- a/src/pages/panel/services/index.tsx +++ b/src/pages/panel/services/index.tsx @@ -1,3 +1,4 @@ +import {POLL_INTERVAL_MS} from "@/lib/constants.ts"; import {useEffect, useState} from "react"; import {DataTable} from "@/components/table"; import PanelPage from "@/layouts/panel"; @@ -87,7 +88,7 @@ export default function ServicesPage() { loadServices(); loadNamespaces(); - const interval = window.setInterval(loadServices, 3000); + const interval = window.setInterval(loadServices, POLL_INTERVAL_MS); return () => { clearInterval(interval); }; diff --git a/src/pages/panel/stateful-sets/index.tsx b/src/pages/panel/stateful-sets/index.tsx index cdab708..14d9562 100644 --- a/src/pages/panel/stateful-sets/index.tsx +++ b/src/pages/panel/stateful-sets/index.tsx @@ -1,3 +1,4 @@ +import {POLL_INTERVAL_MS} from "@/lib/constants.ts"; import {useEffect, useState} from "react"; import {DataTable} from "@/components/table"; import PanelPage from "@/layouts/panel"; @@ -85,7 +86,7 @@ export default function StatefulSetsPage() { loadStatefulSets(); loadNamespaces(); - const interval = window.setInterval(loadStatefulSets, 3000); + const interval = window.setInterval(loadStatefulSets, POLL_INTERVAL_MS); return () => { clearInterval(interval); }; From 284c761d20195a44005fb87321f94cf7568010ae Mon Sep 17 00:00:00 2001 From: mcz Date: Tue, 5 May 2026 20:10:58 +0200 Subject: [PATCH 15/19] Scope auth cookies to root path with SameSite=Lax --- src/store/cookie-store.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/store/cookie-store.ts b/src/store/cookie-store.ts index 61d7b4c..bb21967 100644 --- a/src/store/cookie-store.ts +++ b/src/store/cookie-store.ts @@ -4,9 +4,9 @@ import {getCookie, removeCookie, setCookie} from 'typescript-cookie'; export const cookieStorage = (expiry: number): StateStorage => ({ getItem: (name) => getCookie(name) ?? null, setItem: (name, value) => { - setCookie(name, value, {expires: expiry, secure: true}); + setCookie(name, value, {expires: expiry, secure: true, path: "/", sameSite: "lax"}); }, removeItem: (name) => { - removeCookie(name); + removeCookie(name, {path: "/", sameSite: "lax"}); }, }); From c532ddd6e32fb98fe4184a4c6995d97814b7826a Mon Sep 17 00:00:00 2001 From: mcz Date: Tue, 5 May 2026 20:45:25 +0200 Subject: [PATCH 16/19] Drop since_seconds on first pod log fetch Now that the agent tails logs from the end and bounds them by lines + bytes, the panel no longer needs an arbitrary 1-hour window on first load. Send since_seconds=-1 so short jobs from earlier still show up; polling stays at 2s. --- src/pages/panel/pod/log/index.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pages/panel/pod/log/index.tsx b/src/pages/panel/pod/log/index.tsx index 47bf2d6..4d4bdd3 100644 --- a/src/pages/panel/pod/log/index.tsx +++ b/src/pages/panel/pod/log/index.tsx @@ -31,7 +31,7 @@ export default function PodLogsPage() { const logResponse: PodLogResponse = await podService.log({ namespace: pod.namespace, pod: pod.name, - since_seconds: firstRunRef.current ? 3600 : 2, + since_seconds: firstRunRef.current ? -1 : 2, }); if (!logResponse.success || !isMounted) { return; From 675337c7b4c6a47098b530d49687ed2c441f39b4 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 11 May 2026 18:44:44 +0000 Subject: [PATCH 17/19] Bump react-resizable-panels from 4.10.0 to 4.11.0 Bumps [react-resizable-panels](https://github.com/bvaughn/react-resizable-panels) from 4.10.0 to 4.11.0. - [Release notes](https://github.com/bvaughn/react-resizable-panels/releases) - [Changelog](https://github.com/bvaughn/react-resizable-panels/blob/main/CHANGELOG.md) - [Commits](https://github.com/bvaughn/react-resizable-panels/compare/4.10.0...4.11.0) --- updated-dependencies: - dependency-name: react-resizable-panels dependency-version: 4.11.0 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- package-lock.json | 8 ++++---- package.json | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/package-lock.json b/package-lock.json index f32f842..0eff1d3 100644 --- a/package-lock.json +++ b/package-lock.json @@ -68,7 +68,7 @@ "react-helmet": "^6.1.0", "react-hook-form": "^7.72.1", "react-i18next": "^17.0.2", - "react-resizable-panels": "^4.8.0", + "react-resizable-panels": "^4.11.0", "react-router-dom": "^7.13.0", "react-scan": "^0.5.3", "react-spring": "^10.0.3", @@ -8314,9 +8314,9 @@ } }, "node_modules/react-resizable-panels": { - "version": "4.10.0", - "resolved": "https://registry.npmjs.org/react-resizable-panels/-/react-resizable-panels-4.10.0.tgz", - "integrity": "sha512-frjewRQt7TCv/vCH1pJfjZ7RxAhr5pKuqVQtVgzFq/vherxBFOWyC3xMbryx5Ti2wylViGUFc93Etg4rB3E0UA==", + "version": "4.11.0", + "resolved": "https://registry.npmjs.org/react-resizable-panels/-/react-resizable-panels-4.11.0.tgz", + "integrity": "sha512-LPk/AkFDGkg7SsbOyL93ojrE6E7lhrxxDwnYNjfmnSeI6BE7Sje6dB24PXgZk8DeugdeXNk1LO+ohRqIjhxiLw==", "license": "MIT", "peerDependencies": { "react": "^18.0.0 || ^19.0.0", diff --git a/package.json b/package.json index bd5395f..cced0fa 100644 --- a/package.json +++ b/package.json @@ -70,7 +70,7 @@ "react-helmet": "^6.1.0", "react-hook-form": "^7.72.1", "react-i18next": "^17.0.2", - "react-resizable-panels": "^4.8.0", + "react-resizable-panels": "^4.11.0", "react-router-dom": "^7.13.0", "react-scan": "^0.5.3", "react-spring": "^10.0.3", From 0e05dae39ba1a474e6702fd0a39cc321fef4b3c8 Mon Sep 17 00:00:00 2001 From: mcz Date: Fri, 15 May 2026 18:40:03 +0200 Subject: [PATCH 18/19] Bump nginx to 1.31.0 --- Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Dockerfile b/Dockerfile index d6ef092..df713d7 100644 --- a/Dockerfile +++ b/Dockerfile @@ -8,7 +8,7 @@ RUN npm ci COPY . . RUN VITE_API_BASE_URL=/v1/ npm run build -FROM nginx:1.30.0-alpine +FROM nginx:1.31.0-alpine RUN apk upgrade --no-cache From dcd192056856f5f1421eb8c11be46ebd98e1922a Mon Sep 17 00:00:00 2001 From: mcz Date: Fri, 15 May 2026 19:00:00 +0200 Subject: [PATCH 19/19] Ignore TS deprecations for v6.0 --- tsconfig.app.json | 3 ++- tsconfig.node.json | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/tsconfig.app.json b/tsconfig.app.json index 6508322..2eb2a21 100644 --- a/tsconfig.app.json +++ b/tsconfig.app.json @@ -27,7 +27,8 @@ "baseUrl": "./src", "paths": { "@/*": ["*"] - } + }, + "ignoreDeprecations": "6.0" }, "include": ["src"] } diff --git a/tsconfig.node.json b/tsconfig.node.json index 7802d96..8e0613d 100644 --- a/tsconfig.node.json +++ b/tsconfig.node.json @@ -25,7 +25,8 @@ "baseUrl": "./src", "paths": { "@/*": ["*"] - } + }, + "ignoreDeprecations": "6.0" }, "include": ["vite.config.ts"] }