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:
2026-04-23 08:02:24 +02:00
parent 3c310e1d79
commit 1d50bc1a73
2 changed files with 29 additions and 23 deletions

View File

@@ -32,6 +32,7 @@ import {
Play24Regular,
} from "@fluentui/react-icons";
import { ApiError } from "../../api/client";
import { handleFetchError } from "../../utils/fetchError";
import type {
AddLogPathRequest,
BackendType,
@@ -651,13 +652,17 @@ function InactiveJailDetail({
const styles = useConfigStyles();
const [validating, setValidating] = useState(false);
const [validationResult, setValidationResult] = useState<JailValidationResult | null>(null);
const [validationError, setValidationError] = useState<string | null>(null);
const handleValidate = useCallback((): void => {
setValidating(true);
setValidationResult(null);
setValidationError(null);
onValidate()
.then((result) => { setValidationResult(result); })
.catch(() => { /* validation call failed — ignore */ })
.catch((err: unknown) => {
handleFetchError(err, setValidationError, "Validation request failed.");
})
.finally(() => { setValidating(false); });
}, [onValidate]);
@@ -700,8 +705,17 @@ function InactiveJailDetail({
<Input readOnly value={jail.source_file} className={styles.codeFont} />
</Field>
{/* Validation error panel */}
{validationError !== null && (
<div style={{ marginBottom: tokens.spacingVerticalM }}>
<MessageBar intent="error">
<MessageBarBody>{validationError}</MessageBarBody>
</MessageBar>
</div>
)}
{/* Validation result panel */}
{validationResult !== null && (
{validationResult !== null && validationError === null && (
<div style={{ marginBottom: tokens.spacingVerticalM }}>
{blockingIssues.length === 0 && advisoryIssues.length === 0 ? (
<MessageBar intent="success">
@@ -924,7 +938,7 @@ export function JailsTab({ initialJail }: JailsTabProps): React.JSX.Element {
? (): void => { void handleDeactivateInactive(selectedInactiveJail.name); }
: undefined
}
onValidate={() => { void validateJailConfig(selectedInactiveJail.name); }}
onValidate={async () => validateJailConfig(selectedInactiveJail.name)}
/>
) : null}
</ConfigListDetail>

View File

@@ -15,27 +15,19 @@ describe("useJailConfigs", () => {
jails: [
{
name: "sshd",
enabled: true,
active: true,
ban_time: 600,
find_time: 600,
max_retry: 5,
fail_regex: [],
ignore_regex: [],
log_paths: [],
date_pattern: null,
log_encoding: "auto",
backend: "systemd",
filename: "/etc/fail2ban/jail.d/sshd.conf",
source_file: "/etc/fail2ban/jail.d/sshd.conf",
last_activation: "2024-01-01T00:00:00Z",
bantime: 600,
findtime: 600,
maxretry: 5,
action: [],
logpath: [],
port: [],
ignoreself: false,
filter: "sshd",
destemail: "",
sendername: "",
action_d_files: [],
ignoreip: [],
ignorecommand: "",
banaction: "",
banaction_allports: false,
use_dns: "warn",
prefregex: "",
actions: [],
bantime_escalation: null,
},
],
total: 1,