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, 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>

View File

@@ -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,