- 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>
186 lines
6.3 KiB
TypeScript
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>
|
|
);
|
|
}
|
|
|