Effective config: option kind map, parser behavior map, custom displays

- Add EffectiveConfigOptionKindMap (Single/Multi kind + LastWins/Flag/Multi parser
  behavior) and drive field builder + ConfigSetCache from it.
- Single AddDescriptor() in list extensions that dispatches via kind map to
  AddSingleDescriptor/AddMultiDescriptor.
- ConfigSetCache: ParseOptionValue/ParseOptionWithSource dispatch by option
  name; BuildEffectiveConfig and BuildEffectiveConfigSources use them with
  casts; special cases (NoHosts, AddnHosts, server/local, DhcpLeaseFilePath)
  remain explicit.
- Add IntegerValueDisplay, LogQueriesDisplay, LocalServiceDisplay; register
  integers with bounds/units in EffectiveConfigRenderFragmentRegistry.
- Add EffectiveConfigDescriptorFactory, EffectiveIntegerConfigFieldDescriptor,
  IEffectiveConfigFieldDescriptor for integer descriptor metadata.
This commit is contained in:
2026-02-14 00:18:28 +10:00
parent ecd9e5cc7b
commit 906e222ec1
16 changed files with 999 additions and 368 deletions
@@ -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;
<input type="number" class="form-control form-control-sm" style="max-width: 8rem;"
min="@Min" max="@Max" value="@(current?.ToString() ?? "")" @oninput="OnInput" />
}
else
{
var v = Descriptor.GetValue() is int n ? n : (int?)null;
if (v is null)
{
@("(not set)")
}
else
{
@v
@if (!string.IsNullOrEmpty(ViewSuffix)) { <text> @ViewSuffix</text> }
@if (!string.IsNullOrEmpty(Unit)) { <text> @Unit</text> }
}
}
@code {
[Parameter] public EffectiveConfigFieldDescriptor Descriptor { get; set; } = null!;
[CascadingParameter] public EffectiveConfigDisplayMode DisplayMode { get; set; } = EffectiveConfigDisplayMode.View;
[Parameter] public EventCallback<int?> 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);
}
}
@@ -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)
{
<select class="form-select form-select-sm" style="max-width: 10rem;" value="@(Descriptor.GetValue() as string ?? "")" @onchange="OnChange">
<option value="">(not set)</option>
<option value="net">net</option>
<option value="host">host</option>
</select>
}
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<string?> 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);
}
}
@@ -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)
{
<select class="form-select form-select-sm" style="max-width: 12rem;" value="@GetEditValue()" @onchange="OnChange">
<option value="">Off</option>
<option value="on">On</option>
<option value="extra">On (extra)</option>
<option value="proto">On (proto)</option>
<option value="auth">On (auth)</option>
</select>
}
else
{
@GetViewText()
}
@code {
[Parameter] public EffectiveConfigFieldDescriptor Descriptor { get; set; } = null!;
[CascadingParameter] public EffectiveConfigDisplayMode DisplayMode { get; set; } = EffectiveConfigDisplayMode.View;
[Parameter] public EventCallback<string?> 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);
}
}
@@ -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);
}
@@ -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 @@
<div class="ec-sections small">
@{
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;
/// <summary>View = read-only; Edit = editable (future). Cascaded to field displays.</summary>
[Parameter] public EffectiveConfigDisplayMode DisplayMode { get; set; } = EffectiveConfigDisplayMode.View;
private string _searchTerm = "";
@@ -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;
/// <summary>Builds getItems from config/source list selectors; used by Multi() for fields that have both.</summary>
/// <summary>Builds getItems from config/source list selectors; used by AddDescriptor for multi-value fields that have both config and source lists.</summary>
private static Func<DnsmasqServiceStatus?, IReadOnlyList<ValueWithSource>?> Items(
Func<EffectiveDnsmasqConfig?, IReadOnlyList<string>?> getValues,
Func<EffectiveConfigSources?, IReadOnlyList<ValueWithSource>?> getWithSource)
@@ -37,7 +38,7 @@ public static class EffectiveConfigFieldBuilder
public static string SectionCache => EffectiveConfigSections.SectionCache;
public static string SectionProcess => EffectiveConfigSections.SectionProcess;
public static IReadOnlyList<EffectiveConfigFieldDescriptor> BuildFieldDescriptors(DnsmasqServiceStatus? status)
public static IReadOnlyList<EffectiveConfigFieldDescriptor> BuildFieldDescriptors(DnsmasqServiceStatus? status, IEffectiveConfigRenderFragmentRegistry? registry = null)
{
if (status == null || status.EffectiveConfig == null)
return Array.Empty<EffectiveConfigFieldDescriptor>();
@@ -45,142 +46,158 @@ public static class EffectiveConfigFieldBuilder
var list = new List<EffectiveConfigFieldDescriptor>();
// 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;
}
/// <summary>Creates a single-value descriptor; uses registry factory when registered (e.g. integer), otherwise a plain descriptor. Used by <see cref="EffectiveConfigFieldDescriptorListExtensions.AddSingleDescriptor"/>.</summary>
public static EffectiveConfigFieldDescriptor GetSingleDescriptor(
IEffectiveConfigRenderFragmentRegistry? registry,
string optionName,
DnsmasqServiceStatus? status,
Func<DnsmasqServiceStatus?, object?>? getValue,
Func<DnsmasqServiceStatus?, ConfigValueSource?>? getSource,
Func<DnsmasqServiceStatus?, IReadOnlyList<ValueWithSource>?>? 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<DnsmasqServiceStatus?, object?>? getValue,
Func<DnsmasqServiceStatus?, ConfigValueSource?>? 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<DnsmasqServiceStatus?, IReadOnlyList<ValueWithSource>?>? getItems)
{
var sectionId = EffectiveConfigSections.GetSectionId(optionName);
return new EffectiveConfigFieldDescriptor(sectionId, optionName, true, status, null, null, getItems);
}
}
@@ -0,0 +1,57 @@
using DnsmasqWebUI.Models.Dnsmasq;
using DnsmasqWebUI.Models.Dnsmasq.EffectiveConfig;
using DnsmasqWebUI.Infrastructure.Services.Abstractions;
namespace DnsmasqWebUI.Infrastructure.Helpers.Config;
/// <summary>
/// Extension methods for building lists of effective-config field descriptors. Keeps the field builder readable.
/// </summary>
public static class EffectiveConfigFieldDescriptorListExtensions
{
/// <summary>
/// Adds a field descriptor; single vs multi is determined by <see cref="EffectiveConfigOptionKindMap"/> for the given option name.
/// </summary>
public static void AddDescriptor(
this List<EffectiveConfigFieldDescriptor> list,
IEffectiveConfigRenderFragmentRegistry? registry,
string optionName,
DnsmasqServiceStatus? status,
Func<DnsmasqServiceStatus?, object?>? getValue,
Func<DnsmasqServiceStatus?, ConfigValueSource?>? getSource,
Func<DnsmasqServiceStatus?, IReadOnlyList<ValueWithSource>?>? getItems)
{
if (EffectiveConfigOptionKindMap.GetKind(optionName) == EffectiveConfigFieldKind.Multi)
list.AddMultiDescriptor(optionName, status, getItems);
else
list.AddSingleDescriptor(registry, optionName, status, getValue, getSource, getItems);
}
/// <summary>
/// Adds a single-value field descriptor. Uses the registry's factory when registered (e.g. integer options), otherwise a plain descriptor.
/// </summary>
public static void AddSingleDescriptor(
this List<EffectiveConfigFieldDescriptor> list,
IEffectiveConfigRenderFragmentRegistry? registry,
string optionName,
DnsmasqServiceStatus? status,
Func<DnsmasqServiceStatus?, object?>? getValue,
Func<DnsmasqServiceStatus?, ConfigValueSource?>? getSource,
Func<DnsmasqServiceStatus?, IReadOnlyList<ValueWithSource>?>? getItems)
{
list.Add(EffectiveConfigFieldBuilder.GetSingleDescriptor(registry, optionName, status, getValue, getSource, getItems));
}
/// <summary>
/// Adds a multi-value field descriptor (e.g. server, address, dhcp-range).
/// </summary>
public static void AddMultiDescriptor(
this List<EffectiveConfigFieldDescriptor> list,
string optionName,
DnsmasqServiceStatus? status,
Func<DnsmasqServiceStatus?, IReadOnlyList<ValueWithSource>?>? getItems)
{
var sectionId = EffectiveConfigSections.GetSectionId(optionName);
list.Add(new EffectiveConfigFieldDescriptor(sectionId, optionName, true, status, null, null, getItems));
}
}
@@ -0,0 +1,292 @@
using System.Collections.Generic;
namespace DnsmasqWebUI.Infrastructure.Helpers.Config;
/// <summary>
/// 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.
/// </summary>
public enum EffectiveConfigFieldKind
{
Single,
Multi,
}
/// <summary>
/// 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.
/// </summary>
public enum EffectiveConfigParserBehavior
{
LastWins,
Flag,
Multi,
}
/// <summary>
/// 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.
/// </summary>
public static class EffectiveConfigOptionKindMap
{
private static readonly IReadOnlyDictionary<string, EffectiveConfigFieldKind> KindByOptionName =
new Dictionary<string, EffectiveConfigFieldKind>(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,
};
/// <summary>Returns Single or Multi for the given option name; defaults to Single if unknown.</summary>
public static EffectiveConfigFieldKind GetKind(string optionName) =>
KindByOptionName.TryGetValue(optionName, out var kind) ? kind : EffectiveConfigFieldKind.Single;
}
/// <summary>
/// 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.
/// </summary>
public static class EffectiveConfigParserBehaviorMap
{
private static readonly IReadOnlyDictionary<string, EffectiveConfigParserBehavior> BehaviorByOptionName =
new Dictionary<string, EffectiveConfigParserBehavior>(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,
};
/// <summary>Returns LastWins, Flag, or Multi for the given option name; defaults to LastWins if unknown.</summary>
public static EffectiveConfigParserBehavior GetBehavior(string optionName) =>
BehaviorByOptionName.TryGetValue(optionName, out var b) ? b : EffectiveConfigParserBehavior.LastWins;
}
@@ -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.
/// </summary>
/// <param name="registry">When provided, used to create the correct descriptor type per field (e.g. integer options).</param>
public static IReadOnlyDictionary<string, IReadOnlyList<EffectiveConfigFieldDescriptor>> GetDescriptorsBySection(
DnsmasqServiceStatus? status,
IReadOnlyList<EffectiveConfigSectionView> views)
IReadOnlyList<EffectiveConfigSectionView> views,
IEffectiveConfigRenderFragmentRegistry? registry = null)
{
if (status == null || views.Count == 0)
return new Dictionary<string, IReadOnlyList<EffectiveConfigFieldDescriptor>>();
var allDescriptors = EffectiveConfigFieldBuilder.BuildFieldDescriptors(status);
var allDescriptors = EffectiveConfigFieldBuilder.BuildFieldDescriptors(status, registry);
var result = new Dictionary<string, List<EffectiveConfigFieldDescriptor>>(StringComparer.OrdinalIgnoreCase);
foreach (var view in views)
@@ -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
{
/// <summary>
/// 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.
/// </summary>
RenderFragment<EffectiveConfigFieldDescriptor>? GetDisplayFragment(string sectionId, string optionName);
RenderFragment<EffectiveConfigFieldDescriptor>? BuildFieldComponentFragment(string sectionId, string optionName);
/// <summary>
/// Returns a factory that creates the correct descriptor type for this field (e.g. <see cref="EffectiveIntegerConfigFieldDescriptor"/>), or null to use the default.
/// </summary>
EffectiveConfigDescriptorFactory? GetDescriptorFactory(string sectionId, string optionName);
}
@@ -214,159 +214,183 @@ public sealed class ConfigSetCache : IConfigSetCache, IDisposable
return dict;
}
/// <summary>Dispatches to GetFlag, GetLastValue, or GetMultiValue based on <see cref="EffectiveConfigParserBehaviorMap"/>.</summary>
private static object ParseOptionValue(IReadOnlyList<string> paths, IReadOnlyDictionary<string, string[]> 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),
};
}
/// <summary>Dispatches to GetFlag/GetLastValue/GetMultiValue WithSource based on <see cref="EffectiveConfigParserBehaviorMap"/>.</summary>
private static object ParseOptionWithSource(IReadOnlyList<string> paths, IReadOnlyDictionary<string, string[]> 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<string> paths, IReadOnlyDictionary<string, string[]> 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<string>)ParseOptionValue(paths, pathToLines, DnsmasqConfKeys.RevServer);
var addressValues = (IReadOnlyList<string>)ParseOptionValue(paths, pathToLines, DnsmasqConfKeys.Address);
var interfaces = (IReadOnlyList<string>)ParseOptionValue(paths, pathToLines, DnsmasqConfKeys.Interface);
var listenAddresses = (IReadOnlyList<string>)ParseOptionValue(paths, pathToLines, DnsmasqConfKeys.ListenAddress);
var exceptInterfaces = (IReadOnlyList<string>)ParseOptionValue(paths, pathToLines, DnsmasqConfKeys.ExceptInterface);
var dhcpRanges = (IReadOnlyList<string>)ParseOptionValue(paths, pathToLines, DnsmasqConfKeys.DhcpRange);
var dhcpHostLines = (IReadOnlyList<string>)ParseOptionValue(paths, pathToLines, DnsmasqConfKeys.DhcpHost);
var dhcpOptionLines = (IReadOnlyList<string>)ParseOptionValue(paths, pathToLines, DnsmasqConfKeys.DhcpOption);
var dhcpMatchValues = (IReadOnlyList<string>)ParseOptionValue(paths, pathToLines, DnsmasqConfKeys.DhcpMatch);
var dhcpBootValues = (IReadOnlyList<string>)ParseOptionValue(paths, pathToLines, DnsmasqConfKeys.DhcpBoot);
var dhcpIgnoreValues = (IReadOnlyList<string>)ParseOptionValue(paths, pathToLines, DnsmasqConfKeys.DhcpIgnore);
var dhcpVendorclassValues = (IReadOnlyList<string>)ParseOptionValue(paths, pathToLines, DnsmasqConfKeys.DhcpVendorclass);
var dhcpUserclassValues = (IReadOnlyList<string>)ParseOptionValue(paths, pathToLines, DnsmasqConfKeys.DhcpUserclass);
var raParamValues = (IReadOnlyList<string>)ParseOptionValue(paths, pathToLines, DnsmasqConfKeys.RaParam);
var slaacValues = (IReadOnlyList<string>)ParseOptionValue(paths, pathToLines, DnsmasqConfKeys.Slaac);
var pxeServiceValues = (IReadOnlyList<string>)ParseOptionValue(paths, pathToLines, DnsmasqConfKeys.PxeService);
var trustAnchorValues = (IReadOnlyList<string>)ParseOptionValue(paths, pathToLines, DnsmasqConfKeys.TrustAnchor);
var resolvFiles = (IReadOnlyList<string>)ParseOptionValue(paths, pathToLines, DnsmasqConfKeys.ResolvFile);
var rebindDomainOk = (IReadOnlyList<string>)ParseOptionValue(paths, pathToLines, DnsmasqConfKeys.RebindDomainOk);
var bogusNxdomain = (IReadOnlyList<string>)ParseOptionValue(paths, pathToLines, DnsmasqConfKeys.BogusNxdomain);
var ignoreAddress = (IReadOnlyList<string>)ParseOptionValue(paths, pathToLines, DnsmasqConfKeys.IgnoreAddress);
var alias = (IReadOnlyList<string>)ParseOptionValue(paths, pathToLines, DnsmasqConfKeys.Alias);
var filterRr = (IReadOnlyList<string>)ParseOptionValue(paths, pathToLines, DnsmasqConfKeys.FilterRr);
var cacheRr = (IReadOnlyList<string>)ParseOptionValue(paths, pathToLines, DnsmasqConfKeys.CacheRr);
var authServer = (IReadOnlyList<string>)ParseOptionValue(paths, pathToLines, DnsmasqConfKeys.AuthServer);
var noDhcpInterface = (IReadOnlyList<string>)ParseOptionValue(paths, pathToLines, DnsmasqConfKeys.NoDhcpInterface);
var noDhcpv4Interface = (IReadOnlyList<string>)ParseOptionValue(paths, pathToLines, DnsmasqConfKeys.NoDhcpv4Interface);
var noDhcpv6Interface = (IReadOnlyList<string>)ParseOptionValue(paths, pathToLines, DnsmasqConfKeys.NoDhcpv6Interface);
var domainValues = (IReadOnlyList<string>)ParseOptionValue(paths, pathToLines, DnsmasqConfKeys.Domain);
var cnameValues = (IReadOnlyList<string>)ParseOptionValue(paths, pathToLines, DnsmasqConfKeys.Cname);
var mxHostValues = (IReadOnlyList<string>)ParseOptionValue(paths, pathToLines, DnsmasqConfKeys.MxHost);
var srvValues = (IReadOnlyList<string>)ParseOptionValue(paths, pathToLines, DnsmasqConfKeys.Srv);
var ptrRecordValues = (IReadOnlyList<string>)ParseOptionValue(paths, pathToLines, DnsmasqConfKeys.PtrRecord);
var txtRecordValues = (IReadOnlyList<string>)ParseOptionValue(paths, pathToLines, DnsmasqConfKeys.TxtRecord);
var naptrRecordValues = (IReadOnlyList<string>)ParseOptionValue(paths, pathToLines, DnsmasqConfKeys.NaptrRecord);
var hostRecordValues = (IReadOnlyList<string>)ParseOptionValue(paths, pathToLines, DnsmasqConfKeys.HostRecord);
var dynamicHostValues = (IReadOnlyList<string>)ParseOptionValue(paths, pathToLines, DnsmasqConfKeys.DynamicHost);
var interfaceNameValues = (IReadOnlyList<string>)ParseOptionValue(paths, pathToLines, DnsmasqConfKeys.InterfaceName);
var dhcpOptionForceLines = (IReadOnlyList<string>)ParseOptionValue(paths, pathToLines, DnsmasqConfKeys.DhcpOptionForce);
var ipsetValues = (IReadOnlyList<string>)ParseOptionValue(paths, pathToLines, DnsmasqConfKeys.Ipset);
var nftsetValues = (IReadOnlyList<string>)ParseOptionValue(paths, pathToLines, DnsmasqConfKeys.Nftset);
var dhcpMacValues = (IReadOnlyList<string>)ParseOptionValue(paths, pathToLines, DnsmasqConfKeys.DhcpMac);
var dhcpNameMatchValues = (IReadOnlyList<string>)ParseOptionValue(paths, pathToLines, DnsmasqConfKeys.DhcpNameMatch);
var dhcpIgnoreNamesValues = (IReadOnlyList<string>)ParseOptionValue(paths, pathToLines, DnsmasqConfKeys.DhcpIgnoreNames);
var dhcpHostsfilePaths = (IReadOnlyList<string>)ParseOptionValue(paths, pathToLines, DnsmasqConfKeys.DhcpHostsfile);
var dhcpOptsfilePaths = (IReadOnlyList<string>)ParseOptionValue(paths, pathToLines, DnsmasqConfKeys.DhcpOptsfile);
var dhcpHostsdirPaths = (IReadOnlyList<string>)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<string> paths, IReadOnlyDictionary<string, string[]> 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,
@@ -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;
/// <summary>
/// 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.
/// </summary>
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);
}
/// <inheritdoc />
public RenderFragment<EffectiveConfigFieldDescriptor>? GetDisplayFragment(string sectionId, string optionName)
public EffectiveConfigDescriptorFactory? GetDescriptorFactory(string sectionId, string optionName)
{
return _descriptorFactories.TryGetValue((sectionId, optionName), out var factory) ? factory : null;
}
/// <inheritdoc />
public RenderFragment<EffectiveConfigFieldDescriptor>? BuildFieldComponentFragment(string sectionId, string optionName)
{
if (!_displayComponents.TryGetValue((sectionId, optionName), out var componentType))
return null;
@@ -0,0 +1,16 @@
using DnsmasqWebUI.Models.Dnsmasq;
using DnsmasqWebUI.Models.Dnsmasq.EffectiveConfig;
namespace DnsmasqWebUI.Models.Dnsmasq.EffectiveConfig.Abstractions;
/// <summary>
/// Creates an <see cref="IEffectiveConfigFieldDescriptor"/> 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).
/// </summary>
public delegate IEffectiveConfigFieldDescriptor EffectiveConfigDescriptorFactory(
string sectionId,
string optionName,
DnsmasqServiceStatus? status,
Func<DnsmasqServiceStatus?, object?>? getValue,
Func<DnsmasqServiceStatus?, ConfigValueSource?>? getSource,
Func<DnsmasqServiceStatus?, IReadOnlyList<ValueWithSource>?>? getItems);
@@ -0,0 +1,16 @@
using DnsmasqWebUI.Models.Dnsmasq.EffectiveConfig;
namespace DnsmasqWebUI.Models.Dnsmasq.EffectiveConfig.Abstractions;
/// <summary>
/// Contract for one effective-config field: metadata (section, option name) and resolution of value, source, and items at render time.
/// </summary>
public interface IEffectiveConfigFieldDescriptor
{
string SectionId { get; }
string OptionName { get; }
bool IsMultiValue { get; }
object? GetValue();
ConfigValueSource? GetSource();
IReadOnlyList<ValueWithSource>? GetItems();
}
@@ -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<DnsmasqServiceStatus?, object?>? ResolveValue,
Func<DnsmasqServiceStatus?, ConfigValueSource?>? ResolveSource,
Func<DnsmasqServiceStatus?, IReadOnlyList<ValueWithSource>?>? ResolveItems
)
) : IEffectiveConfigFieldDescriptor
{
public object? GetValue() => ResolveValue?.Invoke(Status);
public ConfigValueSource? GetSource() => ResolveSource?.Invoke(Status);
@@ -0,0 +1,21 @@
using DnsmasqWebUI.Models.Dnsmasq;
namespace DnsmasqWebUI.Models.Dnsmasq.EffectiveConfig;
/// <summary>
/// Descriptor for integer single-value options. Extends the base descriptor with display/validation metadata for number inputs.
/// </summary>
public record EffectiveIntegerConfigFieldDescriptor(
string SectionId,
string OptionName,
bool IsMultiValue,
DnsmasqServiceStatus? Status,
Func<DnsmasqServiceStatus?, object?>? ResolveValue,
Func<DnsmasqServiceStatus?, ConfigValueSource?>? ResolveSource,
Func<DnsmasqServiceStatus?, IReadOnlyList<ValueWithSource>?>? 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);