164 lines
5.8 KiB
TypeScript
164 lines
5.8 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 { useState } from "react";
|
|
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 "../theme/commonStyles";
|
|
import { useDashboardCountryData } from "../hooks/useDashboardCountryData";
|
|
import type { BanOriginFilter, TimeRange } from "../types/ban";
|
|
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// Styles
|
|
// ---------------------------------------------------------------------------
|
|
|
|
const useStyles = makeStyles({
|
|
root: {
|
|
display: "flex",
|
|
flexDirection: "column",
|
|
gap: tokens.spacingVerticalM,
|
|
},
|
|
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.
|
|
*/
|
|
export function DashboardPage(): React.JSX.Element {
|
|
const styles = useStyles();
|
|
const [timeRange, setTimeRange] = useState<TimeRange>("24h");
|
|
const [originFilter, setOriginFilter] = useState<BanOriginFilter>("all");
|
|
|
|
const { countries, countryNames, isLoading: countryLoading, error: countryError, reload: reloadCountry } =
|
|
useDashboardCountryData(timeRange, originFilter);
|
|
|
|
const sectionStyles = useCommonSectionStyles();
|
|
|
|
return (
|
|
<div className={styles.root}>
|
|
{/* ------------------------------------------------------------------ */}
|
|
{/* Server status bar */}
|
|
{/* ------------------------------------------------------------------ */}
|
|
<ServerStatusBar />
|
|
|
|
{/* ------------------------------------------------------------------ */}
|
|
{/* Global filter bar */}
|
|
{/* ------------------------------------------------------------------ */}
|
|
<DashboardFilterBar
|
|
timeRange={timeRange}
|
|
onTimeRangeChange={setTimeRange}
|
|
originFilter={originFilter}
|
|
onOriginFilterChange={setOriginFilter}
|
|
/>
|
|
|
|
{/* ------------------------------------------------------------------ */}
|
|
{/* 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} />
|
|
</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} />
|
|
</div>
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|
|
|