fix(config): re-sync JailConfigDetail form when jail prop updates from server refresh

When useJailConfigs performs a background refresh, it may deliver an updated
JailConfig object for an already-selected jail. Previously, JailConfigDetail
would continue displaying stale locally-edited form values because the component
only re-initialized on jail name changes (via the key prop), not on object
identity changes.

Added a useEffect that detects when the jail prop reference has changed
(indicating a server refresh) and automatically resets all form fields to the
new server state, but only if autoSave is idle and has no pending changes.
This prevents accidentally overwriting external changes when the user saves,
while still letting users continue editing unsaved changes without interruption.

The implementation:
- Tracks the last-synced jail object in a ref
- Compares incoming jail reference to detect server updates
- Checks autoSave status to ensure no pending saves
- Verifies that current form state matches the old jail values
- Resets all 20+ form fields when conditions are met

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This commit is contained in:
2026-04-23 08:08:33 +02:00
parent 1d50bc1a73
commit 3024a4ef07
2 changed files with 73 additions and 66 deletions

View File

@@ -1,68 +1,3 @@
### TASK-BUG-04 — `autoSavePayload` Silently Drops Intentional Zero Values
**Where found**
`frontend/src/components/config/JailsTab.tsx` lines 213215:
```ts
ban_time: Number(banTime) || jail.ban_time,
find_time: Number(findTime) || jail.find_time,
max_retry: Number(maxRetry) || jail.max_retry,
```
The `||` operator treats `0` as falsy, so when a user sets `ban_time` to `0` (which in fail2ban means "ban permanently") the payload silently falls back to the server's current value. The user's intent is discarded without any error.
**Goal**
Replace the `||` fallback with an explicit `NaN` guard:
```ts
ban_time: Number.isNaN(Number(banTime)) ? jail.ban_time : Number(banTime),
find_time: Number.isNaN(Number(findTime)) ? jail.find_time : Number(findTime),
max_retry: Number.isNaN(Number(maxRetry)) ? jail.max_retry : Number(maxRetry),
```
This preserves `0` and other valid numeric values while still falling back when the field contains non-numeric text.
**Possible traps and issues**
- An empty string converts to `0` via `Number("")`, which would then be sent to the API. If empty input is invalid, add a separate guard: only fall back if the trimmed string is empty or `NaN`.
- `max_retry` of `0` is meaningless in fail2ban (would never ban). Consider adding UI validation that shows a warning for `max_retry < 1` rather than silently correcting it.
**Docs changes needed**
None required.
**Why this is needed**
`ban_time = 0` in fail2ban sets a permanent ban — a common use case for admin hardening. The current code makes it impossible to save this value through the UI, with no error shown to the user.
---
### TASK-BUG-05 — `InactiveJailDetail.handleValidate` Swallows Network Failures
**Where found**
`frontend/src/components/config/JailsTab.tsx` inside `InactiveJailDetail`, the `handleValidate` callback:
```ts
onValidate()
.then((result) => { setValidationResult(result); })
.catch(() => { /* validation call failed — ignore */ })
.finally(() => { setValidating(false); });
```
If the API call fails (network error, 500, timeout), the spinner stops, `validationResult` remains `null`, and there is zero user feedback. The user cannot distinguish a clean "no issues" result from a silent failure.
**Goal**
Add error state to `InactiveJailDetail` and render a `MessageBar intent="error"` when validation fails:
```ts
.catch((err: unknown) => {
setValidationError(err instanceof Error ? err.message : "Validation request failed.");
})
```
Clear `validationError` when the user clicks Validate again.
**Possible traps and issues**
- The existing `validationResult` display logic handles the empty-issues case with a success banner. Ensure the new error state renders instead of the success banner when set.
- `handleFetchError` should be used (or replicated) so that auth errors don't show a generic error banner — they should trigger the session-expiry flow instead.
**Docs changes needed**
None required.
**Why this is needed**
Silent failure is worse than a visible error. A user who validates a jail config and sees nothing has no idea whether validation passed or the server is unreachable.
---
### TASK-BUG-06 — `JailConfigDetail` Form State Never Re-syncs After Background Refresh
**Where found**