mirror of
https://github.com/alexhopeoconnor/dnsmasq-webui.git
synced 2026-10-18 11:51:43 +10:00
Assembly-scanned services, options validator marker, UI tweaks, and config fixes
- Register ISettingsModalService, INotificationService, IUpdateCheckService via assembly scanning (marker interfaces) - Add IApplicationOptionsValidator<TOptions> marker; scan only those for IValidateOptions registration - Fix ApplicationLifecycleLoggingHostedService: log RestartCommand/ReloadCommand/StatusCommand (no ApplyCommand) - NavMenu: spin animation for update-check button icon when checking; single icon with conditional nav-footer-spin class - Add NotificationHost/NotificationToast components and NotificationService for toasts - Effective config save flow, restore result, save service; various EffectiveConfig and DnsmasqOptions/validator updates - Test and script updates; docker-compose.test.yml, appsettings, DnsmasqConfKeys/tooltips/sections
This commit is contained in:
+138
@@ -0,0 +1,138 @@
|
||||
using DnsmasqWebUI.Infrastructure.Services.Dnsmasq.Config.Abstractions;
|
||||
using DnsmasqWebUI.Infrastructure.Services.Dnsmasq.Reload.Abstractions;
|
||||
using DnsmasqWebUI.Infrastructure.Services.EffectiveConfig.Abstractions;
|
||||
using DnsmasqWebUI.Models.Dnsmasq.EffectiveConfig;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
namespace DnsmasqWebUI.Infrastructure.Services.EffectiveConfig;
|
||||
|
||||
public sealed class EffectiveConfigSaveService : IEffectiveConfigSaveService
|
||||
{
|
||||
private readonly IDnsmasqConfigSetService _configSetService;
|
||||
private readonly IDnsmasqConfigService _configService;
|
||||
private readonly IReloadService _reloadService;
|
||||
private readonly ILogger<EffectiveConfigSaveService> _logger;
|
||||
|
||||
public EffectiveConfigSaveService(
|
||||
IDnsmasqConfigSetService configSetService,
|
||||
IDnsmasqConfigService configService,
|
||||
IReloadService reloadService,
|
||||
ILogger<EffectiveConfigSaveService> logger)
|
||||
{
|
||||
_configSetService = configSetService;
|
||||
_configService = configService;
|
||||
_reloadService = reloadService;
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public async Task<EffectiveConfigSaveResult> SaveAsync(
|
||||
IReadOnlyList<PendingEffectiveConfigChange> changes,
|
||||
CancellationToken ct = default)
|
||||
{
|
||||
if (changes.Count == 0)
|
||||
return EffectiveConfigSaveResult.NoChanges();
|
||||
|
||||
var set = await _configSetService.GetConfigSetAsync(ct);
|
||||
if (string.IsNullOrWhiteSpace(set.ManagedFilePath))
|
||||
{
|
||||
_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.");
|
||||
}
|
||||
|
||||
var managedPath = set.ManagedFilePath!;
|
||||
var backupPath = BuildBackupPath(managedPath);
|
||||
|
||||
try
|
||||
{
|
||||
CreateBackupIfSourceExists(managedPath, backupPath);
|
||||
await _configService.ApplyEffectiveConfigChangesAsync(changes, ct);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "Failed to write effective config");
|
||||
var backupExists = File.Exists(backupPath);
|
||||
return new EffectiveConfigSaveResult(
|
||||
backupExists,
|
||||
backupExists ? backupPath : null,
|
||||
false,
|
||||
false,
|
||||
-1,
|
||||
null,
|
||||
ex.Message,
|
||||
"write_failed",
|
||||
"Failed to write config.");
|
||||
}
|
||||
|
||||
var reload = await _reloadService.ReloadAsync(ct);
|
||||
var backupCreated = File.Exists(backupPath);
|
||||
|
||||
if (!reload.Success)
|
||||
_logger.LogWarning("Config saved but reload failed: exit {ExitCode}, stderr: {Stderr}", reload.ExitCode, reload.StdErr);
|
||||
|
||||
return new EffectiveConfigSaveResult(
|
||||
BackupCreated: backupCreated,
|
||||
BackupPath: backupCreated ? backupPath : null,
|
||||
Saved: true,
|
||||
Reloaded: reload.Success,
|
||||
ReloadExitCode: reload.ExitCode,
|
||||
ReloadStdOut: reload.StdOut,
|
||||
ReloadStdErr: reload.StdErr,
|
||||
ErrorCode: reload.Success ? null : "reload_failed",
|
||||
UserMessage: reload.Success ? "Saved and reloaded." : "Saved, but reload failed.");
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public async Task<EffectiveConfigRestoreResult> RestoreAsync(string backupPath, CancellationToken ct = default)
|
||||
{
|
||||
var set = await _configSetService.GetConfigSetAsync(ct);
|
||||
if (string.IsNullOrWhiteSpace(set.ManagedFilePath))
|
||||
{
|
||||
_logger.LogWarning("Restore skipped: managed config path is not configured");
|
||||
return new EffectiveConfigRestoreResult(
|
||||
false, false, -1, null, "Managed config path is not configured.");
|
||||
}
|
||||
|
||||
if (!File.Exists(backupPath))
|
||||
{
|
||||
_logger.LogWarning("Restore skipped: backup file not found: {Path}", backupPath);
|
||||
return new EffectiveConfigRestoreResult(
|
||||
false, false, -1, null, "Backup file not found.");
|
||||
}
|
||||
|
||||
var managedPath = set.ManagedFilePath!;
|
||||
File.Copy(backupPath, managedPath, overwrite: true);
|
||||
_logger.LogInformation("Restored managed config from backup: {BackupPath}", backupPath);
|
||||
|
||||
var reload = await _reloadService.ReloadAsync(ct);
|
||||
|
||||
if (!reload.Success)
|
||||
_logger.LogWarning("Restore completed but reload failed: exit {ExitCode}, stderr: {Stderr}", reload.ExitCode, reload.StdErr);
|
||||
|
||||
return new EffectiveConfigRestoreResult(
|
||||
Restored: true,
|
||||
Reloaded: reload.Success,
|
||||
ReloadExitCode: reload.ExitCode,
|
||||
ReloadStdErr: reload.StdErr,
|
||||
UserMessage: reload.Success
|
||||
? "Backup restored and dnsmasq reloaded."
|
||||
: "Backup restored, but reload still failed.");
|
||||
}
|
||||
|
||||
private static string BuildBackupPath(string managedPath) =>
|
||||
$"{managedPath}.bak.{DateTime.UtcNow:yyyyMMdd-HHmmss}";
|
||||
|
||||
private static void CreateBackupIfSourceExists(string managedPath, string backupPath)
|
||||
{
|
||||
if (!File.Exists(managedPath))
|
||||
return;
|
||||
|
||||
var dir = Path.GetDirectoryName(backupPath);
|
||||
if (!string.IsNullOrEmpty(dir) && !Directory.Exists(dir))
|
||||
Directory.CreateDirectory(dir);
|
||||
|
||||
File.Copy(managedPath, backupPath, overwrite: false);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user