188 lines
6.3 KiB
TypeScript
188 lines
6.3 KiB
TypeScript
/**
|
|
* `ServerStatusBar` component.
|
|
*
|
|
* Displays a persistent bar at the top of the dashboard showing the
|
|
* fail2ban server health snapshot: connectivity status, version, active
|
|
* jail count, and aggregated ban/failure totals.
|
|
*
|
|
* Polls `GET /api/dashboard/status` every 30 seconds and on window focus
|
|
* via the {@link useServerStatus} hook.
|
|
*/
|
|
|
|
import {
|
|
Badge,
|
|
Button,
|
|
makeStyles,
|
|
Spinner,
|
|
Text,
|
|
tokens,
|
|
Tooltip,
|
|
} from "@fluentui/react-components";
|
|
import { ArrowClockwiseRegular, ShieldRegular } from "@fluentui/react-icons";
|
|
import { useServerStatus } from "../hooks/useServerStatus";
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// Styles
|
|
// ---------------------------------------------------------------------------
|
|
|
|
const useStyles = makeStyles({
|
|
bar: {
|
|
display: "flex",
|
|
alignItems: "center",
|
|
gap: tokens.spacingHorizontalL,
|
|
padding: `${tokens.spacingVerticalS} ${tokens.spacingHorizontalL}`,
|
|
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,
|
|
marginBottom: tokens.spacingVerticalL,
|
|
flexWrap: "wrap",
|
|
},
|
|
statusGroup: {
|
|
display: "flex",
|
|
alignItems: "center",
|
|
gap: tokens.spacingHorizontalS,
|
|
},
|
|
statGroup: {
|
|
display: "flex",
|
|
alignItems: "center",
|
|
gap: tokens.spacingHorizontalXS,
|
|
},
|
|
statValue: {
|
|
fontVariantNumeric: "tabular-nums",
|
|
fontWeight: 600,
|
|
},
|
|
spacer: {
|
|
flexGrow: 1,
|
|
},
|
|
errorText: {
|
|
color: tokens.colorPaletteRedForeground1,
|
|
fontSize: "12px",
|
|
},
|
|
});
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// Component
|
|
// ---------------------------------------------------------------------------
|
|
|
|
/**
|
|
* Persistent bar displaying fail2ban server health.
|
|
*
|
|
* Render this at the top of the dashboard page (and any page that should
|
|
* show live server status).
|
|
*/
|
|
export function ServerStatusBar(): React.JSX.Element {
|
|
const styles = useStyles();
|
|
const { status, banguiVersion, loading, error, refresh } = useServerStatus();
|
|
|
|
return (
|
|
<div className={styles.bar} role="status" aria-label="fail2ban server status">
|
|
{/* ---------------------------------------------------------------- */}
|
|
{/* Online / Offline badge */}
|
|
{/* ---------------------------------------------------------------- */}
|
|
<div className={styles.statusGroup}>
|
|
<ShieldRegular fontSize={16} />
|
|
{loading && !status ? (
|
|
<Spinner size="extra-tiny" label="Checking…" labelPosition="after" />
|
|
) : (
|
|
<Badge
|
|
appearance="filled"
|
|
color={status?.online ? "success" : "danger"}
|
|
aria-label={status?.online ? "fail2ban online" : "fail2ban offline"}
|
|
>
|
|
{status?.online ? "Online" : "Offline"}
|
|
</Badge>
|
|
)}
|
|
</div>
|
|
|
|
{/* ---------------------------------------------------------------- */}
|
|
{/* Version */}
|
|
{/* ---------------------------------------------------------------- */}
|
|
{status?.version != null && (
|
|
<Tooltip content="fail2ban daemon version" relationship="description">
|
|
<Text size={200} className={styles.statValue}>
|
|
v{status.version}
|
|
</Text>
|
|
</Tooltip>
|
|
)}
|
|
|
|
{banguiVersion != null && (
|
|
<Tooltip content="BanGUI version" relationship="description">
|
|
<Badge appearance="filled" size="small">
|
|
BanGUI v{banguiVersion}
|
|
</Badge>
|
|
</Tooltip>
|
|
)}
|
|
|
|
{/* ---------------------------------------------------------------- */}
|
|
{/* Stats (only when online) */}
|
|
{/* ---------------------------------------------------------------- */}
|
|
{status?.online === true && (
|
|
<>
|
|
<Tooltip content="Active jails" relationship="description">
|
|
<div className={styles.statGroup}>
|
|
<Text size={200}>Jails:</Text>
|
|
<Text size={200} className={styles.statValue}>
|
|
{status.active_jails}
|
|
</Text>
|
|
</div>
|
|
</Tooltip>
|
|
|
|
<Tooltip content="Currently banned IPs" relationship="description">
|
|
<div className={styles.statGroup}>
|
|
<Text size={200}>Bans:</Text>
|
|
<Text size={200} className={styles.statValue}>
|
|
{status.total_bans}
|
|
</Text>
|
|
</div>
|
|
</Tooltip>
|
|
|
|
<Tooltip content="Total failed authentication attempts currently tracked by fail2ban across all active jails" relationship="description">
|
|
<div className={styles.statGroup}>
|
|
<Text size={200}>Failed Attempts:</Text>
|
|
<Text size={200} className={styles.statValue}>
|
|
{status.total_failures}
|
|
</Text>
|
|
</div>
|
|
</Tooltip>
|
|
</>
|
|
)}
|
|
|
|
{/* ---------------------------------------------------------------- */}
|
|
{/* Error message */}
|
|
{/* ---------------------------------------------------------------- */}
|
|
{error != null && (
|
|
<Text className={styles.errorText} aria-live="polite">
|
|
{error}
|
|
</Text>
|
|
)}
|
|
|
|
<div className={styles.spacer} />
|
|
|
|
{/* ---------------------------------------------------------------- */}
|
|
{/* Refresh button */}
|
|
{/* ---------------------------------------------------------------- */}
|
|
<Tooltip content="Refresh server status" relationship="label">
|
|
<Button
|
|
appearance="subtle"
|
|
size="small"
|
|
icon={loading ? <Spinner size="extra-tiny" /> : <ArrowClockwiseRegular />}
|
|
onClick={refresh}
|
|
aria-label="Refresh server status"
|
|
disabled={loading}
|
|
/>
|
|
</Tooltip>
|
|
</div>
|
|
);
|
|
}
|