Effective config: multi-value editor, config service fixes, save result improvements

- Add EffectiveConfigMultiValueListEditor with badge-style actions, draft merge,
  and display-mode editable badges; fix row callback capture and bounds checks
- EffectiveConfigFieldDisplay: compute multi-value state in OnParametersSet +
  HandleMultiValueChanged; BuildItemsWithSource preserves readonly/editable;
  dedupe list on commit; defensive copy for multi-value commit
- DnsmasqConfigService: generic MatchesOption via key parsing; FilterManagedOnly
  + ReadReadonlyMultiValuesByOptionAsync (single-pass readonly); EnsureManagedConfigInvariants
  + EnsureUniqueDirective; fix DHCP write cache notification (re-parse written output)
- EffectiveConfigSaveResult: ErrorCodes constants, IsValidateFailed; set Validated=false
  when validation fails; EffectiveConfigSaveFlow use IsValidateFailed; save service
  use ErrorCodes
- OptionStateBadge: OnEditableClickRequested for list-editor per-item edit
- Validation service and config save flow updates
This commit is contained in:
2026-03-02 21:10:35 +10:00
parent 8ed66cdf48
commit a3cc3968af
19 changed files with 685 additions and 80 deletions
@@ -1,5 +1,6 @@
using DnsmasqWebUI.Infrastructure.Services.Dnsmasq.Config.Abstractions;
using DnsmasqWebUI.Infrastructure.Services.Dnsmasq.Reload.Abstractions;
using DnsmasqWebUI.Infrastructure.Services.Dnsmasq.Validation.Abstractions;
using DnsmasqWebUI.Infrastructure.Services.EffectiveConfig.Abstractions;
using DnsmasqWebUI.Models.Dnsmasq.EffectiveConfig;
using Microsoft.Extensions.Logging;
@@ -10,17 +11,20 @@ public sealed class EffectiveConfigSaveService : IEffectiveConfigSaveService
{
private readonly IDnsmasqConfigSetService _configSetService;
private readonly IDnsmasqConfigService _configService;
private readonly IConfigValidationService _validationService;
private readonly IReloadService _reloadService;
private readonly ILogger<EffectiveConfigSaveService> _logger;
public EffectiveConfigSaveService(
IDnsmasqConfigSetService configSetService,
IDnsmasqConfigService configService,
IConfigValidationService validationService,
IReloadService reloadService,
ILogger<EffectiveConfigSaveService> logger)
{
_configSetService = configSetService;
_configService = configService;
_validationService = validationService;
_reloadService = reloadService;
_logger = logger;
}
@@ -38,8 +42,8 @@ public sealed class EffectiveConfigSaveService : IEffectiveConfigSaveService
{
_logger.LogWarning("Save skipped: managed config path is not configured");
return new EffectiveConfigSaveResult(
false, null, false, false, -1, null, null,
"missing_managed_path", "Managed config path is not configured.");
false, null, false, false, -1, null, null, false, -1, null, null,
EffectiveConfigSaveResult.ErrorCodes.MissingManagedPath, "Managed config path is not configured.");
}
var managedPath = set.ManagedFilePath!;
@@ -61,13 +65,38 @@ public sealed class EffectiveConfigSaveService : IEffectiveConfigSaveService
false,
-1,
null,
null,
false,
-1,
null,
ex.Message,
"write_failed",
EffectiveConfigSaveResult.ErrorCodes.WriteFailed,
"Failed to write config.");
}
var restartResult = await _reloadService.ReloadAsync(ct);
var backupCreated = File.Exists(backupPath);
var validateResult = await _validationService.ValidateAsync(ct);
if (!validateResult.Success)
{
_logger.LogWarning("Config saved but validation failed: exit {ExitCode}, stderr: {Stderr}", validateResult.ExitCode, validateResult.StdErr);
return new EffectiveConfigSaveResult(
BackupCreated: backupCreated,
BackupPath: backupCreated ? backupPath : null,
Saved: true,
Validated: false,
ValidationExitCode: validateResult.ExitCode,
ValidationStdOut: validateResult.StdOut,
ValidationStdErr: validateResult.StdErr,
Restarted: false,
RestartExitCode: -1,
RestartStdOut: null,
RestartStdErr: null,
ErrorCode: EffectiveConfigSaveResult.ErrorCodes.ValidateFailed,
UserMessage: "Saved, but validation failed. Restart was not attempted.");
}
var restartResult = await _reloadService.ReloadAsync(ct);
if (!restartResult.Success)
_logger.LogWarning("Config saved but restart command failed: exit {ExitCode}, stderr: {Stderr}", restartResult.ExitCode, restartResult.StdErr);
@@ -76,12 +105,18 @@ public sealed class EffectiveConfigSaveService : IEffectiveConfigSaveService
BackupCreated: backupCreated,
BackupPath: backupCreated ? backupPath : null,
Saved: true,
Validated: true,
ValidationExitCode: 0,
ValidationStdOut: validateResult.StdOut,
ValidationStdErr: validateResult.StdErr,
Restarted: restartResult.Success,
RestartExitCode: restartResult.ExitCode,
RestartStdOut: restartResult.StdOut,
RestartStdErr: restartResult.StdErr,
ErrorCode: restartResult.Success ? null : "restart_failed",
UserMessage: restartResult.Success ? "Saved and dnsmasq restarted." : "Saved, but the restart command failed.");
ErrorCode: restartResult.Success ? null : EffectiveConfigSaveResult.ErrorCodes.RestartFailed,
UserMessage: restartResult.Success
? "Saved, validated, and dnsmasq restarted."
: "Saved and validated, but the restart command failed.");
}
/// <inheritdoc />