From 5f728900dc6104da0df9dccc9bd5847abcb44e4b Mon Sep 17 00:00:00 2001 From: Alex Hope-O'Connor Date: Mon, 2 Mar 2026 22:28:47 +1000 Subject: [PATCH] Effective config: auto-revert flag when toggled to original; fix stale cache after restore MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Flag options: when user toggles value back to original, clear pending change (same as revert button); trigger revert immediately on change so UI updates without blur - UiContext: add RevertFieldAsync so section can revert a field and refresh; section wires it to Session.RevertChange + StateHasChanged - RestoreAsync: call config cache Invalidate() after File.Copy so next read sees restored file (fixes validate-fail → restore showing pre-restore data) --- .../Composition/EffectiveConfigSection.razor | 3 +- .../Fields/EffectiveConfigFieldDisplay.razor | 42 +++++++++++-------- .../EffectiveConfigSaveService.cs | 4 ++ .../EffectiveConfigUiContext.cs | 2 + 4 files changed, 32 insertions(+), 19 deletions(-) diff --git a/src/DnsmasqWebUI/Components/EffectiveConfig/Composition/EffectiveConfigSection.razor b/src/DnsmasqWebUI/Components/EffectiveConfig/Composition/EffectiveConfigSection.razor index 59c2b24..ab46771 100644 --- a/src/DnsmasqWebUI/Components/EffectiveConfig/Composition/EffectiveConfigSection.razor +++ b/src/DnsmasqWebUI/Components/EffectiveConfig/Composition/EffectiveConfigSection.razor @@ -68,7 +68,8 @@ }, ActivateFieldAsync = fieldKey => { ActivateField(fieldKey); return Task.CompletedTask; }, DeactivateFieldAsync = () => { Session.DeactivateField(); return InvokeAsync(StateHasChanged); }, - CommitFieldAsync = args => { HandleFieldEditCommitted(args); return Task.CompletedTask; } + CommitFieldAsync = args => { HandleFieldEditCommitted(args); return Task.CompletedTask; }, + RevertFieldAsync = (sectionId, optionName) => { Session.RevertChange(sectionId, optionName); return InvokeAsync(StateHasChanged); } }; } diff --git a/src/DnsmasqWebUI/Components/EffectiveConfig/Fields/EffectiveConfigFieldDisplay.razor b/src/DnsmasqWebUI/Components/EffectiveConfig/Fields/EffectiveConfigFieldDisplay.razor index f074ea4..b234e50 100644 --- a/src/DnsmasqWebUI/Components/EffectiveConfig/Fields/EffectiveConfigFieldDisplay.razor +++ b/src/DnsmasqWebUI/Components/EffectiveConfig/Fields/EffectiveConfigFieldDisplay.razor @@ -171,6 +171,8 @@ else private void HandleValueChanged(object? value) { _draftValue = value; + if (_hasPendingChange && ValuesEqual(_valueAtActivation, value)) + _ = InvokeAsync(CommitIfChangedAsync); } private async Task OnBlur(FocusEventArgs _) @@ -182,26 +184,30 @@ else private async Task CommitIfChangedAsync() { var newValue = _draftValue ?? _effectiveValue; - if (!ValuesEqual(_valueAtActivation, newValue)) + if (ValuesEqual(_valueAtActivation, newValue)) { - var validationError = ValidateDraftValue(newValue); - if (validationError != null) - { - NotificationService.ShowError(validationError); - return false; - } - - var source = Descriptor.GetSource(); - object? valueToCommit = newValue; - if (newValue is IReadOnlyList list) - { - var deduped = list.Distinct(StringComparer.Ordinal).ToList(); - valueToCommit = deduped; - } - var args = new EffectiveConfigEditCommittedArgs(Descriptor.SectionId, Descriptor.OptionName, _valueAtActivation, valueToCommit, source?.FilePath); - if (Ui != null) - await Ui.CommitFieldAsync(args); + _draftValue = null; + if (_hasPendingChange && Ui?.RevertFieldAsync != null) + await Ui.RevertFieldAsync(Descriptor.SectionId, Descriptor.OptionName); + return true; } + var validationError = ValidateDraftValue(newValue); + if (validationError != null) + { + NotificationService.ShowError(validationError); + return false; + } + + var source = Descriptor.GetSource(); + object? valueToCommit = newValue; + if (newValue is IReadOnlyList list) + { + var deduped = list.Distinct(StringComparer.Ordinal).ToList(); + valueToCommit = deduped; + } + var args = new EffectiveConfigEditCommittedArgs(Descriptor.SectionId, Descriptor.OptionName, _valueAtActivation, valueToCommit, source?.FilePath); + if (Ui != null) + await Ui.CommitFieldAsync(args); _draftValue = null; return true; } diff --git a/src/DnsmasqWebUI/Infrastructure/Services/EffectiveConfig/EffectiveConfigSaveService.cs b/src/DnsmasqWebUI/Infrastructure/Services/EffectiveConfig/EffectiveConfigSaveService.cs index 5e96ac1..caaf4b1 100644 --- a/src/DnsmasqWebUI/Infrastructure/Services/EffectiveConfig/EffectiveConfigSaveService.cs +++ b/src/DnsmasqWebUI/Infrastructure/Services/EffectiveConfig/EffectiveConfigSaveService.cs @@ -11,6 +11,7 @@ public sealed class EffectiveConfigSaveService : IEffectiveConfigSaveService { private readonly IDnsmasqConfigSetService _configSetService; private readonly IDnsmasqConfigService _configService; + private readonly IConfigSetCache _configSetCache; private readonly IConfigValidationService _validationService; private readonly IReloadService _reloadService; private readonly ILogger _logger; @@ -18,12 +19,14 @@ public sealed class EffectiveConfigSaveService : IEffectiveConfigSaveService public EffectiveConfigSaveService( IDnsmasqConfigSetService configSetService, IDnsmasqConfigService configService, + IConfigSetCache configSetCache, IConfigValidationService validationService, IReloadService reloadService, ILogger logger) { _configSetService = configSetService; _configService = configService; + _configSetCache = configSetCache; _validationService = validationService; _reloadService = reloadService; _logger = logger; @@ -139,6 +142,7 @@ public sealed class EffectiveConfigSaveService : IEffectiveConfigSaveService var managedPath = set.ManagedFilePath!; File.Copy(backupPath, managedPath, overwrite: true); + _configSetCache.Invalidate(); _logger.LogInformation("Restored managed config from backup: {BackupPath}", backupPath); var restartResult = await _reloadService.ReloadAsync(ct); diff --git a/src/DnsmasqWebUI/Models/Dnsmasq/EffectiveConfig/EffectiveConfigUiContext.cs b/src/DnsmasqWebUI/Models/Dnsmasq/EffectiveConfig/EffectiveConfigUiContext.cs index 9363214..2988235 100644 --- a/src/DnsmasqWebUI/Models/Dnsmasq/EffectiveConfig/EffectiveConfigUiContext.cs +++ b/src/DnsmasqWebUI/Models/Dnsmasq/EffectiveConfig/EffectiveConfigUiContext.cs @@ -12,4 +12,6 @@ public sealed class EffectiveConfigUiContext public required Func ActivateFieldAsync { get; init; } public required Func DeactivateFieldAsync { get; init; } public required Func CommitFieldAsync { get; init; } + /// Revert a pending change for the given field and refresh the section (e.g. when user toggles a flag back to its original value). + public Func? RevertFieldAsync { get; init; } }