diff --git a/src/DnsmasqWebUI/Components/Dnsmasq/EffectiveConfig/CustomDisplays/IntegerValueDisplay.razor b/src/DnsmasqWebUI/Components/Dnsmasq/EffectiveConfig/CustomDisplays/IntegerValueDisplay.razor new file mode 100644 index 0000000..335c576 --- /dev/null +++ b/src/DnsmasqWebUI/Components/Dnsmasq/EffectiveConfig/CustomDisplays/IntegerValueDisplay.razor @@ -0,0 +1,46 @@ +@namespace DnsmasqWebUI.Components.Dnsmasq.EffectiveConfig.CustomDisplays +@using DnsmasqWebUI.Models.Dnsmasq + +@* Value-only display for int? options. Edit: number input (unless readonly); View: value + optional suffix/unit. Metadata from EffectiveIntegerConfigFieldDescriptor. *@ +@if (DisplayMode == EffectiveConfigDisplayMode.Edit && Descriptor.GetSource()?.IsReadOnly != true) +{ + var current = Descriptor.GetValue() is int i ? i : DefaultValue; + +} +else +{ + var v = Descriptor.GetValue() is int n ? n : (int?)null; + if (v is null) + { + @("(not set)") + } + else + { + @v + @if (!string.IsNullOrEmpty(ViewSuffix)) { @ViewSuffix } + @if (!string.IsNullOrEmpty(Unit)) { @Unit } + } +} + +@code { + [Parameter] public EffectiveConfigFieldDescriptor Descriptor { get; set; } = null!; + [CascadingParameter] public EffectiveConfigDisplayMode DisplayMode { get; set; } = EffectiveConfigDisplayMode.View; + + [Parameter] public EventCallback ValueChanged { get; set; } + + private EffectiveIntegerConfigFieldDescriptor? IntDescriptor => Descriptor as EffectiveIntegerConfigFieldDescriptor; + private string? ViewSuffix => IntDescriptor?.ViewSuffix; + private string? Unit => IntDescriptor?.Unit; + private int Min => IntDescriptor?.Min ?? 0; + private int Max => IntDescriptor?.Max ?? int.MaxValue; + private int? DefaultValue => IntDescriptor?.DefaultValue; + + private async Task OnInput(ChangeEventArgs e) + { + int? value = null; + if (int.TryParse(e.Value?.ToString(), out var num) && num >= Min && num <= Max) + value = num; + await ValueChanged.InvokeAsync(value); + } +} diff --git a/src/DnsmasqWebUI/Components/Dnsmasq/EffectiveConfig/CustomDisplays/LocalServiceDisplay.razor b/src/DnsmasqWebUI/Components/Dnsmasq/EffectiveConfig/CustomDisplays/LocalServiceDisplay.razor new file mode 100644 index 0000000..7beb981 --- /dev/null +++ b/src/DnsmasqWebUI/Components/Dnsmasq/EffectiveConfig/CustomDisplays/LocalServiceDisplay.razor @@ -0,0 +1,31 @@ +@namespace DnsmasqWebUI.Components.Dnsmasq.EffectiveConfig.CustomDisplays +@using DnsmasqWebUI.Models.Dnsmasq + +@* local-service: net | host. View: (not set) / net / host. Edit: dropdown. *@ +@if (DisplayMode == EffectiveConfigDisplayMode.Edit && Descriptor.GetSource()?.IsReadOnly != true) +{ + +} +else +{ + var v = Descriptor.GetValue() as string; + @(string.IsNullOrEmpty(v) ? "(not set)" : (v.Trim().ToLowerInvariant() is "net" or "host" ? v.Trim() : v)) +} + +@code { + [Parameter] public EffectiveConfigFieldDescriptor Descriptor { get; set; } = null!; + [CascadingParameter] public EffectiveConfigDisplayMode DisplayMode { get; set; } = EffectiveConfigDisplayMode.View; + + [Parameter] public EventCallback ValueChanged { get; set; } + + private async Task OnChange(ChangeEventArgs e) + { + var v = e.Value?.ToString()?.Trim(); + var value = string.IsNullOrEmpty(v) ? null : (v == "net" || v == "host" ? v : null); + await ValueChanged.InvokeAsync(value); + } +} diff --git a/src/DnsmasqWebUI/Components/Dnsmasq/EffectiveConfig/CustomDisplays/LogQueriesDisplay.razor b/src/DnsmasqWebUI/Components/Dnsmasq/EffectiveConfig/CustomDisplays/LogQueriesDisplay.razor new file mode 100644 index 0000000..1d0cf78 --- /dev/null +++ b/src/DnsmasqWebUI/Components/Dnsmasq/EffectiveConfig/CustomDisplays/LogQueriesDisplay.razor @@ -0,0 +1,63 @@ +@namespace DnsmasqWebUI.Components.Dnsmasq.EffectiveConfig.CustomDisplays +@using DnsmasqWebUI.Models.Dnsmasq + +@* log-queries: optional value extra|proto|auth. View: Off / On / On (extra|proto|auth). Edit: dropdown. *@ +@if (DisplayMode == EffectiveConfigDisplayMode.Edit && Descriptor.GetSource()?.IsReadOnly != true) +{ + +} +else +{ + @GetViewText() +} + +@code { + [Parameter] public EffectiveConfigFieldDescriptor Descriptor { get; set; } = null!; + [CascadingParameter] public EffectiveConfigDisplayMode DisplayMode { get; set; } = EffectiveConfigDisplayMode.View; + + [Parameter] public EventCallback ValueChanged { get; set; } + + private string GetEditValue() + { + var v = Descriptor.GetValue() as string; + if (string.IsNullOrEmpty(v)) return ""; + var lower = v.Trim().ToLowerInvariant(); + if (lower is "extra" or "proto" or "auth") return lower; + return "on"; + } + + private string GetViewText() + { + var v = Descriptor.GetValue() as string; + if (string.IsNullOrEmpty(v)) return "Off"; + var lower = v.Trim().ToLowerInvariant(); + return lower switch + { + "extra" => "On (extra)", + "proto" => "On (proto)", + "auth" => "On (auth)", + _ => "On" + }; + } + + private async Task OnChange(ChangeEventArgs e) + { + var sel = e.Value?.ToString()?.Trim().ToLowerInvariant(); + string? newValue = sel switch + { + "" or "off" => null, + "on" => "", + "extra" => "extra", + "proto" => "proto", + "auth" => "auth", + _ => null + }; + await ValueChanged.InvokeAsync(newValue); + } +} diff --git a/src/DnsmasqWebUI/Components/Dnsmasq/EffectiveConfig/EffectiveConfigFieldDisplay.razor b/src/DnsmasqWebUI/Components/Dnsmasq/EffectiveConfig/EffectiveConfigFieldDisplay.razor index 16f3395..efaf37b 100644 --- a/src/DnsmasqWebUI/Components/Dnsmasq/EffectiveConfig/EffectiveConfigFieldDisplay.razor +++ b/src/DnsmasqWebUI/Components/Dnsmasq/EffectiveConfig/EffectiveConfigFieldDisplay.razor @@ -45,7 +45,7 @@ else protected override void OnParametersSet() { - _customFragment = RenderFragmentRegistry.GetDisplayFragment(Descriptor.SectionId, Descriptor.OptionName); + _customFragment = RenderFragmentRegistry.BuildFieldComponentFragment(Descriptor.SectionId, Descriptor.OptionName); _labelTooltip = DnsmasqOptionTooltips.Get(Descriptor.OptionName); _optionHelpKey = DnsmasqOptionTooltips.GetOptionHelpKey(Descriptor.OptionName); } diff --git a/src/DnsmasqWebUI/Components/Dnsmasq/EffectiveConfig/EffectiveConfigSection.razor b/src/DnsmasqWebUI/Components/Dnsmasq/EffectiveConfig/EffectiveConfigSection.razor index f8d9b18..c7a7dcc 100644 --- a/src/DnsmasqWebUI/Components/Dnsmasq/EffectiveConfig/EffectiveConfigSection.razor +++ b/src/DnsmasqWebUI/Components/Dnsmasq/EffectiveConfig/EffectiveConfigSection.razor @@ -1,6 +1,9 @@ @namespace DnsmasqWebUI.Components.Dnsmasq.EffectiveConfig @using DnsmasqWebUI.Models.Dnsmasq @using DnsmasqWebUI.Models.Dnsmasq.EffectiveConfig +@using DnsmasqWebUI.Infrastructure.Helpers.Config +@using DnsmasqWebUI.Infrastructure.Services.Abstractions +@inject IEffectiveConfigRenderFragmentRegistry RenderFragmentRegistry @implements IAsyncDisposable @if (Status != null) @@ -24,7 +27,7 @@
@{ var views = EffectiveConfigViews.GetViewsForContext(Context); - var descriptorsBySection = EffectiveConfigViews.GetDescriptorsBySection(Status, views); + var descriptorsBySection = EffectiveConfigViews.GetDescriptorsBySection(Status, views, RenderFragmentRegistry); var hasSearch = !string.IsNullOrWhiteSpace(_searchTerm); var term = _searchTerm?.Trim() ?? ""; } @@ -48,7 +51,6 @@ [Parameter] public DnsmasqServiceStatus? Status { get; set; } [Parameter] public EffectiveConfigContext Context { get; set; } = EffectiveConfigContext.All; [Parameter] public bool ShowSearchBox { get; set; } = true; - /// View = read-only; Edit = editable (future). Cascaded to field displays. [Parameter] public EffectiveConfigDisplayMode DisplayMode { get; set; } = EffectiveConfigDisplayMode.View; private string _searchTerm = ""; diff --git a/src/DnsmasqWebUI/Infrastructure/Helpers/Config/EffectiveConfigFieldBuilder.cs b/src/DnsmasqWebUI/Infrastructure/Helpers/Config/EffectiveConfigFieldBuilder.cs index 71f7dae..66bc3b0 100644 --- a/src/DnsmasqWebUI/Infrastructure/Helpers/Config/EffectiveConfigFieldBuilder.cs +++ b/src/DnsmasqWebUI/Infrastructure/Helpers/Config/EffectiveConfigFieldBuilder.cs @@ -1,5 +1,6 @@ using DnsmasqWebUI.Models.Dnsmasq; using DnsmasqWebUI.Models.Dnsmasq.EffectiveConfig; +using DnsmasqWebUI.Infrastructure.Services.Abstractions; namespace DnsmasqWebUI.Infrastructure.Helpers.Config; @@ -11,7 +12,7 @@ public static class EffectiveConfigFieldBuilder private static EffectiveDnsmasqConfig? Config(DnsmasqServiceStatus? s) => s?.EffectiveConfig; private static EffectiveConfigSources? Sources(DnsmasqServiceStatus? s) => s?.EffectiveConfigSources; - /// Builds getItems from config/source list selectors; used by Multi() for fields that have both. + /// Builds getItems from config/source list selectors; used by AddDescriptor for multi-value fields that have both config and source lists. private static Func?> Items( Func?> getValues, Func?> getWithSource) @@ -37,7 +38,7 @@ public static class EffectiveConfigFieldBuilder public static string SectionCache => EffectiveConfigSections.SectionCache; public static string SectionProcess => EffectiveConfigSections.SectionProcess; - public static IReadOnlyList BuildFieldDescriptors(DnsmasqServiceStatus? status) + public static IReadOnlyList BuildFieldDescriptors(DnsmasqServiceStatus? status, IEffectiveConfigRenderFragmentRegistry? registry = null) { if (status == null || status.EffectiveConfig == null) return Array.Empty(); @@ -45,142 +46,158 @@ public static class EffectiveConfigFieldBuilder var list = new List(); // Hosts - list.Add(Single(DnsmasqConfKeys.NoHosts, status, s => Config(s)?.NoHosts, s => Sources(s)?.NoHosts, null)); - list.Add(Multi(DnsmasqConfKeys.AddnHosts, status, s => ToValueWithSourceList(s?.AddnHostsPaths, Sources(s)?.AddnHostsPaths))); - list.Add(Single(DnsmasqConfKeys.Hostsdir, status, s => Config(s)?.HostsdirPath, s => Sources(s)?.HostsdirPath, null)); - list.Add(Single(DnsmasqConfKeys.ReadEthers, status, s => Config(s)?.ReadEthers, s => Sources(s)?.ReadEthers, null)); + list.AddDescriptor(registry, DnsmasqConfKeys.NoHosts, status, s => Config(s)?.NoHosts, s => Sources(s)?.NoHosts, null); + list.AddDescriptor(registry, DnsmasqConfKeys.AddnHosts, status, null, null, s => ToValueWithSourceList(s?.AddnHostsPaths, Sources(s)?.AddnHostsPaths)); + list.AddDescriptor(registry, DnsmasqConfKeys.Hostsdir, status, s => Config(s)?.HostsdirPath, s => Sources(s)?.HostsdirPath, null); + list.AddDescriptor(registry, DnsmasqConfKeys.ReadEthers, status, s => Config(s)?.ReadEthers, s => Sources(s)?.ReadEthers, null); // Resolver / DNS - list.Add(Multi(DnsmasqOptionTooltips.ServerLocalLabel, status, Items(ec => ec?.ServerLocalValues, src => src?.ServerLocalValues))); - list.Add(Multi(DnsmasqConfKeys.RevServer, status, Items(ec => ec?.RevServerValues, src => src?.RevServerValues))); - list.Add(Multi(DnsmasqConfKeys.Address, status, Items(ec => ec?.AddressValues, src => src?.AddressValues))); - list.Add(Multi(DnsmasqConfKeys.ResolvFile, status, Items(ec => ec?.ResolvFiles, src => src?.ResolvFiles))); - list.Add(Single(DnsmasqConfKeys.NoResolv, status, s => Config(s)?.NoResolv, s => Sources(s)?.NoResolv, null)); - list.Add(Single(DnsmasqConfKeys.DomainNeeded, status, s => Config(s)?.DomainNeeded, s => Sources(s)?.DomainNeeded, null)); - list.Add(Single(DnsmasqConfKeys.Port, status, s => Config(s)?.Port, s => Sources(s)?.Port, null)); - list.Add(Single(DnsmasqConfKeys.LogQueries, status, s => Config(s)?.LogQueries, s => Sources(s)?.LogQueries, null)); - list.Add(Single(DnsmasqConfKeys.StrictOrder, status, s => Config(s)?.StrictOrder, s => Sources(s)?.StrictOrder, null)); - list.Add(Single(DnsmasqConfKeys.AllServers, status, s => Config(s)?.AllServers, s => Sources(s)?.AllServers, null)); - list.Add(Single(DnsmasqConfKeys.AuthTtl, status, s => Config(s)?.AuthTtl, s => Sources(s)?.AuthTtl, null)); - list.Add(Single(DnsmasqConfKeys.EdnsPacketMax, status, s => Config(s)?.EdnsPacketMax, s => Sources(s)?.EdnsPacketMax, null)); - list.Add(Single(DnsmasqConfKeys.QueryPort, status, s => Config(s)?.QueryPort, s => Sources(s)?.QueryPort, null)); - list.Add(Single(DnsmasqConfKeys.PortLimit, status, s => Config(s)?.PortLimit, s => Sources(s)?.PortLimit, null)); - list.Add(Single(DnsmasqConfKeys.MinPort, status, s => Config(s)?.MinPort, s => Sources(s)?.MinPort, null)); - list.Add(Single(DnsmasqConfKeys.MaxPort, status, s => Config(s)?.MaxPort, s => Sources(s)?.MaxPort, null)); - list.Add(Single(DnsmasqConfKeys.DnsLoopDetect, status, s => Config(s)?.DnsLoopDetect, s => Sources(s)?.DnsLoopDetect, null)); - list.Add(Single(DnsmasqConfKeys.StopDnsRebind, status, s => Config(s)?.StopDnsRebind, s => Sources(s)?.StopDnsRebind, null)); - list.Add(Single(DnsmasqConfKeys.RebindLocalhostOk, status, s => Config(s)?.RebindLocalhostOk, s => Sources(s)?.RebindLocalhostOk, null)); - list.Add(Single(DnsmasqConfKeys.ClearOnReload, status, s => Config(s)?.ClearOnReload, s => Sources(s)?.ClearOnReload, null)); - list.Add(Single(DnsmasqConfKeys.ExpandHosts, status, s => Config(s)?.ExpandHosts, s => Sources(s)?.ExpandHosts, null)); - list.Add(Single(DnsmasqConfKeys.BogusPriv, status, s => Config(s)?.BogusPriv, s => Sources(s)?.BogusPriv, null)); - list.Add(Multi(DnsmasqConfKeys.RebindDomainOk, status, Items(ec => ec?.RebindDomainOkValues, src => src?.RebindDomainOkValues))); - list.Add(Multi(DnsmasqConfKeys.BogusNxdomain, status, Items(ec => ec?.BogusNxdomainValues, src => src?.BogusNxdomainValues))); - list.Add(Multi(DnsmasqConfKeys.IgnoreAddress, status, Items(ec => ec?.IgnoreAddressValues, src => src?.IgnoreAddressValues))); - list.Add(Multi(DnsmasqConfKeys.Alias, status, Items(ec => ec?.AliasValues, src => src?.AliasValues))); - list.Add(Multi(DnsmasqConfKeys.FilterRr, status, Items(ec => ec?.FilterRrValues, src => src?.FilterRrValues))); - list.Add(Single(DnsmasqConfKeys.Filterwin2k, status, s => Config(s)?.Filterwin2k, s => Sources(s)?.Filterwin2k, null)); - list.Add(Single(DnsmasqConfKeys.FilterA, status, s => Config(s)?.FilterA, s => Sources(s)?.FilterA, null)); - list.Add(Single(DnsmasqConfKeys.FilterAaaa, status, s => Config(s)?.FilterAaaa, s => Sources(s)?.FilterAaaa, null)); - list.Add(Single(DnsmasqConfKeys.LocaliseQueries, status, s => Config(s)?.LocaliseQueries, s => Sources(s)?.LocaliseQueries, null)); - list.Add(Single(DnsmasqConfKeys.FastDnsRetry, status, s => Config(s)?.FastDnsRetry, s => Sources(s)?.FastDnsRetry, null)); - list.Add(Multi(DnsmasqConfKeys.Ipset, status, Items(ec => ec?.IpsetValues, src => src?.IpsetValues))); - list.Add(Multi(DnsmasqConfKeys.Nftset, status, Items(ec => ec?.NftsetValues, src => src?.NftsetValues))); + list.AddDescriptor(registry, DnsmasqOptionTooltips.ServerLocalLabel, status, null, null, Items(ec => ec?.ServerLocalValues, src => src?.ServerLocalValues)); + list.AddDescriptor(registry, DnsmasqConfKeys.RevServer, status, null, null, Items(ec => ec?.RevServerValues, src => src?.RevServerValues)); + list.AddDescriptor(registry, DnsmasqConfKeys.Address, status, null, null, Items(ec => ec?.AddressValues, src => src?.AddressValues)); + list.AddDescriptor(registry, DnsmasqConfKeys.ResolvFile, status, null, null, Items(ec => ec?.ResolvFiles, src => src?.ResolvFiles)); + list.AddDescriptor(registry, DnsmasqConfKeys.NoResolv, status, s => Config(s)?.NoResolv, s => Sources(s)?.NoResolv, null); + list.AddDescriptor(registry, DnsmasqConfKeys.DomainNeeded, status, s => Config(s)?.DomainNeeded, s => Sources(s)?.DomainNeeded, null); + list.AddDescriptor(registry, DnsmasqConfKeys.Port, status, s => Config(s)?.Port, s => Sources(s)?.Port, null); + list.AddDescriptor(registry, DnsmasqConfKeys.LogQueries, status, s => Config(s)?.LogQueries, s => Sources(s)?.LogQueries, null); + list.AddDescriptor(registry, DnsmasqConfKeys.StrictOrder, status, s => Config(s)?.StrictOrder, s => Sources(s)?.StrictOrder, null); + list.AddDescriptor(registry, DnsmasqConfKeys.AllServers, status, s => Config(s)?.AllServers, s => Sources(s)?.AllServers, null); + list.AddDescriptor(registry, DnsmasqConfKeys.AuthTtl, status, s => Config(s)?.AuthTtl, s => Sources(s)?.AuthTtl, null); + list.AddDescriptor(registry, DnsmasqConfKeys.EdnsPacketMax, status, s => Config(s)?.EdnsPacketMax, s => Sources(s)?.EdnsPacketMax, null); + list.AddDescriptor(registry, DnsmasqConfKeys.QueryPort, status, s => Config(s)?.QueryPort, s => Sources(s)?.QueryPort, null); + list.AddDescriptor(registry, DnsmasqConfKeys.PortLimit, status, s => Config(s)?.PortLimit, s => Sources(s)?.PortLimit, null); + list.AddDescriptor(registry, DnsmasqConfKeys.MinPort, status, s => Config(s)?.MinPort, s => Sources(s)?.MinPort, null); + list.AddDescriptor(registry, DnsmasqConfKeys.MaxPort, status, s => Config(s)?.MaxPort, s => Sources(s)?.MaxPort, null); + list.AddDescriptor(registry, DnsmasqConfKeys.DnsLoopDetect, status, s => Config(s)?.DnsLoopDetect, s => Sources(s)?.DnsLoopDetect, null); + list.AddDescriptor(registry, DnsmasqConfKeys.StopDnsRebind, status, s => Config(s)?.StopDnsRebind, s => Sources(s)?.StopDnsRebind, null); + list.AddDescriptor(registry, DnsmasqConfKeys.RebindLocalhostOk, status, s => Config(s)?.RebindLocalhostOk, s => Sources(s)?.RebindLocalhostOk, null); + list.AddDescriptor(registry, DnsmasqConfKeys.ClearOnReload, status, s => Config(s)?.ClearOnReload, s => Sources(s)?.ClearOnReload, null); + list.AddDescriptor(registry, DnsmasqConfKeys.ExpandHosts, status, s => Config(s)?.ExpandHosts, s => Sources(s)?.ExpandHosts, null); + list.AddDescriptor(registry, DnsmasqConfKeys.BogusPriv, status, s => Config(s)?.BogusPriv, s => Sources(s)?.BogusPriv, null); + list.AddDescriptor(registry, DnsmasqConfKeys.RebindDomainOk, status, null, null, Items(ec => ec?.RebindDomainOkValues, src => src?.RebindDomainOkValues)); + list.AddDescriptor(registry, DnsmasqConfKeys.BogusNxdomain, status, null, null, Items(ec => ec?.BogusNxdomainValues, src => src?.BogusNxdomainValues)); + list.AddDescriptor(registry, DnsmasqConfKeys.IgnoreAddress, status, null, null, Items(ec => ec?.IgnoreAddressValues, src => src?.IgnoreAddressValues)); + list.AddDescriptor(registry, DnsmasqConfKeys.Alias, status, null, null, Items(ec => ec?.AliasValues, src => src?.AliasValues)); + list.AddDescriptor(registry, DnsmasqConfKeys.FilterRr, status, null, null, Items(ec => ec?.FilterRrValues, src => src?.FilterRrValues)); + list.AddDescriptor(registry, DnsmasqConfKeys.Filterwin2k, status, s => Config(s)?.Filterwin2k, s => Sources(s)?.Filterwin2k, null); + list.AddDescriptor(registry, DnsmasqConfKeys.FilterA, status, s => Config(s)?.FilterA, s => Sources(s)?.FilterA, null); + list.AddDescriptor(registry, DnsmasqConfKeys.FilterAaaa, status, s => Config(s)?.FilterAaaa, s => Sources(s)?.FilterAaaa, null); + list.AddDescriptor(registry, DnsmasqConfKeys.LocaliseQueries, status, s => Config(s)?.LocaliseQueries, s => Sources(s)?.LocaliseQueries, null); + list.AddDescriptor(registry, DnsmasqConfKeys.FastDnsRetry, status, s => Config(s)?.FastDnsRetry, s => Sources(s)?.FastDnsRetry, null); + list.AddDescriptor(registry, DnsmasqConfKeys.Ipset, status, null, null, Items(ec => ec?.IpsetValues, src => src?.IpsetValues)); + list.AddDescriptor(registry, DnsmasqConfKeys.Nftset, status, null, null, Items(ec => ec?.NftsetValues, src => src?.NftsetValues)); // DNS records - list.Add(Multi(DnsmasqConfKeys.Domain, status, Items(ec => ec?.DomainValues, src => src?.DomainValues))); - list.Add(Multi(DnsmasqConfKeys.Cname, status, Items(ec => ec?.CnameValues, src => src?.CnameValues))); - list.Add(Multi(DnsmasqConfKeys.MxHost, status, Items(ec => ec?.MxHostValues, src => src?.MxHostValues))); - list.Add(Multi(DnsmasqConfKeys.Srv, status, Items(ec => ec?.SrvValues, src => src?.SrvValues))); - list.Add(Multi(DnsmasqConfKeys.PtrRecord, status, Items(ec => ec?.PtrRecordValues, src => src?.PtrRecordValues))); - list.Add(Multi(DnsmasqConfKeys.TxtRecord, status, Items(ec => ec?.TxtRecordValues, src => src?.TxtRecordValues))); - list.Add(Multi(DnsmasqConfKeys.NaptrRecord, status, Items(ec => ec?.NaptrRecordValues, src => src?.NaptrRecordValues))); - list.Add(Multi(DnsmasqConfKeys.HostRecord, status, Items(ec => ec?.HostRecordValues, src => src?.HostRecordValues))); - list.Add(Multi(DnsmasqConfKeys.DynamicHost, status, Items(ec => ec?.DynamicHostValues, src => src?.DynamicHostValues))); - list.Add(Multi(DnsmasqConfKeys.InterfaceName, status, Items(ec => ec?.InterfaceNameValues, src => src?.InterfaceNameValues))); - list.Add(Single(DnsmasqConfKeys.MxTarget, status, s => Config(s)?.MxTarget, s => Sources(s)?.MxTarget, null)); - list.Add(Single(DnsmasqConfKeys.Localmx, status, s => Config(s)?.Localmx, s => Sources(s)?.Localmx, null)); - list.Add(Single(DnsmasqConfKeys.Selfmx, status, s => Config(s)?.Selfmx, s => Sources(s)?.Selfmx, null)); + list.AddDescriptor(registry, DnsmasqConfKeys.Domain, status, null, null, Items(ec => ec?.DomainValues, src => src?.DomainValues)); + list.AddDescriptor(registry, DnsmasqConfKeys.Cname, status, null, null, Items(ec => ec?.CnameValues, src => src?.CnameValues)); + list.AddDescriptor(registry, DnsmasqConfKeys.MxHost, status, null, null, Items(ec => ec?.MxHostValues, src => src?.MxHostValues)); + list.AddDescriptor(registry, DnsmasqConfKeys.Srv, status, null, null, Items(ec => ec?.SrvValues, src => src?.SrvValues)); + list.AddDescriptor(registry, DnsmasqConfKeys.PtrRecord, status, null, null, Items(ec => ec?.PtrRecordValues, src => src?.PtrRecordValues)); + list.AddDescriptor(registry, DnsmasqConfKeys.TxtRecord, status, null, null, Items(ec => ec?.TxtRecordValues, src => src?.TxtRecordValues)); + list.AddDescriptor(registry, DnsmasqConfKeys.NaptrRecord, status, null, null, Items(ec => ec?.NaptrRecordValues, src => src?.NaptrRecordValues)); + list.AddDescriptor(registry, DnsmasqConfKeys.HostRecord, status, null, null, Items(ec => ec?.HostRecordValues, src => src?.HostRecordValues)); + list.AddDescriptor(registry, DnsmasqConfKeys.DynamicHost, status, null, null, Items(ec => ec?.DynamicHostValues, src => src?.DynamicHostValues)); + list.AddDescriptor(registry, DnsmasqConfKeys.InterfaceName, status, null, null, Items(ec => ec?.InterfaceNameValues, src => src?.InterfaceNameValues)); + list.AddDescriptor(registry, DnsmasqConfKeys.MxTarget, status, s => Config(s)?.MxTarget, s => Sources(s)?.MxTarget, null); + list.AddDescriptor(registry, DnsmasqConfKeys.Localmx, status, s => Config(s)?.Localmx, s => Sources(s)?.Localmx, null); + list.AddDescriptor(registry, DnsmasqConfKeys.Selfmx, status, s => Config(s)?.Selfmx, s => Sources(s)?.Selfmx, null); // DHCP - list.Add(Single(DnsmasqConfKeys.DhcpAuthoritative, status, s => Config(s)?.DhcpAuthoritative, s => Sources(s)?.DhcpAuthoritative, null)); - list.Add(Single(DnsmasqConfKeys.DhcpRapidCommit, status, s => Config(s)?.DhcpRapidCommit, s => Sources(s)?.DhcpRapidCommit, null)); - list.Add(Single(DnsmasqConfKeys.LeasefileRo, status, s => Config(s)?.LeasefileRo, s => Sources(s)?.LeasefileRo, null)); - list.Add(Single(DnsmasqConfKeys.DhcpScript, status, s => Config(s)?.DhcpScriptPath, s => Sources(s)?.DhcpScriptPath, null)); - list.Add(Single(DnsmasqConfKeys.DhcpLeasefile, status, s => Config(s)?.DhcpLeaseFilePath, s => Sources(s)?.DhcpLeaseFilePath, null)); - list.Add(Single(DnsmasqConfKeys.DhcpLeaseMax, status, s => Config(s)?.DhcpLeaseMax, s => Sources(s)?.DhcpLeaseMax, null)); - list.Add(Single(DnsmasqConfKeys.DhcpTtl, status, s => Config(s)?.DhcpTtl, s => Sources(s)?.DhcpTtl, null)); - list.Add(Multi(DnsmasqConfKeys.DhcpRange, status, Items(ec => ec?.DhcpRanges, src => src?.DhcpRanges))); - list.Add(Multi(DnsmasqConfKeys.DhcpHost, status, Items(ec => ec?.DhcpHostLines, src => src?.DhcpHostLines))); - list.Add(Multi(DnsmasqConfKeys.DhcpOption, status, Items(ec => ec?.DhcpOptionLines, src => src?.DhcpOptionLines))); - list.Add(Multi(DnsmasqConfKeys.DhcpOptionForce, status, Items(ec => ec?.DhcpOptionForceLines, src => src?.DhcpOptionForceLines))); - list.Add(Multi(DnsmasqConfKeys.DhcpMatch, status, Items(ec => ec?.DhcpMatchValues, src => src?.DhcpMatchValues))); - list.Add(Multi(DnsmasqConfKeys.DhcpMac, status, Items(ec => ec?.DhcpMacValues, src => src?.DhcpMacValues))); - list.Add(Multi(DnsmasqConfKeys.DhcpNameMatch, status, Items(ec => ec?.DhcpNameMatchValues, src => src?.DhcpNameMatchValues))); - list.Add(Multi(DnsmasqConfKeys.DhcpIgnoreNames, status, Items(ec => ec?.DhcpIgnoreNamesValues, src => src?.DhcpIgnoreNamesValues))); - list.Add(Multi(DnsmasqConfKeys.DhcpHostsfile, status, Items(ec => ec?.DhcpHostsfilePaths, src => src?.DhcpHostsfilePaths))); - list.Add(Multi(DnsmasqConfKeys.DhcpOptsfile, status, Items(ec => ec?.DhcpOptsfilePaths, src => src?.DhcpOptsfilePaths))); - list.Add(Multi(DnsmasqConfKeys.DhcpHostsdir, status, Items(ec => ec?.DhcpHostsdirPaths, src => src?.DhcpHostsdirPaths))); - list.Add(Multi(DnsmasqConfKeys.DhcpBoot, status, Items(ec => ec?.DhcpBootValues, src => src?.DhcpBootValues))); - list.Add(Multi(DnsmasqConfKeys.DhcpIgnore, status, Items(ec => ec?.DhcpIgnoreValues, src => src?.DhcpIgnoreValues))); - list.Add(Multi(DnsmasqConfKeys.DhcpVendorclass, status, Items(ec => ec?.DhcpVendorclassValues, src => src?.DhcpVendorclassValues))); - list.Add(Multi(DnsmasqConfKeys.DhcpUserclass, status, Items(ec => ec?.DhcpUserclassValues, src => src?.DhcpUserclassValues))); - list.Add(Multi(DnsmasqConfKeys.RaParam, status, Items(ec => ec?.RaParamValues, src => src?.RaParamValues))); - list.Add(Multi(DnsmasqConfKeys.Slaac, status, Items(ec => ec?.SlaacValues, src => src?.SlaacValues))); + list.AddDescriptor(registry, DnsmasqConfKeys.DhcpAuthoritative, status, s => Config(s)?.DhcpAuthoritative, s => Sources(s)?.DhcpAuthoritative, null); + list.AddDescriptor(registry, DnsmasqConfKeys.DhcpRapidCommit, status, s => Config(s)?.DhcpRapidCommit, s => Sources(s)?.DhcpRapidCommit, null); + list.AddDescriptor(registry, DnsmasqConfKeys.LeasefileRo, status, s => Config(s)?.LeasefileRo, s => Sources(s)?.LeasefileRo, null); + list.AddDescriptor(registry, DnsmasqConfKeys.DhcpScript, status, s => Config(s)?.DhcpScriptPath, s => Sources(s)?.DhcpScriptPath, null); + list.AddDescriptor(registry, DnsmasqConfKeys.DhcpLeasefile, status, s => Config(s)?.DhcpLeaseFilePath, s => Sources(s)?.DhcpLeaseFilePath, null); + list.AddDescriptor(registry, DnsmasqConfKeys.DhcpLeaseMax, status, s => Config(s)?.DhcpLeaseMax, s => Sources(s)?.DhcpLeaseMax, null); + list.AddDescriptor(registry, DnsmasqConfKeys.DhcpTtl, status, s => Config(s)?.DhcpTtl, s => Sources(s)?.DhcpTtl, null); + list.AddDescriptor(registry, DnsmasqConfKeys.DhcpRange, status, null, null, Items(ec => ec?.DhcpRanges, src => src?.DhcpRanges)); + list.AddDescriptor(registry, DnsmasqConfKeys.DhcpHost, status, null, null, Items(ec => ec?.DhcpHostLines, src => src?.DhcpHostLines)); + list.AddDescriptor(registry, DnsmasqConfKeys.DhcpOption, status, null, null, Items(ec => ec?.DhcpOptionLines, src => src?.DhcpOptionLines)); + list.AddDescriptor(registry, DnsmasqConfKeys.DhcpOptionForce, status, null, null, Items(ec => ec?.DhcpOptionForceLines, src => src?.DhcpOptionForceLines)); + list.AddDescriptor(registry, DnsmasqConfKeys.DhcpMatch, status, null, null, Items(ec => ec?.DhcpMatchValues, src => src?.DhcpMatchValues)); + list.AddDescriptor(registry, DnsmasqConfKeys.DhcpMac, status, null, null, Items(ec => ec?.DhcpMacValues, src => src?.DhcpMacValues)); + list.AddDescriptor(registry, DnsmasqConfKeys.DhcpNameMatch, status, null, null, Items(ec => ec?.DhcpNameMatchValues, src => src?.DhcpNameMatchValues)); + list.AddDescriptor(registry, DnsmasqConfKeys.DhcpIgnoreNames, status, null, null, Items(ec => ec?.DhcpIgnoreNamesValues, src => src?.DhcpIgnoreNamesValues)); + list.AddDescriptor(registry, DnsmasqConfKeys.DhcpHostsfile, status, null, null, Items(ec => ec?.DhcpHostsfilePaths, src => src?.DhcpHostsfilePaths)); + list.AddDescriptor(registry, DnsmasqConfKeys.DhcpOptsfile, status, null, null, Items(ec => ec?.DhcpOptsfilePaths, src => src?.DhcpOptsfilePaths)); + list.AddDescriptor(registry, DnsmasqConfKeys.DhcpHostsdir, status, null, null, Items(ec => ec?.DhcpHostsdirPaths, src => src?.DhcpHostsdirPaths)); + list.AddDescriptor(registry, DnsmasqConfKeys.DhcpBoot, status, null, null, Items(ec => ec?.DhcpBootValues, src => src?.DhcpBootValues)); + list.AddDescriptor(registry, DnsmasqConfKeys.DhcpIgnore, status, null, null, Items(ec => ec?.DhcpIgnoreValues, src => src?.DhcpIgnoreValues)); + list.AddDescriptor(registry, DnsmasqConfKeys.DhcpVendorclass, status, null, null, Items(ec => ec?.DhcpVendorclassValues, src => src?.DhcpVendorclassValues)); + list.AddDescriptor(registry, DnsmasqConfKeys.DhcpUserclass, status, null, null, Items(ec => ec?.DhcpUserclassValues, src => src?.DhcpUserclassValues)); + list.AddDescriptor(registry, DnsmasqConfKeys.RaParam, status, null, null, Items(ec => ec?.RaParamValues, src => src?.RaParamValues)); + list.AddDescriptor(registry, DnsmasqConfKeys.Slaac, status, null, null, Items(ec => ec?.SlaacValues, src => src?.SlaacValues)); // TFTP / PXE - list.Add(Single(DnsmasqConfKeys.EnableTftp, status, s => Config(s)?.EnableTftp, s => Sources(s)?.EnableTftp, null)); - list.Add(Single(DnsmasqConfKeys.TftpSecure, status, s => Config(s)?.TftpSecure, s => Sources(s)?.TftpSecure, null)); - list.Add(Single(DnsmasqConfKeys.TftpNoFail, status, s => Config(s)?.TftpNoFail, s => Sources(s)?.TftpNoFail, null)); - list.Add(Single(DnsmasqConfKeys.TftpNoBlocksize, status, s => Config(s)?.TftpNoBlocksize, s => Sources(s)?.TftpNoBlocksize, null)); - list.Add(Single(DnsmasqConfKeys.TftpRoot, status, s => Config(s)?.TftpRootPath, s => Sources(s)?.TftpRootPath, null)); - list.Add(Single(DnsmasqConfKeys.PxePrompt, status, s => Config(s)?.PxePrompt, s => Sources(s)?.PxePrompt, null)); - list.Add(Multi(DnsmasqConfKeys.PxeService, status, Items(ec => ec?.PxeServiceValues, src => src?.PxeServiceValues))); + list.AddDescriptor(registry, DnsmasqConfKeys.EnableTftp, status, s => Config(s)?.EnableTftp, s => Sources(s)?.EnableTftp, null); + list.AddDescriptor(registry, DnsmasqConfKeys.TftpSecure, status, s => Config(s)?.TftpSecure, s => Sources(s)?.TftpSecure, null); + list.AddDescriptor(registry, DnsmasqConfKeys.TftpNoFail, status, s => Config(s)?.TftpNoFail, s => Sources(s)?.TftpNoFail, null); + list.AddDescriptor(registry, DnsmasqConfKeys.TftpNoBlocksize, status, s => Config(s)?.TftpNoBlocksize, s => Sources(s)?.TftpNoBlocksize, null); + list.AddDescriptor(registry, DnsmasqConfKeys.TftpRoot, status, s => Config(s)?.TftpRootPath, s => Sources(s)?.TftpRootPath, null); + list.AddDescriptor(registry, DnsmasqConfKeys.PxePrompt, status, s => Config(s)?.PxePrompt, s => Sources(s)?.PxePrompt, null); + list.AddDescriptor(registry, DnsmasqConfKeys.PxeService, status, null, null, Items(ec => ec?.PxeServiceValues, src => src?.PxeServiceValues)); // DNSSEC - list.Add(Single(DnsmasqConfKeys.Dnssec, status, s => Config(s)?.Dnssec, s => Sources(s)?.Dnssec, null)); - list.Add(Single(DnsmasqConfKeys.DnssecCheckUnsigned, status, s => Config(s)?.DnssecCheckUnsigned, s => Sources(s)?.DnssecCheckUnsigned, null)); - list.Add(Single(DnsmasqConfKeys.ProxyDnssec, status, s => Config(s)?.ProxyDnssec, s => Sources(s)?.ProxyDnssec, null)); - list.Add(Multi(DnsmasqConfKeys.TrustAnchor, status, Items(ec => ec?.TrustAnchorValues, src => src?.TrustAnchorValues))); + list.AddDescriptor(registry, DnsmasqConfKeys.Dnssec, status, s => Config(s)?.Dnssec, s => Sources(s)?.Dnssec, null); + list.AddDescriptor(registry, DnsmasqConfKeys.DnssecCheckUnsigned, status, s => Config(s)?.DnssecCheckUnsigned, s => Sources(s)?.DnssecCheckUnsigned, null); + list.AddDescriptor(registry, DnsmasqConfKeys.ProxyDnssec, status, s => Config(s)?.ProxyDnssec, s => Sources(s)?.ProxyDnssec, null); + list.AddDescriptor(registry, DnsmasqConfKeys.TrustAnchor, status, null, null, Items(ec => ec?.TrustAnchorValues, src => src?.TrustAnchorValues)); // Cache - list.Add(Multi(DnsmasqConfKeys.CacheRr, status, Items(ec => ec?.CacheRrValues, src => src?.CacheRrValues))); - list.Add(Single(DnsmasqConfKeys.CacheSize, status, s => Config(s)?.CacheSize, s => Sources(s)?.CacheSize, null)); - list.Add(Single(DnsmasqConfKeys.LocalTtl, status, s => Config(s)?.LocalTtl, s => Sources(s)?.LocalTtl, null)); - list.Add(Single(DnsmasqConfKeys.NoNegcache, status, s => Config(s)?.NoNegcache, s => Sources(s)?.NoNegcache, null)); - list.Add(Single(DnsmasqConfKeys.NegTtl, status, s => Config(s)?.NegTtl, s => Sources(s)?.NegTtl, null)); - list.Add(Single(DnsmasqConfKeys.MaxTtl, status, s => Config(s)?.MaxTtl, s => Sources(s)?.MaxTtl, null)); - list.Add(Single(DnsmasqConfKeys.MaxCacheTtl, status, s => Config(s)?.MaxCacheTtl, s => Sources(s)?.MaxCacheTtl, null)); - list.Add(Single(DnsmasqConfKeys.MinCacheTtl, status, s => Config(s)?.MinCacheTtl, s => Sources(s)?.MinCacheTtl, null)); + list.AddDescriptor(registry, DnsmasqConfKeys.CacheRr, status, null, null, Items(ec => ec?.CacheRrValues, src => src?.CacheRrValues)); + list.AddDescriptor(registry, DnsmasqConfKeys.CacheSize, status, s => Config(s)?.CacheSize, s => Sources(s)?.CacheSize, null); + list.AddDescriptor(registry, DnsmasqConfKeys.LocalTtl, status, s => Config(s)?.LocalTtl, s => Sources(s)?.LocalTtl, null); + list.AddDescriptor(registry, DnsmasqConfKeys.NoNegcache, status, s => Config(s)?.NoNegcache, s => Sources(s)?.NoNegcache, null); + list.AddDescriptor(registry, DnsmasqConfKeys.NegTtl, status, s => Config(s)?.NegTtl, s => Sources(s)?.NegTtl, null); + list.AddDescriptor(registry, DnsmasqConfKeys.MaxTtl, status, s => Config(s)?.MaxTtl, s => Sources(s)?.MaxTtl, null); + list.AddDescriptor(registry, DnsmasqConfKeys.MaxCacheTtl, status, s => Config(s)?.MaxCacheTtl, s => Sources(s)?.MaxCacheTtl, null); + list.AddDescriptor(registry, DnsmasqConfKeys.MinCacheTtl, status, s => Config(s)?.MinCacheTtl, s => Sources(s)?.MinCacheTtl, null); // Process & networking - list.Add(Single(DnsmasqConfKeys.NoPoll, status, s => Config(s)?.NoPoll, s => Sources(s)?.NoPoll, null)); - list.Add(Single(DnsmasqConfKeys.BindInterfaces, status, s => Config(s)?.BindInterfaces, s => Sources(s)?.BindInterfaces, null)); - list.Add(Single(DnsmasqConfKeys.BindDynamic, status, s => Config(s)?.BindDynamic, s => Sources(s)?.BindDynamic, null)); - list.Add(Single(DnsmasqConfKeys.LogDebug, status, s => Config(s)?.LogDebug, s => Sources(s)?.LogDebug, null)); - list.Add(Single(DnsmasqConfKeys.LogAsync, status, s => Config(s)?.LogAsync, s => Sources(s)?.LogAsync, null)); - list.Add(Single(DnsmasqConfKeys.LocalService, status, s => Config(s)?.LocalService, s => Sources(s)?.LocalService, null)); - list.Add(Multi(DnsmasqConfKeys.Interface, status, Items(ec => ec?.Interfaces, src => src?.Interfaces))); - list.Add(Multi(DnsmasqConfKeys.ListenAddress, status, Items(ec => ec?.ListenAddresses, src => src?.ListenAddresses))); - list.Add(Multi(DnsmasqConfKeys.ExceptInterface, status, Items(ec => ec?.ExceptInterfaces, src => src?.ExceptInterfaces))); - list.Add(Multi(DnsmasqConfKeys.AuthServer, status, Items(ec => ec?.AuthServerValues, src => src?.AuthServerValues))); - list.Add(Multi(DnsmasqConfKeys.NoDhcpInterface, status, Items(ec => ec?.NoDhcpInterfaceValues, src => src?.NoDhcpInterfaceValues))); - list.Add(Multi(DnsmasqConfKeys.NoDhcpv4Interface, status, Items(ec => ec?.NoDhcpv4InterfaceValues, src => src?.NoDhcpv4InterfaceValues))); - list.Add(Multi(DnsmasqConfKeys.NoDhcpv6Interface, status, Items(ec => ec?.NoDhcpv6InterfaceValues, src => src?.NoDhcpv6InterfaceValues))); - list.Add(Single(DnsmasqConfKeys.PidFile, status, s => Config(s)?.PidFilePath, s => Sources(s)?.PidFilePath, null)); - list.Add(Single(DnsmasqConfKeys.User, status, s => Config(s)?.User, s => Sources(s)?.User, null)); - list.Add(Single(DnsmasqConfKeys.Group, status, s => Config(s)?.Group, s => Sources(s)?.Group, null)); - list.Add(Single(DnsmasqConfKeys.LogFacility, status, s => Config(s)?.LogFacility, s => Sources(s)?.LogFacility, null)); - list.Add(Single(DnsmasqConfKeys.EnableDbus, status, s => Config(s)?.EnableDbus, s => Sources(s)?.EnableDbus, null)); - list.Add(Single(DnsmasqConfKeys.EnableUbus, status, s => Config(s)?.EnableUbus, s => Sources(s)?.EnableUbus, null)); - list.Add(Single(DnsmasqConfKeys.EnableRa, status, s => Config(s)?.EnableRa, s => Sources(s)?.EnableRa, null)); - list.Add(Single(DnsmasqConfKeys.LogDhcp, status, s => Config(s)?.LogDhcp, s => Sources(s)?.LogDhcp, null)); - list.Add(Single(DnsmasqConfKeys.KeepInForeground, status, s => Config(s)?.KeepInForeground, s => Sources(s)?.KeepInForeground, null)); - list.Add(Single(DnsmasqConfKeys.NoDaemon, status, s => Config(s)?.NoDaemon, s => Sources(s)?.NoDaemon, null)); - list.Add(Single(DnsmasqConfKeys.Conntrack, status, s => Config(s)?.Conntrack, s => Sources(s)?.Conntrack, null)); + list.AddDescriptor(registry, DnsmasqConfKeys.NoPoll, status, s => Config(s)?.NoPoll, s => Sources(s)?.NoPoll, null); + list.AddDescriptor(registry, DnsmasqConfKeys.BindInterfaces, status, s => Config(s)?.BindInterfaces, s => Sources(s)?.BindInterfaces, null); + list.AddDescriptor(registry, DnsmasqConfKeys.BindDynamic, status, s => Config(s)?.BindDynamic, s => Sources(s)?.BindDynamic, null); + list.AddDescriptor(registry, DnsmasqConfKeys.LogDebug, status, s => Config(s)?.LogDebug, s => Sources(s)?.LogDebug, null); + list.AddDescriptor(registry, DnsmasqConfKeys.LogAsync, status, s => Config(s)?.LogAsync, s => Sources(s)?.LogAsync, null); + list.AddDescriptor(registry, DnsmasqConfKeys.LocalService, status, s => Config(s)?.LocalService, s => Sources(s)?.LocalService, null); + list.AddDescriptor(registry, DnsmasqConfKeys.Interface, status, null, null, Items(ec => ec?.Interfaces, src => src?.Interfaces)); + list.AddDescriptor(registry, DnsmasqConfKeys.ListenAddress, status, null, null, Items(ec => ec?.ListenAddresses, src => src?.ListenAddresses)); + list.AddDescriptor(registry, DnsmasqConfKeys.ExceptInterface, status, null, null, Items(ec => ec?.ExceptInterfaces, src => src?.ExceptInterfaces)); + list.AddDescriptor(registry, DnsmasqConfKeys.AuthServer, status, null, null, Items(ec => ec?.AuthServerValues, src => src?.AuthServerValues)); + list.AddDescriptor(registry, DnsmasqConfKeys.NoDhcpInterface, status, null, null, Items(ec => ec?.NoDhcpInterfaceValues, src => src?.NoDhcpInterfaceValues)); + list.AddDescriptor(registry, DnsmasqConfKeys.NoDhcpv4Interface, status, null, null, Items(ec => ec?.NoDhcpv4InterfaceValues, src => src?.NoDhcpv4InterfaceValues)); + list.AddDescriptor(registry, DnsmasqConfKeys.NoDhcpv6Interface, status, null, null, Items(ec => ec?.NoDhcpv6InterfaceValues, src => src?.NoDhcpv6InterfaceValues)); + list.AddDescriptor(registry, DnsmasqConfKeys.PidFile, status, s => Config(s)?.PidFilePath, s => Sources(s)?.PidFilePath, null); + list.AddDescriptor(registry, DnsmasqConfKeys.User, status, s => Config(s)?.User, s => Sources(s)?.User, null); + list.AddDescriptor(registry, DnsmasqConfKeys.Group, status, s => Config(s)?.Group, s => Sources(s)?.Group, null); + list.AddDescriptor(registry, DnsmasqConfKeys.LogFacility, status, s => Config(s)?.LogFacility, s => Sources(s)?.LogFacility, null); + list.AddDescriptor(registry, DnsmasqConfKeys.EnableDbus, status, s => Config(s)?.EnableDbus, s => Sources(s)?.EnableDbus, null); + list.AddDescriptor(registry, DnsmasqConfKeys.EnableUbus, status, s => Config(s)?.EnableUbus, s => Sources(s)?.EnableUbus, null); + list.AddDescriptor(registry, DnsmasqConfKeys.EnableRa, status, s => Config(s)?.EnableRa, s => Sources(s)?.EnableRa, null); + list.AddDescriptor(registry, DnsmasqConfKeys.LogDhcp, status, s => Config(s)?.LogDhcp, s => Sources(s)?.LogDhcp, null); + list.AddDescriptor(registry, DnsmasqConfKeys.KeepInForeground, status, s => Config(s)?.KeepInForeground, s => Sources(s)?.KeepInForeground, null); + list.AddDescriptor(registry, DnsmasqConfKeys.NoDaemon, status, s => Config(s)?.NoDaemon, s => Sources(s)?.NoDaemon, null); + list.AddDescriptor(registry, DnsmasqConfKeys.Conntrack, status, s => Config(s)?.Conntrack, s => Sources(s)?.Conntrack, null); return list; } + /// Creates a single-value descriptor; uses registry factory when registered (e.g. integer), otherwise a plain descriptor. Used by . + public static EffectiveConfigFieldDescriptor GetSingleDescriptor( + IEffectiveConfigRenderFragmentRegistry? registry, + string optionName, + DnsmasqServiceStatus? status, + Func? getValue, + Func? getSource, + Func?>? getItems) + { + var sectionId = EffectiveConfigSections.GetSectionId(optionName); + var factory = registry?.GetDescriptorFactory(sectionId, optionName); + if (factory != null) + return (EffectiveConfigFieldDescriptor)factory(sectionId, optionName, status, getValue, getSource, getItems); + return Single(optionName, status, getValue, getSource, getItems); + } + private static EffectiveConfigFieldDescriptor Single(string optionName, DnsmasqServiceStatus? status, Func? getValue, Func? getSource, @@ -197,11 +214,4 @@ public static class EffectiveConfigFieldBuilder ? withSource.Select(p => new ValueWithSource(p.Path, p.Source)).ToList() : paths.Select(p => new ValueWithSource(p, null)).ToList(); } - - private static EffectiveConfigFieldDescriptor Multi(string optionName, DnsmasqServiceStatus? status, - Func?>? getItems) - { - var sectionId = EffectiveConfigSections.GetSectionId(optionName); - return new EffectiveConfigFieldDescriptor(sectionId, optionName, true, status, null, null, getItems); - } } diff --git a/src/DnsmasqWebUI/Infrastructure/Helpers/Config/EffectiveConfigFieldDescriptorListExtensions.cs b/src/DnsmasqWebUI/Infrastructure/Helpers/Config/EffectiveConfigFieldDescriptorListExtensions.cs new file mode 100644 index 0000000..deef2ff --- /dev/null +++ b/src/DnsmasqWebUI/Infrastructure/Helpers/Config/EffectiveConfigFieldDescriptorListExtensions.cs @@ -0,0 +1,57 @@ +using DnsmasqWebUI.Models.Dnsmasq; +using DnsmasqWebUI.Models.Dnsmasq.EffectiveConfig; +using DnsmasqWebUI.Infrastructure.Services.Abstractions; + +namespace DnsmasqWebUI.Infrastructure.Helpers.Config; + +/// +/// Extension methods for building lists of effective-config field descriptors. Keeps the field builder readable. +/// +public static class EffectiveConfigFieldDescriptorListExtensions +{ + /// + /// Adds a field descriptor; single vs multi is determined by for the given option name. + /// + public static void AddDescriptor( + this List list, + IEffectiveConfigRenderFragmentRegistry? registry, + string optionName, + DnsmasqServiceStatus? status, + Func? getValue, + Func? getSource, + Func?>? getItems) + { + if (EffectiveConfigOptionKindMap.GetKind(optionName) == EffectiveConfigFieldKind.Multi) + list.AddMultiDescriptor(optionName, status, getItems); + else + list.AddSingleDescriptor(registry, optionName, status, getValue, getSource, getItems); + } + + /// + /// Adds a single-value field descriptor. Uses the registry's factory when registered (e.g. integer options), otherwise a plain descriptor. + /// + public static void AddSingleDescriptor( + this List list, + IEffectiveConfigRenderFragmentRegistry? registry, + string optionName, + DnsmasqServiceStatus? status, + Func? getValue, + Func? getSource, + Func?>? getItems) + { + list.Add(EffectiveConfigFieldBuilder.GetSingleDescriptor(registry, optionName, status, getValue, getSource, getItems)); + } + + /// + /// Adds a multi-value field descriptor (e.g. server, address, dhcp-range). + /// + public static void AddMultiDescriptor( + this List list, + string optionName, + DnsmasqServiceStatus? status, + Func?>? getItems) + { + var sectionId = EffectiveConfigSections.GetSectionId(optionName); + list.Add(new EffectiveConfigFieldDescriptor(sectionId, optionName, true, status, null, null, getItems)); + } +} diff --git a/src/DnsmasqWebUI/Infrastructure/Helpers/Config/EffectiveConfigOptionKindMap.cs b/src/DnsmasqWebUI/Infrastructure/Helpers/Config/EffectiveConfigOptionKindMap.cs new file mode 100644 index 0000000..8b2a996 --- /dev/null +++ b/src/DnsmasqWebUI/Infrastructure/Helpers/Config/EffectiveConfigOptionKindMap.cs @@ -0,0 +1,292 @@ +using System.Collections.Generic; + +namespace DnsmasqWebUI.Infrastructure.Helpers.Config; + +/// +/// Whether an effective-config option has a single value (or flag) or multiple values. +/// Single source of truth for descriptor building and UI; flags are treated as single-value. +/// +public enum EffectiveConfigFieldKind +{ + Single, + Multi, +} + +/// +/// How to parse an option from config files: last occurrence wins, key-only flag, or collect all values. +/// Used by ConfigSetCache to choose GetLastValue / GetFlag / GetMultiValue without hard-coding per option. +/// +public enum EffectiveConfigParserBehavior +{ + LastWins, + Flag, + Multi, +} + +/// +/// Maps dnsmasq option names to single vs multi-value kind. Used by the field builder so call sites +/// do not choose AddSingleDescriptor vs AddMultiDescriptor explicitly. +/// +public static class EffectiveConfigOptionKindMap +{ + private static readonly IReadOnlyDictionary KindByOptionName = + new Dictionary(System.StringComparer.Ordinal) + { + [DnsmasqConfKeys.NoHosts] = EffectiveConfigFieldKind.Single, + [DnsmasqConfKeys.AddnHosts] = EffectiveConfigFieldKind.Multi, + [DnsmasqConfKeys.Hostsdir] = EffectiveConfigFieldKind.Single, + [DnsmasqConfKeys.ReadEthers] = EffectiveConfigFieldKind.Single, + [DnsmasqOptionTooltips.ServerLocalLabel] = EffectiveConfigFieldKind.Multi, + [DnsmasqConfKeys.RevServer] = EffectiveConfigFieldKind.Multi, + [DnsmasqConfKeys.Address] = EffectiveConfigFieldKind.Multi, + [DnsmasqConfKeys.ResolvFile] = EffectiveConfigFieldKind.Multi, + [DnsmasqConfKeys.NoResolv] = EffectiveConfigFieldKind.Single, + [DnsmasqConfKeys.DomainNeeded] = EffectiveConfigFieldKind.Single, + [DnsmasqConfKeys.Port] = EffectiveConfigFieldKind.Single, + [DnsmasqConfKeys.LogQueries] = EffectiveConfigFieldKind.Single, + [DnsmasqConfKeys.StrictOrder] = EffectiveConfigFieldKind.Single, + [DnsmasqConfKeys.AllServers] = EffectiveConfigFieldKind.Single, + [DnsmasqConfKeys.AuthTtl] = EffectiveConfigFieldKind.Single, + [DnsmasqConfKeys.EdnsPacketMax] = EffectiveConfigFieldKind.Single, + [DnsmasqConfKeys.QueryPort] = EffectiveConfigFieldKind.Single, + [DnsmasqConfKeys.PortLimit] = EffectiveConfigFieldKind.Single, + [DnsmasqConfKeys.MinPort] = EffectiveConfigFieldKind.Single, + [DnsmasqConfKeys.MaxPort] = EffectiveConfigFieldKind.Single, + [DnsmasqConfKeys.DnsLoopDetect] = EffectiveConfigFieldKind.Single, + [DnsmasqConfKeys.StopDnsRebind] = EffectiveConfigFieldKind.Single, + [DnsmasqConfKeys.RebindLocalhostOk] = EffectiveConfigFieldKind.Single, + [DnsmasqConfKeys.ClearOnReload] = EffectiveConfigFieldKind.Single, + [DnsmasqConfKeys.ExpandHosts] = EffectiveConfigFieldKind.Single, + [DnsmasqConfKeys.BogusPriv] = EffectiveConfigFieldKind.Single, + [DnsmasqConfKeys.RebindDomainOk] = EffectiveConfigFieldKind.Multi, + [DnsmasqConfKeys.BogusNxdomain] = EffectiveConfigFieldKind.Multi, + [DnsmasqConfKeys.IgnoreAddress] = EffectiveConfigFieldKind.Multi, + [DnsmasqConfKeys.Alias] = EffectiveConfigFieldKind.Multi, + [DnsmasqConfKeys.FilterRr] = EffectiveConfigFieldKind.Multi, + [DnsmasqConfKeys.Filterwin2k] = EffectiveConfigFieldKind.Single, + [DnsmasqConfKeys.FilterA] = EffectiveConfigFieldKind.Single, + [DnsmasqConfKeys.FilterAaaa] = EffectiveConfigFieldKind.Single, + [DnsmasqConfKeys.LocaliseQueries] = EffectiveConfigFieldKind.Single, + [DnsmasqConfKeys.FastDnsRetry] = EffectiveConfigFieldKind.Single, + [DnsmasqConfKeys.Ipset] = EffectiveConfigFieldKind.Multi, + [DnsmasqConfKeys.Nftset] = EffectiveConfigFieldKind.Multi, + [DnsmasqConfKeys.Domain] = EffectiveConfigFieldKind.Multi, + [DnsmasqConfKeys.Cname] = EffectiveConfigFieldKind.Multi, + [DnsmasqConfKeys.MxHost] = EffectiveConfigFieldKind.Multi, + [DnsmasqConfKeys.Srv] = EffectiveConfigFieldKind.Multi, + [DnsmasqConfKeys.PtrRecord] = EffectiveConfigFieldKind.Multi, + [DnsmasqConfKeys.TxtRecord] = EffectiveConfigFieldKind.Multi, + [DnsmasqConfKeys.NaptrRecord] = EffectiveConfigFieldKind.Multi, + [DnsmasqConfKeys.HostRecord] = EffectiveConfigFieldKind.Multi, + [DnsmasqConfKeys.DynamicHost] = EffectiveConfigFieldKind.Multi, + [DnsmasqConfKeys.InterfaceName] = EffectiveConfigFieldKind.Multi, + [DnsmasqConfKeys.MxTarget] = EffectiveConfigFieldKind.Single, + [DnsmasqConfKeys.Localmx] = EffectiveConfigFieldKind.Single, + [DnsmasqConfKeys.Selfmx] = EffectiveConfigFieldKind.Single, + [DnsmasqConfKeys.DhcpAuthoritative] = EffectiveConfigFieldKind.Single, + [DnsmasqConfKeys.DhcpRapidCommit] = EffectiveConfigFieldKind.Single, + [DnsmasqConfKeys.LeasefileRo] = EffectiveConfigFieldKind.Single, + [DnsmasqConfKeys.DhcpScript] = EffectiveConfigFieldKind.Single, + [DnsmasqConfKeys.DhcpLeasefile] = EffectiveConfigFieldKind.Single, + [DnsmasqConfKeys.DhcpLeaseMax] = EffectiveConfigFieldKind.Single, + [DnsmasqConfKeys.DhcpTtl] = EffectiveConfigFieldKind.Single, + [DnsmasqConfKeys.DhcpRange] = EffectiveConfigFieldKind.Multi, + [DnsmasqConfKeys.DhcpHost] = EffectiveConfigFieldKind.Multi, + [DnsmasqConfKeys.DhcpOption] = EffectiveConfigFieldKind.Multi, + [DnsmasqConfKeys.DhcpOptionForce] = EffectiveConfigFieldKind.Multi, + [DnsmasqConfKeys.DhcpMatch] = EffectiveConfigFieldKind.Multi, + [DnsmasqConfKeys.DhcpMac] = EffectiveConfigFieldKind.Multi, + [DnsmasqConfKeys.DhcpNameMatch] = EffectiveConfigFieldKind.Multi, + [DnsmasqConfKeys.DhcpIgnoreNames] = EffectiveConfigFieldKind.Multi, + [DnsmasqConfKeys.DhcpHostsfile] = EffectiveConfigFieldKind.Multi, + [DnsmasqConfKeys.DhcpOptsfile] = EffectiveConfigFieldKind.Multi, + [DnsmasqConfKeys.DhcpHostsdir] = EffectiveConfigFieldKind.Multi, + [DnsmasqConfKeys.DhcpBoot] = EffectiveConfigFieldKind.Multi, + [DnsmasqConfKeys.DhcpIgnore] = EffectiveConfigFieldKind.Multi, + [DnsmasqConfKeys.DhcpVendorclass] = EffectiveConfigFieldKind.Multi, + [DnsmasqConfKeys.DhcpUserclass] = EffectiveConfigFieldKind.Multi, + [DnsmasqConfKeys.RaParam] = EffectiveConfigFieldKind.Multi, + [DnsmasqConfKeys.Slaac] = EffectiveConfigFieldKind.Multi, + [DnsmasqConfKeys.EnableTftp] = EffectiveConfigFieldKind.Single, + [DnsmasqConfKeys.TftpSecure] = EffectiveConfigFieldKind.Single, + [DnsmasqConfKeys.TftpNoFail] = EffectiveConfigFieldKind.Single, + [DnsmasqConfKeys.TftpNoBlocksize] = EffectiveConfigFieldKind.Single, + [DnsmasqConfKeys.TftpRoot] = EffectiveConfigFieldKind.Single, + [DnsmasqConfKeys.PxePrompt] = EffectiveConfigFieldKind.Single, + [DnsmasqConfKeys.PxeService] = EffectiveConfigFieldKind.Multi, + [DnsmasqConfKeys.Dnssec] = EffectiveConfigFieldKind.Single, + [DnsmasqConfKeys.DnssecCheckUnsigned] = EffectiveConfigFieldKind.Single, + [DnsmasqConfKeys.ProxyDnssec] = EffectiveConfigFieldKind.Single, + [DnsmasqConfKeys.TrustAnchor] = EffectiveConfigFieldKind.Multi, + [DnsmasqConfKeys.CacheRr] = EffectiveConfigFieldKind.Multi, + [DnsmasqConfKeys.CacheSize] = EffectiveConfigFieldKind.Single, + [DnsmasqConfKeys.LocalTtl] = EffectiveConfigFieldKind.Single, + [DnsmasqConfKeys.NoNegcache] = EffectiveConfigFieldKind.Single, + [DnsmasqConfKeys.NegTtl] = EffectiveConfigFieldKind.Single, + [DnsmasqConfKeys.MaxTtl] = EffectiveConfigFieldKind.Single, + [DnsmasqConfKeys.MaxCacheTtl] = EffectiveConfigFieldKind.Single, + [DnsmasqConfKeys.MinCacheTtl] = EffectiveConfigFieldKind.Single, + [DnsmasqConfKeys.NoPoll] = EffectiveConfigFieldKind.Single, + [DnsmasqConfKeys.BindInterfaces] = EffectiveConfigFieldKind.Single, + [DnsmasqConfKeys.BindDynamic] = EffectiveConfigFieldKind.Single, + [DnsmasqConfKeys.LogDebug] = EffectiveConfigFieldKind.Single, + [DnsmasqConfKeys.LogAsync] = EffectiveConfigFieldKind.Single, + [DnsmasqConfKeys.LocalService] = EffectiveConfigFieldKind.Single, + [DnsmasqConfKeys.Interface] = EffectiveConfigFieldKind.Multi, + [DnsmasqConfKeys.ListenAddress] = EffectiveConfigFieldKind.Multi, + [DnsmasqConfKeys.ExceptInterface] = EffectiveConfigFieldKind.Multi, + [DnsmasqConfKeys.AuthServer] = EffectiveConfigFieldKind.Multi, + [DnsmasqConfKeys.NoDhcpInterface] = EffectiveConfigFieldKind.Multi, + [DnsmasqConfKeys.NoDhcpv4Interface] = EffectiveConfigFieldKind.Multi, + [DnsmasqConfKeys.NoDhcpv6Interface] = EffectiveConfigFieldKind.Multi, + [DnsmasqConfKeys.PidFile] = EffectiveConfigFieldKind.Single, + [DnsmasqConfKeys.User] = EffectiveConfigFieldKind.Single, + [DnsmasqConfKeys.Group] = EffectiveConfigFieldKind.Single, + [DnsmasqConfKeys.LogFacility] = EffectiveConfigFieldKind.Single, + [DnsmasqConfKeys.EnableDbus] = EffectiveConfigFieldKind.Single, + [DnsmasqConfKeys.EnableUbus] = EffectiveConfigFieldKind.Single, + [DnsmasqConfKeys.EnableRa] = EffectiveConfigFieldKind.Single, + [DnsmasqConfKeys.LogDhcp] = EffectiveConfigFieldKind.Single, + [DnsmasqConfKeys.KeepInForeground] = EffectiveConfigFieldKind.Single, + [DnsmasqConfKeys.NoDaemon] = EffectiveConfigFieldKind.Single, + [DnsmasqConfKeys.Conntrack] = EffectiveConfigFieldKind.Single, + }; + + /// Returns Single or Multi for the given option name; defaults to Single if unknown. + public static EffectiveConfigFieldKind GetKind(string optionName) => + KindByOptionName.TryGetValue(optionName, out var kind) ? kind : EffectiveConfigFieldKind.Single; +} + +/// +/// Maps dnsmasq option names to parser behavior (LastWins / Flag / Multi). Used by ConfigSetCache so it +/// dispatches to GetLastValue, GetFlag, or GetMultiValue from one place. Special cases (NoHosts value, +/// AddnHosts, server/local keys array, DhcpLeaseFilePath) remain explicit in the cache. +/// +public static class EffectiveConfigParserBehaviorMap +{ + private static readonly IReadOnlyDictionary BehaviorByOptionName = + new Dictionary(System.StringComparer.Ordinal) + { + [DnsmasqConfKeys.NoHosts] = EffectiveConfigParserBehavior.Flag, + [DnsmasqConfKeys.AddnHosts] = EffectiveConfigParserBehavior.Multi, + [DnsmasqConfKeys.Hostsdir] = EffectiveConfigParserBehavior.LastWins, + [DnsmasqConfKeys.ReadEthers] = EffectiveConfigParserBehavior.Flag, + [DnsmasqConfKeys.RevServer] = EffectiveConfigParserBehavior.Multi, + [DnsmasqConfKeys.Address] = EffectiveConfigParserBehavior.Multi, + [DnsmasqConfKeys.ResolvFile] = EffectiveConfigParserBehavior.Multi, + [DnsmasqConfKeys.NoResolv] = EffectiveConfigParserBehavior.Flag, + [DnsmasqConfKeys.DomainNeeded] = EffectiveConfigParserBehavior.Flag, + [DnsmasqConfKeys.Port] = EffectiveConfigParserBehavior.LastWins, + [DnsmasqConfKeys.LogQueries] = EffectiveConfigParserBehavior.LastWins, + [DnsmasqConfKeys.StrictOrder] = EffectiveConfigParserBehavior.Flag, + [DnsmasqConfKeys.AllServers] = EffectiveConfigParserBehavior.Flag, + [DnsmasqConfKeys.AuthTtl] = EffectiveConfigParserBehavior.LastWins, + [DnsmasqConfKeys.EdnsPacketMax] = EffectiveConfigParserBehavior.LastWins, + [DnsmasqConfKeys.QueryPort] = EffectiveConfigParserBehavior.LastWins, + [DnsmasqConfKeys.PortLimit] = EffectiveConfigParserBehavior.LastWins, + [DnsmasqConfKeys.MinPort] = EffectiveConfigParserBehavior.LastWins, + [DnsmasqConfKeys.MaxPort] = EffectiveConfigParserBehavior.LastWins, + [DnsmasqConfKeys.DnsLoopDetect] = EffectiveConfigParserBehavior.Flag, + [DnsmasqConfKeys.StopDnsRebind] = EffectiveConfigParserBehavior.Flag, + [DnsmasqConfKeys.RebindLocalhostOk] = EffectiveConfigParserBehavior.Flag, + [DnsmasqConfKeys.ClearOnReload] = EffectiveConfigParserBehavior.Flag, + [DnsmasqConfKeys.ExpandHosts] = EffectiveConfigParserBehavior.Flag, + [DnsmasqConfKeys.BogusPriv] = EffectiveConfigParserBehavior.Flag, + [DnsmasqConfKeys.RebindDomainOk] = EffectiveConfigParserBehavior.Multi, + [DnsmasqConfKeys.BogusNxdomain] = EffectiveConfigParserBehavior.Multi, + [DnsmasqConfKeys.IgnoreAddress] = EffectiveConfigParserBehavior.Multi, + [DnsmasqConfKeys.Alias] = EffectiveConfigParserBehavior.Multi, + [DnsmasqConfKeys.FilterRr] = EffectiveConfigParserBehavior.Multi, + [DnsmasqConfKeys.Filterwin2k] = EffectiveConfigParserBehavior.Flag, + [DnsmasqConfKeys.FilterA] = EffectiveConfigParserBehavior.Flag, + [DnsmasqConfKeys.FilterAaaa] = EffectiveConfigParserBehavior.Flag, + [DnsmasqConfKeys.LocaliseQueries] = EffectiveConfigParserBehavior.Flag, + [DnsmasqConfKeys.FastDnsRetry] = EffectiveConfigParserBehavior.LastWins, + [DnsmasqConfKeys.Ipset] = EffectiveConfigParserBehavior.Multi, + [DnsmasqConfKeys.Nftset] = EffectiveConfigParserBehavior.Multi, + [DnsmasqConfKeys.Domain] = EffectiveConfigParserBehavior.Multi, + [DnsmasqConfKeys.Cname] = EffectiveConfigParserBehavior.Multi, + [DnsmasqConfKeys.MxHost] = EffectiveConfigParserBehavior.Multi, + [DnsmasqConfKeys.Srv] = EffectiveConfigParserBehavior.Multi, + [DnsmasqConfKeys.PtrRecord] = EffectiveConfigParserBehavior.Multi, + [DnsmasqConfKeys.TxtRecord] = EffectiveConfigParserBehavior.Multi, + [DnsmasqConfKeys.NaptrRecord] = EffectiveConfigParserBehavior.Multi, + [DnsmasqConfKeys.HostRecord] = EffectiveConfigParserBehavior.Multi, + [DnsmasqConfKeys.DynamicHost] = EffectiveConfigParserBehavior.Multi, + [DnsmasqConfKeys.InterfaceName] = EffectiveConfigParserBehavior.Multi, + [DnsmasqConfKeys.MxTarget] = EffectiveConfigParserBehavior.LastWins, + [DnsmasqConfKeys.Localmx] = EffectiveConfigParserBehavior.Flag, + [DnsmasqConfKeys.Selfmx] = EffectiveConfigParserBehavior.Flag, + [DnsmasqConfKeys.DhcpAuthoritative] = EffectiveConfigParserBehavior.Flag, + [DnsmasqConfKeys.DhcpRapidCommit] = EffectiveConfigParserBehavior.Flag, + [DnsmasqConfKeys.LeasefileRo] = EffectiveConfigParserBehavior.Flag, + [DnsmasqConfKeys.DhcpScript] = EffectiveConfigParserBehavior.LastWins, + [DnsmasqConfKeys.DhcpLeasefile] = EffectiveConfigParserBehavior.LastWins, + [DnsmasqConfKeys.DhcpLeaseMax] = EffectiveConfigParserBehavior.LastWins, + [DnsmasqConfKeys.DhcpTtl] = EffectiveConfigParserBehavior.LastWins, + [DnsmasqConfKeys.DhcpRange] = EffectiveConfigParserBehavior.Multi, + [DnsmasqConfKeys.DhcpHost] = EffectiveConfigParserBehavior.Multi, + [DnsmasqConfKeys.DhcpOption] = EffectiveConfigParserBehavior.Multi, + [DnsmasqConfKeys.DhcpOptionForce] = EffectiveConfigParserBehavior.Multi, + [DnsmasqConfKeys.DhcpMatch] = EffectiveConfigParserBehavior.Multi, + [DnsmasqConfKeys.DhcpMac] = EffectiveConfigParserBehavior.Multi, + [DnsmasqConfKeys.DhcpNameMatch] = EffectiveConfigParserBehavior.Multi, + [DnsmasqConfKeys.DhcpIgnoreNames] = EffectiveConfigParserBehavior.Multi, + [DnsmasqConfKeys.DhcpHostsfile] = EffectiveConfigParserBehavior.Multi, + [DnsmasqConfKeys.DhcpOptsfile] = EffectiveConfigParserBehavior.Multi, + [DnsmasqConfKeys.DhcpHostsdir] = EffectiveConfigParserBehavior.Multi, + [DnsmasqConfKeys.DhcpBoot] = EffectiveConfigParserBehavior.Multi, + [DnsmasqConfKeys.DhcpIgnore] = EffectiveConfigParserBehavior.Multi, + [DnsmasqConfKeys.DhcpVendorclass] = EffectiveConfigParserBehavior.Multi, + [DnsmasqConfKeys.DhcpUserclass] = EffectiveConfigParserBehavior.Multi, + [DnsmasqConfKeys.RaParam] = EffectiveConfigParserBehavior.Multi, + [DnsmasqConfKeys.Slaac] = EffectiveConfigParserBehavior.Multi, + [DnsmasqConfKeys.EnableTftp] = EffectiveConfigParserBehavior.Flag, + [DnsmasqConfKeys.TftpSecure] = EffectiveConfigParserBehavior.Flag, + [DnsmasqConfKeys.TftpNoFail] = EffectiveConfigParserBehavior.Flag, + [DnsmasqConfKeys.TftpNoBlocksize] = EffectiveConfigParserBehavior.Flag, + [DnsmasqConfKeys.TftpRoot] = EffectiveConfigParserBehavior.LastWins, + [DnsmasqConfKeys.PxePrompt] = EffectiveConfigParserBehavior.LastWins, + [DnsmasqConfKeys.PxeService] = EffectiveConfigParserBehavior.Multi, + [DnsmasqConfKeys.Dnssec] = EffectiveConfigParserBehavior.Flag, + [DnsmasqConfKeys.DnssecCheckUnsigned] = EffectiveConfigParserBehavior.Flag, + [DnsmasqConfKeys.ProxyDnssec] = EffectiveConfigParserBehavior.Flag, + [DnsmasqConfKeys.TrustAnchor] = EffectiveConfigParserBehavior.Multi, + [DnsmasqConfKeys.CacheRr] = EffectiveConfigParserBehavior.Multi, + [DnsmasqConfKeys.CacheSize] = EffectiveConfigParserBehavior.LastWins, + [DnsmasqConfKeys.LocalTtl] = EffectiveConfigParserBehavior.LastWins, + [DnsmasqConfKeys.NoNegcache] = EffectiveConfigParserBehavior.Flag, + [DnsmasqConfKeys.NegTtl] = EffectiveConfigParserBehavior.LastWins, + [DnsmasqConfKeys.MaxTtl] = EffectiveConfigParserBehavior.LastWins, + [DnsmasqConfKeys.MaxCacheTtl] = EffectiveConfigParserBehavior.LastWins, + [DnsmasqConfKeys.MinCacheTtl] = EffectiveConfigParserBehavior.LastWins, + [DnsmasqConfKeys.NoPoll] = EffectiveConfigParserBehavior.Flag, + [DnsmasqConfKeys.BindInterfaces] = EffectiveConfigParserBehavior.Flag, + [DnsmasqConfKeys.BindDynamic] = EffectiveConfigParserBehavior.Flag, + [DnsmasqConfKeys.LogDebug] = EffectiveConfigParserBehavior.Flag, + [DnsmasqConfKeys.LogAsync] = EffectiveConfigParserBehavior.LastWins, + [DnsmasqConfKeys.LocalService] = EffectiveConfigParserBehavior.LastWins, + [DnsmasqConfKeys.Interface] = EffectiveConfigParserBehavior.Multi, + [DnsmasqConfKeys.ListenAddress] = EffectiveConfigParserBehavior.Multi, + [DnsmasqConfKeys.ExceptInterface] = EffectiveConfigParserBehavior.Multi, + [DnsmasqConfKeys.AuthServer] = EffectiveConfigParserBehavior.Multi, + [DnsmasqConfKeys.NoDhcpInterface] = EffectiveConfigParserBehavior.Multi, + [DnsmasqConfKeys.NoDhcpv4Interface] = EffectiveConfigParserBehavior.Multi, + [DnsmasqConfKeys.NoDhcpv6Interface] = EffectiveConfigParserBehavior.Multi, + [DnsmasqConfKeys.PidFile] = EffectiveConfigParserBehavior.LastWins, + [DnsmasqConfKeys.User] = EffectiveConfigParserBehavior.LastWins, + [DnsmasqConfKeys.Group] = EffectiveConfigParserBehavior.LastWins, + [DnsmasqConfKeys.LogFacility] = EffectiveConfigParserBehavior.LastWins, + [DnsmasqConfKeys.EnableDbus] = EffectiveConfigParserBehavior.LastWins, + [DnsmasqConfKeys.EnableUbus] = EffectiveConfigParserBehavior.LastWins, + [DnsmasqConfKeys.EnableRa] = EffectiveConfigParserBehavior.Flag, + [DnsmasqConfKeys.LogDhcp] = EffectiveConfigParserBehavior.Flag, + [DnsmasqConfKeys.KeepInForeground] = EffectiveConfigParserBehavior.Flag, + [DnsmasqConfKeys.NoDaemon] = EffectiveConfigParserBehavior.Flag, + [DnsmasqConfKeys.Conntrack] = EffectiveConfigParserBehavior.LastWins, + }; + + /// Returns LastWins, Flag, or Multi for the given option name; defaults to LastWins if unknown. + public static EffectiveConfigParserBehavior GetBehavior(string optionName) => + BehaviorByOptionName.TryGetValue(optionName, out var b) ? b : EffectiveConfigParserBehavior.LastWins; +} diff --git a/src/DnsmasqWebUI/Infrastructure/Helpers/Config/EffectiveConfigViews.cs b/src/DnsmasqWebUI/Infrastructure/Helpers/Config/EffectiveConfigViews.cs index 36b58a0..66d4dd5 100644 --- a/src/DnsmasqWebUI/Infrastructure/Helpers/Config/EffectiveConfigViews.cs +++ b/src/DnsmasqWebUI/Infrastructure/Helpers/Config/EffectiveConfigViews.cs @@ -1,5 +1,6 @@ using DnsmasqWebUI.Models.Dnsmasq; using DnsmasqWebUI.Models.Dnsmasq.EffectiveConfig; +using DnsmasqWebUI.Infrastructure.Services.Abstractions; namespace DnsmasqWebUI.Infrastructure.Helpers.Config; @@ -50,14 +51,16 @@ public static class EffectiveConfigViews /// For each section in the view, returns the context-visible descriptors for that section. /// Does not apply search; component applies search to each section's list and hides sections with no matches. /// + /// When provided, used to create the correct descriptor type per field (e.g. integer options). public static IReadOnlyDictionary> GetDescriptorsBySection( DnsmasqServiceStatus? status, - IReadOnlyList views) + IReadOnlyList views, + IEffectiveConfigRenderFragmentRegistry? registry = null) { if (status == null || views.Count == 0) return new Dictionary>(); - var allDescriptors = EffectiveConfigFieldBuilder.BuildFieldDescriptors(status); + var allDescriptors = EffectiveConfigFieldBuilder.BuildFieldDescriptors(status, registry); var result = new Dictionary>(StringComparer.OrdinalIgnoreCase); foreach (var view in views) diff --git a/src/DnsmasqWebUI/Infrastructure/Services/Abstractions/IEffectiveConfigRenderFragmentRegistry.cs b/src/DnsmasqWebUI/Infrastructure/Services/Abstractions/IEffectiveConfigRenderFragmentRegistry.cs index f47c024..de52e99 100644 --- a/src/DnsmasqWebUI/Infrastructure/Services/Abstractions/IEffectiveConfigRenderFragmentRegistry.cs +++ b/src/DnsmasqWebUI/Infrastructure/Services/Abstractions/IEffectiveConfigRenderFragmentRegistry.cs @@ -1,6 +1,7 @@ using Microsoft.AspNetCore.Components; using DnsmasqWebUI.Models.Dnsmasq; using DnsmasqWebUI.Models.Dnsmasq.EffectiveConfig; +using DnsmasqWebUI.Models.Dnsmasq.EffectiveConfig.Abstractions; namespace DnsmasqWebUI.Infrastructure.Services.Abstractions; @@ -12,7 +13,12 @@ namespace DnsmasqWebUI.Infrastructure.Services.Abstractions; public interface IEffectiveConfigRenderFragmentRegistry : IApplicationSingleton { /// - /// Returns a fragment that renders the custom value UI for this field, or null to use default rendering. + /// Builds a fragment that renders the custom value component for this field, or null to use default rendering. /// - RenderFragment? GetDisplayFragment(string sectionId, string optionName); + RenderFragment? BuildFieldComponentFragment(string sectionId, string optionName); + + /// + /// Returns a factory that creates the correct descriptor type for this field (e.g. ), or null to use the default. + /// + EffectiveConfigDescriptorFactory? GetDescriptorFactory(string sectionId, string optionName); } diff --git a/src/DnsmasqWebUI/Infrastructure/Services/ConfigSetCache.cs b/src/DnsmasqWebUI/Infrastructure/Services/ConfigSetCache.cs index 2672eeb..1457f4a 100644 --- a/src/DnsmasqWebUI/Infrastructure/Services/ConfigSetCache.cs +++ b/src/DnsmasqWebUI/Infrastructure/Services/ConfigSetCache.cs @@ -214,159 +214,183 @@ public sealed class ConfigSetCache : IConfigSetCache, IDisposable return dict; } + /// Dispatches to GetFlag, GetLastValue, or GetMultiValue based on . + private static object ParseOptionValue(IReadOnlyList paths, IReadOnlyDictionary pathToLines, string optionName) + { + return EffectiveConfigParserBehaviorMap.GetBehavior(optionName) switch + { + EffectiveConfigParserBehavior.Flag => DnsmasqConfIncludeParser.GetFlagFromConfigFiles(paths, pathToLines, optionName), + EffectiveConfigParserBehavior.LastWins => DnsmasqConfIncludeParser.GetLastValueFromConfigFiles(paths, pathToLines, optionName), + EffectiveConfigParserBehavior.Multi => DnsmasqConfIncludeParser.GetMultiValueFromConfigFiles(paths, pathToLines, optionName), + _ => DnsmasqConfIncludeParser.GetLastValueFromConfigFiles(paths, pathToLines, optionName), + }; + } + + /// Dispatches to GetFlag/GetLastValue/GetMultiValue WithSource based on . + private static object ParseOptionWithSource(IReadOnlyList paths, IReadOnlyDictionary pathToLines, string optionName, string? managedFilePath) + { + return EffectiveConfigParserBehaviorMap.GetBehavior(optionName) switch + { + EffectiveConfigParserBehavior.Flag => DnsmasqConfIncludeParser.GetFlagFromConfigFilesWithSource(paths, pathToLines, optionName, managedFilePath), + EffectiveConfigParserBehavior.LastWins => DnsmasqConfIncludeParser.GetLastValueFromConfigFilesWithSource(paths, pathToLines, optionName, managedFilePath), + EffectiveConfigParserBehavior.Multi => DnsmasqConfIncludeParser.GetMultiValueFromConfigFilesWithSource(paths, pathToLines, optionName, managedFilePath), + _ => DnsmasqConfIncludeParser.GetLastValueFromConfigFilesWithSource(paths, pathToLines, optionName, managedFilePath), + }; + } + private static EffectiveDnsmasqConfig BuildEffectiveConfig(IReadOnlyList paths, IReadOnlyDictionary pathToLines) { var noHosts = DnsmasqConfIncludeParser.GetNoHostsFromConfigFiles(paths, pathToLines); var addnHosts = DnsmasqConfIncludeParser.GetAddnHostsPathsFromConfigFiles(paths, pathToLines); - var (hostsdirVal, hostsdirDir) = DnsmasqConfIncludeParser.GetLastValueFromConfigFiles(paths, pathToLines, DnsmasqConfKeys.Hostsdir); + var (hostsdirVal, hostsdirDir) = ((string?, string?))ParseOptionValue(paths, pathToLines, DnsmasqConfKeys.Hostsdir); var hostsdirPath = string.IsNullOrWhiteSpace(hostsdirVal) ? null : DnsmasqConfIncludeParser.ResolvePath(hostsdirVal?.Trim(), hostsdirDir); var serverLocal = DnsmasqConfIncludeParser.GetMultiValueFromConfigFiles(paths, pathToLines, DnsmasqConfKeys.ServerLocalKeys); - var revServer = DnsmasqConfIncludeParser.GetMultiValueFromConfigFiles(paths, pathToLines, DnsmasqConfKeys.RevServer); - var addressValues = DnsmasqConfIncludeParser.GetMultiValueFromConfigFiles(paths, pathToLines, DnsmasqConfKeys.Address); - var interfaces = DnsmasqConfIncludeParser.GetMultiValueFromConfigFiles(paths, pathToLines, DnsmasqConfKeys.Interface); - var listenAddresses = DnsmasqConfIncludeParser.GetMultiValueFromConfigFiles(paths, pathToLines, DnsmasqConfKeys.ListenAddress); - var exceptInterfaces = DnsmasqConfIncludeParser.GetMultiValueFromConfigFiles(paths, pathToLines, DnsmasqConfKeys.ExceptInterface); - var dhcpRanges = DnsmasqConfIncludeParser.GetMultiValueFromConfigFiles(paths, pathToLines, DnsmasqConfKeys.DhcpRange); - var dhcpHostLines = DnsmasqConfIncludeParser.GetMultiValueFromConfigFiles(paths, pathToLines, DnsmasqConfKeys.DhcpHost); - var dhcpOptionLines = DnsmasqConfIncludeParser.GetMultiValueFromConfigFiles(paths, pathToLines, DnsmasqConfKeys.DhcpOption); - var dhcpMatchValues = DnsmasqConfIncludeParser.GetMultiValueFromConfigFiles(paths, pathToLines, DnsmasqConfKeys.DhcpMatch); - var dhcpBootValues = DnsmasqConfIncludeParser.GetMultiValueFromConfigFiles(paths, pathToLines, DnsmasqConfKeys.DhcpBoot); - var dhcpIgnoreValues = DnsmasqConfIncludeParser.GetMultiValueFromConfigFiles(paths, pathToLines, DnsmasqConfKeys.DhcpIgnore); - var dhcpVendorclassValues = DnsmasqConfIncludeParser.GetMultiValueFromConfigFiles(paths, pathToLines, DnsmasqConfKeys.DhcpVendorclass); - var dhcpUserclassValues = DnsmasqConfIncludeParser.GetMultiValueFromConfigFiles(paths, pathToLines, DnsmasqConfKeys.DhcpUserclass); - var raParamValues = DnsmasqConfIncludeParser.GetMultiValueFromConfigFiles(paths, pathToLines, DnsmasqConfKeys.RaParam); - var slaacValues = DnsmasqConfIncludeParser.GetMultiValueFromConfigFiles(paths, pathToLines, DnsmasqConfKeys.Slaac); - var pxeServiceValues = DnsmasqConfIncludeParser.GetMultiValueFromConfigFiles(paths, pathToLines, DnsmasqConfKeys.PxeService); - var trustAnchorValues = DnsmasqConfIncludeParser.GetMultiValueFromConfigFiles(paths, pathToLines, DnsmasqConfKeys.TrustAnchor); - var resolvFiles = DnsmasqConfIncludeParser.GetMultiValueFromConfigFiles(paths, pathToLines, DnsmasqConfKeys.ResolvFile); - var rebindDomainOk = DnsmasqConfIncludeParser.GetMultiValueFromConfigFiles(paths, pathToLines, DnsmasqConfKeys.RebindDomainOk); - var bogusNxdomain = DnsmasqConfIncludeParser.GetMultiValueFromConfigFiles(paths, pathToLines, DnsmasqConfKeys.BogusNxdomain); - var ignoreAddress = DnsmasqConfIncludeParser.GetMultiValueFromConfigFiles(paths, pathToLines, DnsmasqConfKeys.IgnoreAddress); - var alias = DnsmasqConfIncludeParser.GetMultiValueFromConfigFiles(paths, pathToLines, DnsmasqConfKeys.Alias); - var filterRr = DnsmasqConfIncludeParser.GetMultiValueFromConfigFiles(paths, pathToLines, DnsmasqConfKeys.FilterRr); - var cacheRr = DnsmasqConfIncludeParser.GetMultiValueFromConfigFiles(paths, pathToLines, DnsmasqConfKeys.CacheRr); - var authServer = DnsmasqConfIncludeParser.GetMultiValueFromConfigFiles(paths, pathToLines, DnsmasqConfKeys.AuthServer); - var noDhcpInterface = DnsmasqConfIncludeParser.GetMultiValueFromConfigFiles(paths, pathToLines, DnsmasqConfKeys.NoDhcpInterface); - var noDhcpv4Interface = DnsmasqConfIncludeParser.GetMultiValueFromConfigFiles(paths, pathToLines, DnsmasqConfKeys.NoDhcpv4Interface); - var noDhcpv6Interface = DnsmasqConfIncludeParser.GetMultiValueFromConfigFiles(paths, pathToLines, DnsmasqConfKeys.NoDhcpv6Interface); - var domainValues = DnsmasqConfIncludeParser.GetMultiValueFromConfigFiles(paths, pathToLines, DnsmasqConfKeys.Domain); - var cnameValues = DnsmasqConfIncludeParser.GetMultiValueFromConfigFiles(paths, pathToLines, DnsmasqConfKeys.Cname); - var mxHostValues = DnsmasqConfIncludeParser.GetMultiValueFromConfigFiles(paths, pathToLines, DnsmasqConfKeys.MxHost); - var srvValues = DnsmasqConfIncludeParser.GetMultiValueFromConfigFiles(paths, pathToLines, DnsmasqConfKeys.Srv); - var ptrRecordValues = DnsmasqConfIncludeParser.GetMultiValueFromConfigFiles(paths, pathToLines, DnsmasqConfKeys.PtrRecord); - var txtRecordValues = DnsmasqConfIncludeParser.GetMultiValueFromConfigFiles(paths, pathToLines, DnsmasqConfKeys.TxtRecord); - var naptrRecordValues = DnsmasqConfIncludeParser.GetMultiValueFromConfigFiles(paths, pathToLines, DnsmasqConfKeys.NaptrRecord); - var hostRecordValues = DnsmasqConfIncludeParser.GetMultiValueFromConfigFiles(paths, pathToLines, DnsmasqConfKeys.HostRecord); - var dynamicHostValues = DnsmasqConfIncludeParser.GetMultiValueFromConfigFiles(paths, pathToLines, DnsmasqConfKeys.DynamicHost); - var interfaceNameValues = DnsmasqConfIncludeParser.GetMultiValueFromConfigFiles(paths, pathToLines, DnsmasqConfKeys.InterfaceName); - var dhcpOptionForceLines = DnsmasqConfIncludeParser.GetMultiValueFromConfigFiles(paths, pathToLines, DnsmasqConfKeys.DhcpOptionForce); - var ipsetValues = DnsmasqConfIncludeParser.GetMultiValueFromConfigFiles(paths, pathToLines, DnsmasqConfKeys.Ipset); - var nftsetValues = DnsmasqConfIncludeParser.GetMultiValueFromConfigFiles(paths, pathToLines, DnsmasqConfKeys.Nftset); - var dhcpMacValues = DnsmasqConfIncludeParser.GetMultiValueFromConfigFiles(paths, pathToLines, DnsmasqConfKeys.DhcpMac); - var dhcpNameMatchValues = DnsmasqConfIncludeParser.GetMultiValueFromConfigFiles(paths, pathToLines, DnsmasqConfKeys.DhcpNameMatch); - var dhcpIgnoreNamesValues = DnsmasqConfIncludeParser.GetMultiValueFromConfigFiles(paths, pathToLines, DnsmasqConfKeys.DhcpIgnoreNames); - var dhcpHostsfilePaths = DnsmasqConfIncludeParser.GetMultiValueFromConfigFiles(paths, pathToLines, DnsmasqConfKeys.DhcpHostsfile); - var dhcpOptsfilePaths = DnsmasqConfIncludeParser.GetMultiValueFromConfigFiles(paths, pathToLines, DnsmasqConfKeys.DhcpOptsfile); - var dhcpHostsdirPaths = DnsmasqConfIncludeParser.GetMultiValueFromConfigFiles(paths, pathToLines, DnsmasqConfKeys.DhcpHostsdir); + var revServer = (IReadOnlyList)ParseOptionValue(paths, pathToLines, DnsmasqConfKeys.RevServer); + var addressValues = (IReadOnlyList)ParseOptionValue(paths, pathToLines, DnsmasqConfKeys.Address); + var interfaces = (IReadOnlyList)ParseOptionValue(paths, pathToLines, DnsmasqConfKeys.Interface); + var listenAddresses = (IReadOnlyList)ParseOptionValue(paths, pathToLines, DnsmasqConfKeys.ListenAddress); + var exceptInterfaces = (IReadOnlyList)ParseOptionValue(paths, pathToLines, DnsmasqConfKeys.ExceptInterface); + var dhcpRanges = (IReadOnlyList)ParseOptionValue(paths, pathToLines, DnsmasqConfKeys.DhcpRange); + var dhcpHostLines = (IReadOnlyList)ParseOptionValue(paths, pathToLines, DnsmasqConfKeys.DhcpHost); + var dhcpOptionLines = (IReadOnlyList)ParseOptionValue(paths, pathToLines, DnsmasqConfKeys.DhcpOption); + var dhcpMatchValues = (IReadOnlyList)ParseOptionValue(paths, pathToLines, DnsmasqConfKeys.DhcpMatch); + var dhcpBootValues = (IReadOnlyList)ParseOptionValue(paths, pathToLines, DnsmasqConfKeys.DhcpBoot); + var dhcpIgnoreValues = (IReadOnlyList)ParseOptionValue(paths, pathToLines, DnsmasqConfKeys.DhcpIgnore); + var dhcpVendorclassValues = (IReadOnlyList)ParseOptionValue(paths, pathToLines, DnsmasqConfKeys.DhcpVendorclass); + var dhcpUserclassValues = (IReadOnlyList)ParseOptionValue(paths, pathToLines, DnsmasqConfKeys.DhcpUserclass); + var raParamValues = (IReadOnlyList)ParseOptionValue(paths, pathToLines, DnsmasqConfKeys.RaParam); + var slaacValues = (IReadOnlyList)ParseOptionValue(paths, pathToLines, DnsmasqConfKeys.Slaac); + var pxeServiceValues = (IReadOnlyList)ParseOptionValue(paths, pathToLines, DnsmasqConfKeys.PxeService); + var trustAnchorValues = (IReadOnlyList)ParseOptionValue(paths, pathToLines, DnsmasqConfKeys.TrustAnchor); + var resolvFiles = (IReadOnlyList)ParseOptionValue(paths, pathToLines, DnsmasqConfKeys.ResolvFile); + var rebindDomainOk = (IReadOnlyList)ParseOptionValue(paths, pathToLines, DnsmasqConfKeys.RebindDomainOk); + var bogusNxdomain = (IReadOnlyList)ParseOptionValue(paths, pathToLines, DnsmasqConfKeys.BogusNxdomain); + var ignoreAddress = (IReadOnlyList)ParseOptionValue(paths, pathToLines, DnsmasqConfKeys.IgnoreAddress); + var alias = (IReadOnlyList)ParseOptionValue(paths, pathToLines, DnsmasqConfKeys.Alias); + var filterRr = (IReadOnlyList)ParseOptionValue(paths, pathToLines, DnsmasqConfKeys.FilterRr); + var cacheRr = (IReadOnlyList)ParseOptionValue(paths, pathToLines, DnsmasqConfKeys.CacheRr); + var authServer = (IReadOnlyList)ParseOptionValue(paths, pathToLines, DnsmasqConfKeys.AuthServer); + var noDhcpInterface = (IReadOnlyList)ParseOptionValue(paths, pathToLines, DnsmasqConfKeys.NoDhcpInterface); + var noDhcpv4Interface = (IReadOnlyList)ParseOptionValue(paths, pathToLines, DnsmasqConfKeys.NoDhcpv4Interface); + var noDhcpv6Interface = (IReadOnlyList)ParseOptionValue(paths, pathToLines, DnsmasqConfKeys.NoDhcpv6Interface); + var domainValues = (IReadOnlyList)ParseOptionValue(paths, pathToLines, DnsmasqConfKeys.Domain); + var cnameValues = (IReadOnlyList)ParseOptionValue(paths, pathToLines, DnsmasqConfKeys.Cname); + var mxHostValues = (IReadOnlyList)ParseOptionValue(paths, pathToLines, DnsmasqConfKeys.MxHost); + var srvValues = (IReadOnlyList)ParseOptionValue(paths, pathToLines, DnsmasqConfKeys.Srv); + var ptrRecordValues = (IReadOnlyList)ParseOptionValue(paths, pathToLines, DnsmasqConfKeys.PtrRecord); + var txtRecordValues = (IReadOnlyList)ParseOptionValue(paths, pathToLines, DnsmasqConfKeys.TxtRecord); + var naptrRecordValues = (IReadOnlyList)ParseOptionValue(paths, pathToLines, DnsmasqConfKeys.NaptrRecord); + var hostRecordValues = (IReadOnlyList)ParseOptionValue(paths, pathToLines, DnsmasqConfKeys.HostRecord); + var dynamicHostValues = (IReadOnlyList)ParseOptionValue(paths, pathToLines, DnsmasqConfKeys.DynamicHost); + var interfaceNameValues = (IReadOnlyList)ParseOptionValue(paths, pathToLines, DnsmasqConfKeys.InterfaceName); + var dhcpOptionForceLines = (IReadOnlyList)ParseOptionValue(paths, pathToLines, DnsmasqConfKeys.DhcpOptionForce); + var ipsetValues = (IReadOnlyList)ParseOptionValue(paths, pathToLines, DnsmasqConfKeys.Ipset); + var nftsetValues = (IReadOnlyList)ParseOptionValue(paths, pathToLines, DnsmasqConfKeys.Nftset); + var dhcpMacValues = (IReadOnlyList)ParseOptionValue(paths, pathToLines, DnsmasqConfKeys.DhcpMac); + var dhcpNameMatchValues = (IReadOnlyList)ParseOptionValue(paths, pathToLines, DnsmasqConfKeys.DhcpNameMatch); + var dhcpIgnoreNamesValues = (IReadOnlyList)ParseOptionValue(paths, pathToLines, DnsmasqConfKeys.DhcpIgnoreNames); + var dhcpHostsfilePaths = (IReadOnlyList)ParseOptionValue(paths, pathToLines, DnsmasqConfKeys.DhcpHostsfile); + var dhcpOptsfilePaths = (IReadOnlyList)ParseOptionValue(paths, pathToLines, DnsmasqConfKeys.DhcpOptsfile); + var dhcpHostsdirPaths = (IReadOnlyList)ParseOptionValue(paths, pathToLines, DnsmasqConfKeys.DhcpHostsdir); - var expandHosts = DnsmasqConfIncludeParser.GetFlagFromConfigFiles(paths, pathToLines, DnsmasqConfKeys.ExpandHosts); - var bogusPriv = DnsmasqConfIncludeParser.GetFlagFromConfigFiles(paths, pathToLines, DnsmasqConfKeys.BogusPriv); - var strictOrder = DnsmasqConfIncludeParser.GetFlagFromConfigFiles(paths, pathToLines, DnsmasqConfKeys.StrictOrder); - var allServers = DnsmasqConfIncludeParser.GetFlagFromConfigFiles(paths, pathToLines, DnsmasqConfKeys.AllServers); - var noResolv = DnsmasqConfIncludeParser.GetFlagFromConfigFiles(paths, pathToLines, DnsmasqConfKeys.NoResolv); - var domainNeeded = DnsmasqConfIncludeParser.GetFlagFromConfigFiles(paths, pathToLines, DnsmasqConfKeys.DomainNeeded); - var noPoll = DnsmasqConfIncludeParser.GetFlagFromConfigFiles(paths, pathToLines, DnsmasqConfKeys.NoPoll); - var bindInterfaces = DnsmasqConfIncludeParser.GetFlagFromConfigFiles(paths, pathToLines, DnsmasqConfKeys.BindInterfaces); - var bindDynamic = DnsmasqConfIncludeParser.GetFlagFromConfigFiles(paths, pathToLines, DnsmasqConfKeys.BindDynamic); - var noNegcache = DnsmasqConfIncludeParser.GetFlagFromConfigFiles(paths, pathToLines, DnsmasqConfKeys.NoNegcache); - var dnsLoopDetect = DnsmasqConfIncludeParser.GetFlagFromConfigFiles(paths, pathToLines, DnsmasqConfKeys.DnsLoopDetect); - var stopDnsRebind = DnsmasqConfIncludeParser.GetFlagFromConfigFiles(paths, pathToLines, DnsmasqConfKeys.StopDnsRebind); - var rebindLocalhostOk = DnsmasqConfIncludeParser.GetFlagFromConfigFiles(paths, pathToLines, DnsmasqConfKeys.RebindLocalhostOk); - var clearOnReload = DnsmasqConfIncludeParser.GetFlagFromConfigFiles(paths, pathToLines, DnsmasqConfKeys.ClearOnReload); - var filterwin2k = DnsmasqConfIncludeParser.GetFlagFromConfigFiles(paths, pathToLines, DnsmasqConfKeys.Filterwin2k); - var filterA = DnsmasqConfIncludeParser.GetFlagFromConfigFiles(paths, pathToLines, DnsmasqConfKeys.FilterA); - var filterAaaa = DnsmasqConfIncludeParser.GetFlagFromConfigFiles(paths, pathToLines, DnsmasqConfKeys.FilterAaaa); - var localiseQueries = DnsmasqConfIncludeParser.GetFlagFromConfigFiles(paths, pathToLines, DnsmasqConfKeys.LocaliseQueries); - var logDebug = DnsmasqConfIncludeParser.GetFlagFromConfigFiles(paths, pathToLines, DnsmasqConfKeys.LogDebug); - var dhcpAuthoritative = DnsmasqConfIncludeParser.GetFlagFromConfigFiles(paths, pathToLines, DnsmasqConfKeys.DhcpAuthoritative); - var leasefileRo = DnsmasqConfIncludeParser.GetFlagFromConfigFiles(paths, pathToLines, DnsmasqConfKeys.LeasefileRo); - var enableTftp = DnsmasqConfIncludeParser.GetFlagFromConfigFiles(paths, pathToLines, DnsmasqConfKeys.EnableTftp); - var tftpSecure = DnsmasqConfIncludeParser.GetFlagFromConfigFiles(paths, pathToLines, DnsmasqConfKeys.TftpSecure); - var dnssec = DnsmasqConfIncludeParser.GetFlagFromConfigFiles(paths, pathToLines, DnsmasqConfKeys.Dnssec); - var dnssecCheckUnsigned = DnsmasqConfIncludeParser.GetFlagFromConfigFiles(paths, pathToLines, DnsmasqConfKeys.DnssecCheckUnsigned); - var readEthers = DnsmasqConfIncludeParser.GetFlagFromConfigFiles(paths, pathToLines, DnsmasqConfKeys.ReadEthers); - var dhcpRapidCommit = DnsmasqConfIncludeParser.GetFlagFromConfigFiles(paths, pathToLines, DnsmasqConfKeys.DhcpRapidCommit); - var tftpNoFail = DnsmasqConfIncludeParser.GetFlagFromConfigFiles(paths, pathToLines, DnsmasqConfKeys.TftpNoFail); - var tftpNoBlocksize = DnsmasqConfIncludeParser.GetFlagFromConfigFiles(paths, pathToLines, DnsmasqConfKeys.TftpNoBlocksize); - var localmx = DnsmasqConfIncludeParser.GetFlagFromConfigFiles(paths, pathToLines, DnsmasqConfKeys.Localmx); - var selfmx = DnsmasqConfIncludeParser.GetFlagFromConfigFiles(paths, pathToLines, DnsmasqConfKeys.Selfmx); - var enableRa = DnsmasqConfIncludeParser.GetFlagFromConfigFiles(paths, pathToLines, DnsmasqConfKeys.EnableRa); - var logDhcp = DnsmasqConfIncludeParser.GetFlagFromConfigFiles(paths, pathToLines, DnsmasqConfKeys.LogDhcp); - var keepInForeground = DnsmasqConfIncludeParser.GetFlagFromConfigFiles(paths, pathToLines, DnsmasqConfKeys.KeepInForeground); - var noDaemon = DnsmasqConfIncludeParser.GetFlagFromConfigFiles(paths, pathToLines, DnsmasqConfKeys.NoDaemon); - var proxyDnssec = DnsmasqConfIncludeParser.GetFlagFromConfigFiles(paths, pathToLines, DnsmasqConfKeys.ProxyDnssec); + var expandHosts = (bool)ParseOptionValue(paths, pathToLines, DnsmasqConfKeys.ExpandHosts); + var bogusPriv = (bool)ParseOptionValue(paths, pathToLines, DnsmasqConfKeys.BogusPriv); + var strictOrder = (bool)ParseOptionValue(paths, pathToLines, DnsmasqConfKeys.StrictOrder); + var allServers = (bool)ParseOptionValue(paths, pathToLines, DnsmasqConfKeys.AllServers); + var noResolv = (bool)ParseOptionValue(paths, pathToLines, DnsmasqConfKeys.NoResolv); + var domainNeeded = (bool)ParseOptionValue(paths, pathToLines, DnsmasqConfKeys.DomainNeeded); + var noPoll = (bool)ParseOptionValue(paths, pathToLines, DnsmasqConfKeys.NoPoll); + var bindInterfaces = (bool)ParseOptionValue(paths, pathToLines, DnsmasqConfKeys.BindInterfaces); + var bindDynamic = (bool)ParseOptionValue(paths, pathToLines, DnsmasqConfKeys.BindDynamic); + var noNegcache = (bool)ParseOptionValue(paths, pathToLines, DnsmasqConfKeys.NoNegcache); + var dnsLoopDetect = (bool)ParseOptionValue(paths, pathToLines, DnsmasqConfKeys.DnsLoopDetect); + var stopDnsRebind = (bool)ParseOptionValue(paths, pathToLines, DnsmasqConfKeys.StopDnsRebind); + var rebindLocalhostOk = (bool)ParseOptionValue(paths, pathToLines, DnsmasqConfKeys.RebindLocalhostOk); + var clearOnReload = (bool)ParseOptionValue(paths, pathToLines, DnsmasqConfKeys.ClearOnReload); + var filterwin2k = (bool)ParseOptionValue(paths, pathToLines, DnsmasqConfKeys.Filterwin2k); + var filterA = (bool)ParseOptionValue(paths, pathToLines, DnsmasqConfKeys.FilterA); + var filterAaaa = (bool)ParseOptionValue(paths, pathToLines, DnsmasqConfKeys.FilterAaaa); + var localiseQueries = (bool)ParseOptionValue(paths, pathToLines, DnsmasqConfKeys.LocaliseQueries); + var logDebug = (bool)ParseOptionValue(paths, pathToLines, DnsmasqConfKeys.LogDebug); + var dhcpAuthoritative = (bool)ParseOptionValue(paths, pathToLines, DnsmasqConfKeys.DhcpAuthoritative); + var leasefileRo = (bool)ParseOptionValue(paths, pathToLines, DnsmasqConfKeys.LeasefileRo); + var enableTftp = (bool)ParseOptionValue(paths, pathToLines, DnsmasqConfKeys.EnableTftp); + var tftpSecure = (bool)ParseOptionValue(paths, pathToLines, DnsmasqConfKeys.TftpSecure); + var dnssec = (bool)ParseOptionValue(paths, pathToLines, DnsmasqConfKeys.Dnssec); + var dnssecCheckUnsigned = (bool)ParseOptionValue(paths, pathToLines, DnsmasqConfKeys.DnssecCheckUnsigned); + var readEthers = (bool)ParseOptionValue(paths, pathToLines, DnsmasqConfKeys.ReadEthers); + var dhcpRapidCommit = (bool)ParseOptionValue(paths, pathToLines, DnsmasqConfKeys.DhcpRapidCommit); + var tftpNoFail = (bool)ParseOptionValue(paths, pathToLines, DnsmasqConfKeys.TftpNoFail); + var tftpNoBlocksize = (bool)ParseOptionValue(paths, pathToLines, DnsmasqConfKeys.TftpNoBlocksize); + var localmx = (bool)ParseOptionValue(paths, pathToLines, DnsmasqConfKeys.Localmx); + var selfmx = (bool)ParseOptionValue(paths, pathToLines, DnsmasqConfKeys.Selfmx); + var enableRa = (bool)ParseOptionValue(paths, pathToLines, DnsmasqConfKeys.EnableRa); + var logDhcp = (bool)ParseOptionValue(paths, pathToLines, DnsmasqConfKeys.LogDhcp); + var keepInForeground = (bool)ParseOptionValue(paths, pathToLines, DnsmasqConfKeys.KeepInForeground); + var noDaemon = (bool)ParseOptionValue(paths, pathToLines, DnsmasqConfKeys.NoDaemon); + var proxyDnssec = (bool)ParseOptionValue(paths, pathToLines, DnsmasqConfKeys.ProxyDnssec); var dhcpLeaseFilePath = DnsmasqConfIncludeParser.GetDhcpLeaseFilePathFromConfigFiles(paths, pathToLines); - var (cacheVal, _) = DnsmasqConfIncludeParser.GetLastValueFromConfigFiles(paths, pathToLines, DnsmasqConfKeys.CacheSize); + var (cacheVal, _) = ((string?, string?))ParseOptionValue(paths, pathToLines, DnsmasqConfKeys.CacheSize); var cacheSize = TryParseInt(cacheVal); - var (portVal, _) = DnsmasqConfIncludeParser.GetLastValueFromConfigFiles(paths, pathToLines, DnsmasqConfKeys.Port); + var (portVal, _) = ((string?, string?))ParseOptionValue(paths, pathToLines, DnsmasqConfKeys.Port); var port = TryParseInt(portVal); - var (localTtlVal, _) = DnsmasqConfIncludeParser.GetLastValueFromConfigFiles(paths, pathToLines, DnsmasqConfKeys.LocalTtl); + var (localTtlVal, _) = ((string?, string?))ParseOptionValue(paths, pathToLines, DnsmasqConfKeys.LocalTtl); var localTtl = TryParseInt(localTtlVal); - var (pidVal, pidDir) = DnsmasqConfIncludeParser.GetLastValueFromConfigFiles(paths, pathToLines, DnsmasqConfKeys.PidFile); + var (pidVal, pidDir) = ((string?, string?))ParseOptionValue(paths, pathToLines, DnsmasqConfKeys.PidFile); var pidFilePath = DnsmasqConfIncludeParser.ResolvePath(pidVal, pidDir); - var (userVal, _) = DnsmasqConfIncludeParser.GetLastValueFromConfigFiles(paths, pathToLines, DnsmasqConfKeys.User); + var (userVal, _) = ((string?, string?))ParseOptionValue(paths, pathToLines, DnsmasqConfKeys.User); var user = string.IsNullOrWhiteSpace(userVal) ? null : userVal.Trim(); - var (groupVal, _) = DnsmasqConfIncludeParser.GetLastValueFromConfigFiles(paths, pathToLines, DnsmasqConfKeys.Group); + var (groupVal, _) = ((string?, string?))ParseOptionValue(paths, pathToLines, DnsmasqConfKeys.Group); var group = string.IsNullOrWhiteSpace(groupVal) ? null : groupVal.Trim(); - var (logFacVal, _) = DnsmasqConfIncludeParser.GetLastValueFromConfigFiles(paths, pathToLines, DnsmasqConfKeys.LogFacility); + var (logFacVal, _) = ((string?, string?))ParseOptionValue(paths, pathToLines, DnsmasqConfKeys.LogFacility); var logFacility = string.IsNullOrWhiteSpace(logFacVal) ? null : logFacVal.Trim(); - var (logQueriesVal, _) = DnsmasqConfIncludeParser.GetLastValueFromConfigFiles(paths, pathToLines, DnsmasqConfKeys.LogQueries); + var (logQueriesVal, _) = ((string?, string?))ParseOptionValue(paths, pathToLines, DnsmasqConfKeys.LogQueries); var logQueries = string.IsNullOrWhiteSpace(logQueriesVal) ? null : logQueriesVal.Trim(); - var (authTtlVal, _) = DnsmasqConfIncludeParser.GetLastValueFromConfigFiles(paths, pathToLines, DnsmasqConfKeys.AuthTtl); + var (authTtlVal, _) = ((string?, string?))ParseOptionValue(paths, pathToLines, DnsmasqConfKeys.AuthTtl); var authTtl = TryParseInt(authTtlVal); - var (ednsPacketMaxVal, _) = DnsmasqConfIncludeParser.GetLastValueFromConfigFiles(paths, pathToLines, DnsmasqConfKeys.EdnsPacketMax); + var (ednsPacketMaxVal, _) = ((string?, string?))ParseOptionValue(paths, pathToLines, DnsmasqConfKeys.EdnsPacketMax); var ednsPacketMax = TryParseInt(ednsPacketMaxVal); - var (queryPortVal, _) = DnsmasqConfIncludeParser.GetLastValueFromConfigFiles(paths, pathToLines, DnsmasqConfKeys.QueryPort); + var (queryPortVal, _) = ((string?, string?))ParseOptionValue(paths, pathToLines, DnsmasqConfKeys.QueryPort); var queryPort = TryParseInt(queryPortVal); - var (portLimitVal, _) = DnsmasqConfIncludeParser.GetLastValueFromConfigFiles(paths, pathToLines, DnsmasqConfKeys.PortLimit); + var (portLimitVal, _) = ((string?, string?))ParseOptionValue(paths, pathToLines, DnsmasqConfKeys.PortLimit); var portLimit = TryParseInt(portLimitVal); - var (minPortVal, _) = DnsmasqConfIncludeParser.GetLastValueFromConfigFiles(paths, pathToLines, DnsmasqConfKeys.MinPort); + var (minPortVal, _) = ((string?, string?))ParseOptionValue(paths, pathToLines, DnsmasqConfKeys.MinPort); var minPort = TryParseInt(minPortVal); - var (maxPortVal, _) = DnsmasqConfIncludeParser.GetLastValueFromConfigFiles(paths, pathToLines, DnsmasqConfKeys.MaxPort); + var (maxPortVal, _) = ((string?, string?))ParseOptionValue(paths, pathToLines, DnsmasqConfKeys.MaxPort); var maxPort = TryParseInt(maxPortVal); - var (logAsyncVal, _) = DnsmasqConfIncludeParser.GetLastValueFromConfigFiles(paths, pathToLines, DnsmasqConfKeys.LogAsync); + var (logAsyncVal, _) = ((string?, string?))ParseOptionValue(paths, pathToLines, DnsmasqConfKeys.LogAsync); var logAsync = string.IsNullOrWhiteSpace(logAsyncVal) ? null : logAsyncVal.Trim(); - var (localServiceVal, _) = DnsmasqConfIncludeParser.GetLastValueFromConfigFiles(paths, pathToLines, DnsmasqConfKeys.LocalService); + var (localServiceVal, _) = ((string?, string?))ParseOptionValue(paths, pathToLines, DnsmasqConfKeys.LocalService); var localService = string.IsNullOrWhiteSpace(localServiceVal) ? null : localServiceVal.Trim(); - var (leaseMaxVal, _) = DnsmasqConfIncludeParser.GetLastValueFromConfigFiles(paths, pathToLines, DnsmasqConfKeys.DhcpLeaseMax); + var (leaseMaxVal, _) = ((string?, string?))ParseOptionValue(paths, pathToLines, DnsmasqConfKeys.DhcpLeaseMax); var dhcpLeaseMax = TryParseInt(leaseMaxVal); - var (negTtlVal, _) = DnsmasqConfIncludeParser.GetLastValueFromConfigFiles(paths, pathToLines, DnsmasqConfKeys.NegTtl); + var (negTtlVal, _) = ((string?, string?))ParseOptionValue(paths, pathToLines, DnsmasqConfKeys.NegTtl); var negTtl = TryParseInt(negTtlVal); - var (maxTtlVal, _) = DnsmasqConfIncludeParser.GetLastValueFromConfigFiles(paths, pathToLines, DnsmasqConfKeys.MaxTtl); + var (maxTtlVal, _) = ((string?, string?))ParseOptionValue(paths, pathToLines, DnsmasqConfKeys.MaxTtl); var maxTtl = TryParseInt(maxTtlVal); - var (maxCacheTtlVal, _) = DnsmasqConfIncludeParser.GetLastValueFromConfigFiles(paths, pathToLines, DnsmasqConfKeys.MaxCacheTtl); + var (maxCacheTtlVal, _) = ((string?, string?))ParseOptionValue(paths, pathToLines, DnsmasqConfKeys.MaxCacheTtl); var maxCacheTtl = TryParseInt(maxCacheTtlVal); - var (minCacheTtlVal, _) = DnsmasqConfIncludeParser.GetLastValueFromConfigFiles(paths, pathToLines, DnsmasqConfKeys.MinCacheTtl); + var (minCacheTtlVal, _) = ((string?, string?))ParseOptionValue(paths, pathToLines, DnsmasqConfKeys.MinCacheTtl); var minCacheTtl = TryParseInt(minCacheTtlVal); - var (dhcpTtlVal, _) = DnsmasqConfIncludeParser.GetLastValueFromConfigFiles(paths, pathToLines, DnsmasqConfKeys.DhcpTtl); + var (dhcpTtlVal, _) = ((string?, string?))ParseOptionValue(paths, pathToLines, DnsmasqConfKeys.DhcpTtl); var dhcpTtl = TryParseInt(dhcpTtlVal); - var (tftpRootVal, tftpRootDir) = DnsmasqConfIncludeParser.GetLastValueFromConfigFiles(paths, pathToLines, DnsmasqConfKeys.TftpRoot); + var (tftpRootVal, tftpRootDir) = ((string?, string?))ParseOptionValue(paths, pathToLines, DnsmasqConfKeys.TftpRoot); var tftpRootPath = string.IsNullOrWhiteSpace(tftpRootVal) ? null : DnsmasqConfIncludeParser.ResolvePath(tftpRootVal?.Trim(), tftpRootDir); - var (pxePromptVal, _) = DnsmasqConfIncludeParser.GetLastValueFromConfigFiles(paths, pathToLines, DnsmasqConfKeys.PxePrompt); + var (pxePromptVal, _) = ((string?, string?))ParseOptionValue(paths, pathToLines, DnsmasqConfKeys.PxePrompt); var pxePrompt = string.IsNullOrWhiteSpace(pxePromptVal) ? null : pxePromptVal.Trim(); - var (enableDbusVal, _) = DnsmasqConfIncludeParser.GetLastValueFromConfigFiles(paths, pathToLines, DnsmasqConfKeys.EnableDbus); + var (enableDbusVal, _) = ((string?, string?))ParseOptionValue(paths, pathToLines, DnsmasqConfKeys.EnableDbus); var enableDbus = string.IsNullOrWhiteSpace(enableDbusVal) ? null : enableDbusVal.Trim(); - var (enableUbusVal, _) = DnsmasqConfIncludeParser.GetLastValueFromConfigFiles(paths, pathToLines, DnsmasqConfKeys.EnableUbus); + var (enableUbusVal, _) = ((string?, string?))ParseOptionValue(paths, pathToLines, DnsmasqConfKeys.EnableUbus); var enableUbus = string.IsNullOrWhiteSpace(enableUbusVal) ? null : enableUbusVal.Trim(); - var (fastDnsRetryVal, _) = DnsmasqConfIncludeParser.GetLastValueFromConfigFiles(paths, pathToLines, DnsmasqConfKeys.FastDnsRetry); + var (fastDnsRetryVal, _) = ((string?, string?))ParseOptionValue(paths, pathToLines, DnsmasqConfKeys.FastDnsRetry); var fastDnsRetry = string.IsNullOrWhiteSpace(fastDnsRetryVal) ? null : fastDnsRetryVal.Trim(); - var (dhcpScriptVal, dhcpScriptDir) = DnsmasqConfIncludeParser.GetLastValueFromConfigFiles(paths, pathToLines, DnsmasqConfKeys.DhcpScript); + var (dhcpScriptVal, dhcpScriptDir) = ((string?, string?))ParseOptionValue(paths, pathToLines, DnsmasqConfKeys.DhcpScript); var dhcpScriptPath = string.IsNullOrWhiteSpace(dhcpScriptVal) ? null : DnsmasqConfIncludeParser.ResolvePath(dhcpScriptVal?.Trim(), dhcpScriptDir); - var (mxTargetVal, _) = DnsmasqConfIncludeParser.GetLastValueFromConfigFiles(paths, pathToLines, DnsmasqConfKeys.MxTarget); + var (mxTargetVal, _) = ((string?, string?))ParseOptionValue(paths, pathToLines, DnsmasqConfKeys.MxTarget); var mxTarget = string.IsNullOrWhiteSpace(mxTargetVal) ? null : mxTargetVal.Trim(); - var (conntrackVal, _) = DnsmasqConfIncludeParser.GetLastValueFromConfigFiles(paths, pathToLines, DnsmasqConfKeys.Conntrack); + var (conntrackVal, _) = ((string?, string?))ParseOptionValue(paths, pathToLines, DnsmasqConfKeys.Conntrack); var conntrack = string.IsNullOrWhiteSpace(conntrackVal) ? null : conntrackVal.Trim(); return new EffectiveDnsmasqConfig( @@ -390,126 +414,126 @@ public sealed class ConfigSetCache : IConfigSetCache, IDisposable private static EffectiveConfigSources BuildEffectiveConfigSources(IReadOnlyList paths, IReadOnlyDictionary pathToLines, string? managedFilePath) { - var (_, noHostsSource) = DnsmasqConfIncludeParser.GetFlagFromConfigFilesWithSource(paths, pathToLines, DnsmasqConfKeys.NoHosts, managedFilePath); + var (_, noHostsSource) = ((bool, ConfigValueSource?))ParseOptionWithSource(paths, pathToLines, DnsmasqConfKeys.NoHosts, managedFilePath); var addnHostsWithSource = DnsmasqConfIncludeParser.GetAddnHostsPathsFromConfigFilesWithSource(paths, pathToLines, managedFilePath); - var (_, hostsdirPathSource) = DnsmasqConfIncludeParser.GetLastValueFromConfigFilesWithSource(paths, pathToLines, DnsmasqConfKeys.Hostsdir, managedFilePath); + var (_, hostsdirPathSource) = ((string?, ConfigValueSource?))ParseOptionWithSource(paths, pathToLines, DnsmasqConfKeys.Hostsdir, managedFilePath); var serverLocalWithSource = DnsmasqConfIncludeParser.GetMultiValueFromConfigFilesWithSource(paths, pathToLines, DnsmasqConfKeys.ServerLocalKeys, managedFilePath); - var revServerWithSource = DnsmasqConfIncludeParser.GetMultiValueFromConfigFilesWithSource(paths, pathToLines, DnsmasqConfKeys.RevServer, managedFilePath); - var addressWithSource = DnsmasqConfIncludeParser.GetMultiValueFromConfigFilesWithSource(paths, pathToLines, DnsmasqConfKeys.Address, managedFilePath); - var interfacesWithSource = DnsmasqConfIncludeParser.GetMultiValueFromConfigFilesWithSource(paths, pathToLines, DnsmasqConfKeys.Interface, managedFilePath); - var listenAddressesWithSource = DnsmasqConfIncludeParser.GetMultiValueFromConfigFilesWithSource(paths, pathToLines, DnsmasqConfKeys.ListenAddress, managedFilePath); - var exceptInterfacesWithSource = DnsmasqConfIncludeParser.GetMultiValueFromConfigFilesWithSource(paths, pathToLines, DnsmasqConfKeys.ExceptInterface, managedFilePath); - var dhcpRangesWithSource = DnsmasqConfIncludeParser.GetMultiValueFromConfigFilesWithSource(paths, pathToLines, DnsmasqConfKeys.DhcpRange, managedFilePath); - var dhcpHostLinesWithSource = DnsmasqConfIncludeParser.GetMultiValueFromConfigFilesWithSource(paths, pathToLines, DnsmasqConfKeys.DhcpHost, managedFilePath); - var dhcpOptionLinesWithSource = DnsmasqConfIncludeParser.GetMultiValueFromConfigFilesWithSource(paths, pathToLines, DnsmasqConfKeys.DhcpOption, managedFilePath); - var dhcpMatchWithSource = DnsmasqConfIncludeParser.GetMultiValueFromConfigFilesWithSource(paths, pathToLines, DnsmasqConfKeys.DhcpMatch, managedFilePath); - var dhcpBootWithSource = DnsmasqConfIncludeParser.GetMultiValueFromConfigFilesWithSource(paths, pathToLines, DnsmasqConfKeys.DhcpBoot, managedFilePath); - var dhcpIgnoreWithSource = DnsmasqConfIncludeParser.GetMultiValueFromConfigFilesWithSource(paths, pathToLines, DnsmasqConfKeys.DhcpIgnore, managedFilePath); - var dhcpVendorclassWithSource = DnsmasqConfIncludeParser.GetMultiValueFromConfigFilesWithSource(paths, pathToLines, DnsmasqConfKeys.DhcpVendorclass, managedFilePath); - var dhcpUserclassWithSource = DnsmasqConfIncludeParser.GetMultiValueFromConfigFilesWithSource(paths, pathToLines, DnsmasqConfKeys.DhcpUserclass, managedFilePath); - var raParamWithSource = DnsmasqConfIncludeParser.GetMultiValueFromConfigFilesWithSource(paths, pathToLines, DnsmasqConfKeys.RaParam, managedFilePath); - var slaacWithSource = DnsmasqConfIncludeParser.GetMultiValueFromConfigFilesWithSource(paths, pathToLines, DnsmasqConfKeys.Slaac, managedFilePath); - var pxeServiceWithSource = DnsmasqConfIncludeParser.GetMultiValueFromConfigFilesWithSource(paths, pathToLines, DnsmasqConfKeys.PxeService, managedFilePath); - var trustAnchorWithSource = DnsmasqConfIncludeParser.GetMultiValueFromConfigFilesWithSource(paths, pathToLines, DnsmasqConfKeys.TrustAnchor, managedFilePath); - var resolvFilesWithSource = DnsmasqConfIncludeParser.GetMultiValueFromConfigFilesWithSource(paths, pathToLines, DnsmasqConfKeys.ResolvFile, managedFilePath); - var rebindDomainOkWithSource = DnsmasqConfIncludeParser.GetMultiValueFromConfigFilesWithSource(paths, pathToLines, DnsmasqConfKeys.RebindDomainOk, managedFilePath); - var bogusNxdomainWithSource = DnsmasqConfIncludeParser.GetMultiValueFromConfigFilesWithSource(paths, pathToLines, DnsmasqConfKeys.BogusNxdomain, managedFilePath); - var ignoreAddressWithSource = DnsmasqConfIncludeParser.GetMultiValueFromConfigFilesWithSource(paths, pathToLines, DnsmasqConfKeys.IgnoreAddress, managedFilePath); - var aliasWithSource = DnsmasqConfIncludeParser.GetMultiValueFromConfigFilesWithSource(paths, pathToLines, DnsmasqConfKeys.Alias, managedFilePath); - var filterRrWithSource = DnsmasqConfIncludeParser.GetMultiValueFromConfigFilesWithSource(paths, pathToLines, DnsmasqConfKeys.FilterRr, managedFilePath); - var cacheRrWithSource = DnsmasqConfIncludeParser.GetMultiValueFromConfigFilesWithSource(paths, pathToLines, DnsmasqConfKeys.CacheRr, managedFilePath); - var authServerWithSource = DnsmasqConfIncludeParser.GetMultiValueFromConfigFilesWithSource(paths, pathToLines, DnsmasqConfKeys.AuthServer, managedFilePath); - var noDhcpInterfaceWithSource = DnsmasqConfIncludeParser.GetMultiValueFromConfigFilesWithSource(paths, pathToLines, DnsmasqConfKeys.NoDhcpInterface, managedFilePath); - var noDhcpv4InterfaceWithSource = DnsmasqConfIncludeParser.GetMultiValueFromConfigFilesWithSource(paths, pathToLines, DnsmasqConfKeys.NoDhcpv4Interface, managedFilePath); - var noDhcpv6InterfaceWithSource = DnsmasqConfIncludeParser.GetMultiValueFromConfigFilesWithSource(paths, pathToLines, DnsmasqConfKeys.NoDhcpv6Interface, managedFilePath); - var domainWithSource = DnsmasqConfIncludeParser.GetMultiValueFromConfigFilesWithSource(paths, pathToLines, DnsmasqConfKeys.Domain, managedFilePath); - var cnameWithSource = DnsmasqConfIncludeParser.GetMultiValueFromConfigFilesWithSource(paths, pathToLines, DnsmasqConfKeys.Cname, managedFilePath); - var mxHostWithSource = DnsmasqConfIncludeParser.GetMultiValueFromConfigFilesWithSource(paths, pathToLines, DnsmasqConfKeys.MxHost, managedFilePath); - var srvWithSource = DnsmasqConfIncludeParser.GetMultiValueFromConfigFilesWithSource(paths, pathToLines, DnsmasqConfKeys.Srv, managedFilePath); - var ptrRecordWithSource = DnsmasqConfIncludeParser.GetMultiValueFromConfigFilesWithSource(paths, pathToLines, DnsmasqConfKeys.PtrRecord, managedFilePath); - var txtRecordWithSource = DnsmasqConfIncludeParser.GetMultiValueFromConfigFilesWithSource(paths, pathToLines, DnsmasqConfKeys.TxtRecord, managedFilePath); - var naptrRecordWithSource = DnsmasqConfIncludeParser.GetMultiValueFromConfigFilesWithSource(paths, pathToLines, DnsmasqConfKeys.NaptrRecord, managedFilePath); - var hostRecordWithSource = DnsmasqConfIncludeParser.GetMultiValueFromConfigFilesWithSource(paths, pathToLines, DnsmasqConfKeys.HostRecord, managedFilePath); - var dynamicHostWithSource = DnsmasqConfIncludeParser.GetMultiValueFromConfigFilesWithSource(paths, pathToLines, DnsmasqConfKeys.DynamicHost, managedFilePath); - var interfaceNameWithSource = DnsmasqConfIncludeParser.GetMultiValueFromConfigFilesWithSource(paths, pathToLines, DnsmasqConfKeys.InterfaceName, managedFilePath); - var dhcpOptionForceWithSource = DnsmasqConfIncludeParser.GetMultiValueFromConfigFilesWithSource(paths, pathToLines, DnsmasqConfKeys.DhcpOptionForce, managedFilePath); - var ipsetWithSource = DnsmasqConfIncludeParser.GetMultiValueFromConfigFilesWithSource(paths, pathToLines, DnsmasqConfKeys.Ipset, managedFilePath); - var nftsetWithSource = DnsmasqConfIncludeParser.GetMultiValueFromConfigFilesWithSource(paths, pathToLines, DnsmasqConfKeys.Nftset, managedFilePath); - var dhcpMacWithSource = DnsmasqConfIncludeParser.GetMultiValueFromConfigFilesWithSource(paths, pathToLines, DnsmasqConfKeys.DhcpMac, managedFilePath); - var dhcpNameMatchWithSource = DnsmasqConfIncludeParser.GetMultiValueFromConfigFilesWithSource(paths, pathToLines, DnsmasqConfKeys.DhcpNameMatch, managedFilePath); - var dhcpIgnoreNamesWithSource = DnsmasqConfIncludeParser.GetMultiValueFromConfigFilesWithSource(paths, pathToLines, DnsmasqConfKeys.DhcpIgnoreNames, managedFilePath); - var dhcpHostsfileWithSource = DnsmasqConfIncludeParser.GetMultiValueFromConfigFilesWithSource(paths, pathToLines, DnsmasqConfKeys.DhcpHostsfile, managedFilePath); - var dhcpOptsfileWithSource = DnsmasqConfIncludeParser.GetMultiValueFromConfigFilesWithSource(paths, pathToLines, DnsmasqConfKeys.DhcpOptsfile, managedFilePath); - var dhcpHostsdirWithSource = DnsmasqConfIncludeParser.GetMultiValueFromConfigFilesWithSource(paths, pathToLines, DnsmasqConfKeys.DhcpHostsdir, managedFilePath); + var revServerWithSource = (IReadOnlyList<(string Value, ConfigValueSource Source)>)ParseOptionWithSource(paths, pathToLines, DnsmasqConfKeys.RevServer, managedFilePath); + var addressWithSource = (IReadOnlyList<(string Value, ConfigValueSource Source)>)ParseOptionWithSource(paths, pathToLines, DnsmasqConfKeys.Address, managedFilePath); + var interfacesWithSource = (IReadOnlyList<(string Value, ConfigValueSource Source)>)ParseOptionWithSource(paths, pathToLines, DnsmasqConfKeys.Interface, managedFilePath); + var listenAddressesWithSource = (IReadOnlyList<(string Value, ConfigValueSource Source)>)ParseOptionWithSource(paths, pathToLines, DnsmasqConfKeys.ListenAddress, managedFilePath); + var exceptInterfacesWithSource = (IReadOnlyList<(string Value, ConfigValueSource Source)>)ParseOptionWithSource(paths, pathToLines, DnsmasqConfKeys.ExceptInterface, managedFilePath); + var dhcpRangesWithSource = (IReadOnlyList<(string Value, ConfigValueSource Source)>)ParseOptionWithSource(paths, pathToLines, DnsmasqConfKeys.DhcpRange, managedFilePath); + var dhcpHostLinesWithSource = (IReadOnlyList<(string Value, ConfigValueSource Source)>)ParseOptionWithSource(paths, pathToLines, DnsmasqConfKeys.DhcpHost, managedFilePath); + var dhcpOptionLinesWithSource = (IReadOnlyList<(string Value, ConfigValueSource Source)>)ParseOptionWithSource(paths, pathToLines, DnsmasqConfKeys.DhcpOption, managedFilePath); + var dhcpMatchWithSource = (IReadOnlyList<(string Value, ConfigValueSource Source)>)ParseOptionWithSource(paths, pathToLines, DnsmasqConfKeys.DhcpMatch, managedFilePath); + var dhcpBootWithSource = (IReadOnlyList<(string Value, ConfigValueSource Source)>)ParseOptionWithSource(paths, pathToLines, DnsmasqConfKeys.DhcpBoot, managedFilePath); + var dhcpIgnoreWithSource = (IReadOnlyList<(string Value, ConfigValueSource Source)>)ParseOptionWithSource(paths, pathToLines, DnsmasqConfKeys.DhcpIgnore, managedFilePath); + var dhcpVendorclassWithSource = (IReadOnlyList<(string Value, ConfigValueSource Source)>)ParseOptionWithSource(paths, pathToLines, DnsmasqConfKeys.DhcpVendorclass, managedFilePath); + var dhcpUserclassWithSource = (IReadOnlyList<(string Value, ConfigValueSource Source)>)ParseOptionWithSource(paths, pathToLines, DnsmasqConfKeys.DhcpUserclass, managedFilePath); + var raParamWithSource = (IReadOnlyList<(string Value, ConfigValueSource Source)>)ParseOptionWithSource(paths, pathToLines, DnsmasqConfKeys.RaParam, managedFilePath); + var slaacWithSource = (IReadOnlyList<(string Value, ConfigValueSource Source)>)ParseOptionWithSource(paths, pathToLines, DnsmasqConfKeys.Slaac, managedFilePath); + var pxeServiceWithSource = (IReadOnlyList<(string Value, ConfigValueSource Source)>)ParseOptionWithSource(paths, pathToLines, DnsmasqConfKeys.PxeService, managedFilePath); + var trustAnchorWithSource = (IReadOnlyList<(string Value, ConfigValueSource Source)>)ParseOptionWithSource(paths, pathToLines, DnsmasqConfKeys.TrustAnchor, managedFilePath); + var resolvFilesWithSource = (IReadOnlyList<(string Value, ConfigValueSource Source)>)ParseOptionWithSource(paths, pathToLines, DnsmasqConfKeys.ResolvFile, managedFilePath); + var rebindDomainOkWithSource = (IReadOnlyList<(string Value, ConfigValueSource Source)>)ParseOptionWithSource(paths, pathToLines, DnsmasqConfKeys.RebindDomainOk, managedFilePath); + var bogusNxdomainWithSource = (IReadOnlyList<(string Value, ConfigValueSource Source)>)ParseOptionWithSource(paths, pathToLines, DnsmasqConfKeys.BogusNxdomain, managedFilePath); + var ignoreAddressWithSource = (IReadOnlyList<(string Value, ConfigValueSource Source)>)ParseOptionWithSource(paths, pathToLines, DnsmasqConfKeys.IgnoreAddress, managedFilePath); + var aliasWithSource = (IReadOnlyList<(string Value, ConfigValueSource Source)>)ParseOptionWithSource(paths, pathToLines, DnsmasqConfKeys.Alias, managedFilePath); + var filterRrWithSource = (IReadOnlyList<(string Value, ConfigValueSource Source)>)ParseOptionWithSource(paths, pathToLines, DnsmasqConfKeys.FilterRr, managedFilePath); + var cacheRrWithSource = (IReadOnlyList<(string Value, ConfigValueSource Source)>)ParseOptionWithSource(paths, pathToLines, DnsmasqConfKeys.CacheRr, managedFilePath); + var authServerWithSource = (IReadOnlyList<(string Value, ConfigValueSource Source)>)ParseOptionWithSource(paths, pathToLines, DnsmasqConfKeys.AuthServer, managedFilePath); + var noDhcpInterfaceWithSource = (IReadOnlyList<(string Value, ConfigValueSource Source)>)ParseOptionWithSource(paths, pathToLines, DnsmasqConfKeys.NoDhcpInterface, managedFilePath); + var noDhcpv4InterfaceWithSource = (IReadOnlyList<(string Value, ConfigValueSource Source)>)ParseOptionWithSource(paths, pathToLines, DnsmasqConfKeys.NoDhcpv4Interface, managedFilePath); + var noDhcpv6InterfaceWithSource = (IReadOnlyList<(string Value, ConfigValueSource Source)>)ParseOptionWithSource(paths, pathToLines, DnsmasqConfKeys.NoDhcpv6Interface, managedFilePath); + var domainWithSource = (IReadOnlyList<(string Value, ConfigValueSource Source)>)ParseOptionWithSource(paths, pathToLines, DnsmasqConfKeys.Domain, managedFilePath); + var cnameWithSource = (IReadOnlyList<(string Value, ConfigValueSource Source)>)ParseOptionWithSource(paths, pathToLines, DnsmasqConfKeys.Cname, managedFilePath); + var mxHostWithSource = (IReadOnlyList<(string Value, ConfigValueSource Source)>)ParseOptionWithSource(paths, pathToLines, DnsmasqConfKeys.MxHost, managedFilePath); + var srvWithSource = (IReadOnlyList<(string Value, ConfigValueSource Source)>)ParseOptionWithSource(paths, pathToLines, DnsmasqConfKeys.Srv, managedFilePath); + var ptrRecordWithSource = (IReadOnlyList<(string Value, ConfigValueSource Source)>)ParseOptionWithSource(paths, pathToLines, DnsmasqConfKeys.PtrRecord, managedFilePath); + var txtRecordWithSource = (IReadOnlyList<(string Value, ConfigValueSource Source)>)ParseOptionWithSource(paths, pathToLines, DnsmasqConfKeys.TxtRecord, managedFilePath); + var naptrRecordWithSource = (IReadOnlyList<(string Value, ConfigValueSource Source)>)ParseOptionWithSource(paths, pathToLines, DnsmasqConfKeys.NaptrRecord, managedFilePath); + var hostRecordWithSource = (IReadOnlyList<(string Value, ConfigValueSource Source)>)ParseOptionWithSource(paths, pathToLines, DnsmasqConfKeys.HostRecord, managedFilePath); + var dynamicHostWithSource = (IReadOnlyList<(string Value, ConfigValueSource Source)>)ParseOptionWithSource(paths, pathToLines, DnsmasqConfKeys.DynamicHost, managedFilePath); + var interfaceNameWithSource = (IReadOnlyList<(string Value, ConfigValueSource Source)>)ParseOptionWithSource(paths, pathToLines, DnsmasqConfKeys.InterfaceName, managedFilePath); + var dhcpOptionForceWithSource = (IReadOnlyList<(string Value, ConfigValueSource Source)>)ParseOptionWithSource(paths, pathToLines, DnsmasqConfKeys.DhcpOptionForce, managedFilePath); + var ipsetWithSource = (IReadOnlyList<(string Value, ConfigValueSource Source)>)ParseOptionWithSource(paths, pathToLines, DnsmasqConfKeys.Ipset, managedFilePath); + var nftsetWithSource = (IReadOnlyList<(string Value, ConfigValueSource Source)>)ParseOptionWithSource(paths, pathToLines, DnsmasqConfKeys.Nftset, managedFilePath); + var dhcpMacWithSource = (IReadOnlyList<(string Value, ConfigValueSource Source)>)ParseOptionWithSource(paths, pathToLines, DnsmasqConfKeys.DhcpMac, managedFilePath); + var dhcpNameMatchWithSource = (IReadOnlyList<(string Value, ConfigValueSource Source)>)ParseOptionWithSource(paths, pathToLines, DnsmasqConfKeys.DhcpNameMatch, managedFilePath); + var dhcpIgnoreNamesWithSource = (IReadOnlyList<(string Value, ConfigValueSource Source)>)ParseOptionWithSource(paths, pathToLines, DnsmasqConfKeys.DhcpIgnoreNames, managedFilePath); + var dhcpHostsfileWithSource = (IReadOnlyList<(string Value, ConfigValueSource Source)>)ParseOptionWithSource(paths, pathToLines, DnsmasqConfKeys.DhcpHostsfile, managedFilePath); + var dhcpOptsfileWithSource = (IReadOnlyList<(string Value, ConfigValueSource Source)>)ParseOptionWithSource(paths, pathToLines, DnsmasqConfKeys.DhcpOptsfile, managedFilePath); + var dhcpHostsdirWithSource = (IReadOnlyList<(string Value, ConfigValueSource Source)>)ParseOptionWithSource(paths, pathToLines, DnsmasqConfKeys.DhcpHostsdir, managedFilePath); - var (_, expandHostsSource) = DnsmasqConfIncludeParser.GetFlagFromConfigFilesWithSource(paths, pathToLines, DnsmasqConfKeys.ExpandHosts, managedFilePath); - var (_, bogusPrivSource) = DnsmasqConfIncludeParser.GetFlagFromConfigFilesWithSource(paths, pathToLines, DnsmasqConfKeys.BogusPriv, managedFilePath); - var (_, strictOrderSource) = DnsmasqConfIncludeParser.GetFlagFromConfigFilesWithSource(paths, pathToLines, DnsmasqConfKeys.StrictOrder, managedFilePath); - var (_, allServersSource) = DnsmasqConfIncludeParser.GetFlagFromConfigFilesWithSource(paths, pathToLines, DnsmasqConfKeys.AllServers, managedFilePath); - var (_, noResolvSource) = DnsmasqConfIncludeParser.GetFlagFromConfigFilesWithSource(paths, pathToLines, DnsmasqConfKeys.NoResolv, managedFilePath); - var (_, domainNeededSource) = DnsmasqConfIncludeParser.GetFlagFromConfigFilesWithSource(paths, pathToLines, DnsmasqConfKeys.DomainNeeded, managedFilePath); - var (_, noPollSource) = DnsmasqConfIncludeParser.GetFlagFromConfigFilesWithSource(paths, pathToLines, DnsmasqConfKeys.NoPoll, managedFilePath); - var (_, bindInterfacesSource) = DnsmasqConfIncludeParser.GetFlagFromConfigFilesWithSource(paths, pathToLines, DnsmasqConfKeys.BindInterfaces, managedFilePath); - var (_, bindDynamicSource) = DnsmasqConfIncludeParser.GetFlagFromConfigFilesWithSource(paths, pathToLines, DnsmasqConfKeys.BindDynamic, managedFilePath); - var (_, noNegcacheSource) = DnsmasqConfIncludeParser.GetFlagFromConfigFilesWithSource(paths, pathToLines, DnsmasqConfKeys.NoNegcache, managedFilePath); - var (_, dnsLoopDetectSource) = DnsmasqConfIncludeParser.GetFlagFromConfigFilesWithSource(paths, pathToLines, DnsmasqConfKeys.DnsLoopDetect, managedFilePath); - var (_, stopDnsRebindSource) = DnsmasqConfIncludeParser.GetFlagFromConfigFilesWithSource(paths, pathToLines, DnsmasqConfKeys.StopDnsRebind, managedFilePath); - var (_, rebindLocalhostOkSource) = DnsmasqConfIncludeParser.GetFlagFromConfigFilesWithSource(paths, pathToLines, DnsmasqConfKeys.RebindLocalhostOk, managedFilePath); - var (_, clearOnReloadSource) = DnsmasqConfIncludeParser.GetFlagFromConfigFilesWithSource(paths, pathToLines, DnsmasqConfKeys.ClearOnReload, managedFilePath); - var (_, filterwin2kSource) = DnsmasqConfIncludeParser.GetFlagFromConfigFilesWithSource(paths, pathToLines, DnsmasqConfKeys.Filterwin2k, managedFilePath); - var (_, filterASource) = DnsmasqConfIncludeParser.GetFlagFromConfigFilesWithSource(paths, pathToLines, DnsmasqConfKeys.FilterA, managedFilePath); - var (_, filterAaaaSource) = DnsmasqConfIncludeParser.GetFlagFromConfigFilesWithSource(paths, pathToLines, DnsmasqConfKeys.FilterAaaa, managedFilePath); - var (_, localiseQueriesSource) = DnsmasqConfIncludeParser.GetFlagFromConfigFilesWithSource(paths, pathToLines, DnsmasqConfKeys.LocaliseQueries, managedFilePath); - var (_, logDebugSource) = DnsmasqConfIncludeParser.GetFlagFromConfigFilesWithSource(paths, pathToLines, DnsmasqConfKeys.LogDebug, managedFilePath); - var (_, dhcpAuthoritativeSource) = DnsmasqConfIncludeParser.GetFlagFromConfigFilesWithSource(paths, pathToLines, DnsmasqConfKeys.DhcpAuthoritative, managedFilePath); - var (_, leasefileRoSource) = DnsmasqConfIncludeParser.GetFlagFromConfigFilesWithSource(paths, pathToLines, DnsmasqConfKeys.LeasefileRo, managedFilePath); - var (_, enableTftpSource) = DnsmasqConfIncludeParser.GetFlagFromConfigFilesWithSource(paths, pathToLines, DnsmasqConfKeys.EnableTftp, managedFilePath); - var (_, tftpSecureSource) = DnsmasqConfIncludeParser.GetFlagFromConfigFilesWithSource(paths, pathToLines, DnsmasqConfKeys.TftpSecure, managedFilePath); - var (_, dnssecSource) = DnsmasqConfIncludeParser.GetFlagFromConfigFilesWithSource(paths, pathToLines, DnsmasqConfKeys.Dnssec, managedFilePath); - var (_, dnssecCheckUnsignedSource) = DnsmasqConfIncludeParser.GetFlagFromConfigFilesWithSource(paths, pathToLines, DnsmasqConfKeys.DnssecCheckUnsigned, managedFilePath); - var (_, readEthersSource) = DnsmasqConfIncludeParser.GetFlagFromConfigFilesWithSource(paths, pathToLines, DnsmasqConfKeys.ReadEthers, managedFilePath); - var (_, dhcpRapidCommitSource) = DnsmasqConfIncludeParser.GetFlagFromConfigFilesWithSource(paths, pathToLines, DnsmasqConfKeys.DhcpRapidCommit, managedFilePath); - var (_, tftpNoFailSource) = DnsmasqConfIncludeParser.GetFlagFromConfigFilesWithSource(paths, pathToLines, DnsmasqConfKeys.TftpNoFail, managedFilePath); - var (_, tftpNoBlocksizeSource) = DnsmasqConfIncludeParser.GetFlagFromConfigFilesWithSource(paths, pathToLines, DnsmasqConfKeys.TftpNoBlocksize, managedFilePath); - var (_, localmxSource) = DnsmasqConfIncludeParser.GetFlagFromConfigFilesWithSource(paths, pathToLines, DnsmasqConfKeys.Localmx, managedFilePath); - var (_, selfmxSource) = DnsmasqConfIncludeParser.GetFlagFromConfigFilesWithSource(paths, pathToLines, DnsmasqConfKeys.Selfmx, managedFilePath); - var (_, enableRaSource) = DnsmasqConfIncludeParser.GetFlagFromConfigFilesWithSource(paths, pathToLines, DnsmasqConfKeys.EnableRa, managedFilePath); - var (_, logDhcpSource) = DnsmasqConfIncludeParser.GetFlagFromConfigFilesWithSource(paths, pathToLines, DnsmasqConfKeys.LogDhcp, managedFilePath); - var (_, keepInForegroundSource) = DnsmasqConfIncludeParser.GetFlagFromConfigFilesWithSource(paths, pathToLines, DnsmasqConfKeys.KeepInForeground, managedFilePath); - var (_, noDaemonSource) = DnsmasqConfIncludeParser.GetFlagFromConfigFilesWithSource(paths, pathToLines, DnsmasqConfKeys.NoDaemon, managedFilePath); - var (_, proxyDnssecSource) = DnsmasqConfIncludeParser.GetFlagFromConfigFilesWithSource(paths, pathToLines, DnsmasqConfKeys.ProxyDnssec, managedFilePath); + var (_, expandHostsSource) = ((bool, ConfigValueSource?))ParseOptionWithSource(paths, pathToLines, DnsmasqConfKeys.ExpandHosts, managedFilePath); + var (_, bogusPrivSource) = ((bool, ConfigValueSource?))ParseOptionWithSource(paths, pathToLines, DnsmasqConfKeys.BogusPriv, managedFilePath); + var (_, strictOrderSource) = ((bool, ConfigValueSource?))ParseOptionWithSource(paths, pathToLines, DnsmasqConfKeys.StrictOrder, managedFilePath); + var (_, allServersSource) = ((bool, ConfigValueSource?))ParseOptionWithSource(paths, pathToLines, DnsmasqConfKeys.AllServers, managedFilePath); + var (_, noResolvSource) = ((bool, ConfigValueSource?))ParseOptionWithSource(paths, pathToLines, DnsmasqConfKeys.NoResolv, managedFilePath); + var (_, domainNeededSource) = ((bool, ConfigValueSource?))ParseOptionWithSource(paths, pathToLines, DnsmasqConfKeys.DomainNeeded, managedFilePath); + var (_, noPollSource) = ((bool, ConfigValueSource?))ParseOptionWithSource(paths, pathToLines, DnsmasqConfKeys.NoPoll, managedFilePath); + var (_, bindInterfacesSource) = ((bool, ConfigValueSource?))ParseOptionWithSource(paths, pathToLines, DnsmasqConfKeys.BindInterfaces, managedFilePath); + var (_, bindDynamicSource) = ((bool, ConfigValueSource?))ParseOptionWithSource(paths, pathToLines, DnsmasqConfKeys.BindDynamic, managedFilePath); + var (_, noNegcacheSource) = ((bool, ConfigValueSource?))ParseOptionWithSource(paths, pathToLines, DnsmasqConfKeys.NoNegcache, managedFilePath); + var (_, dnsLoopDetectSource) = ((bool, ConfigValueSource?))ParseOptionWithSource(paths, pathToLines, DnsmasqConfKeys.DnsLoopDetect, managedFilePath); + var (_, stopDnsRebindSource) = ((bool, ConfigValueSource?))ParseOptionWithSource(paths, pathToLines, DnsmasqConfKeys.StopDnsRebind, managedFilePath); + var (_, rebindLocalhostOkSource) = ((bool, ConfigValueSource?))ParseOptionWithSource(paths, pathToLines, DnsmasqConfKeys.RebindLocalhostOk, managedFilePath); + var (_, clearOnReloadSource) = ((bool, ConfigValueSource?))ParseOptionWithSource(paths, pathToLines, DnsmasqConfKeys.ClearOnReload, managedFilePath); + var (_, filterwin2kSource) = ((bool, ConfigValueSource?))ParseOptionWithSource(paths, pathToLines, DnsmasqConfKeys.Filterwin2k, managedFilePath); + var (_, filterASource) = ((bool, ConfigValueSource?))ParseOptionWithSource(paths, pathToLines, DnsmasqConfKeys.FilterA, managedFilePath); + var (_, filterAaaaSource) = ((bool, ConfigValueSource?))ParseOptionWithSource(paths, pathToLines, DnsmasqConfKeys.FilterAaaa, managedFilePath); + var (_, localiseQueriesSource) = ((bool, ConfigValueSource?))ParseOptionWithSource(paths, pathToLines, DnsmasqConfKeys.LocaliseQueries, managedFilePath); + var (_, logDebugSource) = ((bool, ConfigValueSource?))ParseOptionWithSource(paths, pathToLines, DnsmasqConfKeys.LogDebug, managedFilePath); + var (_, dhcpAuthoritativeSource) = ((bool, ConfigValueSource?))ParseOptionWithSource(paths, pathToLines, DnsmasqConfKeys.DhcpAuthoritative, managedFilePath); + var (_, leasefileRoSource) = ((bool, ConfigValueSource?))ParseOptionWithSource(paths, pathToLines, DnsmasqConfKeys.LeasefileRo, managedFilePath); + var (_, enableTftpSource) = ((bool, ConfigValueSource?))ParseOptionWithSource(paths, pathToLines, DnsmasqConfKeys.EnableTftp, managedFilePath); + var (_, tftpSecureSource) = ((bool, ConfigValueSource?))ParseOptionWithSource(paths, pathToLines, DnsmasqConfKeys.TftpSecure, managedFilePath); + var (_, dnssecSource) = ((bool, ConfigValueSource?))ParseOptionWithSource(paths, pathToLines, DnsmasqConfKeys.Dnssec, managedFilePath); + var (_, dnssecCheckUnsignedSource) = ((bool, ConfigValueSource?))ParseOptionWithSource(paths, pathToLines, DnsmasqConfKeys.DnssecCheckUnsigned, managedFilePath); + var (_, readEthersSource) = ((bool, ConfigValueSource?))ParseOptionWithSource(paths, pathToLines, DnsmasqConfKeys.ReadEthers, managedFilePath); + var (_, dhcpRapidCommitSource) = ((bool, ConfigValueSource?))ParseOptionWithSource(paths, pathToLines, DnsmasqConfKeys.DhcpRapidCommit, managedFilePath); + var (_, tftpNoFailSource) = ((bool, ConfigValueSource?))ParseOptionWithSource(paths, pathToLines, DnsmasqConfKeys.TftpNoFail, managedFilePath); + var (_, tftpNoBlocksizeSource) = ((bool, ConfigValueSource?))ParseOptionWithSource(paths, pathToLines, DnsmasqConfKeys.TftpNoBlocksize, managedFilePath); + var (_, localmxSource) = ((bool, ConfigValueSource?))ParseOptionWithSource(paths, pathToLines, DnsmasqConfKeys.Localmx, managedFilePath); + var (_, selfmxSource) = ((bool, ConfigValueSource?))ParseOptionWithSource(paths, pathToLines, DnsmasqConfKeys.Selfmx, managedFilePath); + var (_, enableRaSource) = ((bool, ConfigValueSource?))ParseOptionWithSource(paths, pathToLines, DnsmasqConfKeys.EnableRa, managedFilePath); + var (_, logDhcpSource) = ((bool, ConfigValueSource?))ParseOptionWithSource(paths, pathToLines, DnsmasqConfKeys.LogDhcp, managedFilePath); + var (_, keepInForegroundSource) = ((bool, ConfigValueSource?))ParseOptionWithSource(paths, pathToLines, DnsmasqConfKeys.KeepInForeground, managedFilePath); + var (_, noDaemonSource) = ((bool, ConfigValueSource?))ParseOptionWithSource(paths, pathToLines, DnsmasqConfKeys.NoDaemon, managedFilePath); + var (_, proxyDnssecSource) = ((bool, ConfigValueSource?))ParseOptionWithSource(paths, pathToLines, DnsmasqConfKeys.ProxyDnssec, managedFilePath); var (_, dhcpLeaseFilePathSource) = DnsmasqConfIncludeParser.GetDhcpLeaseFilePathFromConfigFilesWithSource(paths, pathToLines, managedFilePath); - var (_, cacheSizeSource) = DnsmasqConfIncludeParser.GetLastValueFromConfigFilesWithSource(paths, pathToLines, DnsmasqConfKeys.CacheSize, managedFilePath); - var (_, portSource) = DnsmasqConfIncludeParser.GetLastValueFromConfigFilesWithSource(paths, pathToLines, DnsmasqConfKeys.Port, managedFilePath); - var (_, localTtlSource) = DnsmasqConfIncludeParser.GetLastValueFromConfigFilesWithSource(paths, pathToLines, DnsmasqConfKeys.LocalTtl, managedFilePath); - var (_, pidFilePathSource) = DnsmasqConfIncludeParser.GetLastValueFromConfigFilesWithSource(paths, pathToLines, DnsmasqConfKeys.PidFile, managedFilePath); - var (_, userSource) = DnsmasqConfIncludeParser.GetLastValueFromConfigFilesWithSource(paths, pathToLines, DnsmasqConfKeys.User, managedFilePath); - var (_, groupSource) = DnsmasqConfIncludeParser.GetLastValueFromConfigFilesWithSource(paths, pathToLines, DnsmasqConfKeys.Group, managedFilePath); - var (_, logFacilitySource) = DnsmasqConfIncludeParser.GetLastValueFromConfigFilesWithSource(paths, pathToLines, DnsmasqConfKeys.LogFacility, managedFilePath); - var (_, logQueriesSource) = DnsmasqConfIncludeParser.GetLastValueFromConfigFilesWithSource(paths, pathToLines, DnsmasqConfKeys.LogQueries, managedFilePath); - var (_, authTtlSource) = DnsmasqConfIncludeParser.GetLastValueFromConfigFilesWithSource(paths, pathToLines, DnsmasqConfKeys.AuthTtl, managedFilePath); - var (_, ednsPacketMaxSource) = DnsmasqConfIncludeParser.GetLastValueFromConfigFilesWithSource(paths, pathToLines, DnsmasqConfKeys.EdnsPacketMax, managedFilePath); - var (_, queryPortSource) = DnsmasqConfIncludeParser.GetLastValueFromConfigFilesWithSource(paths, pathToLines, DnsmasqConfKeys.QueryPort, managedFilePath); - var (_, portLimitSource) = DnsmasqConfIncludeParser.GetLastValueFromConfigFilesWithSource(paths, pathToLines, DnsmasqConfKeys.PortLimit, managedFilePath); - var (_, minPortSource) = DnsmasqConfIncludeParser.GetLastValueFromConfigFilesWithSource(paths, pathToLines, DnsmasqConfKeys.MinPort, managedFilePath); - var (_, maxPortSource) = DnsmasqConfIncludeParser.GetLastValueFromConfigFilesWithSource(paths, pathToLines, DnsmasqConfKeys.MaxPort, managedFilePath); - var (_, logAsyncSource) = DnsmasqConfIncludeParser.GetLastValueFromConfigFilesWithSource(paths, pathToLines, DnsmasqConfKeys.LogAsync, managedFilePath); - var (_, localServiceSource) = DnsmasqConfIncludeParser.GetLastValueFromConfigFilesWithSource(paths, pathToLines, DnsmasqConfKeys.LocalService, managedFilePath); - var (_, dhcpLeaseMaxSource) = DnsmasqConfIncludeParser.GetLastValueFromConfigFilesWithSource(paths, pathToLines, DnsmasqConfKeys.DhcpLeaseMax, managedFilePath); - var (_, negTtlSource) = DnsmasqConfIncludeParser.GetLastValueFromConfigFilesWithSource(paths, pathToLines, DnsmasqConfKeys.NegTtl, managedFilePath); - var (_, maxTtlSource) = DnsmasqConfIncludeParser.GetLastValueFromConfigFilesWithSource(paths, pathToLines, DnsmasqConfKeys.MaxTtl, managedFilePath); - var (_, maxCacheTtlSource) = DnsmasqConfIncludeParser.GetLastValueFromConfigFilesWithSource(paths, pathToLines, DnsmasqConfKeys.MaxCacheTtl, managedFilePath); - var (_, minCacheTtlSource) = DnsmasqConfIncludeParser.GetLastValueFromConfigFilesWithSource(paths, pathToLines, DnsmasqConfKeys.MinCacheTtl, managedFilePath); - var (_, dhcpTtlSource) = DnsmasqConfIncludeParser.GetLastValueFromConfigFilesWithSource(paths, pathToLines, DnsmasqConfKeys.DhcpTtl, managedFilePath); - var (_, tftpRootPathSource) = DnsmasqConfIncludeParser.GetLastValueFromConfigFilesWithSource(paths, pathToLines, DnsmasqConfKeys.TftpRoot, managedFilePath); - var (_, pxePromptSource) = DnsmasqConfIncludeParser.GetLastValueFromConfigFilesWithSource(paths, pathToLines, DnsmasqConfKeys.PxePrompt, managedFilePath); - var (_, enableDbusSource) = DnsmasqConfIncludeParser.GetLastValueFromConfigFilesWithSource(paths, pathToLines, DnsmasqConfKeys.EnableDbus, managedFilePath); - var (_, enableUbusSource) = DnsmasqConfIncludeParser.GetLastValueFromConfigFilesWithSource(paths, pathToLines, DnsmasqConfKeys.EnableUbus, managedFilePath); - var (_, fastDnsRetrySource) = DnsmasqConfIncludeParser.GetLastValueFromConfigFilesWithSource(paths, pathToLines, DnsmasqConfKeys.FastDnsRetry, managedFilePath); - var (_, dhcpScriptPathSource) = DnsmasqConfIncludeParser.GetLastValueFromConfigFilesWithSource(paths, pathToLines, DnsmasqConfKeys.DhcpScript, managedFilePath); - var (_, mxTargetSource) = DnsmasqConfIncludeParser.GetLastValueFromConfigFilesWithSource(paths, pathToLines, DnsmasqConfKeys.MxTarget, managedFilePath); - var (_, conntrackSource) = DnsmasqConfIncludeParser.GetLastValueFromConfigFilesWithSource(paths, pathToLines, DnsmasqConfKeys.Conntrack, managedFilePath); + var (_, cacheSizeSource) = ((string?, ConfigValueSource?))ParseOptionWithSource(paths, pathToLines, DnsmasqConfKeys.CacheSize, managedFilePath); + var (_, portSource) = ((string?, ConfigValueSource?))ParseOptionWithSource(paths, pathToLines, DnsmasqConfKeys.Port, managedFilePath); + var (_, localTtlSource) = ((string?, ConfigValueSource?))ParseOptionWithSource(paths, pathToLines, DnsmasqConfKeys.LocalTtl, managedFilePath); + var (_, pidFilePathSource) = ((string?, ConfigValueSource?))ParseOptionWithSource(paths, pathToLines, DnsmasqConfKeys.PidFile, managedFilePath); + var (_, userSource) = ((string?, ConfigValueSource?))ParseOptionWithSource(paths, pathToLines, DnsmasqConfKeys.User, managedFilePath); + var (_, groupSource) = ((string?, ConfigValueSource?))ParseOptionWithSource(paths, pathToLines, DnsmasqConfKeys.Group, managedFilePath); + var (_, logFacilitySource) = ((string?, ConfigValueSource?))ParseOptionWithSource(paths, pathToLines, DnsmasqConfKeys.LogFacility, managedFilePath); + var (_, logQueriesSource) = ((string?, ConfigValueSource?))ParseOptionWithSource(paths, pathToLines, DnsmasqConfKeys.LogQueries, managedFilePath); + var (_, authTtlSource) = ((string?, ConfigValueSource?))ParseOptionWithSource(paths, pathToLines, DnsmasqConfKeys.AuthTtl, managedFilePath); + var (_, ednsPacketMaxSource) = ((string?, ConfigValueSource?))ParseOptionWithSource(paths, pathToLines, DnsmasqConfKeys.EdnsPacketMax, managedFilePath); + var (_, queryPortSource) = ((string?, ConfigValueSource?))ParseOptionWithSource(paths, pathToLines, DnsmasqConfKeys.QueryPort, managedFilePath); + var (_, portLimitSource) = ((string?, ConfigValueSource?))ParseOptionWithSource(paths, pathToLines, DnsmasqConfKeys.PortLimit, managedFilePath); + var (_, minPortSource) = ((string?, ConfigValueSource?))ParseOptionWithSource(paths, pathToLines, DnsmasqConfKeys.MinPort, managedFilePath); + var (_, maxPortSource) = ((string?, ConfigValueSource?))ParseOptionWithSource(paths, pathToLines, DnsmasqConfKeys.MaxPort, managedFilePath); + var (_, logAsyncSource) = ((string?, ConfigValueSource?))ParseOptionWithSource(paths, pathToLines, DnsmasqConfKeys.LogAsync, managedFilePath); + var (_, localServiceSource) = ((string?, ConfigValueSource?))ParseOptionWithSource(paths, pathToLines, DnsmasqConfKeys.LocalService, managedFilePath); + var (_, dhcpLeaseMaxSource) = ((string?, ConfigValueSource?))ParseOptionWithSource(paths, pathToLines, DnsmasqConfKeys.DhcpLeaseMax, managedFilePath); + var (_, negTtlSource) = ((string?, ConfigValueSource?))ParseOptionWithSource(paths, pathToLines, DnsmasqConfKeys.NegTtl, managedFilePath); + var (_, maxTtlSource) = ((string?, ConfigValueSource?))ParseOptionWithSource(paths, pathToLines, DnsmasqConfKeys.MaxTtl, managedFilePath); + var (_, maxCacheTtlSource) = ((string?, ConfigValueSource?))ParseOptionWithSource(paths, pathToLines, DnsmasqConfKeys.MaxCacheTtl, managedFilePath); + var (_, minCacheTtlSource) = ((string?, ConfigValueSource?))ParseOptionWithSource(paths, pathToLines, DnsmasqConfKeys.MinCacheTtl, managedFilePath); + var (_, dhcpTtlSource) = ((string?, ConfigValueSource?))ParseOptionWithSource(paths, pathToLines, DnsmasqConfKeys.DhcpTtl, managedFilePath); + var (_, tftpRootPathSource) = ((string?, ConfigValueSource?))ParseOptionWithSource(paths, pathToLines, DnsmasqConfKeys.TftpRoot, managedFilePath); + var (_, pxePromptSource) = ((string?, ConfigValueSource?))ParseOptionWithSource(paths, pathToLines, DnsmasqConfKeys.PxePrompt, managedFilePath); + var (_, enableDbusSource) = ((string?, ConfigValueSource?))ParseOptionWithSource(paths, pathToLines, DnsmasqConfKeys.EnableDbus, managedFilePath); + var (_, enableUbusSource) = ((string?, ConfigValueSource?))ParseOptionWithSource(paths, pathToLines, DnsmasqConfKeys.EnableUbus, managedFilePath); + var (_, fastDnsRetrySource) = ((string?, ConfigValueSource?))ParseOptionWithSource(paths, pathToLines, DnsmasqConfKeys.FastDnsRetry, managedFilePath); + var (_, dhcpScriptPathSource) = ((string?, ConfigValueSource?))ParseOptionWithSource(paths, pathToLines, DnsmasqConfKeys.DhcpScript, managedFilePath); + var (_, mxTargetSource) = ((string?, ConfigValueSource?))ParseOptionWithSource(paths, pathToLines, DnsmasqConfKeys.MxTarget, managedFilePath); + var (_, conntrackSource) = ((string?, ConfigValueSource?))ParseOptionWithSource(paths, pathToLines, DnsmasqConfKeys.Conntrack, managedFilePath); return new EffectiveConfigSources( noHostsSource, addnHostsWithSource.Select(t => new PathWithSource(t.Path, t.Source)).ToList(), hostsdirPathSource, diff --git a/src/DnsmasqWebUI/Infrastructure/Services/EffectiveConfigRenderFragmentRegistry.cs b/src/DnsmasqWebUI/Infrastructure/Services/EffectiveConfigRenderFragmentRegistry.cs index 10ecada..585615a 100644 --- a/src/DnsmasqWebUI/Infrastructure/Services/EffectiveConfigRenderFragmentRegistry.cs +++ b/src/DnsmasqWebUI/Infrastructure/Services/EffectiveConfigRenderFragmentRegistry.cs @@ -2,23 +2,41 @@ using Microsoft.AspNetCore.Components; using DnsmasqWebUI.Infrastructure.Helpers.Config; using DnsmasqWebUI.Models.Dnsmasq; using DnsmasqWebUI.Models.Dnsmasq.EffectiveConfig; +using DnsmasqWebUI.Models.Dnsmasq.EffectiveConfig.Abstractions; using DnsmasqWebUI.Infrastructure.Services.Abstractions; using DnsmasqWebUI.Components.Dnsmasq.EffectiveConfig.CustomDisplays; namespace DnsmasqWebUI.Infrastructure.Services; /// -/// Maps (sectionId, optionName) to custom effective-config value display component types and builds -/// RenderFragments that render those components with the descriptor. +/// Maps (sectionId, optionName) to custom effective-config value display component types and descriptor factories. +/// Builds RenderFragments that render those components with the descriptor. /// public class EffectiveConfigRenderFragmentRegistry : IEffectiveConfigRenderFragmentRegistry { private readonly Dictionary<(string SectionId, string OptionName), Type> _displayComponents = new(); + private readonly Dictionary<(string SectionId, string OptionName), EffectiveConfigDescriptorFactory> _descriptorFactories = new(); public EffectiveConfigRenderFragmentRegistry() { // Port: single-field display (e.g. "53 (default DNS port)"). - _displayComponents[(EffectiveConfigFieldBuilder.SectionResolver, DnsmasqConfKeys.Port)] = typeof(PortValueDisplay); + RegisterComponent(EffectiveConfigFieldBuilder.SectionResolver, DnsmasqConfKeys.Port, typeof(PortValueDisplay)); + + // Integer single-value options: component type + factory that creates EffectiveIntegerConfigFieldDescriptor. + RegisterInteger(EffectiveConfigFieldBuilder.SectionResolver, DnsmasqConfKeys.AuthTtl, unit: "seconds"); + RegisterInteger(EffectiveConfigFieldBuilder.SectionResolver, DnsmasqConfKeys.EdnsPacketMax, max: 65535); + RegisterInteger(EffectiveConfigFieldBuilder.SectionResolver, DnsmasqConfKeys.QueryPort, max: 65535, defaultValue: 0); + RegisterInteger(EffectiveConfigFieldBuilder.SectionResolver, DnsmasqConfKeys.PortLimit, max: 65535); + RegisterInteger(EffectiveConfigFieldBuilder.SectionResolver, DnsmasqConfKeys.MinPort, min: 1, max: 65535); + RegisterInteger(EffectiveConfigFieldBuilder.SectionResolver, DnsmasqConfKeys.MaxPort, min: 1, max: 65535); + RegisterInteger(EffectiveConfigFieldBuilder.SectionCache, DnsmasqConfKeys.CacheSize, min: 0); + RegisterInteger(EffectiveConfigFieldBuilder.SectionCache, DnsmasqConfKeys.LocalTtl, unit: "seconds"); + RegisterInteger(EffectiveConfigFieldBuilder.SectionCache, DnsmasqConfKeys.NegTtl, unit: "seconds"); + RegisterInteger(EffectiveConfigFieldBuilder.SectionCache, DnsmasqConfKeys.MaxTtl, unit: "seconds"); + RegisterInteger(EffectiveConfigFieldBuilder.SectionCache, DnsmasqConfKeys.MaxCacheTtl, unit: "seconds"); + RegisterInteger(EffectiveConfigFieldBuilder.SectionCache, DnsmasqConfKeys.MinCacheTtl, unit: "seconds"); + RegisterInteger(EffectiveConfigFieldBuilder.SectionDhcp, DnsmasqConfKeys.DhcpLeaseMax, min: 0); + RegisterInteger(EffectiveConfigFieldBuilder.SectionDhcp, DnsmasqConfKeys.DhcpTtl); // Boolean flags: shared display ("Enabled" / "Disabled") for all flag options. RegisterFlag(EffectiveConfigFieldBuilder.SectionHosts, DnsmasqConfKeys.NoHosts); @@ -58,6 +76,17 @@ public class EffectiveConfigRenderFragmentRegistry : IEffectiveConfigRenderFragm RegisterFlag(EffectiveConfigFieldBuilder.SectionProcess, DnsmasqConfKeys.KeepInForeground); RegisterFlag(EffectiveConfigFieldBuilder.SectionProcess, DnsmasqConfKeys.NoDaemon); RegisterFlag(EffectiveConfigFieldBuilder.SectionDnssec, DnsmasqConfKeys.ProxyDnssec); + + // log-queries: dropdown (Off / On / extra / proto / auth). + RegisterComponent(EffectiveConfigFieldBuilder.SectionResolver, DnsmasqConfKeys.LogQueries, typeof(LogQueriesDisplay)); + + // local-service: dropdown (not set / net / host). + RegisterComponent(EffectiveConfigFieldBuilder.SectionProcess, DnsmasqConfKeys.LocalService, typeof(LocalServiceDisplay)); + } + + private void RegisterComponent(string sectionId, string optionName, Type componentType) + { + _displayComponents[(sectionId, optionName)] = componentType; } private void RegisterFlag(string sectionId, string optionName) @@ -65,8 +94,22 @@ public class EffectiveConfigRenderFragmentRegistry : IEffectiveConfigRenderFragm _displayComponents[(sectionId, optionName)] = typeof(FlagValueDisplay); } + private void RegisterInteger(string sectionId, string optionName, + string? viewSuffix = null, string? unit = null, int min = 0, int max = int.MaxValue, int? defaultValue = null) + { + _displayComponents[(sectionId, optionName)] = typeof(IntegerValueDisplay); + _descriptorFactories[(sectionId, optionName)] = (sectionId, optionName, status, getValue, getSource, getItems) => + new EffectiveIntegerConfigFieldDescriptor(sectionId, optionName, false, status, getValue, getSource, getItems, viewSuffix, unit, min, max, defaultValue); + } + /// - public RenderFragment? GetDisplayFragment(string sectionId, string optionName) + public EffectiveConfigDescriptorFactory? GetDescriptorFactory(string sectionId, string optionName) + { + return _descriptorFactories.TryGetValue((sectionId, optionName), out var factory) ? factory : null; + } + + /// + public RenderFragment? BuildFieldComponentFragment(string sectionId, string optionName) { if (!_displayComponents.TryGetValue((sectionId, optionName), out var componentType)) return null; diff --git a/src/DnsmasqWebUI/Models/Dnsmasq/EffectiveConfig/Abstractions/EffectiveConfigDescriptorFactory.cs b/src/DnsmasqWebUI/Models/Dnsmasq/EffectiveConfig/Abstractions/EffectiveConfigDescriptorFactory.cs new file mode 100644 index 0000000..99d223f --- /dev/null +++ b/src/DnsmasqWebUI/Models/Dnsmasq/EffectiveConfig/Abstractions/EffectiveConfigDescriptorFactory.cs @@ -0,0 +1,16 @@ +using DnsmasqWebUI.Models.Dnsmasq; +using DnsmasqWebUI.Models.Dnsmasq.EffectiveConfig; + +namespace DnsmasqWebUI.Models.Dnsmasq.EffectiveConfig.Abstractions; + +/// +/// Creates an for a single-value field given section, option, status, and resolve delegates. +/// Used by the registry so the builder can request the correct concrete descriptor type per registration (e.g. integer options). +/// +public delegate IEffectiveConfigFieldDescriptor EffectiveConfigDescriptorFactory( + string sectionId, + string optionName, + DnsmasqServiceStatus? status, + Func? getValue, + Func? getSource, + Func?>? getItems); diff --git a/src/DnsmasqWebUI/Models/Dnsmasq/EffectiveConfig/Abstractions/IEffectiveConfigFieldDescriptor.cs b/src/DnsmasqWebUI/Models/Dnsmasq/EffectiveConfig/Abstractions/IEffectiveConfigFieldDescriptor.cs new file mode 100644 index 0000000..5e2c383 --- /dev/null +++ b/src/DnsmasqWebUI/Models/Dnsmasq/EffectiveConfig/Abstractions/IEffectiveConfigFieldDescriptor.cs @@ -0,0 +1,16 @@ +using DnsmasqWebUI.Models.Dnsmasq.EffectiveConfig; + +namespace DnsmasqWebUI.Models.Dnsmasq.EffectiveConfig.Abstractions; + +/// +/// Contract for one effective-config field: metadata (section, option name) and resolution of value, source, and items at render time. +/// +public interface IEffectiveConfigFieldDescriptor +{ + string SectionId { get; } + string OptionName { get; } + bool IsMultiValue { get; } + object? GetValue(); + ConfigValueSource? GetSource(); + IReadOnlyList? GetItems(); +} diff --git a/src/DnsmasqWebUI/Models/Dnsmasq/EffectiveConfig/EffectiveConfigFieldDescriptor.cs b/src/DnsmasqWebUI/Models/Dnsmasq/EffectiveConfig/EffectiveConfigFieldDescriptor.cs index 00d6c6a..11d8cd1 100644 --- a/src/DnsmasqWebUI/Models/Dnsmasq/EffectiveConfig/EffectiveConfigFieldDescriptor.cs +++ b/src/DnsmasqWebUI/Models/Dnsmasq/EffectiveConfig/EffectiveConfigFieldDescriptor.cs @@ -1,4 +1,5 @@ using DnsmasqWebUI.Models.Dnsmasq; +using DnsmasqWebUI.Models.Dnsmasq.EffectiveConfig.Abstractions; namespace DnsmasqWebUI.Models.Dnsmasq.EffectiveConfig; @@ -13,7 +14,7 @@ public record EffectiveConfigFieldDescriptor( Func? ResolveValue, Func? ResolveSource, Func?>? ResolveItems -) +) : IEffectiveConfigFieldDescriptor { public object? GetValue() => ResolveValue?.Invoke(Status); public ConfigValueSource? GetSource() => ResolveSource?.Invoke(Status); diff --git a/src/DnsmasqWebUI/Models/Dnsmasq/EffectiveConfig/EffectiveIntegerConfigFieldDescriptor.cs b/src/DnsmasqWebUI/Models/Dnsmasq/EffectiveConfig/EffectiveIntegerConfigFieldDescriptor.cs new file mode 100644 index 0000000..5363cb2 --- /dev/null +++ b/src/DnsmasqWebUI/Models/Dnsmasq/EffectiveConfig/EffectiveIntegerConfigFieldDescriptor.cs @@ -0,0 +1,21 @@ +using DnsmasqWebUI.Models.Dnsmasq; + +namespace DnsmasqWebUI.Models.Dnsmasq.EffectiveConfig; + +/// +/// Descriptor for integer single-value options. Extends the base descriptor with display/validation metadata for number inputs. +/// +public record EffectiveIntegerConfigFieldDescriptor( + string SectionId, + string OptionName, + bool IsMultiValue, + DnsmasqServiceStatus? Status, + Func? ResolveValue, + Func? ResolveSource, + Func?>? ResolveItems, + string? ViewSuffix = null, + string? Unit = null, + int Min = 0, + int Max = int.MaxValue, + int? DefaultValue = null +) : EffectiveConfigFieldDescriptor(SectionId, OptionName, IsMultiValue, Status, ResolveValue, ResolveSource, ResolveItems);