Task 11: Remove direct API calls from components
This commit is contained in:
@@ -32,19 +32,8 @@ import {
|
||||
Play24Regular,
|
||||
} from "@fluentui/react-icons";
|
||||
import { ApiError } from "../../api/client";
|
||||
import {
|
||||
addLogPath,
|
||||
deactivateJail,
|
||||
deleteJailLocalOverride,
|
||||
deleteLogPath,
|
||||
fetchInactiveJails,
|
||||
fetchJailConfigFileContent,
|
||||
updateJailConfigFile,
|
||||
validateJailConfig,
|
||||
} from "../../api/config";
|
||||
import type {
|
||||
AddLogPathRequest,
|
||||
ConfFileUpdateRequest,
|
||||
InactiveJail,
|
||||
JailConfig,
|
||||
JailConfigUpdate,
|
||||
@@ -54,6 +43,8 @@ import type {
|
||||
import { useAutoSave } from "../../hooks/useAutoSave";
|
||||
import { useConfigActiveStatus } from "../../hooks/useConfigActiveStatus";
|
||||
import { useJailConfigs } from "../../hooks/useConfig";
|
||||
import { useJailAdmin } from "../../hooks/useJailAdmin";
|
||||
import { useJailConfigOperations } from "../../hooks/useJailConfigOperations";
|
||||
import { ActivateJailDialog } from "./ActivateJailDialog";
|
||||
import { AutoSaveIndicator } from "./AutoSaveIndicator";
|
||||
import { ConfigListDetail } from "./ConfigListDetail";
|
||||
@@ -166,12 +157,19 @@ function JailConfigDetail({
|
||||
esc0?.overall_jails ?? false,
|
||||
);
|
||||
|
||||
const {
|
||||
addLogPath: addLogPathToJail,
|
||||
deleteLogPath: deleteJailLogPath,
|
||||
fetchRawContent,
|
||||
saveRawContent,
|
||||
} = useJailConfigOperations(jail.name);
|
||||
|
||||
const handleDeleteLogPath = useCallback(
|
||||
async (path: string) => {
|
||||
setDeletingPath(path);
|
||||
setMsg(null);
|
||||
try {
|
||||
await deleteLogPath(jail.name, path);
|
||||
await deleteJailLogPath(path);
|
||||
setLogPaths((prev) => prev.filter((p) => p !== path));
|
||||
setMsg({ text: `Removed log path: ${path}`, ok: true });
|
||||
} catch (err: unknown) {
|
||||
@@ -183,7 +181,7 @@ function JailConfigDetail({
|
||||
setDeletingPath(null);
|
||||
}
|
||||
},
|
||||
[jail.name],
|
||||
[deleteJailLogPath],
|
||||
);
|
||||
|
||||
const handleAddLogPath = useCallback(async () => {
|
||||
@@ -193,7 +191,7 @@ function JailConfigDetail({
|
||||
setMsg(null);
|
||||
try {
|
||||
const req: AddLogPathRequest = { log_path: trimmed, tail: newLogPathTail };
|
||||
await addLogPath(jail.name, req);
|
||||
await addLogPathToJail(req);
|
||||
setLogPaths((prev) => [...prev, trimmed]);
|
||||
setNewLogPath("");
|
||||
setMsg({ text: `Added log path: ${trimmed}`, ok: true });
|
||||
@@ -205,7 +203,7 @@ function JailConfigDetail({
|
||||
} finally {
|
||||
setAddingLogPath(false);
|
||||
}
|
||||
}, [jail.name, newLogPath, newLogPathTail]);
|
||||
}, [addLogPathToJail, newLogPath, newLogPathTail]);
|
||||
|
||||
const autoSavePayload = useMemo<JailConfigUpdate>(
|
||||
() => ({
|
||||
@@ -249,16 +247,14 @@ function JailConfigDetail({
|
||||
|
||||
// Raw config file fetch/save helpers — uses jail.d/<name>.conf convention.
|
||||
const fetchRaw = useCallback(async (): Promise<string> => {
|
||||
const result = await fetchJailConfigFileContent(`${jail.name}.conf`);
|
||||
return result.content;
|
||||
}, [jail.name]);
|
||||
return await fetchRawContent();
|
||||
}, [fetchRawContent]);
|
||||
|
||||
const saveRaw = useCallback(
|
||||
async (content: string): Promise<void> => {
|
||||
const req: ConfFileUpdateRequest = { content };
|
||||
await updateJailConfigFile(`${jail.name}.conf`, req);
|
||||
await saveRawContent(content);
|
||||
},
|
||||
[jail.name],
|
||||
[saveRawContent],
|
||||
);
|
||||
|
||||
return (
|
||||
@@ -629,6 +625,7 @@ interface InactiveJailDetailProps {
|
||||
onActivate: () => void;
|
||||
/** Called when the user requests removal of the .local override file. */
|
||||
onDeactivate?: () => void;
|
||||
onValidate: () => Promise<JailValidationResult>;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -646,6 +643,7 @@ function InactiveJailDetail({
|
||||
jail,
|
||||
onActivate,
|
||||
onDeactivate,
|
||||
onValidate,
|
||||
}: InactiveJailDetailProps): React.JSX.Element {
|
||||
const styles = useConfigStyles();
|
||||
const [validating, setValidating] = useState(false);
|
||||
@@ -654,11 +652,11 @@ function InactiveJailDetail({
|
||||
const handleValidate = useCallback((): void => {
|
||||
setValidating(true);
|
||||
setValidationResult(null);
|
||||
validateJailConfig(jail.name)
|
||||
onValidate()
|
||||
.then((result) => { setValidationResult(result); })
|
||||
.catch(() => { /* validation call failed — ignore */ })
|
||||
.finally(() => { setValidating(false); });
|
||||
}, [jail.name]);
|
||||
}, [onValidate]);
|
||||
|
||||
const blockingIssues: JailValidationIssue[] =
|
||||
validationResult?.issues.filter((i) => i.field !== "logpath") ?? [];
|
||||
@@ -767,51 +765,51 @@ export function JailsTab({ initialJail }: JailsTabProps): React.JSX.Element {
|
||||
const { jails, loading, error, refresh, updateJail } =
|
||||
useJailConfigs();
|
||||
const { activeJails } = useConfigActiveStatus();
|
||||
const {
|
||||
inactiveJails,
|
||||
inactiveLoading,
|
||||
refreshInactiveJails,
|
||||
deactivateJail,
|
||||
deleteJailLocalOverride,
|
||||
validateJailConfig,
|
||||
activateJail,
|
||||
createJailConfigFile,
|
||||
} = useJailAdmin();
|
||||
const [selectedName, setSelectedName] = useState<string | null>(null);
|
||||
const [createDialogOpen, setCreateDialogOpen] = useState(false);
|
||||
|
||||
// Inactive jails
|
||||
const [inactiveJails, setInactiveJails] = useState<InactiveJail[]>([]);
|
||||
const [inactiveLoading, setInactiveLoading] = useState(false);
|
||||
const [activateTarget, setActivateTarget] = useState<InactiveJail | null>(null);
|
||||
|
||||
const loadInactive = useCallback((): void => {
|
||||
setInactiveLoading(true);
|
||||
fetchInactiveJails()
|
||||
.then((res) => { setInactiveJails(res.jails); })
|
||||
.catch(() => { /* non-critical — active-only view still works */ })
|
||||
.finally(() => { setInactiveLoading(false); });
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
loadInactive();
|
||||
}, [loadInactive]);
|
||||
|
||||
const handleDeactivate = useCallback((name: string): void => {
|
||||
deactivateJail(name)
|
||||
.then(() => {
|
||||
const handleDeactivate = useCallback(
|
||||
async (name: string): Promise<void> => {
|
||||
try {
|
||||
await deactivateJail(name);
|
||||
setSelectedName(null);
|
||||
refresh();
|
||||
loadInactive();
|
||||
})
|
||||
.catch(() => { /* non-critical — list refreshes on next load */ });
|
||||
}, [refresh, loadInactive]);
|
||||
refreshInactiveJails();
|
||||
} catch {
|
||||
/* non-critical — list refreshes on next load */ }
|
||||
},
|
||||
[deactivateJail, refresh, refreshInactiveJails],
|
||||
);
|
||||
|
||||
const handleDeactivateInactive = useCallback((name: string): void => {
|
||||
deleteJailLocalOverride(name)
|
||||
.then(() => {
|
||||
const handleDeactivateInactive = useCallback(
|
||||
async (name: string): Promise<void> => {
|
||||
try {
|
||||
await deleteJailLocalOverride(name);
|
||||
setSelectedName(null);
|
||||
loadInactive();
|
||||
})
|
||||
.catch(() => { /* non-critical — list refreshes on next load */ });
|
||||
}, [loadInactive]);
|
||||
refreshInactiveJails();
|
||||
} catch {
|
||||
/* non-critical — list refreshes on next load */ }
|
||||
},
|
||||
[deleteJailLocalOverride, refreshInactiveJails],
|
||||
);
|
||||
|
||||
const handleActivated = useCallback((): void => {
|
||||
setActivateTarget(null);
|
||||
setSelectedName(null);
|
||||
refresh();
|
||||
loadInactive();
|
||||
}, [refresh, loadInactive]);
|
||||
refreshInactiveJails();
|
||||
}, [refresh, refreshInactiveJails]);
|
||||
|
||||
/** Unified list items: active jails first (from useJailConfigs), then inactive. */
|
||||
const listItems = useMemo<Array<{ name: string; kind: "active" | "inactive" }>>(() => {
|
||||
@@ -924,6 +922,7 @@ export function JailsTab({ initialJail }: JailsTabProps): React.JSX.Element {
|
||||
? (): void => { handleDeactivateInactive(selectedInactiveJail.name); }
|
||||
: undefined
|
||||
}
|
||||
onValidate={() => validateJailConfig(selectedInactiveJail.name)}
|
||||
/>
|
||||
) : null}
|
||||
</ConfigListDetail>
|
||||
@@ -934,15 +933,36 @@ export function JailsTab({ initialJail }: JailsTabProps): React.JSX.Element {
|
||||
open={activateTarget !== null}
|
||||
onClose={() => { setActivateTarget(null); }}
|
||||
onActivated={handleActivated}
|
||||
onValidate={async () => {
|
||||
if (!activateTarget) {
|
||||
return { jail_name: "", valid: false, issues: [] };
|
||||
}
|
||||
return await validateJailConfig(activateTarget.name);
|
||||
}}
|
||||
onActivate={async (payload) => {
|
||||
if (!activateTarget) {
|
||||
return {
|
||||
name: "",
|
||||
active: false,
|
||||
message: "No jail selected.",
|
||||
fail2ban_running: false,
|
||||
validation_warnings: [],
|
||||
};
|
||||
}
|
||||
return await activateJail(activateTarget.name, payload);
|
||||
}}
|
||||
/>
|
||||
|
||||
<CreateJailDialog
|
||||
open={createDialogOpen}
|
||||
onClose={() => { setCreateDialogOpen(false); }}
|
||||
onCreateJail={async (payload) => {
|
||||
await createJailConfigFile(payload);
|
||||
}}
|
||||
onCreated={() => {
|
||||
setCreateDialogOpen(false);
|
||||
refresh();
|
||||
loadInactive();
|
||||
refreshInactiveJails();
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user