@namespace DnsmasqWebUI.Components.EffectiveConfig @using DnsmasqWebUI.Models.Dnsmasq @using DnsmasqWebUI.Models.Dnsmasq.EffectiveConfig @using DnsmasqWebUI.Infrastructure.Services.EffectiveConfig.Abstractions @code { [Parameter] public IReadOnlyList PendingChanges { get; set; } = null!; [Parameter] public DnsmasqServiceStatus? Status { get; set; } [Parameter] public IEffectiveConfigEditSession Session { get; set; } = null!; [Parameter] public EffectiveConfigSaveFlow.SaveUiState SaveState { get; set; } [Parameter] public EffectiveConfigSaveResult? SaveResult { get; set; } [Parameter] public EffectiveConfigRestoreResult? RestoreResult { get; set; } [Parameter] public EventCallback OnClose { get; set; } [Parameter] public EventCallback OnContinueEditing { get; set; } [Parameter] public EventCallback OnSave { get; set; } [Parameter] public EventCallback OnRestore { get; set; } [Parameter] public EventCallback OnReverted { get; set; } [Parameter] public EventCallback OnCloseAndEndEdit { get; set; } private bool AllowClose => SaveState != EffectiveConfigSaveFlow.SaveUiState.Saving && SaveState != EffectiveConfigSaveFlow.SaveUiState.RestoreInProgress; private string? ManagedFilePath => Status?.ManagedFilePath; private IReadOnlyList ValidationErrors => Session.GetValidationSummary().Where(i => i.Severity == FieldIssueSeverity.Error).ToList(); private IReadOnlyList ValidationWarnings => Session.GetValidationSummary().Where(i => i.Severity == FieldIssueSeverity.Warning).ToList(); private bool HasBlockingValidationErrors => Session.HasBlockingValidationErrors(); private static string GetValidationFailedOutput(EffectiveConfigSaveResult result) { var err = (result.ValidationStdErr ?? "").Trim(); var outp = (result.ValidationStdOut ?? "").Trim(); if (err.Length > 0 && outp.Length > 0) return err + "\n\n" + outp; if (err.Length > 0) return err; if (outp.Length > 0) return outp; var exitInfo = result.ValidationExitCode >= 0 ? $"Exit code: {result.ValidationExitCode}." : ""; return string.IsNullOrEmpty(exitInfo) ? "(No output captured)" : exitInfo + " (No output captured)"; } private static string GetRestartFailedOutput(EffectiveConfigSaveResult result) { var err = (result.RestartStdErr ?? "").Trim(); var outp = (result.RestartStdOut ?? "").Trim(); if (err.Length > 0 && outp.Length > 0) return err + "\n\n" + outp; if (err.Length > 0) return err; if (outp.Length > 0) return outp; var exitInfo = result.RestartExitCode >= 0 ? $"Exit code: {result.RestartExitCode}." : ""; return string.IsNullOrEmpty(exitInfo) ? "(No output captured)" : exitInfo + " (No output captured)"; } private static string GetRestoreFailedOutput(EffectiveConfigRestoreResult result) { var err = (result.RestartStdErr ?? "").Trim(); if (err.Length > 0) return err; var exitInfo = result.RestartExitCode >= 0 ? $"Exit code: {result.RestartExitCode}." : ""; return string.IsNullOrEmpty(exitInfo) ? "(No output captured)" : exitInfo + " (No output captured)"; } /// If the value is a list of strings (multi-value option), return it; otherwise null. private static IReadOnlyList? AsStringList(object? v) { if (v == null) return null; if (v is IReadOnlyList ro) return ro; if (v is List list) return list; if (v is string[] arr) return arr; return null; } private static string FormatValue(object? v, string? optionName = null) { if (optionName != null && EffectiveConfigWriteSemantics.GetBehavior(optionName) == EffectiveConfigWriteBehavior.KeyOnlyOrValue) return FormatKeyOnlyOrValue(v); if (v == null) return "(not set)"; if (v is bool b) return b ? "on" : "off"; var list = AsStringList(v); if (list != null) return list.Count == 0 ? "(none)" : string.Join(", ", list); return v.ToString() ?? "(not set)"; } private static string FormatKeyOnlyOrValue(object? v) { if (v == null) return "(not set)"; var s = v.ToString(); if (string.IsNullOrEmpty(s)) return "(set)"; return s; } private async Task Revert(PendingEffectiveConfigChange change) { Session.RevertChange(change.SectionId, change.OptionName); await OnReverted.InvokeAsync(); } private async Task OnBackdropClick() { if (!AllowClose) return; await Close(); } private async Task Close() { if (!AllowClose) return; await OnClose.InvokeAsync(); } private async Task ContinueEditing() { if (!AllowClose) return; await OnContinueEditing.InvokeAsync(); } private async Task Save() { await OnSave.InvokeAsync(); } private async Task Restore(string backupPath) { await OnRestore.InvokeAsync(backupPath); } private async Task CloseAndEndEdit() { if (!AllowClose) return; await OnCloseAndEndEdit.InvokeAsync(); } }