Files
dnsmasq-webui/src/DnsmasqWebUI/Infrastructure/Services/EffectiveConfig/Metadata/EffectiveConfigSections.cs
T
alex 31c0f65c86 Hosts page UX: simpler table, projection, and effective-config tweaks
Remove the Status column and row-level badges/actions; keep source-level
Editable on group headers. Double-click managed rows to edit; commit staged
drafts when focus leaves the row via isFocusOutsideElement in the shared
scroll-cue module. Hide inactive system-host rows when dnsmasq ignores them
and move system-hosts / expand-hosts notes below the content area.

Memoize VisibleGroups so BuildGroups runs once per render during search.
Move read-ethers metadata to the DHCP section; adjust Hosts page state copy
and related effective-config tooltips/builders where needed.
2026-03-29 09:31:29 +10:00

250 lines
10 KiB
C#

using System.Collections.Frozen;
namespace DnsmasqWebUI.Infrastructure.Services.EffectiveConfig.Metadata;
/// <summary>
/// Single source of truth: which effective-config sections exist, their display order and titles,
/// and which option (conf key) belongs to which section. Used by the field builder (section lookup)
/// and by the UI (section order, context filtering).
/// </summary>
public static class EffectiveConfigSections
{
// --- Section IDs (used by registry, views, etc.) ---
public const string SectionHosts = "hosts";
public const string SectionResolver = "resolver";
public const string SectionDnsRecords = "dns-records";
public const string SectionDhcp = "dhcp";
public const string SectionTftpPxe = "tftp-pxe";
public const string SectionDnssec = "dnssec";
public const string SectionCache = "cache";
public const string SectionProcess = "process";
/// <summary>Section definition: id, display title, option names in display order.</summary>
public sealed record SectionDef(string SectionId, string Title, string[] OptionNames);
/// <summary>All sections in display order with their option names. Defines option → section mapping.</summary>
public static readonly IReadOnlyList<SectionDef> Sections =
[
new SectionDef(SectionHosts, "Hosts", [
DnsmasqConfKeys.NoHosts,
DnsmasqConfKeys.AddnHosts,
DnsmasqConfKeys.Hostsdir,
DnsmasqConfKeys.ExpandHosts,
DnsmasqConfKeys.Domain,
]),
new SectionDef(SectionResolver, "Resolver / DNS", [
DnsmasqConfKeys.Server,
DnsmasqConfKeys.Local,
DnsmasqConfKeys.RevServer,
DnsmasqConfKeys.Address,
DnsmasqConfKeys.ResolvFile,
DnsmasqConfKeys.NoResolv,
DnsmasqConfKeys.DomainNeeded,
DnsmasqConfKeys.Port,
DnsmasqConfKeys.LogQueries,
DnsmasqConfKeys.StrictOrder,
DnsmasqConfKeys.AllServers,
DnsmasqConfKeys.AuthTtl,
DnsmasqConfKeys.EdnsPacketMax,
DnsmasqConfKeys.QueryPort,
DnsmasqConfKeys.PortLimit,
DnsmasqConfKeys.MinPort,
DnsmasqConfKeys.MaxPort,
DnsmasqConfKeys.DnsLoopDetect,
DnsmasqConfKeys.StopDnsRebind,
DnsmasqConfKeys.RebindLocalhostOk,
DnsmasqConfKeys.ClearOnReload,
DnsmasqConfKeys.BogusPriv,
DnsmasqConfKeys.RebindDomainOk,
DnsmasqConfKeys.BogusNxdomain,
DnsmasqConfKeys.IgnoreAddress,
DnsmasqConfKeys.Alias,
DnsmasqConfKeys.FilterRr,
DnsmasqConfKeys.Filterwin2k,
DnsmasqConfKeys.FilterA,
DnsmasqConfKeys.FilterAaaa,
DnsmasqConfKeys.LocaliseQueries,
DnsmasqConfKeys.FastDnsRetry,
DnsmasqConfKeys.Ipset,
DnsmasqConfKeys.Nftset,
DnsmasqConfKeys.ConnmarkAllowlistEnable,
DnsmasqConfKeys.NoRoundRobin,
DnsmasqConfKeys.DnsForwardMax,
DnsmasqConfKeys.ConnmarkAllowlist,
DnsmasqConfKeys.AddCpeId,
DnsmasqConfKeys.Do0x20Encode,
]),
new SectionDef(SectionDnsRecords, "DNS records", [
DnsmasqConfKeys.Cname,
DnsmasqConfKeys.MxHost,
DnsmasqConfKeys.Srv,
DnsmasqConfKeys.PtrRecord,
DnsmasqConfKeys.TxtRecord,
DnsmasqConfKeys.NaptrRecord,
DnsmasqConfKeys.HostRecord,
DnsmasqConfKeys.DynamicHost,
DnsmasqConfKeys.InterfaceName,
DnsmasqConfKeys.CaaRecord,
DnsmasqConfKeys.DnsRr,
DnsmasqConfKeys.SynthDomain,
DnsmasqConfKeys.AuthZone,
DnsmasqConfKeys.AuthSoa,
DnsmasqConfKeys.AuthSecServers,
DnsmasqConfKeys.AuthPeer,
DnsmasqConfKeys.MxTarget,
DnsmasqConfKeys.Localmx,
DnsmasqConfKeys.Selfmx,
]),
new SectionDef(SectionDhcp, "DHCP", [
DnsmasqConfKeys.NoDhcpInterface,
DnsmasqConfKeys.NoDhcpv4Interface,
DnsmasqConfKeys.NoDhcpv6Interface,
DnsmasqConfKeys.EnableRa,
DnsmasqConfKeys.DhcpAuthoritative,
DnsmasqConfKeys.DhcpRapidCommit,
DnsmasqConfKeys.LeasefileRo,
DnsmasqConfKeys.Leasequery,
DnsmasqConfKeys.DhcpGenerateNames,
DnsmasqConfKeys.ReadEthers,
DnsmasqConfKeys.DhcpBroadcast,
DnsmasqConfKeys.DhcpSequentialIp,
DnsmasqConfKeys.DhcpIgnoreClid,
DnsmasqConfKeys.BootpDynamic,
DnsmasqConfKeys.NoPing,
DnsmasqConfKeys.ScriptArp,
DnsmasqConfKeys.ScriptOnRenewal,
DnsmasqConfKeys.DhcpNoOverride,
DnsmasqConfKeys.DhcpScript,
DnsmasqConfKeys.DhcpAlternatePort,
DnsmasqConfKeys.DhcpDuid,
DnsmasqConfKeys.DhcpLuascript,
DnsmasqConfKeys.DhcpScriptuser,
DnsmasqConfKeys.DhcpLeasefile,
DnsmasqConfKeys.DhcpLeaseMax,
DnsmasqConfKeys.DhcpTtl,
DnsmasqConfKeys.DhcpRange,
DnsmasqConfKeys.DhcpHost,
DnsmasqConfKeys.DhcpOption,
DnsmasqConfKeys.DhcpOptionForce,
DnsmasqConfKeys.DhcpMatch,
DnsmasqConfKeys.DhcpMac,
DnsmasqConfKeys.DhcpNameMatch,
DnsmasqConfKeys.DhcpIgnoreNames,
DnsmasqConfKeys.DhcpHostsfile,
DnsmasqConfKeys.DhcpOptsfile,
DnsmasqConfKeys.DhcpHostsdir,
DnsmasqConfKeys.DhcpOptsdir,
DnsmasqConfKeys.DhcpBoot,
DnsmasqConfKeys.DhcpIgnore,
DnsmasqConfKeys.DhcpVendorclass,
DnsmasqConfKeys.DhcpUserclass,
DnsmasqConfKeys.DhcpRelay,
DnsmasqConfKeys.DhcpCircuitid,
DnsmasqConfKeys.DhcpRemoteid,
DnsmasqConfKeys.DhcpSubscrid,
DnsmasqConfKeys.DhcpProxy,
DnsmasqConfKeys.TagIf,
DnsmasqConfKeys.BridgeInterface,
DnsmasqConfKeys.SharedNetwork,
DnsmasqConfKeys.RaParam,
DnsmasqConfKeys.Slaac,
]),
new SectionDef(SectionTftpPxe, "TFTP / PXE", [
DnsmasqConfKeys.EnableTftp,
DnsmasqConfKeys.TftpSecure,
DnsmasqConfKeys.TftpNoFail,
DnsmasqConfKeys.TftpNoBlocksize,
DnsmasqConfKeys.TftpRoot,
DnsmasqConfKeys.PxePrompt,
DnsmasqConfKeys.PxeService,
DnsmasqConfKeys.DhcpPxeVendor,
DnsmasqConfKeys.DhcpOptionPxe,
]),
new SectionDef(SectionDnssec, "DNSSEC", [
DnsmasqConfKeys.Dnssec,
DnsmasqConfKeys.DnssecCheckUnsigned,
DnsmasqConfKeys.DnssecNoTimecheck,
DnsmasqConfKeys.DnssecDebug,
DnsmasqConfKeys.ProxyDnssec,
DnsmasqConfKeys.DnssecTimestamp,
DnsmasqConfKeys.DnssecLimits,
DnsmasqConfKeys.TrustAnchor,
]),
new SectionDef(SectionCache, "Cache", [
DnsmasqConfKeys.CacheRr,
DnsmasqConfKeys.CacheSize,
DnsmasqConfKeys.LocalTtl,
DnsmasqConfKeys.NoNegcache,
DnsmasqConfKeys.NegTtl,
DnsmasqConfKeys.MaxTtl,
DnsmasqConfKeys.MaxCacheTtl,
DnsmasqConfKeys.MinCacheTtl,
DnsmasqConfKeys.UseStaleCache,
DnsmasqConfKeys.AddMac,
DnsmasqConfKeys.StripMac,
DnsmasqConfKeys.AddSubnet,
DnsmasqConfKeys.StripSubnet,
DnsmasqConfKeys.Umbrella,
]),
new SectionDef(SectionProcess, "Process & networking", [
DnsmasqConfKeys.NoPoll,
DnsmasqConfKeys.BindInterfaces,
DnsmasqConfKeys.BindDynamic,
DnsmasqConfKeys.LogDebug,
DnsmasqConfKeys.LogAsync,
DnsmasqConfKeys.LocalService,
DnsmasqConfKeys.Interface,
DnsmasqConfKeys.ListenAddress,
DnsmasqConfKeys.ExceptInterface,
DnsmasqConfKeys.AuthServer,
DnsmasqConfKeys.Dumpfile,
DnsmasqConfKeys.Dumpmask,
DnsmasqConfKeys.PidFile,
DnsmasqConfKeys.User,
DnsmasqConfKeys.Group,
DnsmasqConfKeys.LogFacility,
DnsmasqConfKeys.EnableDbus,
DnsmasqConfKeys.EnableUbus,
DnsmasqConfKeys.LogDhcp,
DnsmasqConfKeys.KeepInForeground,
DnsmasqConfKeys.NoDaemon,
DnsmasqConfKeys.Conntrack,
DnsmasqConfKeys.QuietDhcp,
DnsmasqConfKeys.QuietDhcp6,
DnsmasqConfKeys.QuietRa,
DnsmasqConfKeys.QuietTftp,
]),
];
private static FrozenDictionary<string, string>? _optionToSection;
/// <summary>Maps option name (conf key or display label) to section id. Used by Single/Multi in the builder.</summary>
public static string GetSectionId(string optionName)
{
_optionToSection ??= BuildOptionToSection();
return _optionToSection.TryGetValue(optionName, out var sectionId) ? sectionId : SectionResolver;
}
/// <summary>Section id and title in display order. Use for rendering section headers.</summary>
public static IReadOnlyList<(string SectionId, string Title)> GetSectionsInOrder() =>
Sections.Select(s => (s.SectionId, s.Title)).ToList();
/// <summary>Option names in the given section (display order). For context/views that restrict by section.</summary>
public static IReadOnlyList<string> GetOptionsInSection(string sectionId)
{
var def = Sections.FirstOrDefault(s => string.Equals(s.SectionId, sectionId, StringComparison.OrdinalIgnoreCase));
return def?.OptionNames ?? Array.Empty<string>();
}
private static FrozenDictionary<string, string> BuildOptionToSection()
{
var dict = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
foreach (var section in Sections)
{
foreach (var option in section.OptionNames)
dict[option] = section.SectionId;
}
return dict.ToFrozenDictionary(StringComparer.OrdinalIgnoreCase);
}
}