Standardise frontend hook fetch error handling and mark Task 12 done
This commit is contained in:
@@ -7,6 +7,7 @@
|
||||
|
||||
import { useCallback, useEffect, useRef, useState } from "react";
|
||||
import { fetchBanTrend } from "../api/dashboard";
|
||||
import { handleFetchError } from "../utils/fetchError";
|
||||
import type { BanTrendBucket, BanOriginFilter, TimeRange } from "../types/ban";
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
@@ -65,7 +66,7 @@ export function useBanTrend(
|
||||
})
|
||||
.catch((err: unknown) => {
|
||||
if (controller.signal.aborted) return;
|
||||
setError(err instanceof Error ? err.message : "Failed to fetch trend data");
|
||||
handleFetchError(err, setError, "Failed to fetch trend data");
|
||||
})
|
||||
.finally(() => {
|
||||
if (!controller.signal.aborted) {
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
|
||||
import { useCallback, useEffect, useRef, useState } from "react";
|
||||
import { fetchBans } from "../api/dashboard";
|
||||
import { handleFetchError } from "../utils/fetchError";
|
||||
import type { DashboardBanItem, TimeRange, BanOriginFilter } from "../types/ban";
|
||||
|
||||
/** Items per page for the ban table. */
|
||||
@@ -63,7 +64,7 @@ export function useBans(
|
||||
setBanItems(data.items);
|
||||
setTotal(data.total);
|
||||
} catch (err: unknown) {
|
||||
setError(err instanceof Error ? err.message : "Failed to fetch data");
|
||||
handleFetchError(err, setError, "Failed to fetch bans");
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
|
||||
@@ -14,12 +14,14 @@ import {
|
||||
updateBlocklist,
|
||||
updateSchedule,
|
||||
} from "../api/blocklist";
|
||||
import { handleFetchError } from "../utils/fetchError";
|
||||
import type {
|
||||
BlocklistSource,
|
||||
BlocklistSourceCreate,
|
||||
BlocklistSourceUpdate,
|
||||
ImportLogListResponse,
|
||||
ImportRunResult,
|
||||
PreviewResponse,
|
||||
ScheduleConfig,
|
||||
ScheduleInfo,
|
||||
} from "../types/blocklist";
|
||||
@@ -65,7 +67,7 @@ export function useBlocklists(): UseBlocklistsReturn {
|
||||
})
|
||||
.catch((err: unknown) => {
|
||||
if (!ctrl.signal.aborted) {
|
||||
setError(err instanceof Error ? err.message : "Failed to load blocklists");
|
||||
handleFetchError(err, setError, "Failed to load blocklists");
|
||||
setLoading(false);
|
||||
}
|
||||
});
|
||||
@@ -144,7 +146,7 @@ export function useSchedule(): UseScheduleReturn {
|
||||
setLoading(false);
|
||||
})
|
||||
.catch((err: unknown) => {
|
||||
setError(err instanceof Error ? err.message : "Failed to load schedule");
|
||||
handleFetchError(err, setError, "Failed to load schedule");
|
||||
setLoading(false);
|
||||
});
|
||||
}, []);
|
||||
@@ -200,7 +202,7 @@ export function useImportLog(
|
||||
})
|
||||
.catch((err: unknown) => {
|
||||
if (!ctrl.signal.aborted) {
|
||||
setError(err instanceof Error ? err.message : "Failed to load import log");
|
||||
handleFetchError(err, setError, "Failed to load import log");
|
||||
setLoading(false);
|
||||
}
|
||||
});
|
||||
@@ -242,7 +244,7 @@ export function useRunImport(): UseRunImportReturn {
|
||||
const result = await runImportNow();
|
||||
setLastResult(result);
|
||||
} catch (err: unknown) {
|
||||
setError(err instanceof Error ? err.message : "Import failed");
|
||||
handleFetchError(err, setError, "Import failed");
|
||||
} finally {
|
||||
setRunning(false);
|
||||
}
|
||||
|
||||
@@ -12,11 +12,13 @@ import {
|
||||
flushLogs,
|
||||
previewLog,
|
||||
reloadConfig,
|
||||
restartFail2Ban,
|
||||
testRegex,
|
||||
updateGlobalConfig,
|
||||
updateJailConfig,
|
||||
updateServerSettings,
|
||||
} from "../api/config";
|
||||
import { handleFetchError } from "../utils/fetchError";
|
||||
import type {
|
||||
AddLogPathRequest,
|
||||
GlobalConfig,
|
||||
@@ -65,9 +67,7 @@ export function useJailConfigs(): UseJailConfigsResult {
|
||||
setTotal(resp.total);
|
||||
})
|
||||
.catch((err: unknown) => {
|
||||
if (err instanceof Error && err.name !== "AbortError") {
|
||||
setError(err.message);
|
||||
}
|
||||
handleFetchError(err, setError, "Failed to fetch jail configs");
|
||||
})
|
||||
.finally(() => {
|
||||
setLoading(false);
|
||||
@@ -128,9 +128,7 @@ export function useJailConfigDetail(name: string): UseJailConfigDetailResult {
|
||||
setJail(resp.jail);
|
||||
})
|
||||
.catch((err: unknown) => {
|
||||
if (err instanceof Error && err.name !== "AbortError") {
|
||||
setError(err.message);
|
||||
}
|
||||
handleFetchError(err, setError, "Failed to fetch jail config");
|
||||
})
|
||||
.finally(() => {
|
||||
setLoading(false);
|
||||
@@ -191,9 +189,7 @@ export function useGlobalConfig(): UseGlobalConfigResult {
|
||||
fetchGlobalConfig()
|
||||
.then(setConfig)
|
||||
.catch((err: unknown) => {
|
||||
if (err instanceof Error && err.name !== "AbortError") {
|
||||
setError(err.message);
|
||||
}
|
||||
handleFetchError(err, setError, "Failed to fetch global config");
|
||||
})
|
||||
.finally(() => {
|
||||
setLoading(false);
|
||||
@@ -251,9 +247,7 @@ export function useServerSettings(): UseServerSettingsResult {
|
||||
setSettings(resp.settings);
|
||||
})
|
||||
.catch((err: unknown) => {
|
||||
if (err instanceof Error && err.name !== "AbortError") {
|
||||
setError(err.message);
|
||||
}
|
||||
handleFetchError(err, setError, "Failed to fetch server settings");
|
||||
})
|
||||
.finally(() => {
|
||||
setLoading(false);
|
||||
|
||||
@@ -13,6 +13,7 @@
|
||||
import { useCallback, useEffect, useRef, useState } from "react";
|
||||
import { fetchJails } from "../api/jails";
|
||||
import { fetchJailConfigs } from "../api/config";
|
||||
import { handleFetchError } from "../utils/fetchError";
|
||||
import type { JailConfig } from "../types/config";
|
||||
import type { JailSummary } from "../types/jail";
|
||||
|
||||
@@ -110,7 +111,7 @@ export function useConfigActiveStatus(): UseConfigActiveStatusResult {
|
||||
})
|
||||
.catch((err: unknown) => {
|
||||
if (ctrl.signal.aborted) return;
|
||||
setError(err instanceof Error ? err.message : "Failed to load status.");
|
||||
handleFetchError(err, setError, "Failed to load active status.");
|
||||
setLoading(false);
|
||||
});
|
||||
}, []);
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
* Generic config hook for loading and saving a single entity.
|
||||
*/
|
||||
import { useCallback, useEffect, useRef, useState } from "react";
|
||||
import { handleFetchError } from "../utils/fetchError";
|
||||
|
||||
export interface UseConfigItemResult<T, U> {
|
||||
data: T | null;
|
||||
@@ -46,7 +47,7 @@ export function useConfigItem<T, U>(
|
||||
})
|
||||
.catch((err: unknown) => {
|
||||
if (controller.signal.aborted) return;
|
||||
setError(err instanceof Error ? err.message : "Failed to load data");
|
||||
handleFetchError(err, setError, "Failed to load data");
|
||||
setLoading(false);
|
||||
});
|
||||
}, [fetchFn]);
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
|
||||
import { useCallback, useEffect, useRef, useState } from "react";
|
||||
import { fetchBansByCountry } from "../api/map";
|
||||
import { handleFetchError } from "../utils/fetchError";
|
||||
import type { DashboardBanItem, BanOriginFilter, TimeRange } from "../types/ban";
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
@@ -77,7 +78,7 @@ export function useDashboardCountryData(
|
||||
})
|
||||
.catch((err: unknown) => {
|
||||
if (controller.signal.aborted) return;
|
||||
setError(err instanceof Error ? err.message : "Failed to fetch data");
|
||||
handleFetchError(err, setError, "Failed to fetch dashboard country data");
|
||||
})
|
||||
.finally(() => {
|
||||
if (!controller.signal.aborted) {
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
|
||||
import { useCallback, useEffect, useRef, useState } from "react";
|
||||
import { fetchHistory, fetchIpHistory } from "../api/history";
|
||||
import { handleFetchError } from "../utils/fetchError";
|
||||
import type {
|
||||
HistoryBanItem,
|
||||
HistoryQuery,
|
||||
@@ -44,9 +45,7 @@ export function useHistory(query: HistoryQuery = {}): UseHistoryResult {
|
||||
setTotal(resp.total);
|
||||
})
|
||||
.catch((err: unknown) => {
|
||||
if (err instanceof Error && err.name !== "AbortError") {
|
||||
setError(err.message);
|
||||
}
|
||||
handleFetchError(err, setError, "Failed to fetch history");
|
||||
})
|
||||
.finally((): void => {
|
||||
setLoading(false);
|
||||
@@ -91,9 +90,7 @@ export function useIpHistory(ip: string): UseIpHistoryResult {
|
||||
setDetail(resp);
|
||||
})
|
||||
.catch((err: unknown) => {
|
||||
if (err instanceof Error && err.name !== "AbortError") {
|
||||
setError(err.message);
|
||||
}
|
||||
handleFetchError(err, setError, "Failed to fetch IP history");
|
||||
})
|
||||
.finally((): void => {
|
||||
setLoading(false);
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
|
||||
import { useCallback, useEffect, useRef, useState } from "react";
|
||||
import { fetchBansByJail } from "../api/dashboard";
|
||||
import { handleFetchError } from "../utils/fetchError";
|
||||
import type { BanOriginFilter, JailBanCount, TimeRange } from "../types/ban";
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
@@ -65,9 +66,7 @@ export function useJailDistribution(
|
||||
})
|
||||
.catch((err: unknown) => {
|
||||
if (controller.signal.aborted) return;
|
||||
setError(
|
||||
err instanceof Error ? err.message : "Failed to fetch jail distribution",
|
||||
);
|
||||
handleFetchError(err, setError, "Failed to fetch jail distribution");
|
||||
})
|
||||
.finally(() => {
|
||||
if (!controller.signal.aborted) {
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
*/
|
||||
|
||||
import { useCallback, useEffect, useRef, useState } from "react";
|
||||
import { handleFetchError } from "../utils/fetchError";
|
||||
import {
|
||||
addIgnoreIp,
|
||||
banIp,
|
||||
@@ -92,7 +93,7 @@ export function useJails(): UseJailsResult {
|
||||
})
|
||||
.catch((err: unknown) => {
|
||||
if (!ctrl.signal.aborted) {
|
||||
setError(err instanceof Error ? err.message : String(err));
|
||||
handleFetchError(err, setError, "Failed to load jails");
|
||||
}
|
||||
})
|
||||
.finally(() => {
|
||||
@@ -195,7 +196,7 @@ export function useJailDetail(name: string): UseJailDetailResult {
|
||||
})
|
||||
.catch((err: unknown) => {
|
||||
if (!ctrl.signal.aborted) {
|
||||
setError(err instanceof Error ? err.message : String(err));
|
||||
handleFetchError(err, setError, "Failed to fetch jail detail");
|
||||
}
|
||||
})
|
||||
.finally(() => {
|
||||
@@ -309,7 +310,7 @@ export function useJailBannedIps(jailName: string): UseJailBannedIpsResult {
|
||||
setItems(resp.items);
|
||||
setTotal(resp.total);
|
||||
} catch (err: unknown) {
|
||||
setError(err instanceof Error ? err.message : String(err));
|
||||
handleFetchError(err, setError, "Failed to fetch jailed IPs");
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
@@ -415,7 +416,7 @@ export function useActiveBans(): UseActiveBansResult {
|
||||
})
|
||||
.catch((err: unknown) => {
|
||||
if (!ctrl.signal.aborted) {
|
||||
setError(err instanceof Error ? err.message : String(err));
|
||||
handleFetchError(err, setError, "Failed to fetch active bans");
|
||||
}
|
||||
})
|
||||
.finally(() => {
|
||||
@@ -496,7 +497,7 @@ export function useIpLookup(): UseIpLookupResult {
|
||||
setResult(res);
|
||||
})
|
||||
.catch((err: unknown) => {
|
||||
setError(err instanceof Error ? err.message : String(err));
|
||||
handleFetchError(err, setError, "Failed to lookup IP");
|
||||
})
|
||||
.finally(() => {
|
||||
setLoading(false);
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { useCallback, useEffect, useState } from "react";
|
||||
import { fetchMapColorThresholds, updateMapColorThresholds } from "../api/config";
|
||||
import { handleFetchError } from "../utils/fetchError";
|
||||
import type {
|
||||
MapColorThresholdsResponse,
|
||||
MapColorThresholdsUpdate,
|
||||
@@ -26,7 +27,7 @@ export function useMapColorThresholds(): UseMapColorThresholdsResult {
|
||||
const data = await fetchMapColorThresholds();
|
||||
setThresholds(data);
|
||||
} catch (err: unknown) {
|
||||
setError(err instanceof Error ? err.message : "Failed to fetch map color thresholds");
|
||||
handleFetchError(err, setError, "Failed to fetch map color thresholds");
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
|
||||
import { useCallback, useEffect, useRef, useState } from "react";
|
||||
import { fetchBansByCountry } from "../api/map";
|
||||
import { handleFetchError } from "../utils/fetchError";
|
||||
import type { BansByCountryResponse, MapBanItem, TimeRange } from "../types/map";
|
||||
import type { BanOriginFilter } from "../types/ban";
|
||||
|
||||
@@ -68,9 +69,7 @@ export function useMapData(
|
||||
setData(resp);
|
||||
})
|
||||
.catch((err: unknown) => {
|
||||
if (err instanceof Error && err.name !== "AbortError") {
|
||||
setError(err.message);
|
||||
}
|
||||
handleFetchError(err, setError, "Failed to fetch map data");
|
||||
})
|
||||
.finally((): void => {
|
||||
setLoading(false);
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
|
||||
import { useCallback, useEffect, useRef, useState } from "react";
|
||||
import { fetchServerStatus } from "../api/dashboard";
|
||||
import { handleFetchError } from "../utils/fetchError";
|
||||
import type { ServerStatus } from "../types/server";
|
||||
|
||||
/** How often to poll the status endpoint (milliseconds). */
|
||||
@@ -49,7 +50,7 @@ export function useServerStatus(): UseServerStatusResult {
|
||||
setBanguiVersion(data.bangui_version);
|
||||
setError(null);
|
||||
} catch (err: unknown) {
|
||||
setError(err instanceof Error ? err.message : "Failed to fetch server status");
|
||||
handleFetchError(err, setError, "Failed to fetch server status");
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
|
||||
import { useCallback, useEffect, useState } from "react";
|
||||
import { ApiError } from "../api/client";
|
||||
import { handleFetchError } from "../utils/fetchError";
|
||||
import { getSetupStatus, submitSetup } from "../api/setup";
|
||||
import type {
|
||||
SetupRequest,
|
||||
@@ -44,9 +45,11 @@ export function useSetup(): UseSetupResult {
|
||||
const resp = await getSetupStatus();
|
||||
setStatus(resp);
|
||||
} catch (err: unknown) {
|
||||
const errorMessage = err instanceof Error ? err.message : "Failed to fetch setup status";
|
||||
console.warn("Setup status check failed:", errorMessage);
|
||||
setError(errorMessage);
|
||||
const fallback = "Failed to fetch setup status";
|
||||
handleFetchError(err, setError, fallback);
|
||||
if (!(err instanceof DOMException && err.name === "AbortError")) {
|
||||
console.warn("Setup status check failed:", err instanceof Error ? err.message : fallback);
|
||||
}
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { useCallback, useEffect, useState } from "react";
|
||||
import { fetchTimezone } from "../api/setup";
|
||||
import { handleFetchError } from "../utils/fetchError";
|
||||
|
||||
export interface UseTimezoneDataResult {
|
||||
timezone: string;
|
||||
@@ -21,7 +22,7 @@ export function useTimezoneData(): UseTimezoneDataResult {
|
||||
const resp = await fetchTimezone();
|
||||
setTimezone(resp.timezone);
|
||||
} catch (err: unknown) {
|
||||
setError(err instanceof Error ? err.message : "Failed to fetch timezone");
|
||||
handleFetchError(err, setError, "Failed to fetch timezone");
|
||||
setTimezone("UTC");
|
||||
} finally {
|
||||
setLoading(false);
|
||||
|
||||
14
frontend/src/utils/fetchError.ts
Normal file
14
frontend/src/utils/fetchError.ts
Normal file
@@ -0,0 +1,14 @@
|
||||
/**
|
||||
* Normalize fetch error handling across hooks.
|
||||
*/
|
||||
export function handleFetchError(
|
||||
err: unknown,
|
||||
setError: (value: string | null) => void,
|
||||
fallback: string = "Unknown error",
|
||||
): void {
|
||||
if (err instanceof DOMException && err.name === "AbortError") {
|
||||
return;
|
||||
}
|
||||
|
||||
setError(err instanceof Error ? err.message : fallback);
|
||||
}
|
||||
Reference in New Issue
Block a user