Refactor frontend API calls into hooks and complete task states

This commit is contained in:
2026-03-20 15:18:04 +01:00
parent d30d138146
commit 28a7610276
16 changed files with 483 additions and 409 deletions

View File

@@ -51,7 +51,6 @@ import {
useRunImport,
useSchedule,
} from "../hooks/useBlocklist";
import { previewBlocklist } from "../api/blocklist";
import type {
BlocklistSource,
ImportRunResult,
@@ -262,9 +261,10 @@ interface PreviewDialogProps {
open: boolean;
source: BlocklistSource | null;
onClose: () => void;
fetchPreview: (id: number) => Promise<PreviewResponse>;
}
function PreviewDialog({ open, source, onClose }: PreviewDialogProps): React.JSX.Element {
function PreviewDialog({ open, source, onClose, fetchPreview }: PreviewDialogProps): React.JSX.Element {
const styles = useStyles();
const [data, setData] = useState<PreviewResponse | null>(null);
const [loading, setLoading] = useState(false);
@@ -276,7 +276,7 @@ function PreviewDialog({ open, source, onClose }: PreviewDialogProps): React.JSX
setData(null);
setError(null);
setLoading(true);
previewBlocklist(source.id)
fetchPreview(source.id)
.then((result) => {
setData(result);
setLoading(false);
@@ -285,7 +285,7 @@ function PreviewDialog({ open, source, onClose }: PreviewDialogProps): React.JSX
setError(err instanceof Error ? err.message : "Failed to fetch preview");
setLoading(false);
});
}, [source]);
}, [source, fetchPreview]);
return (
<Dialog
@@ -400,7 +400,7 @@ interface SourcesSectionProps {
function SourcesSection({ onRunImport, runImportRunning }: SourcesSectionProps): React.JSX.Element {
const styles = useStyles();
const { sources, loading, error, refresh, createSource, updateSource, removeSource } =
const { sources, loading, error, refresh, createSource, updateSource, removeSource, previewSource } =
useBlocklists();
const [dialogOpen, setDialogOpen] = useState(false);
@@ -410,7 +410,7 @@ function SourcesSection({ onRunImport, runImportRunning }: SourcesSectionProps):
const [saving, setSaving] = useState(false);
const [saveError, setSaveError] = useState<string | null>(null);
const [previewOpen, setPreviewOpen] = useState(false);
const [previewSource, setPreviewSource] = useState<BlocklistSource | null>(null);
const [previewSourceItem, setPreviewSourceItem] = useState<BlocklistSource | null>(null);
const openAdd = useCallback((): void => {
setDialogMode("add");
@@ -466,7 +466,7 @@ function SourcesSection({ onRunImport, runImportRunning }: SourcesSectionProps):
);
const handlePreview = useCallback((source: BlocklistSource): void => {
setPreviewSource(source);
setPreviewSourceItem(source);
setPreviewOpen(true);
}, []);
@@ -594,10 +594,11 @@ function SourcesSection({ onRunImport, runImportRunning }: SourcesSectionProps):
<PreviewDialog
open={previewOpen}
source={previewSource}
source={previewSourceItem}
onClose={() => {
setPreviewOpen(false);
}}
fetchPreview={previewSource}
/>
</div>
);

View File

@@ -33,7 +33,7 @@ import {
StopRegular,
} from "@fluentui/react-icons";
import { Link, useNavigate, useParams } from "react-router-dom";
import { useJailDetail } from "../hooks/useJails";
import { useJailDetail, useJailBannedIps } from "../hooks/useJails";
import type { Jail } from "../types/jail";
import { BannedIpsSection } from "../components/jail/BannedIpsSection";
@@ -574,6 +574,21 @@ export function JailDetailPage(): React.JSX.Element {
const { name = "" } = useParams<{ name: string }>();
const { jail, ignoreList, ignoreSelf, loading, error, refresh, addIp, removeIp, toggleIgnoreSelf, start, stop, reload, setIdle } =
useJailDetail(name);
const {
items,
total,
page,
pageSize,
search,
loading: bannedLoading,
error: bannedError,
opError,
refresh: refreshBanned,
setPage,
setPageSize,
setSearch,
unban,
} = useJailBannedIps(name);
if (loading && !jail) {
return (
@@ -618,7 +633,21 @@ export function JailDetailPage(): React.JSX.Element {
</div>
<JailInfoSection jail={jail} onRefresh={refresh} onStart={start} onStop={stop} onReload={reload} onSetIdle={setIdle} />
<BannedIpsSection jailName={name} />
<BannedIpsSection
items={items}
total={total}
page={page}
pageSize={pageSize}
search={search}
loading={bannedLoading}
error={bannedError}
opError={opError}
onSearch={setSearch}
onPageChange={setPage}
onPageSizeChange={setPageSize}
onRefresh={refreshBanned}
onUnban={unban}
/>
<PatternsSection jail={jail} />
<BantimeEscalationSection jail={jail} />
<IgnoreListSection

View File

@@ -29,7 +29,7 @@ import { ArrowCounterclockwiseRegular, DismissRegular } from "@fluentui/react-ic
import { DashboardFilterBar } from "../components/DashboardFilterBar";
import { WorldMap } from "../components/WorldMap";
import { useMapData } from "../hooks/useMapData";
import { fetchMapColorThresholds } from "../api/config";
import { useMapColorThresholds } from "../hooks/useMapColorThresholds";
import type { TimeRange } from "../types/map";
import type { BanOriginFilter } from "../types/ban";
@@ -79,28 +79,25 @@ export function MapPage(): React.JSX.Element {
const [range, setRange] = useState<TimeRange>("24h");
const [originFilter, setOriginFilter] = useState<BanOriginFilter>("all");
const [selectedCountry, setSelectedCountry] = useState<string | null>(null);
const [thresholdLow, setThresholdLow] = useState<number>(20);
const [thresholdMedium, setThresholdMedium] = useState<number>(50);
const [thresholdHigh, setThresholdHigh] = useState<number>(100);
const { countries, countryNames, bans, total, loading, error, refresh } =
useMapData(range, originFilter);
// Fetch color thresholds on mount
const {
thresholds: mapThresholds,
error: mapThresholdError,
} = useMapColorThresholds();
const thresholdLow = mapThresholds?.threshold_low ?? 20;
const thresholdMedium = mapThresholds?.threshold_medium ?? 50;
const thresholdHigh = mapThresholds?.threshold_high ?? 100;
useEffect(() => {
const loadThresholds = async (): Promise<void> => {
try {
const thresholds = await fetchMapColorThresholds();
setThresholdLow(thresholds.threshold_low);
setThresholdMedium(thresholds.threshold_medium);
setThresholdHigh(thresholds.threshold_high);
} catch (err) {
// Silently fall back to defaults if fetch fails
console.warn("Failed to load map color thresholds:", err);
}
};
void loadThresholds();
}, []);
if (mapThresholdError) {
// Silently fall back to defaults if fetch fails
console.warn("Failed to load map color thresholds:", mapThresholdError);
}
}, [mapThresholdError]);
/** Bans visible in the companion table (filtered by selected country). */
const visibleBans = useMemo(() => {

View File

@@ -41,6 +41,21 @@ const {
// Mock the jail detail hook — tests control the returned state directly.
vi.mock("../../hooks/useJails", () => ({
useJailDetail: vi.fn(),
useJailBannedIps: vi.fn(() => ({
items: [],
total: 0,
page: 1,
pageSize: 25,
search: "",
loading: false,
error: null,
opError: null,
refresh: vi.fn(),
setPage: vi.fn(),
setPageSize: vi.fn(),
setSearch: vi.fn(),
unban: vi.fn(),
})),
}));
// Mock API functions used by JailInfoSection control buttons to avoid side effects.