Improve error boundary granularity with page and section level boundaries
Implement three-level error boundary strategy: - Top-level (app shell): catches critical failures - Page-level: preserves navigation when page crashes - Section-level: graceful degradation for charts/tables Create new components: - PageErrorBoundary: wraps page routes - SectionErrorBoundary: wraps data-heavy sections Enhance ErrorBoundary with customizable titles, messages, and reload behavior. Apply page boundaries to all route handlers in App.tsx. Apply section boundaries to: - DashboardPage: server status, ban trend, country charts, ban list - JailsPage: jail overview, ban/unban form, IP lookup - MapPage: world map, ban table - ConfigPage: configuration editor - HistoryPage: history table, IP detail view - BlocklistsPage: sources, schedule, import log Update Web-Development.md with error boundary strategy documentation. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This commit is contained in:
@@ -4,6 +4,8 @@
|
||||
* Shows a paginated, filterable table of every ban ever recorded in the
|
||||
* fail2ban database. Clicking an IP address opens a per-IP timeline view.
|
||||
* Rows with repeatedly-banned IPs are highlighted in amber.
|
||||
*
|
||||
* The history table is wrapped with SectionErrorBoundary for resilience.
|
||||
*/
|
||||
|
||||
import { useCallback, useEffect, useMemo, useState } from "react";
|
||||
@@ -30,6 +32,7 @@ import {
|
||||
ChevronRightRegular,
|
||||
} from "@fluentui/react-icons";
|
||||
import { DashboardFilterBar } from "../components/DashboardFilterBar";
|
||||
import { SectionErrorBoundary } from "../components/SectionErrorBoundary";
|
||||
import { useHistory } from "../hooks/useHistory";
|
||||
import { IpDetailView } from "./history/IpDetailView";
|
||||
import { HISTORY_PAGE_SIZE } from "../utils/constants";
|
||||
@@ -236,12 +239,14 @@ export function HistoryPage(): React.JSX.Element {
|
||||
if (selectedIp !== null) {
|
||||
return (
|
||||
<div className={styles.root}>
|
||||
<IpDetailView
|
||||
ip={selectedIp}
|
||||
onBack={(): void => {
|
||||
setSelectedIp(null);
|
||||
}}
|
||||
/>
|
||||
<SectionErrorBoundary sectionName="IP Detail View">
|
||||
<IpDetailView
|
||||
ip={selectedIp}
|
||||
onBack={(): void => {
|
||||
setSelectedIp(null);
|
||||
}}
|
||||
/>
|
||||
</SectionErrorBoundary>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -306,38 +311,40 @@ export function HistoryPage(): React.JSX.Element {
|
||||
{/* DataGrid table */}
|
||||
{/* ---------------------------------------------------------------- */}
|
||||
{!loading && !error && (
|
||||
<div className={styles.tableWrapper}>
|
||||
<DataGrid
|
||||
items={items}
|
||||
columns={columns}
|
||||
getRowId={(item: HistoryBanItem) => `${item.ip}-${item.banned_at}`}
|
||||
focusMode="composite"
|
||||
>
|
||||
<DataGridHeader>
|
||||
<DataGridRow>
|
||||
{({ renderHeaderCell }) => (
|
||||
<DataGridHeaderCell>{renderHeaderCell()}</DataGridHeaderCell>
|
||||
)}
|
||||
</DataGridRow>
|
||||
</DataGridHeader>
|
||||
<DataGridBody<HistoryBanItem>>
|
||||
{({ item }) => (
|
||||
<DataGridRow<HistoryBanItem>
|
||||
key={`${item.ip}-${item.banned_at}`}
|
||||
className={
|
||||
item.ban_count >= HIGH_BAN_THRESHOLD
|
||||
? styles.highBanRow
|
||||
: undefined
|
||||
}
|
||||
>
|
||||
{({ renderCell }) => (
|
||||
<DataGridCell>{renderCell(item)}</DataGridCell>
|
||||
<SectionErrorBoundary sectionName="History Table">
|
||||
<div className={styles.tableWrapper}>
|
||||
<DataGrid
|
||||
items={items}
|
||||
columns={columns}
|
||||
getRowId={(item: HistoryBanItem) => `${item.ip}-${item.banned_at}`}
|
||||
focusMode="composite"
|
||||
>
|
||||
<DataGridHeader>
|
||||
<DataGridRow>
|
||||
{({ renderHeaderCell }) => (
|
||||
<DataGridHeaderCell>{renderHeaderCell()}</DataGridHeaderCell>
|
||||
)}
|
||||
</DataGridRow>
|
||||
)}
|
||||
</DataGridBody>
|
||||
</DataGrid>
|
||||
</div>
|
||||
</DataGridHeader>
|
||||
<DataGridBody<HistoryBanItem>>
|
||||
{({ item }) => (
|
||||
<DataGridRow<HistoryBanItem>
|
||||
key={`${item.ip}-${item.banned_at}`}
|
||||
className={
|
||||
item.ban_count >= HIGH_BAN_THRESHOLD
|
||||
? styles.highBanRow
|
||||
: undefined
|
||||
}
|
||||
>
|
||||
{({ renderCell }) => (
|
||||
<DataGridCell>{renderCell(item)}</DataGridCell>
|
||||
)}
|
||||
</DataGridRow>
|
||||
)}
|
||||
</DataGridBody>
|
||||
</DataGrid>
|
||||
</div>
|
||||
</SectionErrorBoundary>
|
||||
)}
|
||||
|
||||
{/* ---------------------------------------------------------------- */}
|
||||
|
||||
Reference in New Issue
Block a user