Refactor frontend date formatting helpers and mark Task 10 done
This commit is contained in:
@@ -27,6 +27,7 @@ import {
|
||||
import { PageEmpty, PageError, PageLoading } from "./PageFeedback";
|
||||
import { ChevronLeftRegular, ChevronRightRegular } from "@fluentui/react-icons";
|
||||
import { useBans } from "../hooks/useBans";
|
||||
import { formatTimestamp } from "../utils/formatDate";
|
||||
import type { DashboardBanItem, TimeRange, BanOriginFilter } from "../types/ban";
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
@@ -90,31 +91,6 @@ const useStyles = makeStyles({
|
||||
},
|
||||
});
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Helpers
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Format an ISO 8601 timestamp for display.
|
||||
*
|
||||
* @param iso - ISO 8601 UTC string.
|
||||
* @returns Localised date+time string.
|
||||
*/
|
||||
function formatTimestamp(iso: string): string {
|
||||
try {
|
||||
return new Date(iso).toLocaleString(undefined, {
|
||||
year: "numeric",
|
||||
month: "2-digit",
|
||||
day: "2-digit",
|
||||
hour: "2-digit",
|
||||
minute: "2-digit",
|
||||
second: "2-digit",
|
||||
});
|
||||
} catch {
|
||||
return iso;
|
||||
}
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Column definitions
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
@@ -32,6 +32,7 @@ import {
|
||||
type TableColumnDefinition,
|
||||
createTableColumn,
|
||||
} from "@fluentui/react-components";
|
||||
import { formatTimestamp } from "../../utils/formatDate";
|
||||
import {
|
||||
ArrowClockwiseRegular,
|
||||
ChevronLeftRegular,
|
||||
@@ -126,31 +127,6 @@ const useStyles = makeStyles({
|
||||
},
|
||||
});
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Helpers
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Format an ISO 8601 timestamp for compact display.
|
||||
*
|
||||
* @param iso - ISO 8601 string or `null`.
|
||||
* @returns A locale time string, or `"—"` when `null`.
|
||||
*/
|
||||
function fmtTime(iso: string | null): string {
|
||||
if (!iso) return "—";
|
||||
try {
|
||||
return new Date(iso).toLocaleString(undefined, {
|
||||
year: "numeric",
|
||||
month: "2-digit",
|
||||
day: "2-digit",
|
||||
hour: "2-digit",
|
||||
minute: "2-digit",
|
||||
});
|
||||
} catch {
|
||||
return iso;
|
||||
}
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Column definitions
|
||||
// ---------------------------------------------------------------------------
|
||||
@@ -191,12 +167,16 @@ const columns: TableColumnDefinition<BanRow>[] = [
|
||||
createTableColumn<BanRow>({
|
||||
columnId: "banned_at",
|
||||
renderHeaderCell: () => "Banned At",
|
||||
renderCell: ({ ban }) => <Text size={200}>{fmtTime(ban.banned_at)}</Text>,
|
||||
renderCell: ({ ban }) => (
|
||||
<Text size={200}>{ban.banned_at ? formatTimestamp(ban.banned_at) : "—"}</Text>
|
||||
),
|
||||
}),
|
||||
createTableColumn<BanRow>({
|
||||
columnId: "expires_at",
|
||||
renderHeaderCell: () => "Expires At",
|
||||
renderCell: ({ ban }) => <Text size={200}>{fmtTime(ban.expires_at)}</Text>,
|
||||
renderCell: ({ ban }) => (
|
||||
<Text size={200}>{ban.expires_at ? formatTimestamp(ban.expires_at) : "—"}</Text>
|
||||
),
|
||||
}),
|
||||
createTableColumn<BanRow>({
|
||||
columnId: "actions",
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -10,6 +10,7 @@
|
||||
*/
|
||||
|
||||
import { useState } from "react";
|
||||
import { formatSeconds } from "../utils/formatDate";
|
||||
import {
|
||||
Badge,
|
||||
Button,
|
||||
@@ -140,17 +141,6 @@ const useStyles = makeStyles({
|
||||
lookupLabel: { fontWeight: tokens.fontWeightSemibold },
|
||||
});
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Helpers
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
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`;
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Jail overview columns
|
||||
// ---------------------------------------------------------------------------
|
||||
@@ -198,12 +188,12 @@ const jailColumns: TableColumnDefinition<JailSummary>[] = [
|
||||
createTableColumn<JailSummary>({
|
||||
columnId: "findTime",
|
||||
renderHeaderCell: () => "Find Time",
|
||||
renderCell: (j) => <Text size={200}>{fmtSeconds(j.find_time)}</Text>,
|
||||
renderCell: (j) => <Text size={200}>{formatSeconds(j.find_time)}</Text>,
|
||||
}),
|
||||
createTableColumn<JailSummary>({
|
||||
columnId: "banTime",
|
||||
renderHeaderCell: () => "Ban Time",
|
||||
renderCell: (j) => <Text size={200}>{fmtSeconds(j.ban_time)}</Text>,
|
||||
renderCell: (j) => <Text size={200}>{formatSeconds(j.ban_time)}</Text>,
|
||||
}),
|
||||
createTableColumn<JailSummary>({
|
||||
columnId: "maxRetry",
|
||||
|
||||
@@ -130,3 +130,34 @@ export function formatRelative(
|
||||
return formatDate(isoUtc, timezone);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Format an ISO 8601 timestamp for display with local browser timezone.
|
||||
*
|
||||
* Keeps parity with existing code paths in the UI that render full date+time
|
||||
* strings inside table rows.
|
||||
*/
|
||||
export function formatTimestamp(iso: string): string {
|
||||
try {
|
||||
return new Date(iso).toLocaleString(undefined, {
|
||||
year: "numeric",
|
||||
month: "2-digit",
|
||||
day: "2-digit",
|
||||
hour: "2-digit",
|
||||
minute: "2-digit",
|
||||
second: "2-digit",
|
||||
});
|
||||
} catch {
|
||||
return iso;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Format a duration in seconds to a compact text representation.
|
||||
*/
|
||||
export function formatSeconds(seconds: number): string {
|
||||
if (seconds < 0) return "permanent";
|
||||
if (seconds < 60) return `${String(seconds)} s`;
|
||||
if (seconds < 3600) return `${String(Math.round(seconds / 60))} min`;
|
||||
return `${String(Math.round(seconds / 3600))} h`;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user