Refactor frontend date formatting helpers and mark Task 10 done

This commit is contained in:
2026-03-21 17:25:45 +01:00
parent a442836c5c
commit 8a6bcc4d94
6 changed files with 111 additions and 150 deletions

View File

@@ -34,6 +34,7 @@ import {
} from "@fluentui/react-icons";
import { Link, useNavigate, useParams } from "react-router-dom";
import { useJailDetail, useJailBannedIps } from "../hooks/useJails";
import { formatSeconds } from "../utils/formatDate";
import type { Jail } from "../types/jail";
import { BannedIpsSection } from "../components/jail/BannedIpsSection";
@@ -146,16 +147,9 @@ const useStyles = makeStyles({
});
// ---------------------------------------------------------------------------
// Helpers
// Components
// ---------------------------------------------------------------------------
function fmtSeconds(s: number): string {
if (s < 0) return "permanent";
if (s < 60) return `${String(s)} s`;
if (s < 3600) return `${String(Math.round(s / 60))} min`;
return `${String(Math.round(s / 3600))} h`;
}
function CodeList({ items, empty }: { items: string[]; empty: string }): React.JSX.Element {
const styles = useStyles();
if (items.length === 0) {
@@ -313,9 +307,9 @@ function JailInfoSection({ jail, onRefresh, onStart, onStop, onSetIdle, onReload
<Text className={styles.label}>Backend:</Text>
<Text className={styles.mono}>{jail.backend}</Text>
<Text className={styles.label}>Find time:</Text>
<Text>{fmtSeconds(jail.find_time)}</Text>
<Text>{formatSeconds(jail.find_time)}</Text>
<Text className={styles.label}>Ban time:</Text>
<Text>{fmtSeconds(jail.ban_time)}</Text>
<Text>{formatSeconds(jail.ban_time)}</Text>
<Text className={styles.label}>Max retry:</Text>
<Text>{String(jail.max_retry)}</Text>
{jail.date_pattern && (
@@ -413,13 +407,13 @@ function BantimeEscalationSection({ jail }: { jail: Jail }): React.JSX.Element |
{esc.max_time !== null && (
<>
<Text className={styles.label}>Max time:</Text>
<Text>{fmtSeconds(esc.max_time)}</Text>
<Text>{formatSeconds(esc.max_time)}</Text>
</>
)}
{esc.rnd_time !== null && (
<>
<Text className={styles.label}>Random jitter:</Text>
<Text>{fmtSeconds(esc.rnd_time)}</Text>
<Text>{formatSeconds(esc.rnd_time)}</Text>
</>
)}
<Text className={styles.label}>Count across all jails:</Text>

View File

@@ -9,7 +9,8 @@
* geo-location details.
*/
import { useMemo, useState } from "react";
import { useState } from "react";
import { formatSeconds } from "../utils/formatDate";
import {
Badge,
Button,
@@ -42,7 +43,7 @@ import {
SearchRegular,
StopRegular,
} from "@fluentui/react-icons";
import { useNavigate } from "react-router-dom";
import { Link } from "react-router-dom";
import { useActiveBans, useIpLookup, useJails } from "../hooks/useJails";
import type { JailSummary } from "../types/jail";
import { ApiError } from "../api/client";
@@ -141,15 +142,65 @@ const useStyles = makeStyles({
});
// ---------------------------------------------------------------------------
// Helpers
// Jail overview columns
// ---------------------------------------------------------------------------
function fmtSeconds(s: number): string {
if (s < 0) return "permanent";
if (s < 60) return `${String(s)}s`;
if (s < 3600) return `${String(Math.round(s / 60))}m`;
return `${String(Math.round(s / 3600))}h`;
}
const jailColumns: TableColumnDefinition<JailSummary>[] = [
createTableColumn<JailSummary>({
columnId: "name",
renderHeaderCell: () => "Jail",
renderCell: (j) => (
<Link to={`/jails/${encodeURIComponent(j.name)}`} style={{ textDecoration: "none" }}>
<Text style={{ fontFamily: "Consolas, 'Courier New', monospace", fontSize: "0.85rem" }}>
{j.name}
</Text>
</Link>
),
}),
createTableColumn<JailSummary>({
columnId: "status",
renderHeaderCell: () => "Status",
renderCell: (j) => {
if (!j.running) return <Badge appearance="filled" color="danger">stopped</Badge>;
if (j.idle) return <Badge appearance="filled" color="warning">idle</Badge>;
return <Badge appearance="filled" color="success">running</Badge>;
},
}),
createTableColumn<JailSummary>({
columnId: "backend",
renderHeaderCell: () => "Backend",
renderCell: (j) => <Text size={200}>{j.backend}</Text>,
}),
createTableColumn<JailSummary>({
columnId: "banned",
renderHeaderCell: () => "Banned",
renderCell: (j) => (
<Text size={200}>{j.status ? String(j.status.currently_banned) : "—"}</Text>
),
}),
createTableColumn<JailSummary>({
columnId: "failed",
renderHeaderCell: () => "Failed",
renderCell: (j) => (
<Text size={200}>{j.status ? String(j.status.currently_failed) : "—"}</Text>
),
}),
createTableColumn<JailSummary>({
columnId: "findTime",
renderHeaderCell: () => "Find Time",
renderCell: (j) => <Text size={200}>{formatSeconds(j.find_time)}</Text>,
}),
createTableColumn<JailSummary>({
columnId: "banTime",
renderHeaderCell: () => "Ban Time",
renderCell: (j) => <Text size={200}>{formatSeconds(j.ban_time)}</Text>,
}),
createTableColumn<JailSummary>({
columnId: "maxRetry",
renderHeaderCell: () => "Max Retry",
renderCell: (j) => <Text size={200}>{String(j.max_retry)}</Text>,
}),
];
// ---------------------------------------------------------------------------
// Sub-component: Jail overview section
@@ -157,82 +208,10 @@ function fmtSeconds(s: number): string {
function JailOverviewSection(): React.JSX.Element {
const styles = useStyles();
const navigate = useNavigate();
const { jails, total, loading, error, refresh, startJail, stopJail, setIdle, reloadJail, reloadAll } =
useJails();
const [opError, setOpError] = useState<string | null>(null);
const jailColumns = useMemo<TableColumnDefinition<JailSummary>[]>(
() => [
createTableColumn<JailSummary>({
columnId: "name",
renderHeaderCell: () => "Jail",
renderCell: (j) => (
<Button
appearance="transparent"
size="small"
style={{ padding: 0, minWidth: 0, justifyContent: "flex-start" }}
onClick={() =>
navigate("/config", {
state: { tab: "jails", jail: j.name },
})
}
>
<Text
style={{ fontFamily: "Consolas, 'Courier New', monospace", fontSize: "0.85rem" }}
>
{j.name}
</Text>
</Button>
),
}),
createTableColumn<JailSummary>({
columnId: "status",
renderHeaderCell: () => "Status",
renderCell: (j) => {
if (!j.running) return <Badge appearance="filled" color="danger">stopped</Badge>;
if (j.idle) return <Badge appearance="filled" color="warning">idle</Badge>;
return <Badge appearance="filled" color="success">running</Badge>;
},
}),
createTableColumn<JailSummary>({
columnId: "backend",
renderHeaderCell: () => "Backend",
renderCell: (j) => <Text size={200}>{j.backend}</Text>,
}),
createTableColumn<JailSummary>({
columnId: "banned",
renderHeaderCell: () => "Banned",
renderCell: (j) => (
<Text size={200}>{j.status ? String(j.status.currently_banned) : "—"}</Text>
),
}),
createTableColumn<JailSummary>({
columnId: "failed",
renderHeaderCell: () => "Failed",
renderCell: (j) => (
<Text size={200}>{j.status ? String(j.status.currently_failed) : "—"}</Text>
),
}),
createTableColumn<JailSummary>({
columnId: "findTime",
renderHeaderCell: () => "Find Time",
renderCell: (j) => <Text size={200}>{fmtSeconds(j.find_time)}</Text>,
}),
createTableColumn<JailSummary>({
columnId: "banTime",
renderHeaderCell: () => "Ban Time",
renderCell: (j) => <Text size={200}>{fmtSeconds(j.ban_time)}</Text>,
}),
createTableColumn<JailSummary>({
columnId: "maxRetry",
renderHeaderCell: () => "Max Retry",
renderCell: (j) => <Text size={200}>{String(j.max_retry)}</Text>,
}),
],
[navigate],
);
const handle = (fn: () => Promise<void>): void => {
setOpError(null);
fn().catch((err: unknown) => {