Task 1: Remove ActiveBansSection from JailsPage
- Delete buildBanColumns, fmtTimestamp, ActiveBansSection
- Remove Dialog/Delete/Dismiss imports, ActiveBan type
- Update JSDoc to reflect three sections
Task 2: Remove JailDistributionChart from Dashboard
- Delete import and JSX block from DashboardPage.tsx
Task 3: Fix transparent pie chart (TopCountriesPieChart)
- Add Cell import and per-slice <Cell fill={slice.fill}> children inside <Pie>
- Suppress @typescript-eslint/no-deprecated (recharts v3 types)
Task 4: Allow /config/log as safe log prefix
- Add '/config/log' to _SAFE_LOG_PREFIXES in config_service.py
- Update error message to list both allowed directories
Task 5: Block jail activation on missing filter/logpath
- activate_jail refuses to proceed when filter/logpath issues found
- ActivateJailDialog treats all validation issues as blocking
- Trigger immediate _run_probe after activation in config router
- /api/health now reports fail2ban online/offline from cached probe
- Add TestActivateJailBlocking tests; fix existing tests to mock validation
181 lines
6.3 KiB
TypeScript
181 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 { 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 { useDashboardCountryData } from "../hooks/useDashboardCountryData";
|
|
import type { BanOriginFilter, TimeRange } from "../types/ban";
|
|
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// Styles
|
|
// ---------------------------------------------------------------------------
|
|
|
|
const useStyles = makeStyles({
|
|
root: {
|
|
display: "flex",
|
|
flexDirection: "column",
|
|
gap: tokens.spacingVerticalM,
|
|
},
|
|
section: {
|
|
display: "flex",
|
|
flexDirection: "column",
|
|
gap: tokens.spacingVerticalS,
|
|
backgroundColor: tokens.colorNeutralBackground1,
|
|
borderRadius: tokens.borderRadiusMedium,
|
|
borderTopWidth: "1px",
|
|
borderTopStyle: "solid",
|
|
borderTopColor: tokens.colorNeutralStroke2,
|
|
borderRightWidth: "1px",
|
|
borderRightStyle: "solid",
|
|
borderRightColor: tokens.colorNeutralStroke2,
|
|
borderBottomWidth: "1px",
|
|
borderBottomStyle: "solid",
|
|
borderBottomColor: tokens.colorNeutralStroke2,
|
|
borderLeftWidth: "1px",
|
|
borderLeftStyle: "solid",
|
|
borderLeftColor: tokens.colorNeutralStroke2,
|
|
padding: 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);
|
|
|
|
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={styles.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={styles.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={styles.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>
|
|
);
|
|
}
|
|
|