From 20412dd94be8fcd845ddfc0870f063f454ede345 Mon Sep 17 00:00:00 2001 From: Lukas Date: Mon, 20 Apr 2026 19:28:29 +0200 Subject: [PATCH] Memoize dashboard and history table columns --- Docs/Tasks.md | 6 ++++-- frontend/src/components/BanTable.tsx | 3 ++- frontend/src/pages/HistoryPage.tsx | 19 ++++++++++--------- 3 files changed, 16 insertions(+), 12 deletions(-) diff --git a/Docs/Tasks.md b/Docs/Tasks.md index 2030fe0..ed147fa 100644 --- a/Docs/Tasks.md +++ b/Docs/Tasks.md @@ -128,7 +128,7 @@ Issues are grouped by category and ordered roughly by severity. Each entry descr --- -### TASK-007 — Setup page password validation too weak +### TASK-007 — Setup page password validation too weak (done) **Where found:** `frontend/src/pages/SetupPage.tsx`, `validate()` function. Only `masterPassword.length < 8` is checked. @@ -148,7 +148,9 @@ Issues are grouped by category and ordered roughly by severity. Each entry descr --- -### TASK-008 — `buildBanColumns` and `HISTORY_COLUMNS` recreated on every render +### TASK-008 — `buildBanColumns` and `HISTORY_COLUMNS` recreated on every render (done) + +**Where fixed:** `frontend/src/components/BanTable.tsx`, `frontend/src/pages/HistoryPage.tsx` **Where found:** - `frontend/src/components/BanTable.tsx` — `buildBanColumns(styles)` called unconditionally in the render body. diff --git a/frontend/src/components/BanTable.tsx b/frontend/src/components/BanTable.tsx index 85879ac..83a9242 100644 --- a/frontend/src/components/BanTable.tsx +++ b/frontend/src/components/BanTable.tsx @@ -8,6 +8,7 @@ * Columns: Time, IP, Service, Country, Jail, Ban Count. */ +import { useMemo } from "react"; import { Badge, Button, @@ -194,7 +195,7 @@ export function BanTable({ timeRange, origin = "all", source = "fail2ban" }: Ban const styles = useStyles(); const { banItems, total, page, setPage, loading, error, refresh } = useBans(timeRange, origin, source); - const banColumns = buildBanColumns(styles); + const banColumns = useMemo(() => buildBanColumns(styles), [styles]); // -------------------------------------------------------------------------- // Loading state diff --git a/frontend/src/pages/HistoryPage.tsx b/frontend/src/pages/HistoryPage.tsx index e432a6b..f20f2f0 100644 --- a/frontend/src/pages/HistoryPage.tsx +++ b/frontend/src/pages/HistoryPage.tsx @@ -6,7 +6,7 @@ * Rows with repeatedly-banned IPs are highlighted in amber. */ -import { useEffect, useRef, useState } from "react"; +import { useCallback, useEffect, useMemo, useRef, useState } from "react"; import { Badge, Button, @@ -214,6 +214,15 @@ export function HistoryPage(): React.JSX.Element { const { items, total, page, loading, error, setPage, refresh } = useHistory(appliedQuery); + const handleIpClick = useCallback((ip: string): void => { + setSelectedIp(ip); + }, []); + + const columns = useMemo( + () => HISTORY_COLUMNS(handleIpClick, styles), + [handleIpClick, styles], + ); + useEffect((): void => { const nextQuery: HistoryQuery = { range, @@ -236,14 +245,6 @@ export function HistoryPage(): React.JSX.Element { const totalPages = Math.max(1, Math.ceil(total / PAGE_SIZE)); - /** History table columns with IP click handler. */ - const columns = HISTORY_COLUMNS( - (ip: string): void => { - setSelectedIp(ip); - }, - styles, - ); - // If an IP is selected, show the detail view. if (selectedIp !== null) { return (