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.
This commit is contained in:
2026-03-29 09:31:29 +10:00
parent c7e7844ce7
commit 31c0f65c86
10 changed files with 141 additions and 199 deletions
+79 -63
View File
@@ -8,6 +8,7 @@
@using DnsmasqWebUI.Infrastructure.Services.EffectiveConfig.Abstractions @using DnsmasqWebUI.Infrastructure.Services.EffectiveConfig.Abstractions
@using DnsmasqWebUI.Infrastructure.Services.Dnsmasq.Hosts.Abstractions @using DnsmasqWebUI.Infrastructure.Services.Dnsmasq.Hosts.Abstractions
@using DnsmasqWebUI.Components.EffectiveConfig @using DnsmasqWebUI.Components.EffectiveConfig
@using Microsoft.AspNetCore.Components.Web
@using Microsoft.JSInterop @using Microsoft.JSInterop
@implements IDisposable @implements IDisposable
@inject IStatusClient StatusClient @inject IStatusClient StatusClient
@@ -37,13 +38,6 @@ else
<EffectiveConfigSection Status="_status" Context="EffectiveConfigContext.Hosts" ShowSearchBox="true" OnSaveCompleted="RefreshStatusAsync" /> <EffectiveConfigSection Status="_status" Context="EffectiveConfigContext.Hosts" ShowSearchBox="true" OnSaveCompleted="RefreshStatusAsync" />
</section> </section>
@if (!pageState.SystemHostsActive && !string.IsNullOrEmpty(pageState.SystemHostsInactiveReason))
{
<div class="alert alert-info">
<strong>System hosts</strong> is inactive: @pageState.SystemHostsInactiveReason
</div>
}
@if (!pageState.ManagedHostsAvailable) @if (!pageState.ManagedHostsAvailable)
{ {
<div class="alert alert-warning"> <div class="alert alert-warning">
@@ -97,14 +91,13 @@ else
<th scope="col">Address</th> <th scope="col">Address</th>
<th scope="col">Names</th> <th scope="col">Names</th>
<th scope="col">Effective names</th> <th scope="col">Effective names</th>
<th scope="col">Status</th>
</tr> </tr>
</thead> </thead>
@foreach (var group in VisibleGroups) @foreach (var group in VisibleGroups)
{ {
<tbody @key="@group.Key"> <tbody @key="@group.Key">
<tr class="hosts-group-header"> <tr class="hosts-group-header">
<th colspan="4" scope="colgroup" class="hosts-group-header-cell"> <th colspan="3" scope="colgroup" class="hosts-group-header-cell">
<div class="hosts-group-header__content"> <div class="hosts-group-header__content">
<div class="hosts-group-header__title"> <div class="hosts-group-header__title">
<span>@group.Title</span> <span>@group.Title</span>
@@ -128,32 +121,25 @@ else
{ {
@if (IsEditingRow(row)) @if (IsEditingRow(row))
{ {
<tr class="hosts-row hosts-row--editing table-info" @key="@("edit:" + row.Id)"> <tr class="hosts-row hosts-row--editing table-info"
@key="@("edit:" + row.Id)"
@ref="_editingRowRef"
@onfocusout="OnEditingRowFocusOut">
<td> <td>
<input class="form-control form-control-sm" @bind="_editAddress" /> <input class="form-control form-control-sm" @bind="_editAddress" autofocus />
</td> </td>
<td> <td>
<input class="form-control form-control-sm" placeholder="Comma-separated names" @bind="_editNames" /> <input class="form-control form-control-sm" placeholder="Comma-separated names" @bind="_editNames" />
</td> </td>
<td class="text-muted small">@string.Join(", ", HostsEffectiveNames.Expand(ParseNamesList(_editNames), pageState.ExpandHostsEnabled, pageState.ExpansionDomain))</td> <td class="text-muted small">@string.Join(", ", HostsEffectiveNames.Expand(ParseNamesList(_editNames), pageState.ExpandHostsEnabled, pageState.ExpansionDomain))</td>
<td>
<div class="hosts-status-cell">
<span class="hosts-status-pill hosts-status-pill--editable">Editable</span>
@if (row.IsComment)
{
<span class="hosts-status-pill hosts-status-pill--muted">Commented</span>
}
<div class="hosts-status-actions">
<button type="button" class="btn btn-sm btn-primary" @onclick="SaveEditAsync">Save</button>
<button type="button" class="btn btn-sm btn-outline-secondary" @onclick="CancelEdit">Cancel</button>
</div>
</div>
</td>
</tr> </tr>
} }
else else
{ {
<tr class="@RowClass(row)" @key="@row.Id"> <tr class="@RowClass(row)"
@key="@row.Id"
title="@(row.IsEditable ? "Double-click to edit" : string.Empty)"
@ondblclick="@(() => OnRowDoubleClick(row))">
<td><code>@row.Address</code></td> <td><code>@row.Address</code></td>
<td>@string.Join(", ", row.Names)</td> <td>@string.Join(", ", row.Names)</td>
<td> <td>
@@ -166,30 +152,6 @@ else
@string.Join(", ", row.EffectiveNames) @string.Join(", ", row.EffectiveNames)
} }
</td> </td>
<td>
<div class="hosts-status-cell">
@if (!row.IsActive)
{
<span class="hosts-status-pill hosts-status-pill--inactive" title="@row.InactiveReason">Inactive</span>
}
@if (row.IsComment)
{
<span class="hosts-status-pill hosts-status-pill--muted">Commented</span>
}
@if (row.IsEditable)
{
<span class="hosts-status-pill hosts-status-pill--editable">Editable</span>
}
else
{
<span class="hosts-status-pill hosts-status-pill--readonly">Read-only</span>
}
@if (row.IsEditable && string.IsNullOrEmpty(_editingRowId))
{
<button type="button" class="btn btn-sm btn-outline-primary hosts-inline-action" @onclick="() => BeginEdit(row)">Edit</button>
}
</div>
</td>
</tr> </tr>
} }
} }
@@ -200,17 +162,25 @@ else
</div> </div>
} }
@if (pageState.ExpandHostsEnabled && !string.IsNullOrEmpty(pageState.ExpansionDomain))
{
<div class="alert alert-info mt-3 mb-0">
<strong>expand-hosts</strong> is enabled with domain <code>@pageState.ExpansionDomain</code>.
Simple hostnames (without dots) will be expanded to include the domain suffix.
The <strong>Effective names</strong> column shows how dnsmasq will resolve each entry.
</div>
}
</section> </section>
} }
<div class="hosts-page-notes mt-3 d-flex flex-column gap-2">
@if (!pageState.SystemHostsActive && !string.IsNullOrEmpty(pageState.SystemHostsInactiveReason))
{
<div class="alert alert-info mb-0">
<strong>System hosts</strong>: @pageState.SystemHostsInactiveReason
</div>
}
@if (pageState.ExpandHostsEnabled && !string.IsNullOrEmpty(pageState.ExpansionDomain))
{
<div class="alert alert-info mb-0">
<strong>expand-hosts</strong> is enabled with domain <code>@pageState.ExpansionDomain</code>.
Simple hostnames (without dots) will be expanded to include the domain suffix.
The <strong>Effective names</strong> column shows how dnsmasq will resolve each entry.
</div>
}
</div>
} }
</div> </div>
@@ -227,10 +197,31 @@ else
private string? _editingRowId; private string? _editingRowId;
private string _editAddress = ""; private string _editAddress = "";
private string _editNames = ""; private string _editNames = "";
private ElementReference _editingRowRef;
private IJSObjectReference? _hostsTableScrollCueModule; private IJSObjectReference? _hostsTableScrollCueModule;
private IReadOnlyList<HostsPageGroup> VisibleGroups => // Memoize: markup reads VisibleGroups.Count then foreach — avoid building twice per render.
HostsProjection.BuildGroups(_unifiedRows, BuildQuery()); private IReadOnlyList<HostsPageGroup>? _visibleGroupsCache;
private object? _visibleGroupsCacheRowsRef;
private string _visibleGroupsCacheSearch = "\0";
private IReadOnlyList<HostsPageGroup> VisibleGroups
{
get
{
if (_visibleGroupsCache != null
&& ReferenceEquals(_visibleGroupsCacheRowsRef, _unifiedRows)
&& _visibleGroupsCacheSearch == _search)
{
return _visibleGroupsCache;
}
_visibleGroupsCache = HostsProjection.BuildGroups(_unifiedRows, BuildQuery());
_visibleGroupsCacheRowsRef = _unifiedRows;
_visibleGroupsCacheSearch = _search;
return _visibleGroupsCache;
}
}
private HostsPageQueryState BuildQuery() private HostsPageQueryState BuildQuery()
{ {
@@ -247,13 +238,15 @@ else
private static string RowClass(HostsPageRow row) private static string RowClass(HostsPageRow row)
{ {
var parts = new List<string> { "hosts-row" }; var parts = new List<string> { "hosts-row" };
if (row.IsComment) if (row.IsEditable)
parts.Add("table-secondary"); parts.Add("hosts-row--editable");
if (!row.IsActive) if (!row.IsActive)
parts.Add("hosts-row--inactive"); parts.Add("hosts-row--inactive");
return string.Join(" ", parts); return string.Join(" ", parts);
} }
private void OnRowDoubleClick(HostsPageRow row) => BeginEdit(row);
private bool IsEditingRow(HostsPageRow row) => private bool IsEditingRow(HostsPageRow row) =>
!string.IsNullOrEmpty(_editingRowId) && string.Equals(_editingRowId, row.Id, StringComparison.Ordinal); !string.IsNullOrEmpty(_editingRowId) && string.Equals(_editingRowId, row.Id, StringComparison.Ordinal);
@@ -276,7 +269,30 @@ else
_editNames = ""; _editNames = "";
} }
private async Task SaveEditAsync() private async Task OnEditingRowFocusOut(FocusEventArgs _)
{
if (string.IsNullOrEmpty(_editingRowId))
return;
await Task.Delay(10);
if (string.IsNullOrEmpty(_editingRowId))
return;
var outside = true;
try
{
if (_hostsTableScrollCueModule != null)
outside = await _hostsTableScrollCueModule.InvokeAsync<bool>("isFocusOutsideElement", _editingRowRef);
}
catch (JSDisconnectedException)
{
outside = true;
}
if (outside)
await CommitAndEndInlineEditAsync();
}
private async Task CommitAndEndInlineEditAsync()
{ {
if (string.IsNullOrEmpty(_editingRowId) || _status == null) if (string.IsNullOrEmpty(_editingRowId) || _status == null)
return; return;
@@ -139,6 +139,14 @@
opacity: 0.72; opacity: 0.72;
} }
::deep .hosts-row--editable {
cursor: pointer;
}
::deep .hosts-row--editable:hover {
background-color: rgba(13, 110, 253, 0.06);
}
::deep .hosts-row code { ::deep .hosts-row code {
font-size: 0.93em; font-size: 0.93em;
color: #0f172a; color: #0f172a;
@@ -151,66 +159,6 @@
background: #f0f7ff; background: #f0f7ff;
} }
.hosts-status-cell {
display: flex;
flex-wrap: wrap;
align-items: center;
gap: 0.3rem;
}
.hosts-status-pill {
display: inline-flex;
align-items: center;
padding: 0.22rem 0.48rem;
border-radius: 999px;
font-size: 0.72rem;
font-weight: 600;
line-height: 1.1;
border: 1px solid transparent;
white-space: nowrap;
}
.hosts-status-pill--active {
color: #0f5132;
background: #d1e7dd;
border-color: #badbcc;
}
.hosts-status-pill--inactive {
color: #5c3d00;
background: #fff3cd;
border-color: #ffe69c;
}
.hosts-status-pill--editable {
color: #084298;
background: #dbeafe;
border-color: #b6d4fe;
}
.hosts-status-pill--readonly {
color: #475569;
background: #f1f5f9;
border-color: #d8e0ea;
}
.hosts-status-pill--muted {
color: #495057;
background: #f3f4f6;
border-color: #e5e7eb;
}
.hosts-inline-action {
margin-left: 0.1rem;
}
.hosts-status-actions {
display: inline-flex;
flex-wrap: wrap;
gap: 0.3rem;
margin-left: 0.1rem;
}
@media (max-width: 991.98px) { @media (max-width: 991.98px) {
.hosts-controls-bar { .hosts-controls-bar {
padding: 0.75rem; padding: 0.75rem;
@@ -31,13 +31,15 @@ public sealed class HostsPageProjectionService : IHostsPageProjectionService
if (query.ActiveFilter is { } act) if (query.ActiveFilter is { } act)
filtered = filtered.Where(r => r.IsActive == act); filtered = filtered.Where(r => r.IsActive == act);
// When dnsmasq ignores system hosts (e.g. no-hosts), do not show those rows in the table.
filtered = filtered.Where(r =>
!(r.SourceKind == HostsRowSourceKind.SystemHosts && !r.IsActive));
var list = filtered.ToList(); var list = filtered.ToList();
if (list.Count == 0) if (list.Count == 0)
return Array.Empty<HostsPageGroup>(); return Array.Empty<HostsPageGroup>();
return query.Grouping == HostsGroupingMode.Activity return BuildSourceGroups(list, query);
? BuildActivityGroups(list, query)
: BuildSourceGroups(list, query);
} }
private static IReadOnlyList<HostsPageGroup> BuildSourceGroups(List<HostsPageRow> rows, HostsPageQueryState query) private static IReadOnlyList<HostsPageGroup> BuildSourceGroups(List<HostsPageRow> rows, HostsPageQueryState query)
@@ -76,40 +78,6 @@ public sealed class HostsPageProjectionService : IHostsPageProjectionService
return result; return result;
} }
private static IReadOnlyList<HostsPageGroup> BuildActivityGroups(List<HostsPageRow> rows, HostsPageQueryState query)
{
var orderedGroups = rows
.GroupBy(r => r.IsActive)
.OrderByDescending(g => g.Key);
var result = new List<HostsPageGroup>();
foreach (var g in orderedGroups)
{
var sortedRows = SortRows(g, query.Sort, query.Descending).ToList();
if (sortedRows.Count == 0)
continue;
var active = g.Key;
var title = active ? "Active" : "Inactive";
var subtitle = active
? "Entries from sources dnsmasq loads for hosts."
: "Entries from sources that are ignored or unavailable (e.g. system hosts under no-hosts).";
result.Add(new HostsPageGroup(
Key: active ? "activity:active" : "activity:inactive",
Title: title,
Subtitle: subtitle,
SourceKind: null,
GroupContainsEditableRows: sortedRows.Any(r => r.IsEditable),
IsActive: active,
InactiveReason: null,
VisibleRowCount: sortedRows.Count,
Rows: sortedRows));
}
return result;
}
private static IEnumerable<HostsPageRow> SortRows(IEnumerable<HostsPageRow> rows, HostsSortMode sort, bool descending) private static IEnumerable<HostsPageRow> SortRows(IEnumerable<HostsPageRow> rows, HostsSortMode sort, bool descending)
{ {
IEnumerable<HostsPageRow> ordered = sort switch IEnumerable<HostsPageRow> ordered = sort switch
@@ -150,7 +150,7 @@ public static class DnsmasqOptionTooltips
[DnsmasqConfKeys.DnssecCheckUnsigned] = "Treat unsigned zones as bogus.", [DnsmasqConfKeys.DnssecCheckUnsigned] = "Treat unsigned zones as bogus.",
[DnsmasqConfKeys.DnssecNoTimecheck] = "Do not validate DNSSEC signatures against the current time (e.g. for testing).", [DnsmasqConfKeys.DnssecNoTimecheck] = "Do not validate DNSSEC signatures against the current time (e.g. for testing).",
[DnsmasqConfKeys.DnssecDebug] = "Log DNSSEC validation failures at debug level.", [DnsmasqConfKeys.DnssecDebug] = "Log DNSSEC validation failures at debug level.",
[DnsmasqConfKeys.AddCpeId] = "Add CPE-ID to DHCP requests (for identification).", [DnsmasqConfKeys.AddCpeId] = "Add an identifying string to DNS queries forwarded upstream (EDNS0).",
[DnsmasqConfKeys.DnssecTimestamp] = "Path to file for DNSSEC timestamp (e.g. for validation).", [DnsmasqConfKeys.DnssecTimestamp] = "Path to file for DNSSEC timestamp (e.g. for validation).",
[DnsmasqConfKeys.DnssecLimits] = "DNSSEC validation limits (e.g. max signed zones).", [DnsmasqConfKeys.DnssecLimits] = "DNSSEC validation limits (e.g. max signed zones).",
[DnsmasqConfKeys.TrustAnchor] = "DNSSEC trust anchors. Can repeat.", [DnsmasqConfKeys.TrustAnchor] = "DNSSEC trust anchors. Can repeat.",
@@ -163,8 +163,8 @@ public static class DnsmasqOptionTooltips
[DnsmasqConfKeys.MaxTtl] = "Maximum TTL for cached responses (cap on upstream TTL).", [DnsmasqConfKeys.MaxTtl] = "Maximum TTL for cached responses (cap on upstream TTL).",
[DnsmasqConfKeys.MaxCacheTtl] = "Maximum TTL for positive cache entries.", [DnsmasqConfKeys.MaxCacheTtl] = "Maximum TTL for positive cache entries.",
[DnsmasqConfKeys.MinCacheTtl] = "Minimum TTL for positive cache entries.", [DnsmasqConfKeys.MinCacheTtl] = "Minimum TTL for positive cache entries.",
[DnsmasqConfKeys.Dumpfile] = "Dump cache to this file (for debugging).", [DnsmasqConfKeys.Dumpfile] = "Pcap file path for packet capture (debugging).",
[DnsmasqConfKeys.Dumpmask] = "Mask for cache dump (which data to include).", [DnsmasqConfKeys.Dumpmask] = "Bitmask selecting which packet types are written to the dump file.",
[DnsmasqConfKeys.UseStaleCache] = "Use stale cache when upstream is unavailable. Off, On (key-only), or custom seconds.", [DnsmasqConfKeys.UseStaleCache] = "Use stale cache when upstream is unavailable. Off, On (key-only), or custom seconds.",
[DnsmasqConfKeys.AddMac] = "Add MAC address to DNS queries. Off, On (key-only), or custom value.", [DnsmasqConfKeys.AddMac] = "Add MAC address to DNS queries. Off, On (key-only), or custom value.",
[DnsmasqConfKeys.StripMac] = "Strip MAC address information from forwarded DNS queries.", [DnsmasqConfKeys.StripMac] = "Strip MAC address information from forwarded DNS queries.",
@@ -54,7 +54,8 @@ public sealed class EffectiveConfigFieldBuilder : IEffectiveConfigFieldBuilder
list.AddDescriptor(registry, DnsmasqConfKeys.NoHosts, status, s => Config(s)?.NoHosts, s => Sources(s)?.NoHosts, 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.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.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); list.AddDescriptor(registry, DnsmasqConfKeys.ExpandHosts, status, s => Config(s)?.ExpandHosts, s => Sources(s)?.ExpandHosts, null);
list.AddDescriptor(registry, DnsmasqConfKeys.Domain, status, null, null, Items(ec => ec?.DomainValues, src => src?.DomainValues));
// Resolver / DNS // Resolver / DNS
list.AddDescriptor(registry, DnsmasqConfKeys.Server, status, null, null, Items(ec => ec?.ServerValues, src => src?.ServerValues)); list.AddDescriptor(registry, DnsmasqConfKeys.Server, status, null, null, Items(ec => ec?.ServerValues, src => src?.ServerValues));
@@ -78,7 +79,6 @@ public sealed class EffectiveConfigFieldBuilder : IEffectiveConfigFieldBuilder
list.AddDescriptor(registry, DnsmasqConfKeys.StopDnsRebind, status, s => Config(s)?.StopDnsRebind, s => Sources(s)?.StopDnsRebind, 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.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.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.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.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.BogusNxdomain, status, null, null, Items(ec => ec?.BogusNxdomainValues, src => src?.BogusNxdomainValues));
@@ -96,10 +96,10 @@ public sealed class EffectiveConfigFieldBuilder : IEffectiveConfigFieldBuilder
list.AddDescriptor(registry, DnsmasqConfKeys.NoRoundRobin, status, s => Config(s)?.NoRoundRobin, s => Sources(s)?.NoRoundRobin, null); list.AddDescriptor(registry, DnsmasqConfKeys.NoRoundRobin, status, s => Config(s)?.NoRoundRobin, s => Sources(s)?.NoRoundRobin, null);
list.AddDescriptor(registry, DnsmasqConfKeys.DnsForwardMax, status, s => Config(s)?.DnsForwardMax, s => Sources(s)?.DnsForwardMax, null); list.AddDescriptor(registry, DnsmasqConfKeys.DnsForwardMax, status, s => Config(s)?.DnsForwardMax, s => Sources(s)?.DnsForwardMax, null);
list.AddDescriptor(registry, DnsmasqConfKeys.ConnmarkAllowlist, status, null, null, Items(ec => ec?.ConnmarkAllowlistValues, src => src?.ConnmarkAllowlistValues)); list.AddDescriptor(registry, DnsmasqConfKeys.ConnmarkAllowlist, status, null, null, Items(ec => ec?.ConnmarkAllowlistValues, src => src?.ConnmarkAllowlistValues));
list.AddDescriptor(registry, DnsmasqConfKeys.AddCpeId, status, s => Config(s)?.AddCpeId, s => Sources(s)?.AddCpeId, null);
list.AddDescriptor(registry, DnsmasqConfKeys.Do0x20Encode, status, s => Config(s)?.Do0x20EncodeState, s => Sources(s)?.Do0x20Encode, null); list.AddDescriptor(registry, DnsmasqConfKeys.Do0x20Encode, status, s => Config(s)?.Do0x20EncodeState, s => Sources(s)?.Do0x20Encode, null);
// DNS records // DNS records
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.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.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.Srv, status, null, null, Items(ec => ec?.SrvValues, src => src?.SrvValues));
@@ -121,11 +121,16 @@ public sealed class EffectiveConfigFieldBuilder : IEffectiveConfigFieldBuilder
list.AddDescriptor(registry, DnsmasqConfKeys.Selfmx, status, s => Config(s)?.Selfmx, s => Sources(s)?.Selfmx, null); list.AddDescriptor(registry, DnsmasqConfKeys.Selfmx, status, s => Config(s)?.Selfmx, s => Sources(s)?.Selfmx, null);
// DHCP // DHCP
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.EnableRa, status, s => Config(s)?.EnableRa, s => Sources(s)?.EnableRa, null);
list.AddDescriptor(registry, DnsmasqConfKeys.DhcpAuthoritative, status, s => Config(s)?.DhcpAuthoritative, s => Sources(s)?.DhcpAuthoritative, null); 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.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.LeasefileRo, status, s => Config(s)?.LeasefileRo, s => Sources(s)?.LeasefileRo, null);
list.AddDescriptor(registry, DnsmasqConfKeys.Leasequery, status, null, null, Items(ec => ec?.LeasequeryValues, src => src?.LeasequeryValues)); list.AddDescriptor(registry, DnsmasqConfKeys.Leasequery, status, null, null, Items(ec => ec?.LeasequeryValues, src => src?.LeasequeryValues));
list.AddDescriptor(registry, DnsmasqConfKeys.DhcpGenerateNames, status, s => Config(s)?.DhcpGenerateNames, s => Sources(s)?.DhcpGenerateNames, null); list.AddDescriptor(registry, DnsmasqConfKeys.DhcpGenerateNames, status, s => Config(s)?.DhcpGenerateNames, s => Sources(s)?.DhcpGenerateNames, null);
list.AddDescriptor(registry, DnsmasqConfKeys.ReadEthers, status, s => Config(s)?.ReadEthers, s => Sources(s)?.ReadEthers, null);
list.AddDescriptor(registry, DnsmasqConfKeys.DhcpBroadcast, status, s => Config(s)?.DhcpBroadcast, s => Sources(s)?.DhcpBroadcast, null); list.AddDescriptor(registry, DnsmasqConfKeys.DhcpBroadcast, status, s => Config(s)?.DhcpBroadcast, s => Sources(s)?.DhcpBroadcast, null);
list.AddDescriptor(registry, DnsmasqConfKeys.DhcpSequentialIp, status, s => Config(s)?.DhcpSequentialIp, s => Sources(s)?.DhcpSequentialIp, null); list.AddDescriptor(registry, DnsmasqConfKeys.DhcpSequentialIp, status, s => Config(s)?.DhcpSequentialIp, s => Sources(s)?.DhcpSequentialIp, null);
list.AddDescriptor(registry, DnsmasqConfKeys.DhcpIgnoreClid, status, s => Config(s)?.DhcpIgnoreClid, s => Sources(s)?.DhcpIgnoreClid, null); list.AddDescriptor(registry, DnsmasqConfKeys.DhcpIgnoreClid, status, s => Config(s)?.DhcpIgnoreClid, s => Sources(s)?.DhcpIgnoreClid, null);
@@ -186,7 +191,6 @@ public sealed class EffectiveConfigFieldBuilder : IEffectiveConfigFieldBuilder
list.AddDescriptor(registry, DnsmasqConfKeys.DnssecNoTimecheck, status, s => Config(s)?.DnssecNoTimecheck, s => Sources(s)?.DnssecNoTimecheck, null); list.AddDescriptor(registry, DnsmasqConfKeys.DnssecNoTimecheck, status, s => Config(s)?.DnssecNoTimecheck, s => Sources(s)?.DnssecNoTimecheck, null);
list.AddDescriptor(registry, DnsmasqConfKeys.DnssecDebug, status, s => Config(s)?.DnssecDebug, s => Sources(s)?.DnssecDebug, null); list.AddDescriptor(registry, DnsmasqConfKeys.DnssecDebug, status, s => Config(s)?.DnssecDebug, s => Sources(s)?.DnssecDebug, null);
list.AddDescriptor(registry, DnsmasqConfKeys.ProxyDnssec, status, s => Config(s)?.ProxyDnssec, s => Sources(s)?.ProxyDnssec, null); list.AddDescriptor(registry, DnsmasqConfKeys.ProxyDnssec, status, s => Config(s)?.ProxyDnssec, s => Sources(s)?.ProxyDnssec, null);
list.AddDescriptor(registry, DnsmasqConfKeys.AddCpeId, status, s => Config(s)?.AddCpeId, s => Sources(s)?.AddCpeId, null);
list.AddDescriptor(registry, DnsmasqConfKeys.DnssecTimestamp, status, s => Config(s)?.DnssecTimestamp, s => Sources(s)?.DnssecTimestamp, null); list.AddDescriptor(registry, DnsmasqConfKeys.DnssecTimestamp, status, s => Config(s)?.DnssecTimestamp, s => Sources(s)?.DnssecTimestamp, null);
list.AddDescriptor(registry, DnsmasqConfKeys.DnssecLimits, status, s => Config(s)?.DnssecLimits, s => Sources(s)?.DnssecLimits, null); list.AddDescriptor(registry, DnsmasqConfKeys.DnssecLimits, status, s => Config(s)?.DnssecLimits, s => Sources(s)?.DnssecLimits, null);
list.AddDescriptor(registry, DnsmasqConfKeys.TrustAnchor, status, null, null, Items(ec => ec?.TrustAnchorValues, src => src?.TrustAnchorValues)); list.AddDescriptor(registry, DnsmasqConfKeys.TrustAnchor, status, null, null, Items(ec => ec?.TrustAnchorValues, src => src?.TrustAnchorValues));
@@ -196,8 +200,6 @@ public sealed class EffectiveConfigFieldBuilder : IEffectiveConfigFieldBuilder
list.AddDescriptor(registry, DnsmasqConfKeys.CacheSize, status, s => Config(s)?.CacheSize, s => Sources(s)?.CacheSize, null); 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.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.NoNegcache, status, s => Config(s)?.NoNegcache, s => Sources(s)?.NoNegcache, null);
list.AddDescriptor(registry, DnsmasqConfKeys.Dumpfile, status, s => Config(s)?.DumpfilePath, s => Sources(s)?.DumpfilePath, null);
list.AddDescriptor(registry, DnsmasqConfKeys.Dumpmask, status, s => Config(s)?.Dumpmask, s => Sources(s)?.Dumpmask, null);
list.AddDescriptor(registry, DnsmasqConfKeys.UseStaleCache, status, s => Config(s)?.UseStaleCache, s => Sources(s)?.UseStaleCache, null); list.AddDescriptor(registry, DnsmasqConfKeys.UseStaleCache, status, s => Config(s)?.UseStaleCache, s => Sources(s)?.UseStaleCache, null);
list.AddDescriptor(registry, DnsmasqConfKeys.AddMac, status, s => Config(s)?.AddMac, s => Sources(s)?.AddMac, null); list.AddDescriptor(registry, DnsmasqConfKeys.AddMac, status, s => Config(s)?.AddMac, s => Sources(s)?.AddMac, null);
list.AddDescriptor(registry, DnsmasqConfKeys.StripMac, status, s => Config(s)?.StripMac, s => Sources(s)?.StripMac, null); list.AddDescriptor(registry, DnsmasqConfKeys.StripMac, status, s => Config(s)?.StripMac, s => Sources(s)?.StripMac, null);
@@ -220,16 +222,14 @@ public sealed class EffectiveConfigFieldBuilder : IEffectiveConfigFieldBuilder
list.AddDescriptor(registry, DnsmasqConfKeys.ListenAddress, status, null, null, Items(ec => ec?.ListenAddresses, src => src?.ListenAddresses)); 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.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.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.Dumpfile, status, s => Config(s)?.DumpfilePath, s => Sources(s)?.DumpfilePath, null);
list.AddDescriptor(registry, DnsmasqConfKeys.NoDhcpv4Interface, status, null, null, Items(ec => ec?.NoDhcpv4InterfaceValues, src => src?.NoDhcpv4InterfaceValues)); list.AddDescriptor(registry, DnsmasqConfKeys.Dumpmask, status, s => Config(s)?.Dumpmask, s => Sources(s)?.Dumpmask, null);
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.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.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.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.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.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.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.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.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.NoDaemon, status, s => Config(s)?.NoDaemon, s => Sources(s)?.NoDaemon, null);
@@ -29,6 +29,8 @@ public static class EffectiveConfigSections
DnsmasqConfKeys.NoHosts, DnsmasqConfKeys.NoHosts,
DnsmasqConfKeys.AddnHosts, DnsmasqConfKeys.AddnHosts,
DnsmasqConfKeys.Hostsdir, DnsmasqConfKeys.Hostsdir,
DnsmasqConfKeys.ExpandHosts,
DnsmasqConfKeys.Domain,
]), ]),
new SectionDef(SectionResolver, "Resolver / DNS", [ new SectionDef(SectionResolver, "Resolver / DNS", [
DnsmasqConfKeys.Server, DnsmasqConfKeys.Server,
@@ -52,7 +54,6 @@ public static class EffectiveConfigSections
DnsmasqConfKeys.StopDnsRebind, DnsmasqConfKeys.StopDnsRebind,
DnsmasqConfKeys.RebindLocalhostOk, DnsmasqConfKeys.RebindLocalhostOk,
DnsmasqConfKeys.ClearOnReload, DnsmasqConfKeys.ClearOnReload,
DnsmasqConfKeys.ExpandHosts,
DnsmasqConfKeys.BogusPriv, DnsmasqConfKeys.BogusPriv,
DnsmasqConfKeys.RebindDomainOk, DnsmasqConfKeys.RebindDomainOk,
DnsmasqConfKeys.BogusNxdomain, DnsmasqConfKeys.BogusNxdomain,
@@ -70,10 +71,10 @@ public static class EffectiveConfigSections
DnsmasqConfKeys.NoRoundRobin, DnsmasqConfKeys.NoRoundRobin,
DnsmasqConfKeys.DnsForwardMax, DnsmasqConfKeys.DnsForwardMax,
DnsmasqConfKeys.ConnmarkAllowlist, DnsmasqConfKeys.ConnmarkAllowlist,
DnsmasqConfKeys.AddCpeId,
DnsmasqConfKeys.Do0x20Encode, DnsmasqConfKeys.Do0x20Encode,
]), ]),
new SectionDef(SectionDnsRecords, "DNS records", [ new SectionDef(SectionDnsRecords, "DNS records", [
DnsmasqConfKeys.Domain,
DnsmasqConfKeys.Cname, DnsmasqConfKeys.Cname,
DnsmasqConfKeys.MxHost, DnsmasqConfKeys.MxHost,
DnsmasqConfKeys.Srv, DnsmasqConfKeys.Srv,
@@ -95,6 +96,10 @@ public static class EffectiveConfigSections
DnsmasqConfKeys.Selfmx, DnsmasqConfKeys.Selfmx,
]), ]),
new SectionDef(SectionDhcp, "DHCP", [ new SectionDef(SectionDhcp, "DHCP", [
DnsmasqConfKeys.NoDhcpInterface,
DnsmasqConfKeys.NoDhcpv4Interface,
DnsmasqConfKeys.NoDhcpv6Interface,
DnsmasqConfKeys.EnableRa,
DnsmasqConfKeys.DhcpAuthoritative, DnsmasqConfKeys.DhcpAuthoritative,
DnsmasqConfKeys.DhcpRapidCommit, DnsmasqConfKeys.DhcpRapidCommit,
DnsmasqConfKeys.LeasefileRo, DnsmasqConfKeys.LeasefileRo,
@@ -161,7 +166,6 @@ public static class EffectiveConfigSections
DnsmasqConfKeys.DnssecNoTimecheck, DnsmasqConfKeys.DnssecNoTimecheck,
DnsmasqConfKeys.DnssecDebug, DnsmasqConfKeys.DnssecDebug,
DnsmasqConfKeys.ProxyDnssec, DnsmasqConfKeys.ProxyDnssec,
DnsmasqConfKeys.AddCpeId,
DnsmasqConfKeys.DnssecTimestamp, DnsmasqConfKeys.DnssecTimestamp,
DnsmasqConfKeys.DnssecLimits, DnsmasqConfKeys.DnssecLimits,
DnsmasqConfKeys.TrustAnchor, DnsmasqConfKeys.TrustAnchor,
@@ -175,8 +179,6 @@ public static class EffectiveConfigSections
DnsmasqConfKeys.MaxTtl, DnsmasqConfKeys.MaxTtl,
DnsmasqConfKeys.MaxCacheTtl, DnsmasqConfKeys.MaxCacheTtl,
DnsmasqConfKeys.MinCacheTtl, DnsmasqConfKeys.MinCacheTtl,
DnsmasqConfKeys.Dumpfile,
DnsmasqConfKeys.Dumpmask,
DnsmasqConfKeys.UseStaleCache, DnsmasqConfKeys.UseStaleCache,
DnsmasqConfKeys.AddMac, DnsmasqConfKeys.AddMac,
DnsmasqConfKeys.StripMac, DnsmasqConfKeys.StripMac,
@@ -195,16 +197,14 @@ public static class EffectiveConfigSections
DnsmasqConfKeys.ListenAddress, DnsmasqConfKeys.ListenAddress,
DnsmasqConfKeys.ExceptInterface, DnsmasqConfKeys.ExceptInterface,
DnsmasqConfKeys.AuthServer, DnsmasqConfKeys.AuthServer,
DnsmasqConfKeys.NoDhcpInterface, DnsmasqConfKeys.Dumpfile,
DnsmasqConfKeys.NoDhcpv4Interface, DnsmasqConfKeys.Dumpmask,
DnsmasqConfKeys.NoDhcpv6Interface,
DnsmasqConfKeys.PidFile, DnsmasqConfKeys.PidFile,
DnsmasqConfKeys.User, DnsmasqConfKeys.User,
DnsmasqConfKeys.Group, DnsmasqConfKeys.Group,
DnsmasqConfKeys.LogFacility, DnsmasqConfKeys.LogFacility,
DnsmasqConfKeys.EnableDbus, DnsmasqConfKeys.EnableDbus,
DnsmasqConfKeys.EnableUbus, DnsmasqConfKeys.EnableUbus,
DnsmasqConfKeys.EnableRa,
DnsmasqConfKeys.LogDhcp, DnsmasqConfKeys.LogDhcp,
DnsmasqConfKeys.KeepInForeground, DnsmasqConfKeys.KeepInForeground,
DnsmasqConfKeys.NoDaemon, DnsmasqConfKeys.NoDaemon,
@@ -38,9 +38,8 @@ public static class EffectiveConfigViews
DnsmasqConfKeys.NoHosts, DnsmasqConfKeys.NoHosts,
DnsmasqConfKeys.AddnHosts, DnsmasqConfKeys.AddnHosts,
DnsmasqConfKeys.Hostsdir, DnsmasqConfKeys.Hostsdir,
]),
new(EffectiveConfigSections.SectionResolver, "Resolver / DNS", [
DnsmasqConfKeys.ExpandHosts, DnsmasqConfKeys.ExpandHosts,
DnsmasqConfKeys.Domain,
]), ]),
]; ];
@@ -71,10 +71,9 @@ public class EffectiveConfigRenderFragmentRegistry : IEffectiveConfigRenderFragm
private void RegisterFlagDisplays() private void RegisterFlagDisplays()
{ {
RegisterFlags(EffectiveConfigSections.SectionHosts, DnsmasqConfKeys.NoHosts); RegisterFlags(EffectiveConfigSections.SectionHosts, DnsmasqConfKeys.NoHosts, DnsmasqConfKeys.ExpandHosts);
RegisterFlags( RegisterFlags(
EffectiveConfigSections.SectionResolver, EffectiveConfigSections.SectionResolver,
DnsmasqConfKeys.ExpandHosts,
DnsmasqConfKeys.BogusPriv, DnsmasqConfKeys.BogusPriv,
DnsmasqConfKeys.StrictOrder, DnsmasqConfKeys.StrictOrder,
DnsmasqConfKeys.AllServers, DnsmasqConfKeys.AllServers,
@@ -91,6 +90,7 @@ public class EffectiveConfigRenderFragmentRegistry : IEffectiveConfigRenderFragm
DnsmasqConfKeys.NoRoundRobin); DnsmasqConfKeys.NoRoundRobin);
RegisterFlags( RegisterFlags(
EffectiveConfigSections.SectionDhcp, EffectiveConfigSections.SectionDhcp,
DnsmasqConfKeys.EnableRa,
DnsmasqConfKeys.DhcpAuthoritative, DnsmasqConfKeys.DhcpAuthoritative,
DnsmasqConfKeys.ReadEthers, DnsmasqConfKeys.ReadEthers,
DnsmasqConfKeys.DhcpRapidCommit, DnsmasqConfKeys.DhcpRapidCommit,
@@ -125,7 +125,6 @@ public class EffectiveConfigRenderFragmentRegistry : IEffectiveConfigRenderFragm
DnsmasqConfKeys.BindInterfaces, DnsmasqConfKeys.BindInterfaces,
DnsmasqConfKeys.BindDynamic, DnsmasqConfKeys.BindDynamic,
DnsmasqConfKeys.LogDebug, DnsmasqConfKeys.LogDebug,
DnsmasqConfKeys.EnableRa,
DnsmasqConfKeys.LogDhcp, DnsmasqConfKeys.LogDhcp,
DnsmasqConfKeys.KeepInForeground, DnsmasqConfKeys.KeepInForeground,
DnsmasqConfKeys.NoDaemon, DnsmasqConfKeys.NoDaemon,
@@ -177,11 +176,9 @@ public class EffectiveConfigRenderFragmentRegistry : IEffectiveConfigRenderFragm
RegisterSemanticSingles( RegisterSemanticSingles(
EffectiveConfigSections.SectionDnssec, EffectiveConfigSections.SectionDnssec,
DnsmasqConfKeys.DnssecTimestamp); DnsmasqConfKeys.DnssecTimestamp);
RegisterSemanticSingles(
EffectiveConfigSections.SectionCache,
DnsmasqConfKeys.Dumpfile);
RegisterSemanticSingles( RegisterSemanticSingles(
EffectiveConfigSections.SectionProcess, EffectiveConfigSections.SectionProcess,
DnsmasqConfKeys.Dumpfile,
DnsmasqConfKeys.PidFile); DnsmasqConfKeys.PidFile);
} }
@@ -193,7 +190,10 @@ public class EffectiveConfigRenderFragmentRegistry : IEffectiveConfigRenderFragm
behavior: new DistinctMultiValueEditBehavior()); behavior: new DistinctMultiValueEditBehavior());
RegisterSemanticMultiDescriptor(EffectiveConfigSections.SectionProcess, DnsmasqConfKeys.ListenAddress); RegisterSemanticMultiDescriptor(EffectiveConfigSections.SectionProcess, DnsmasqConfKeys.ListenAddress);
RegisterSemanticMultis(EffectiveConfigSections.SectionHosts, DnsmasqConfKeys.AddnHosts); RegisterSemanticMultis(
EffectiveConfigSections.SectionHosts,
DnsmasqConfKeys.AddnHosts,
DnsmasqConfKeys.Domain);
RegisterSemanticMultis( RegisterSemanticMultis(
EffectiveConfigSections.SectionResolver, EffectiveConfigSections.SectionResolver,
DnsmasqConfKeys.Local, DnsmasqConfKeys.Local,
@@ -209,6 +209,9 @@ public class EffectiveConfigRenderFragmentRegistry : IEffectiveConfigRenderFragm
DnsmasqConfKeys.ConnmarkAllowlist); DnsmasqConfKeys.ConnmarkAllowlist);
RegisterSemanticMultis( RegisterSemanticMultis(
EffectiveConfigSections.SectionDhcp, EffectiveConfigSections.SectionDhcp,
DnsmasqConfKeys.NoDhcpInterface,
DnsmasqConfKeys.NoDhcpv4Interface,
DnsmasqConfKeys.NoDhcpv6Interface,
DnsmasqConfKeys.DhcpRange, DnsmasqConfKeys.DhcpRange,
DnsmasqConfKeys.DhcpHost, DnsmasqConfKeys.DhcpHost,
DnsmasqConfKeys.DhcpOption, DnsmasqConfKeys.DhcpOption,
@@ -239,7 +242,6 @@ public class EffectiveConfigRenderFragmentRegistry : IEffectiveConfigRenderFragm
DnsmasqConfKeys.FilterRr); DnsmasqConfKeys.FilterRr);
RegisterSemanticMultis( RegisterSemanticMultis(
EffectiveConfigSections.SectionDnsRecords, EffectiveConfigSections.SectionDnsRecords,
DnsmasqConfKeys.Domain,
DnsmasqConfKeys.Cname, DnsmasqConfKeys.Cname,
DnsmasqConfKeys.MxHost, DnsmasqConfKeys.MxHost,
DnsmasqConfKeys.PtrRecord, DnsmasqConfKeys.PtrRecord,
@@ -273,10 +275,7 @@ public class EffectiveConfigRenderFragmentRegistry : IEffectiveConfigRenderFragm
EffectiveConfigSections.SectionProcess, EffectiveConfigSections.SectionProcess,
DnsmasqConfKeys.Interface, DnsmasqConfKeys.Interface,
DnsmasqConfKeys.ExceptInterface, DnsmasqConfKeys.ExceptInterface,
DnsmasqConfKeys.AuthServer, DnsmasqConfKeys.AuthServer);
DnsmasqConfKeys.NoDhcpInterface,
DnsmasqConfKeys.NoDhcpv4Interface,
DnsmasqConfKeys.NoDhcpv6Interface);
} }
private void RegisterComponents(string sectionId, Type componentType, params string[] optionNames) private void RegisterComponents(string sectionId, Type componentType, params string[] optionNames)
@@ -39,11 +39,11 @@ public sealed record HostsPageState(
: "Hosts editing is unavailable: managed hosts path is not configured. Set Dnsmasq:MainConfigPath (and optionally Dnsmasq:ManagedHostsFileName) so the app can create and edit the managed hosts file.", : "Hosts editing is unavailable: managed hosts path is not configured. Set Dnsmasq:MainConfigPath (and optionally Dnsmasq:ManagedHostsFileName) so the app can create and edit the managed hosts file.",
SystemHostsActive: systemHostsActive, SystemHostsActive: systemHostsActive,
SystemHostsInactiveReason: noHosts SystemHostsInactiveReason: noHosts
? "System hosts is ignored because no-hosts is enabled in dnsmasq config." ? "Dnsmasq is ignoring system hosts (no-hosts is enabled), so system hosts rows are hidden from this table."
: string.IsNullOrEmpty(status.SystemHostsPath) : string.IsNullOrEmpty(status.SystemHostsPath)
? "System hosts path is not configured." ? "System hosts path is not configured; system hosts rows are hidden from this table."
: !status.SystemHostsPathExists : !status.SystemHostsPathExists
? $"System hosts file not found: {status.SystemHostsPath}" ? $"System hosts file not found ({status.SystemHostsPath}); those rows are hidden from this table."
: null, : null,
ExpandHostsEnabled: expandHosts, ExpandHostsEnabled: expandHosts,
ExpansionDomain: expandHosts ? domain : null); ExpansionDomain: expandHosts ? domain : null);
@@ -158,3 +158,15 @@ export function disposeTableScrollCue() {
if (right) right.remove(); if (right) right.remove();
}); });
} }
/**
* Returns true if the active element is not inside `container` (e.g. focus left a table row).
* Use after a short delay following focusout so document.activeElement has updated.
* @param {HTMLElement | null} container
*/
export function isFocusOutsideElement(container) {
if (!container) return true;
const active = document.activeElement;
if (!active) return true;
return !container.contains(active);
}