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,9 @@
|
||||
* Composes the fail2ban server status bar at the top, a shared time-range
|
||||
* selector, and the ban list showing aggregate bans from the fail2ban
|
||||
* database. The time-range selection controls how far back to look.
|
||||
*
|
||||
* Sections are wrapped with SectionErrorBoundary to provide graceful
|
||||
* degradation if individual charts or tables fail to render.
|
||||
*/
|
||||
|
||||
import { Text, makeStyles, tokens } from "@fluentui/react-components";
|
||||
@@ -14,6 +17,7 @@ import { DashboardFilterBar } from "../components/DashboardFilterBar";
|
||||
import { ServerStatusBar } from "../components/ServerStatusBar";
|
||||
import { TopCountriesBarChart } from "../components/TopCountriesBarChart";
|
||||
import { TopCountriesPieChart } from "../components/TopCountriesPieChart";
|
||||
import { SectionErrorBoundary } from "../components/SectionErrorBoundary";
|
||||
import { useCommonSectionStyles } from "../components/commonStyles";
|
||||
import { useDashboardCountryData } from "../hooks/useDashboardCountryData";
|
||||
import { DashboardFilterProvider, useDashboardFilters } from "./DashboardFilterProvider";
|
||||
@@ -69,7 +73,8 @@ const useStyles = makeStyles({
|
||||
* Main dashboard landing page.
|
||||
*
|
||||
* Displays the fail2ban server status, a time-range selector, and the
|
||||
* ban list table.
|
||||
* ban list table. Each section is protected with a SectionErrorBoundary
|
||||
* so that a failure in one section does not crash the entire page.
|
||||
*/
|
||||
function DashboardPageContent(): React.JSX.Element {
|
||||
const styles = useStyles();
|
||||
@@ -85,7 +90,9 @@ function DashboardPageContent(): React.JSX.Element {
|
||||
{/* ------------------------------------------------------------------ */}
|
||||
{/* Server status bar */}
|
||||
{/* ------------------------------------------------------------------ */}
|
||||
<ServerStatusBar />
|
||||
<SectionErrorBoundary sectionName="Server Status">
|
||||
<ServerStatusBar />
|
||||
</SectionErrorBoundary>
|
||||
|
||||
{/* ------------------------------------------------------------------ */}
|
||||
{/* Global filter bar */}
|
||||
@@ -109,11 +116,13 @@ function DashboardPageContent(): React.JSX.Element {
|
||||
</Text>
|
||||
</div>
|
||||
<div className={styles.tabContent}>
|
||||
<BanTrendChart
|
||||
timeRange={timeRange}
|
||||
origin={originFilter}
|
||||
source={source}
|
||||
/>
|
||||
<SectionErrorBoundary sectionName="Ban Trend Chart">
|
||||
<BanTrendChart
|
||||
timeRange={timeRange}
|
||||
origin={originFilter}
|
||||
source={source}
|
||||
/>
|
||||
</SectionErrorBoundary>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -127,28 +136,30 @@ function DashboardPageContent(): React.JSX.Element {
|
||||
</Text>
|
||||
</div>
|
||||
<div className={styles.tabContent}>
|
||||
<ChartStateWrapper
|
||||
isLoading={countryLoading}
|
||||
error={countryError}
|
||||
onRetry={reloadCountry}
|
||||
isEmpty={!countryLoading && Object.keys(countries).length === 0}
|
||||
emptyMessage="No ban data for the selected period."
|
||||
>
|
||||
<div className={styles.chartsRow}>
|
||||
<div className={styles.chartCard}>
|
||||
<TopCountriesPieChart
|
||||
countries={countries}
|
||||
countryNames={countryNames}
|
||||
/>
|
||||
<SectionErrorBoundary sectionName="Country Charts">
|
||||
<ChartStateWrapper
|
||||
isLoading={countryLoading}
|
||||
error={countryError}
|
||||
onRetry={reloadCountry}
|
||||
isEmpty={!countryLoading && Object.keys(countries).length === 0}
|
||||
emptyMessage="No ban data for the selected period."
|
||||
>
|
||||
<div className={styles.chartsRow}>
|
||||
<div className={styles.chartCard}>
|
||||
<TopCountriesPieChart
|
||||
countries={countries}
|
||||
countryNames={countryNames}
|
||||
/>
|
||||
</div>
|
||||
<div className={styles.chartCard}>
|
||||
<TopCountriesBarChart
|
||||
countries={countries}
|
||||
countryNames={countryNames}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div className={styles.chartCard}>
|
||||
<TopCountriesBarChart
|
||||
countries={countries}
|
||||
countryNames={countryNames}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</ChartStateWrapper>
|
||||
</ChartStateWrapper>
|
||||
</SectionErrorBoundary>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -164,11 +175,13 @@ function DashboardPageContent(): React.JSX.Element {
|
||||
|
||||
{/* Ban table */}
|
||||
<div className={styles.tabContent}>
|
||||
<BanTable
|
||||
timeRange={timeRange}
|
||||
origin={originFilter}
|
||||
source={source}
|
||||
/>
|
||||
<SectionErrorBoundary sectionName="Ban List">
|
||||
<BanTable
|
||||
timeRange={timeRange}
|
||||
origin={originFilter}
|
||||
source={source}
|
||||
/>
|
||||
</SectionErrorBoundary>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -183,3 +196,4 @@ export function DashboardPage(): React.JSX.Element {
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user