Files
BanGUI/frontend/src/pages/DashboardPage.tsx
Lukas 814000fe68 Refactor DashboardFilterBar to use props exclusively, eliminate dual state source
- DashboardFilterBar now requires all filter props (timeRange, onTimeRangeChange, originFilter, onOriginFilterChange) instead of falling back to context
- Removed useDashboardFilters() hook dependency from DashboardFilterBar, BanTrendChart, and JailDistributionChart
- Updated DashboardPage to explicitly pass all filter values and callbacks from context to components
- Made props required on BanTrendChart and JailDistributionChart
- Updated all tests to reflect new prop requirements
- This eliminates the silent dual-source behavior that could lead to subtle bugs when components are used with different data sources

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-04-23 09:24:16 +02:00

186 lines
6.3 KiB
TypeScript

/**
* Dashboard page.
*
* 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.
*/
import { Text, makeStyles, tokens } from "@fluentui/react-components";
import { BanTable } from "../components/BanTable";
import { BanTrendChart } from "../components/BanTrendChart";
import { ChartStateWrapper } from "../components/ChartStateWrapper";
import { DashboardFilterBar } from "../components/DashboardFilterBar";
import { ServerStatusBar } from "../components/ServerStatusBar";
import { TopCountriesBarChart } from "../components/TopCountriesBarChart";
import { TopCountriesPieChart } from "../components/TopCountriesPieChart";
import { useCommonSectionStyles } from "../components/commonStyles";
import { useDashboardCountryData } from "../hooks/useDashboardCountryData";
import { DashboardFilterProvider, useDashboardFilters } from "../providers/DashboardFilterProvider";
// ---------------------------------------------------------------------------
// Styles
// ---------------------------------------------------------------------------
const useStyles = makeStyles({
root: {
display: "flex",
flexDirection: "column",
gap: tokens.spacingVerticalM,
},
filterRow: {
display: "flex",
alignItems: "center",
gap: tokens.spacingHorizontalM,
flexWrap: "wrap",
},
sectionHeader: {
display: "flex",
alignItems: "center",
justifyContent: "space-between",
flexWrap: "wrap",
gap: tokens.spacingHorizontalM,
paddingBottom: tokens.spacingVerticalS,
borderBottomWidth: "1px",
borderBottomStyle: "solid",
borderBottomColor: tokens.colorNeutralStroke2,
},
tabContent: {
paddingTop: tokens.spacingVerticalS,
},
chartsRow: {
display: "flex",
flexDirection: "row",
gap: tokens.spacingHorizontalL,
flexWrap: "wrap",
},
chartCard: {
flex: "1 1 300px",
minWidth: "280px",
},
});
// ---------------------------------------------------------------------------
// Component
// ---------------------------------------------------------------------------
/**
* Main dashboard landing page.
*
* Displays the fail2ban server status, a time-range selector, and the
* ban list table.
*/
function DashboardPageContent(): React.JSX.Element {
const styles = useStyles();
const { timeRange, originFilter, source, setTimeRange, setOriginFilter } = useDashboardFilters();
const { countries, countryNames, loading: countryLoading, error: countryError, reload: reloadCountry } =
useDashboardCountryData(timeRange, originFilter, source);
const sectionStyles = useCommonSectionStyles();
return (
<div className={styles.root}>
{/* ------------------------------------------------------------------ */}
{/* Server status bar */}
{/* ------------------------------------------------------------------ */}
<ServerStatusBar />
{/* ------------------------------------------------------------------ */}
{/* Global filter bar */}
{/* ------------------------------------------------------------------ */}
<div className={styles.filterRow}>
<DashboardFilterBar
timeRange={timeRange}
onTimeRangeChange={setTimeRange}
originFilter={originFilter}
onOriginFilterChange={setOriginFilter}
/>
</div>
{/* ------------------------------------------------------------------ */}
{/* Ban Trend section */}
{/* ------------------------------------------------------------------ */}
<div className={sectionStyles.section}>
<div className={styles.sectionHeader}>
<Text as="h2" size={500} weight="semibold">
Ban Trend
</Text>
</div>
<div className={styles.tabContent}>
<BanTrendChart
timeRange={timeRange}
origin={originFilter}
source={source}
/>
</div>
</div>
{/* ------------------------------------------------------------------ */}
{/* Charts section */}
{/* ------------------------------------------------------------------ */}
<div className={sectionStyles.section}>
<div className={styles.sectionHeader}>
<Text as="h2" size={500} weight="semibold">
Top Countries
</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}
/>
</div>
<div className={styles.chartCard}>
<TopCountriesBarChart
countries={countries}
countryNames={countryNames}
/>
</div>
</div>
</ChartStateWrapper>
</div>
</div>
{/* ------------------------------------------------------------------ */}
{/* Ban list section */}
{/* ------------------------------------------------------------------ */}
<div className={sectionStyles.section}>
<div className={styles.sectionHeader}>
<Text as="h2" size={500} weight="semibold">
Ban List
</Text>
</div>
{/* Ban table */}
<div className={styles.tabContent}>
<BanTable
timeRange={timeRange}
origin={originFilter}
source={source}
/>
</div>
</div>
</div>
);
}
export function DashboardPage(): React.JSX.Element {
return (
<DashboardFilterProvider>
<DashboardPageContent />
</DashboardFilterProvider>
);
}