fix: add validation error handling to InactiveJailDetail
- Add validationError state to show network/API failures to user - Use handleFetchError to properly handle auth errors (suppress generic error banner, trigger session-expiry flow) - Clear validationError when user clicks Validate again - Ensure error MessageBar renders instead of success banner when validation fails - Fix InactiveJailDetail onValidate to return Promise as expected by prop type - Fix useJailConfigs test to use correct JailConfig interface Fixes TASK-BUG-05: prevents silent validation failures where user cannot distinguish between clean 'no issues' result and server error. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This commit is contained in:
@@ -32,6 +32,7 @@ import {
|
|||||||
Play24Regular,
|
Play24Regular,
|
||||||
} from "@fluentui/react-icons";
|
} from "@fluentui/react-icons";
|
||||||
import { ApiError } from "../../api/client";
|
import { ApiError } from "../../api/client";
|
||||||
|
import { handleFetchError } from "../../utils/fetchError";
|
||||||
import type {
|
import type {
|
||||||
AddLogPathRequest,
|
AddLogPathRequest,
|
||||||
BackendType,
|
BackendType,
|
||||||
@@ -651,13 +652,17 @@ function InactiveJailDetail({
|
|||||||
const styles = useConfigStyles();
|
const styles = useConfigStyles();
|
||||||
const [validating, setValidating] = useState(false);
|
const [validating, setValidating] = useState(false);
|
||||||
const [validationResult, setValidationResult] = useState<JailValidationResult | null>(null);
|
const [validationResult, setValidationResult] = useState<JailValidationResult | null>(null);
|
||||||
|
const [validationError, setValidationError] = useState<string | null>(null);
|
||||||
|
|
||||||
const handleValidate = useCallback((): void => {
|
const handleValidate = useCallback((): void => {
|
||||||
setValidating(true);
|
setValidating(true);
|
||||||
setValidationResult(null);
|
setValidationResult(null);
|
||||||
|
setValidationError(null);
|
||||||
onValidate()
|
onValidate()
|
||||||
.then((result) => { setValidationResult(result); })
|
.then((result) => { setValidationResult(result); })
|
||||||
.catch(() => { /* validation call failed — ignore */ })
|
.catch((err: unknown) => {
|
||||||
|
handleFetchError(err, setValidationError, "Validation request failed.");
|
||||||
|
})
|
||||||
.finally(() => { setValidating(false); });
|
.finally(() => { setValidating(false); });
|
||||||
}, [onValidate]);
|
}, [onValidate]);
|
||||||
|
|
||||||
@@ -700,8 +705,17 @@ function InactiveJailDetail({
|
|||||||
<Input readOnly value={jail.source_file} className={styles.codeFont} />
|
<Input readOnly value={jail.source_file} className={styles.codeFont} />
|
||||||
</Field>
|
</Field>
|
||||||
|
|
||||||
|
{/* Validation error panel */}
|
||||||
|
{validationError !== null && (
|
||||||
|
<div style={{ marginBottom: tokens.spacingVerticalM }}>
|
||||||
|
<MessageBar intent="error">
|
||||||
|
<MessageBarBody>{validationError}</MessageBarBody>
|
||||||
|
</MessageBar>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
{/* Validation result panel */}
|
{/* Validation result panel */}
|
||||||
{validationResult !== null && (
|
{validationResult !== null && validationError === null && (
|
||||||
<div style={{ marginBottom: tokens.spacingVerticalM }}>
|
<div style={{ marginBottom: tokens.spacingVerticalM }}>
|
||||||
{blockingIssues.length === 0 && advisoryIssues.length === 0 ? (
|
{blockingIssues.length === 0 && advisoryIssues.length === 0 ? (
|
||||||
<MessageBar intent="success">
|
<MessageBar intent="success">
|
||||||
@@ -924,7 +938,7 @@ export function JailsTab({ initialJail }: JailsTabProps): React.JSX.Element {
|
|||||||
? (): void => { void handleDeactivateInactive(selectedInactiveJail.name); }
|
? (): void => { void handleDeactivateInactive(selectedInactiveJail.name); }
|
||||||
: undefined
|
: undefined
|
||||||
}
|
}
|
||||||
onValidate={() => { void validateJailConfig(selectedInactiveJail.name); }}
|
onValidate={async () => validateJailConfig(selectedInactiveJail.name)}
|
||||||
/>
|
/>
|
||||||
) : null}
|
) : null}
|
||||||
</ConfigListDetail>
|
</ConfigListDetail>
|
||||||
|
|||||||
@@ -15,27 +15,19 @@ describe("useJailConfigs", () => {
|
|||||||
jails: [
|
jails: [
|
||||||
{
|
{
|
||||||
name: "sshd",
|
name: "sshd",
|
||||||
enabled: true,
|
ban_time: 600,
|
||||||
active: true,
|
find_time: 600,
|
||||||
|
max_retry: 5,
|
||||||
|
fail_regex: [],
|
||||||
|
ignore_regex: [],
|
||||||
|
log_paths: [],
|
||||||
|
date_pattern: null,
|
||||||
|
log_encoding: "auto",
|
||||||
backend: "systemd",
|
backend: "systemd",
|
||||||
filename: "/etc/fail2ban/jail.d/sshd.conf",
|
use_dns: "warn",
|
||||||
source_file: "/etc/fail2ban/jail.d/sshd.conf",
|
prefregex: "",
|
||||||
last_activation: "2024-01-01T00:00:00Z",
|
actions: [],
|
||||||
bantime: 600,
|
bantime_escalation: null,
|
||||||
findtime: 600,
|
|
||||||
maxretry: 5,
|
|
||||||
action: [],
|
|
||||||
logpath: [],
|
|
||||||
port: [],
|
|
||||||
ignoreself: false,
|
|
||||||
filter: "sshd",
|
|
||||||
destemail: "",
|
|
||||||
sendername: "",
|
|
||||||
action_d_files: [],
|
|
||||||
ignoreip: [],
|
|
||||||
ignorecommand: "",
|
|
||||||
banaction: "",
|
|
||||||
banaction_allports: false,
|
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
total: 1,
|
total: 1,
|
||||||
|
|||||||
Reference in New Issue
Block a user