mirror of
https://github.com/alexhopeoconnor/dnsmasq-webui.git
synced 2026-10-03 22:12:00 +10:00
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:
@@ -8,6 +8,7 @@
|
||||
@using DnsmasqWebUI.Infrastructure.Services.EffectiveConfig.Abstractions
|
||||
@using DnsmasqWebUI.Infrastructure.Services.Dnsmasq.Hosts.Abstractions
|
||||
@using DnsmasqWebUI.Components.EffectiveConfig
|
||||
@using Microsoft.AspNetCore.Components.Web
|
||||
@using Microsoft.JSInterop
|
||||
@implements IDisposable
|
||||
@inject IStatusClient StatusClient
|
||||
@@ -37,13 +38,6 @@ else
|
||||
<EffectiveConfigSection Status="_status" Context="EffectiveConfigContext.Hosts" ShowSearchBox="true" OnSaveCompleted="RefreshStatusAsync" />
|
||||
</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)
|
||||
{
|
||||
<div class="alert alert-warning">
|
||||
@@ -97,14 +91,13 @@ else
|
||||
<th scope="col">Address</th>
|
||||
<th scope="col">Names</th>
|
||||
<th scope="col">Effective names</th>
|
||||
<th scope="col">Status</th>
|
||||
</tr>
|
||||
</thead>
|
||||
@foreach (var group in VisibleGroups)
|
||||
{
|
||||
<tbody @key="@group.Key">
|
||||
<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__title">
|
||||
<span>@group.Title</span>
|
||||
@@ -128,32 +121,25 @@ else
|
||||
{
|
||||
@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>
|
||||
<input class="form-control form-control-sm" @bind="_editAddress" />
|
||||
<input class="form-control form-control-sm" @bind="_editAddress" autofocus />
|
||||
</td>
|
||||
<td>
|
||||
<input class="form-control form-control-sm" placeholder="Comma-separated names" @bind="_editNames" />
|
||||
</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>
|
||||
}
|
||||
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>@string.Join(", ", row.Names)</td>
|
||||
<td>
|
||||
@@ -166,30 +152,6 @@ else
|
||||
@string.Join(", ", row.EffectiveNames)
|
||||
}
|
||||
</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>
|
||||
}
|
||||
}
|
||||
@@ -200,17 +162,25 @@ else
|
||||
</div>
|
||||
}
|
||||
|
||||
</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 mt-3 mb-0">
|
||||
<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>
|
||||
}
|
||||
|
||||
</section>
|
||||
}
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
|
||||
@@ -227,10 +197,31 @@ else
|
||||
private string? _editingRowId;
|
||||
private string _editAddress = "";
|
||||
private string _editNames = "";
|
||||
private ElementReference _editingRowRef;
|
||||
private IJSObjectReference? _hostsTableScrollCueModule;
|
||||
|
||||
private IReadOnlyList<HostsPageGroup> VisibleGroups =>
|
||||
HostsProjection.BuildGroups(_unifiedRows, BuildQuery());
|
||||
// Memoize: markup reads VisibleGroups.Count then foreach — avoid building twice per render.
|
||||
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()
|
||||
{
|
||||
@@ -247,13 +238,15 @@ else
|
||||
private static string RowClass(HostsPageRow row)
|
||||
{
|
||||
var parts = new List<string> { "hosts-row" };
|
||||
if (row.IsComment)
|
||||
parts.Add("table-secondary");
|
||||
if (row.IsEditable)
|
||||
parts.Add("hosts-row--editable");
|
||||
if (!row.IsActive)
|
||||
parts.Add("hosts-row--inactive");
|
||||
return string.Join(" ", parts);
|
||||
}
|
||||
|
||||
private void OnRowDoubleClick(HostsPageRow row) => BeginEdit(row);
|
||||
|
||||
private bool IsEditingRow(HostsPageRow row) =>
|
||||
!string.IsNullOrEmpty(_editingRowId) && string.Equals(_editingRowId, row.Id, StringComparison.Ordinal);
|
||||
|
||||
@@ -276,7 +269,30 @@ else
|
||||
_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)
|
||||
return;
|
||||
|
||||
@@ -139,6 +139,14 @@
|
||||
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 {
|
||||
font-size: 0.93em;
|
||||
color: #0f172a;
|
||||
@@ -151,66 +159,6 @@
|
||||
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) {
|
||||
.hosts-controls-bar {
|
||||
padding: 0.75rem;
|
||||
|
||||
+5
-37
@@ -31,13 +31,15 @@ public sealed class HostsPageProjectionService : IHostsPageProjectionService
|
||||
if (query.ActiveFilter is { } 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();
|
||||
if (list.Count == 0)
|
||||
return Array.Empty<HostsPageGroup>();
|
||||
|
||||
return query.Grouping == HostsGroupingMode.Activity
|
||||
? BuildActivityGroups(list, query)
|
||||
: BuildSourceGroups(list, query);
|
||||
return BuildSourceGroups(list, query);
|
||||
}
|
||||
|
||||
private static IReadOnlyList<HostsPageGroup> BuildSourceGroups(List<HostsPageRow> rows, HostsPageQueryState query)
|
||||
@@ -76,40 +78,6 @@ public sealed class HostsPageProjectionService : IHostsPageProjectionService
|
||||
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)
|
||||
{
|
||||
IEnumerable<HostsPageRow> ordered = sort switch
|
||||
|
||||
+3
-3
@@ -150,7 +150,7 @@ public static class DnsmasqOptionTooltips
|
||||
[DnsmasqConfKeys.DnssecCheckUnsigned] = "Treat unsigned zones as bogus.",
|
||||
[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.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.DnssecLimits] = "DNSSEC validation limits (e.g. max signed zones).",
|
||||
[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.MaxCacheTtl] = "Maximum TTL for positive cache entries.",
|
||||
[DnsmasqConfKeys.MinCacheTtl] = "Minimum TTL for positive cache entries.",
|
||||
[DnsmasqConfKeys.Dumpfile] = "Dump cache to this file (for debugging).",
|
||||
[DnsmasqConfKeys.Dumpmask] = "Mask for cache dump (which data to include).",
|
||||
[DnsmasqConfKeys.Dumpfile] = "Pcap file path for packet capture (debugging).",
|
||||
[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.AddMac] = "Add MAC address to DNS queries. Off, On (key-only), or custom value.",
|
||||
[DnsmasqConfKeys.StripMac] = "Strip MAC address information from forwarded DNS queries.",
|
||||
|
||||
+10
-10
@@ -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.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);
|
||||
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
|
||||
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.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));
|
||||
@@ -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.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.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);
|
||||
|
||||
// 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.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));
|
||||
@@ -121,11 +121,16 @@ public sealed class EffectiveConfigFieldBuilder : IEffectiveConfigFieldBuilder
|
||||
list.AddDescriptor(registry, DnsmasqConfKeys.Selfmx, status, s => Config(s)?.Selfmx, s => Sources(s)?.Selfmx, null);
|
||||
|
||||
// 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.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.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.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.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);
|
||||
@@ -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.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.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.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));
|
||||
@@ -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.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.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.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);
|
||||
@@ -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.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.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.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);
|
||||
|
||||
+9
-9
@@ -29,6 +29,8 @@ public static class EffectiveConfigSections
|
||||
DnsmasqConfKeys.NoHosts,
|
||||
DnsmasqConfKeys.AddnHosts,
|
||||
DnsmasqConfKeys.Hostsdir,
|
||||
DnsmasqConfKeys.ExpandHosts,
|
||||
DnsmasqConfKeys.Domain,
|
||||
]),
|
||||
new SectionDef(SectionResolver, "Resolver / DNS", [
|
||||
DnsmasqConfKeys.Server,
|
||||
@@ -52,7 +54,6 @@ public static class EffectiveConfigSections
|
||||
DnsmasqConfKeys.StopDnsRebind,
|
||||
DnsmasqConfKeys.RebindLocalhostOk,
|
||||
DnsmasqConfKeys.ClearOnReload,
|
||||
DnsmasqConfKeys.ExpandHosts,
|
||||
DnsmasqConfKeys.BogusPriv,
|
||||
DnsmasqConfKeys.RebindDomainOk,
|
||||
DnsmasqConfKeys.BogusNxdomain,
|
||||
@@ -70,10 +71,10 @@ public static class EffectiveConfigSections
|
||||
DnsmasqConfKeys.NoRoundRobin,
|
||||
DnsmasqConfKeys.DnsForwardMax,
|
||||
DnsmasqConfKeys.ConnmarkAllowlist,
|
||||
DnsmasqConfKeys.AddCpeId,
|
||||
DnsmasqConfKeys.Do0x20Encode,
|
||||
]),
|
||||
new SectionDef(SectionDnsRecords, "DNS records", [
|
||||
DnsmasqConfKeys.Domain,
|
||||
DnsmasqConfKeys.Cname,
|
||||
DnsmasqConfKeys.MxHost,
|
||||
DnsmasqConfKeys.Srv,
|
||||
@@ -95,6 +96,10 @@ public static class EffectiveConfigSections
|
||||
DnsmasqConfKeys.Selfmx,
|
||||
]),
|
||||
new SectionDef(SectionDhcp, "DHCP", [
|
||||
DnsmasqConfKeys.NoDhcpInterface,
|
||||
DnsmasqConfKeys.NoDhcpv4Interface,
|
||||
DnsmasqConfKeys.NoDhcpv6Interface,
|
||||
DnsmasqConfKeys.EnableRa,
|
||||
DnsmasqConfKeys.DhcpAuthoritative,
|
||||
DnsmasqConfKeys.DhcpRapidCommit,
|
||||
DnsmasqConfKeys.LeasefileRo,
|
||||
@@ -161,7 +166,6 @@ public static class EffectiveConfigSections
|
||||
DnsmasqConfKeys.DnssecNoTimecheck,
|
||||
DnsmasqConfKeys.DnssecDebug,
|
||||
DnsmasqConfKeys.ProxyDnssec,
|
||||
DnsmasqConfKeys.AddCpeId,
|
||||
DnsmasqConfKeys.DnssecTimestamp,
|
||||
DnsmasqConfKeys.DnssecLimits,
|
||||
DnsmasqConfKeys.TrustAnchor,
|
||||
@@ -175,8 +179,6 @@ public static class EffectiveConfigSections
|
||||
DnsmasqConfKeys.MaxTtl,
|
||||
DnsmasqConfKeys.MaxCacheTtl,
|
||||
DnsmasqConfKeys.MinCacheTtl,
|
||||
DnsmasqConfKeys.Dumpfile,
|
||||
DnsmasqConfKeys.Dumpmask,
|
||||
DnsmasqConfKeys.UseStaleCache,
|
||||
DnsmasqConfKeys.AddMac,
|
||||
DnsmasqConfKeys.StripMac,
|
||||
@@ -195,16 +197,14 @@ public static class EffectiveConfigSections
|
||||
DnsmasqConfKeys.ListenAddress,
|
||||
DnsmasqConfKeys.ExceptInterface,
|
||||
DnsmasqConfKeys.AuthServer,
|
||||
DnsmasqConfKeys.NoDhcpInterface,
|
||||
DnsmasqConfKeys.NoDhcpv4Interface,
|
||||
DnsmasqConfKeys.NoDhcpv6Interface,
|
||||
DnsmasqConfKeys.Dumpfile,
|
||||
DnsmasqConfKeys.Dumpmask,
|
||||
DnsmasqConfKeys.PidFile,
|
||||
DnsmasqConfKeys.User,
|
||||
DnsmasqConfKeys.Group,
|
||||
DnsmasqConfKeys.LogFacility,
|
||||
DnsmasqConfKeys.EnableDbus,
|
||||
DnsmasqConfKeys.EnableUbus,
|
||||
DnsmasqConfKeys.EnableRa,
|
||||
DnsmasqConfKeys.LogDhcp,
|
||||
DnsmasqConfKeys.KeepInForeground,
|
||||
DnsmasqConfKeys.NoDaemon,
|
||||
|
||||
+1
-2
@@ -38,9 +38,8 @@ public static class EffectiveConfigViews
|
||||
DnsmasqConfKeys.NoHosts,
|
||||
DnsmasqConfKeys.AddnHosts,
|
||||
DnsmasqConfKeys.Hostsdir,
|
||||
]),
|
||||
new(EffectiveConfigSections.SectionResolver, "Resolver / DNS", [
|
||||
DnsmasqConfKeys.ExpandHosts,
|
||||
DnsmasqConfKeys.Domain,
|
||||
]),
|
||||
];
|
||||
|
||||
|
||||
+11
-12
@@ -71,10 +71,9 @@ public class EffectiveConfigRenderFragmentRegistry : IEffectiveConfigRenderFragm
|
||||
|
||||
private void RegisterFlagDisplays()
|
||||
{
|
||||
RegisterFlags(EffectiveConfigSections.SectionHosts, DnsmasqConfKeys.NoHosts);
|
||||
RegisterFlags(EffectiveConfigSections.SectionHosts, DnsmasqConfKeys.NoHosts, DnsmasqConfKeys.ExpandHosts);
|
||||
RegisterFlags(
|
||||
EffectiveConfigSections.SectionResolver,
|
||||
DnsmasqConfKeys.ExpandHosts,
|
||||
DnsmasqConfKeys.BogusPriv,
|
||||
DnsmasqConfKeys.StrictOrder,
|
||||
DnsmasqConfKeys.AllServers,
|
||||
@@ -91,6 +90,7 @@ public class EffectiveConfigRenderFragmentRegistry : IEffectiveConfigRenderFragm
|
||||
DnsmasqConfKeys.NoRoundRobin);
|
||||
RegisterFlags(
|
||||
EffectiveConfigSections.SectionDhcp,
|
||||
DnsmasqConfKeys.EnableRa,
|
||||
DnsmasqConfKeys.DhcpAuthoritative,
|
||||
DnsmasqConfKeys.ReadEthers,
|
||||
DnsmasqConfKeys.DhcpRapidCommit,
|
||||
@@ -125,7 +125,6 @@ public class EffectiveConfigRenderFragmentRegistry : IEffectiveConfigRenderFragm
|
||||
DnsmasqConfKeys.BindInterfaces,
|
||||
DnsmasqConfKeys.BindDynamic,
|
||||
DnsmasqConfKeys.LogDebug,
|
||||
DnsmasqConfKeys.EnableRa,
|
||||
DnsmasqConfKeys.LogDhcp,
|
||||
DnsmasqConfKeys.KeepInForeground,
|
||||
DnsmasqConfKeys.NoDaemon,
|
||||
@@ -177,11 +176,9 @@ public class EffectiveConfigRenderFragmentRegistry : IEffectiveConfigRenderFragm
|
||||
RegisterSemanticSingles(
|
||||
EffectiveConfigSections.SectionDnssec,
|
||||
DnsmasqConfKeys.DnssecTimestamp);
|
||||
RegisterSemanticSingles(
|
||||
EffectiveConfigSections.SectionCache,
|
||||
DnsmasqConfKeys.Dumpfile);
|
||||
RegisterSemanticSingles(
|
||||
EffectiveConfigSections.SectionProcess,
|
||||
DnsmasqConfKeys.Dumpfile,
|
||||
DnsmasqConfKeys.PidFile);
|
||||
}
|
||||
|
||||
@@ -193,7 +190,10 @@ public class EffectiveConfigRenderFragmentRegistry : IEffectiveConfigRenderFragm
|
||||
behavior: new DistinctMultiValueEditBehavior());
|
||||
RegisterSemanticMultiDescriptor(EffectiveConfigSections.SectionProcess, DnsmasqConfKeys.ListenAddress);
|
||||
|
||||
RegisterSemanticMultis(EffectiveConfigSections.SectionHosts, DnsmasqConfKeys.AddnHosts);
|
||||
RegisterSemanticMultis(
|
||||
EffectiveConfigSections.SectionHosts,
|
||||
DnsmasqConfKeys.AddnHosts,
|
||||
DnsmasqConfKeys.Domain);
|
||||
RegisterSemanticMultis(
|
||||
EffectiveConfigSections.SectionResolver,
|
||||
DnsmasqConfKeys.Local,
|
||||
@@ -209,6 +209,9 @@ public class EffectiveConfigRenderFragmentRegistry : IEffectiveConfigRenderFragm
|
||||
DnsmasqConfKeys.ConnmarkAllowlist);
|
||||
RegisterSemanticMultis(
|
||||
EffectiveConfigSections.SectionDhcp,
|
||||
DnsmasqConfKeys.NoDhcpInterface,
|
||||
DnsmasqConfKeys.NoDhcpv4Interface,
|
||||
DnsmasqConfKeys.NoDhcpv6Interface,
|
||||
DnsmasqConfKeys.DhcpRange,
|
||||
DnsmasqConfKeys.DhcpHost,
|
||||
DnsmasqConfKeys.DhcpOption,
|
||||
@@ -239,7 +242,6 @@ public class EffectiveConfigRenderFragmentRegistry : IEffectiveConfigRenderFragm
|
||||
DnsmasqConfKeys.FilterRr);
|
||||
RegisterSemanticMultis(
|
||||
EffectiveConfigSections.SectionDnsRecords,
|
||||
DnsmasqConfKeys.Domain,
|
||||
DnsmasqConfKeys.Cname,
|
||||
DnsmasqConfKeys.MxHost,
|
||||
DnsmasqConfKeys.PtrRecord,
|
||||
@@ -273,10 +275,7 @@ public class EffectiveConfigRenderFragmentRegistry : IEffectiveConfigRenderFragm
|
||||
EffectiveConfigSections.SectionProcess,
|
||||
DnsmasqConfKeys.Interface,
|
||||
DnsmasqConfKeys.ExceptInterface,
|
||||
DnsmasqConfKeys.AuthServer,
|
||||
DnsmasqConfKeys.NoDhcpInterface,
|
||||
DnsmasqConfKeys.NoDhcpv4Interface,
|
||||
DnsmasqConfKeys.NoDhcpv6Interface);
|
||||
DnsmasqConfKeys.AuthServer);
|
||||
}
|
||||
|
||||
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.",
|
||||
SystemHostsActive: systemHostsActive,
|
||||
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)
|
||||
? "System hosts path is not configured."
|
||||
? "System hosts path is not configured; system hosts rows are hidden from this table."
|
||||
: !status.SystemHostsPathExists
|
||||
? $"System hosts file not found: {status.SystemHostsPath}"
|
||||
? $"System hosts file not found ({status.SystemHostsPath}); those rows are hidden from this table."
|
||||
: null,
|
||||
ExpandHostsEnabled: expandHosts,
|
||||
ExpansionDomain: expandHosts ? domain : null);
|
||||
|
||||
@@ -158,3 +158,15 @@ export function disposeTableScrollCue() {
|
||||
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);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user