Task 11: Remove direct API calls from components
This commit is contained in:
91
frontend/src/hooks/useActionList.ts
Normal file
91
frontend/src/hooks/useActionList.ts
Normal file
@@ -0,0 +1,91 @@
|
||||
/**
|
||||
* React hook for loading action metadata used by the actions tab.
|
||||
*/
|
||||
import { useCallback, useEffect, useRef, useState } from "react";
|
||||
import { fetchActions, removeActionFromJail, createAction, assignActionToJail } from "../api/config";
|
||||
import { handleFetchError } from "../utils/fetchError";
|
||||
import type { ActionConfig, ActionCreateRequest } from "../types/config";
|
||||
|
||||
export interface UseActionListResult {
|
||||
actions: ActionConfig[];
|
||||
loading: boolean;
|
||||
error: string | null;
|
||||
refresh: () => void;
|
||||
removeActionFromJail: (jailName: string, actionName: string) => Promise<void>;
|
||||
createAction: (payload: ActionCreateRequest) => Promise<ActionConfig>;
|
||||
assignActionToJail: (jailName: string, payload: { action_name: string }, reload: boolean) => Promise<void>;
|
||||
}
|
||||
|
||||
/**
|
||||
* Load the action inventory and expose related action operations.
|
||||
*/
|
||||
export function useActionList(): UseActionListResult {
|
||||
const [actions, setActions] = useState<ActionConfig[]>([]);
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
const abortRef = useRef<AbortController | null>(null);
|
||||
|
||||
const refresh = useCallback((): void => {
|
||||
abortRef.current?.abort();
|
||||
const controller = new AbortController();
|
||||
abortRef.current = controller;
|
||||
|
||||
setLoading(true);
|
||||
setError(null);
|
||||
|
||||
fetchActions()
|
||||
.then((resp) => {
|
||||
if (!controller.signal.aborted) {
|
||||
setActions(resp.actions);
|
||||
}
|
||||
})
|
||||
.catch((err: unknown) => {
|
||||
if (!controller.signal.aborted) {
|
||||
handleFetchError(err, setError, "Failed to load actions");
|
||||
}
|
||||
})
|
||||
.finally(() => {
|
||||
if (!controller.signal.aborted) {
|
||||
setLoading(false);
|
||||
}
|
||||
});
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
refresh();
|
||||
return (): void => {
|
||||
abortRef.current?.abort();
|
||||
};
|
||||
}, [refresh]);
|
||||
|
||||
const handleRemoveActionFromJail = useCallback(
|
||||
async (jailName: string, actionName: string): Promise<void> => {
|
||||
await removeActionFromJail(jailName, actionName);
|
||||
},
|
||||
[],
|
||||
);
|
||||
|
||||
const handleCreateAction = useCallback(
|
||||
async (payload: ActionCreateRequest): Promise<ActionConfig> => {
|
||||
return await createAction(payload);
|
||||
},
|
||||
[],
|
||||
);
|
||||
|
||||
const handleAssignActionToJail = useCallback(
|
||||
async (jailName: string, payload: { action_name: string }, reload: boolean): Promise<void> => {
|
||||
await assignActionToJail(jailName, payload, reload);
|
||||
},
|
||||
[],
|
||||
);
|
||||
|
||||
return {
|
||||
actions,
|
||||
loading,
|
||||
error,
|
||||
refresh,
|
||||
removeActionFromJail: handleRemoveActionFromJail,
|
||||
createAction: handleCreateAction,
|
||||
assignActionToJail: handleAssignActionToJail,
|
||||
};
|
||||
}
|
||||
32
frontend/src/hooks/useActionRawFile.ts
Normal file
32
frontend/src/hooks/useActionRawFile.ts
Normal file
@@ -0,0 +1,32 @@
|
||||
/**
|
||||
* React hook for loading and saving a single raw action file.
|
||||
*/
|
||||
import { useCallback } from "react";
|
||||
import { fetchActionFile, updateActionFile } from "../api/file_config";
|
||||
|
||||
export interface UseActionRawFileResult {
|
||||
fetchRawContent: () => Promise<string>;
|
||||
saveRawContent: (content: string) => Promise<void>;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return raw config file operations for an action file.
|
||||
*/
|
||||
export function useActionRawFile(name: string): UseActionRawFileResult {
|
||||
const fetchRawContent = useCallback(async (): Promise<string> => {
|
||||
const result = await fetchActionFile(name);
|
||||
return result.content;
|
||||
}, [name]);
|
||||
|
||||
const saveRawContent = useCallback(
|
||||
async (content: string): Promise<void> => {
|
||||
await updateActionFile(name, { content });
|
||||
},
|
||||
[name],
|
||||
);
|
||||
|
||||
return {
|
||||
fetchRawContent,
|
||||
saveRawContent,
|
||||
};
|
||||
}
|
||||
82
frontend/src/hooks/useFilterList.ts
Normal file
82
frontend/src/hooks/useFilterList.ts
Normal file
@@ -0,0 +1,82 @@
|
||||
/**
|
||||
* React hook for loading filter config metadata used by the filter tab.
|
||||
*/
|
||||
import { useCallback, useEffect, useRef, useState } from "react";
|
||||
import { fetchFilters, createFilter, assignFilterToJail } from "../api/config";
|
||||
import { handleFetchError } from "../utils/fetchError";
|
||||
import type { FilterConfig, FilterCreateRequest } from "../types/config";
|
||||
|
||||
export interface UseFilterListResult {
|
||||
filters: FilterConfig[];
|
||||
loading: boolean;
|
||||
error: string | null;
|
||||
refresh: () => void;
|
||||
createFilter: (payload: FilterCreateRequest) => Promise<FilterConfig>;
|
||||
assignFilterToJail: (jailName: string, payload: { filter_name: string }, reload: boolean) => Promise<void>;
|
||||
}
|
||||
|
||||
/**
|
||||
* Load the filter inventory and expose refresh semantics.
|
||||
*/
|
||||
export function useFilterList(): UseFilterListResult {
|
||||
const [filters, setFilters] = useState<FilterConfig[]>([]);
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
const abortRef = useRef<AbortController | null>(null);
|
||||
|
||||
const refresh = useCallback((): void => {
|
||||
abortRef.current?.abort();
|
||||
const controller = new AbortController();
|
||||
abortRef.current = controller;
|
||||
|
||||
setLoading(true);
|
||||
setError(null);
|
||||
|
||||
fetchFilters()
|
||||
.then((resp) => {
|
||||
if (!controller.signal.aborted) {
|
||||
setFilters(resp.filters);
|
||||
}
|
||||
})
|
||||
.catch((err: unknown) => {
|
||||
if (!controller.signal.aborted) {
|
||||
handleFetchError(err, setError, "Failed to load filters");
|
||||
}
|
||||
})
|
||||
.finally(() => {
|
||||
if (!controller.signal.aborted) {
|
||||
setLoading(false);
|
||||
}
|
||||
});
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
refresh();
|
||||
return (): void => {
|
||||
abortRef.current?.abort();
|
||||
};
|
||||
}, [refresh]);
|
||||
|
||||
const handleCreateFilter = useCallback(
|
||||
async (payload: FilterCreateRequest): Promise<FilterConfig> => {
|
||||
return await createFilter(payload);
|
||||
},
|
||||
[],
|
||||
);
|
||||
|
||||
const handleAssignFilterToJail = useCallback(
|
||||
async (jailName: string, payload: { filter_name: string }, reload: boolean): Promise<void> => {
|
||||
await assignFilterToJail(jailName, payload, reload);
|
||||
},
|
||||
[],
|
||||
);
|
||||
|
||||
return {
|
||||
filters,
|
||||
loading,
|
||||
error,
|
||||
refresh,
|
||||
createFilter: handleCreateFilter,
|
||||
assignFilterToJail: handleAssignFilterToJail,
|
||||
};
|
||||
}
|
||||
32
frontend/src/hooks/useFilterRawFile.ts
Normal file
32
frontend/src/hooks/useFilterRawFile.ts
Normal file
@@ -0,0 +1,32 @@
|
||||
/**
|
||||
* React hook for loading and saving a single raw filter file.
|
||||
*/
|
||||
import { useCallback } from "react";
|
||||
import { fetchFilterFile, updateFilterFile } from "../api/file_config";
|
||||
|
||||
export interface UseFilterRawFileResult {
|
||||
fetchRawContent: () => Promise<string>;
|
||||
saveRawContent: (content: string) => Promise<void>;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return raw config file operations for a filter file.
|
||||
*/
|
||||
export function useFilterRawFile(name: string): UseFilterRawFileResult {
|
||||
const fetchRawContent = useCallback(async (): Promise<string> => {
|
||||
const result = await fetchFilterFile(name);
|
||||
return result.content;
|
||||
}, [name]);
|
||||
|
||||
const saveRawContent = useCallback(
|
||||
async (content: string): Promise<void> => {
|
||||
await updateFilterFile(name, { content });
|
||||
},
|
||||
[name],
|
||||
);
|
||||
|
||||
return {
|
||||
fetchRawContent,
|
||||
saveRawContent,
|
||||
};
|
||||
}
|
||||
123
frontend/src/hooks/useJailAdmin.ts
Normal file
123
frontend/src/hooks/useJailAdmin.ts
Normal file
@@ -0,0 +1,123 @@
|
||||
/**
|
||||
* React hook for managing inactive jail operations and configuration actions.
|
||||
*/
|
||||
import { useCallback, useEffect, useRef, useState } from "react";
|
||||
import {
|
||||
activateJail,
|
||||
deactivateJail,
|
||||
deleteJailLocalOverride,
|
||||
fetchInactiveJails,
|
||||
validateJailConfig,
|
||||
createJailConfigFile,
|
||||
} from "../api/config";
|
||||
import { handleFetchError } from "../utils/fetchError";
|
||||
import type {
|
||||
ActivateJailRequest,
|
||||
ConfFileCreateRequest,
|
||||
InactiveJail,
|
||||
JailActivationResponse,
|
||||
JailValidationResult,
|
||||
} from "../types/config";
|
||||
|
||||
export interface UseJailAdminResult {
|
||||
inactiveJails: InactiveJail[];
|
||||
inactiveLoading: boolean;
|
||||
inactiveError: string | null;
|
||||
refreshInactiveJails: () => void;
|
||||
deactivateJail: (name: string) => Promise<void>;
|
||||
deleteJailLocalOverride: (name: string) => Promise<void>;
|
||||
validateJailConfig: (name: string) => Promise<JailValidationResult>;
|
||||
activateJail: (name: string, payload: ActivateJailRequest) => Promise<JailActivationResponse>;
|
||||
createJailConfigFile: (payload: ConfFileCreateRequest) => Promise<void>;
|
||||
}
|
||||
|
||||
/**
|
||||
* Load inactive fail2ban jails and expose the admin actions used by the
|
||||
* jail configuration tab.
|
||||
*/
|
||||
export function useJailAdmin(): UseJailAdminResult {
|
||||
const [inactiveJails, setInactiveJails] = useState<InactiveJail[]>([]);
|
||||
const [inactiveLoading, setInactiveLoading] = useState(false);
|
||||
const [inactiveError, setInactiveError] = useState<string | null>(null);
|
||||
const abortRef = useRef<AbortController | null>(null);
|
||||
|
||||
const refreshInactiveJails = useCallback((): void => {
|
||||
abortRef.current?.abort();
|
||||
const ctrl = new AbortController();
|
||||
abortRef.current = ctrl;
|
||||
|
||||
setInactiveLoading(true);
|
||||
setInactiveError(null);
|
||||
|
||||
fetchInactiveJails()
|
||||
.then((resp) => {
|
||||
if (!ctrl.signal.aborted) {
|
||||
setInactiveJails(resp.jails);
|
||||
}
|
||||
})
|
||||
.catch((err: unknown) => {
|
||||
if (!ctrl.signal.aborted) {
|
||||
handleFetchError(err, setInactiveError, "Failed to load inactive jails");
|
||||
}
|
||||
})
|
||||
.finally(() => {
|
||||
if (!ctrl.signal.aborted) {
|
||||
setInactiveLoading(false);
|
||||
}
|
||||
});
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
refreshInactiveJails();
|
||||
return (): void => {
|
||||
abortRef.current?.abort();
|
||||
};
|
||||
}, [refreshInactiveJails]);
|
||||
|
||||
const handleDeactivateJail = useCallback(
|
||||
async (name: string): Promise<void> => {
|
||||
await deactivateJail(name);
|
||||
},
|
||||
[],
|
||||
);
|
||||
|
||||
const handleDeleteLocalOverride = useCallback(
|
||||
async (name: string): Promise<void> => {
|
||||
await deleteJailLocalOverride(name);
|
||||
},
|
||||
[],
|
||||
);
|
||||
|
||||
const handleValidateJailConfig = useCallback(
|
||||
async (name: string): Promise<JailValidationResult> => {
|
||||
return await validateJailConfig(name);
|
||||
},
|
||||
[],
|
||||
);
|
||||
|
||||
const handleActivateJail = useCallback(
|
||||
async (name: string, payload: ActivateJailRequest): Promise<JailActivationResponse> => {
|
||||
return await activateJail(name, payload);
|
||||
},
|
||||
[],
|
||||
);
|
||||
|
||||
const handleCreateJailConfigFile = useCallback(
|
||||
async (payload: ConfFileCreateRequest): Promise<void> => {
|
||||
await createJailConfigFile(payload);
|
||||
},
|
||||
[],
|
||||
);
|
||||
|
||||
return {
|
||||
inactiveJails,
|
||||
inactiveLoading,
|
||||
inactiveError,
|
||||
refreshInactiveJails,
|
||||
deactivateJail: handleDeactivateJail,
|
||||
deleteJailLocalOverride: handleDeleteLocalOverride,
|
||||
validateJailConfig: handleValidateJailConfig,
|
||||
activateJail: handleActivateJail,
|
||||
createJailConfigFile: handleCreateJailConfigFile,
|
||||
};
|
||||
}
|
||||
57
frontend/src/hooks/useJailConfigOperations.ts
Normal file
57
frontend/src/hooks/useJailConfigOperations.ts
Normal file
@@ -0,0 +1,57 @@
|
||||
/**
|
||||
* React hook for performing jail-specific configuration operations.
|
||||
*/
|
||||
import { useCallback } from "react";
|
||||
import {
|
||||
addLogPath,
|
||||
deleteLogPath,
|
||||
fetchJailConfigFileContent,
|
||||
updateJailConfigFile,
|
||||
} from "../api/config";
|
||||
import type { AddLogPathRequest } from "../types/config";
|
||||
|
||||
export interface UseJailConfigOperationsResult {
|
||||
addLogPath: (payload: AddLogPathRequest) => Promise<void>;
|
||||
deleteLogPath: (path: string) => Promise<void>;
|
||||
fetchRawContent: () => Promise<string>;
|
||||
saveRawContent: (content: string) => Promise<void>;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create callbacks for jail-specific config operations that are used by
|
||||
* jail config detail components.
|
||||
*/
|
||||
export function useJailConfigOperations(jailName: string): UseJailConfigOperationsResult {
|
||||
const addLog = useCallback(
|
||||
async (payload: AddLogPathRequest): Promise<void> => {
|
||||
await addLogPath(jailName, payload);
|
||||
},
|
||||
[jailName],
|
||||
);
|
||||
|
||||
const deletePath = useCallback(
|
||||
async (path: string): Promise<void> => {
|
||||
await deleteLogPath(jailName, path);
|
||||
},
|
||||
[jailName],
|
||||
);
|
||||
|
||||
const fetchRawContent = useCallback(async (): Promise<string> => {
|
||||
const result = await fetchJailConfigFileContent(`${jailName}.conf`);
|
||||
return result.content;
|
||||
}, [jailName]);
|
||||
|
||||
const saveRawContent = useCallback(
|
||||
async (content: string): Promise<void> => {
|
||||
await updateJailConfigFile(`${jailName}.conf`, { content });
|
||||
},
|
||||
[jailName],
|
||||
);
|
||||
|
||||
return {
|
||||
addLogPath: addLog,
|
||||
deleteLogPath: deletePath,
|
||||
fetchRawContent,
|
||||
saveRawContent,
|
||||
};
|
||||
}
|
||||
53
frontend/src/hooks/useServerHealth.ts
Normal file
53
frontend/src/hooks/useServerHealth.ts
Normal file
@@ -0,0 +1,53 @@
|
||||
/**
|
||||
* React hook for service health and log viewer data fetching.
|
||||
*/
|
||||
import { useCallback, useState } from "react";
|
||||
import { fetchFail2BanLog, fetchServiceStatus } from "../api/config";
|
||||
import type { Fail2BanLogResponse, ServiceStatusResponse } from "../types/config";
|
||||
|
||||
export interface UseServerHealthResult {
|
||||
status: ServiceStatusResponse | null;
|
||||
logData: Fail2BanLogResponse | null;
|
||||
error: string | null;
|
||||
refresh: () => Promise<void>;
|
||||
}
|
||||
|
||||
/**
|
||||
* Load service status and fail2ban log data for the server health panel.
|
||||
*/
|
||||
export function useServerHealth(
|
||||
linesCount: number,
|
||||
filterValue: string,
|
||||
): UseServerHealthResult {
|
||||
const [status, setStatus] = useState<ServiceStatusResponse | null>(null);
|
||||
const [logData, setLogData] = useState<Fail2BanLogResponse | null>(null);
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
|
||||
const refresh = useCallback(async (): Promise<void> => {
|
||||
try {
|
||||
const [svcResult, logResult] = await Promise.allSettled([
|
||||
fetchServiceStatus(),
|
||||
fetchFail2BanLog(linesCount, filterValue || undefined),
|
||||
]);
|
||||
|
||||
if (svcResult.status === "fulfilled") {
|
||||
setStatus(svcResult.value);
|
||||
} else {
|
||||
setStatus(null);
|
||||
}
|
||||
|
||||
if (logResult.status === "fulfilled") {
|
||||
setLogData(logResult.value);
|
||||
setError(null);
|
||||
} else {
|
||||
const reason: unknown = logResult.reason;
|
||||
setError(reason instanceof Error ? reason.message : "Failed to load log data.");
|
||||
}
|
||||
} catch (err: unknown) {
|
||||
const reason = err instanceof Error ? err.message : String(err);
|
||||
setError(reason);
|
||||
}
|
||||
}, [filterValue, linesCount]);
|
||||
|
||||
return { status, logData, error, refresh };
|
||||
}
|
||||
Reference in New Issue
Block a user