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:
@@ -6,7 +6,7 @@
|
||||
* raw-config editor.
|
||||
*/
|
||||
|
||||
import { useCallback, useEffect, useMemo, useState } from "react";
|
||||
import { useCallback, useEffect, useMemo, useRef, useState } from "react";
|
||||
import {
|
||||
Badge,
|
||||
Button,
|
||||
@@ -249,6 +249,78 @@ function JailConfigDetail({
|
||||
const { status: saveStatus, errorText: saveErrorText, retry: retrySave } =
|
||||
useAutoSave(autoSavePayload, saveCurrent);
|
||||
|
||||
// Ref to track the last jail object we synced from. When the incoming jail
|
||||
// prop is a new object (indicating a server refresh), and autoSave is idle
|
||||
// with no pending changes, we reset the form to the new server state.
|
||||
const lastSyncedJailRef = useRef<JailConfig>(jail);
|
||||
|
||||
// Resync form fields when the jail prop changes identity (server refresh)
|
||||
// but only if autoSave has completed and there are no pending changes.
|
||||
useEffect(() => {
|
||||
if (
|
||||
lastSyncedJailRef.current !== jail &&
|
||||
saveStatus === "idle" &&
|
||||
!saveErrorText
|
||||
) {
|
||||
// Payload is equal to the server state, so we can safely reset.
|
||||
if (
|
||||
JSON.stringify(autoSavePayload) === JSON.stringify({
|
||||
ban_time: lastSyncedJailRef.current.ban_time,
|
||||
find_time: lastSyncedJailRef.current.find_time,
|
||||
max_retry: lastSyncedJailRef.current.max_retry,
|
||||
fail_regex: lastSyncedJailRef.current.fail_regex,
|
||||
ignore_regex: lastSyncedJailRef.current.ignore_regex,
|
||||
date_pattern: lastSyncedJailRef.current.date_pattern ?? null,
|
||||
dns_mode: lastSyncedJailRef.current.use_dns,
|
||||
log_encoding: lastSyncedJailRef.current.log_encoding,
|
||||
prefregex: lastSyncedJailRef.current.prefregex,
|
||||
bantime_escalation: {
|
||||
increment: lastSyncedJailRef.current.bantime_escalation?.increment ?? false,
|
||||
factor: lastSyncedJailRef.current.bantime_escalation?.factor ?? null,
|
||||
formula: lastSyncedJailRef.current.bantime_escalation?.formula ?? null,
|
||||
multipliers: lastSyncedJailRef.current.bantime_escalation?.multipliers ?? null,
|
||||
max_time: lastSyncedJailRef.current.bantime_escalation?.max_time ?? null,
|
||||
rnd_time: lastSyncedJailRef.current.bantime_escalation?.rnd_time ?? null,
|
||||
overall_jails: lastSyncedJailRef.current.bantime_escalation?.overall_jails ?? false,
|
||||
},
|
||||
})
|
||||
) {
|
||||
// Reset all form state to new jail prop values.
|
||||
setBanTime(String(jail.ban_time));
|
||||
setFindTime(String(jail.find_time));
|
||||
setMaxRetry(String(jail.max_retry));
|
||||
setFailRegex(jail.fail_regex);
|
||||
setIgnoreRegex(jail.ignore_regex);
|
||||
setLogPaths(jail.log_paths);
|
||||
setDatePattern(jail.date_pattern ?? "");
|
||||
setDnsMode(jail.use_dns);
|
||||
setBackend(jail.backend);
|
||||
setLogEncoding(jail.log_encoding);
|
||||
setPrefRegex(jail.prefregex);
|
||||
setEscEnabled(jail.bantime_escalation?.increment ?? false);
|
||||
setEscFactor(
|
||||
jail.bantime_escalation?.factor != null
|
||||
? String(jail.bantime_escalation.factor)
|
||||
: "",
|
||||
);
|
||||
setEscFormula(jail.bantime_escalation?.formula ?? "");
|
||||
setEscMultipliers(jail.bantime_escalation?.multipliers ?? "");
|
||||
setEscMaxTime(
|
||||
jail.bantime_escalation?.max_time != null
|
||||
? String(jail.bantime_escalation.max_time)
|
||||
: "",
|
||||
);
|
||||
setEscRndTime(
|
||||
jail.bantime_escalation?.rnd_time != null
|
||||
? String(jail.bantime_escalation.rnd_time)
|
||||
: "",
|
||||
);
|
||||
setEscOverallJails(jail.bantime_escalation?.overall_jails ?? false);
|
||||
}
|
||||
lastSyncedJailRef.current = jail;
|
||||
}
|
||||
}, [jail, saveStatus, saveErrorText, autoSavePayload]);
|
||||
|
||||
// Raw config file fetch/save helpers — uses jail.d/<name>.conf convention.
|
||||
const fetchRaw = useCallback(async (): Promise<string> => {
|
||||
return await fetchRawContent();
|
||||
|
||||
Reference in New Issue
Block a user