mirror of
https://github.com/alexhopeoconnor/dnsmasq-webui.git
synced 2026-10-04 04:18:13 +10:00
Add DNS Records and Filters pages; unify effective config layout
- Add EffectiveConfigContext.DnsRecords and .Filters; map in EffectiveConfigViews - Add DnsRecords.razor and Filters.razor with context-scoped effective config - Add nav links for DNS records and Filters (icons: diagram-3-fill, funnel-fill) - Put effective config block at top of Hosts, DHCP, DNS Records, Filters (no config file list on sub-pages) - Hosts view: add expand-hosts to allow-list. Filters view: add domain-needed, bogus-priv, filterwin2k, no-round-robin - Page titles: Hosts Configuration, DHCP Configuration, DNS Records Configuration, Filters Configuration - Remove 'Config affecting...' subheadings and page intro text; add .page-intro and .page-chip styles
This commit is contained in:
@@ -39,6 +39,16 @@
|
|||||||
<i class="bi bi-hdd-network nav-icon" aria-hidden="true"></i> DHCP
|
<i class="bi bi-hdd-network nav-icon" aria-hidden="true"></i> DHCP
|
||||||
</NavLink>
|
</NavLink>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="nav-item px-3">
|
||||||
|
<NavLink class="nav-link" href="dns-records">
|
||||||
|
<i class="bi bi-diagram-3-fill nav-icon" aria-hidden="true"></i> DNS records
|
||||||
|
</NavLink>
|
||||||
|
</div>
|
||||||
|
<div class="nav-item px-3">
|
||||||
|
<NavLink class="nav-link" href="filters">
|
||||||
|
<i class="bi bi-funnel-fill nav-icon" aria-hidden="true"></i> Filters
|
||||||
|
</NavLink>
|
||||||
|
</div>
|
||||||
</nav>
|
</nav>
|
||||||
<div class="nav-footer px-3 py-2" aria-label="Links and version">
|
<div class="nav-footer px-3 py-2" aria-label="Links and version">
|
||||||
<a class="nav-footer-link" href="https://github.com/alexhopeoconnor/dnsmasq-webui" target="_blank" rel="noopener noreferrer" title="dnsmasq-webui project on GitHub">Project</a>
|
<a class="nav-footer-link" href="https://github.com/alexhopeoconnor/dnsmasq-webui" target="_blank" rel="noopener noreferrer" title="dnsmasq-webui project on GitHub">Project</a>
|
||||||
|
|||||||
@@ -13,7 +13,7 @@
|
|||||||
|
|
||||||
<PageTitle>DHCP | @AppTitle</PageTitle>
|
<PageTitle>DHCP | @AppTitle</PageTitle>
|
||||||
|
|
||||||
<h1 class="page-title">DHCP</h1>
|
<h1 class="page-title">DHCP Configuration</h1>
|
||||||
|
|
||||||
@if (_status == null)
|
@if (_status == null)
|
||||||
{
|
{
|
||||||
@@ -24,6 +24,10 @@
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
<section class="page-section">
|
||||||
|
<EffectiveConfigSection Status="_status" Context="EffectiveConfigContext.Dhcp" ShowSearchBox="true" OnSaveCompleted="RefreshStatusAsync" />
|
||||||
|
</section>
|
||||||
|
|
||||||
@* ---- Static hosts (dhcp-host= in managed file) ---- *@
|
@* ---- Static hosts (dhcp-host= in managed file) ---- *@
|
||||||
<section class="page-section">
|
<section class="page-section">
|
||||||
<h2 class="page-section-title">Static hosts</h2>
|
<h2 class="page-section-title">Static hosts</h2>
|
||||||
@@ -323,10 +327,6 @@ else
|
|||||||
}
|
}
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
<section class="page-section">
|
|
||||||
<h2 class="page-section-title">Config affecting DHCP</h2>
|
|
||||||
<EffectiveConfigSection Status="_status" Context="EffectiveConfigContext.Dhcp" ShowSearchBox="true" OnSaveCompleted="RefreshStatusAsync" />
|
|
||||||
</section>
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@code {
|
@code {
|
||||||
|
|||||||
@@ -0,0 +1,64 @@
|
|||||||
|
@page "/dns-records"
|
||||||
|
@rendermode InteractiveServer
|
||||||
|
@namespace DnsmasqWebUI.Components.Pages
|
||||||
|
@inject IStatusClient StatusClient
|
||||||
|
@inject IOptions<ApplicationOptions> AppOptions
|
||||||
|
|
||||||
|
<PageTitle>DNS records | @AppTitle</PageTitle>
|
||||||
|
|
||||||
|
<h1 class="page-title">DNS Records Configuration</h1>
|
||||||
|
|
||||||
|
@if (_status == null)
|
||||||
|
{
|
||||||
|
<p class="d-flex align-items-center gap-2">
|
||||||
|
<span class="spinner-border spinner-border-sm" role="status" aria-hidden="true"></span>
|
||||||
|
Loading...
|
||||||
|
</p>
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
<section class="page-section">
|
||||||
|
<EffectiveConfigSection Status="_status" Context="EffectiveConfigContext.DnsRecords" ShowSearchBox="true" OnSaveCompleted="RefreshStatusAsync" />
|
||||||
|
</section>
|
||||||
|
<div class="page-chips" role="group" aria-label="Record type">
|
||||||
|
<span class="page-chip active">All</span>
|
||||||
|
<span class="page-chip">CNAME</span>
|
||||||
|
<span class="page-chip">MX / SRV</span>
|
||||||
|
<span class="page-chip">PTR</span>
|
||||||
|
<span class="page-chip">TXT</span>
|
||||||
|
<span class="page-chip">A / AAAA</span>
|
||||||
|
<span class="page-chip">Other</span>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
|
||||||
|
@code {
|
||||||
|
private string AppTitle => AppOptions!.Value!.EffectiveTitle;
|
||||||
|
private DnsmasqServiceStatus? _status;
|
||||||
|
|
||||||
|
protected override async Task OnInitializedAsync()
|
||||||
|
{
|
||||||
|
await Load();
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task RefreshStatusAsync()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
_status = await StatusClient.GetStatusAsync();
|
||||||
|
await InvokeAsync(StateHasChanged);
|
||||||
|
}
|
||||||
|
catch { /* ignore */ }
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task Load()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
_status = await StatusClient.GetStatusAsync();
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
// Status will stay null; UI shows loading or empty
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,63 @@
|
|||||||
|
@page "/filters"
|
||||||
|
@rendermode InteractiveServer
|
||||||
|
@namespace DnsmasqWebUI.Components.Pages
|
||||||
|
@inject IStatusClient StatusClient
|
||||||
|
@inject IOptions<ApplicationOptions> AppOptions
|
||||||
|
|
||||||
|
<PageTitle>Filters | @AppTitle</PageTitle>
|
||||||
|
|
||||||
|
<h1 class="page-title">Filters Configuration</h1>
|
||||||
|
|
||||||
|
@if (_status == null)
|
||||||
|
{
|
||||||
|
<p class="d-flex align-items-center gap-2">
|
||||||
|
<span class="spinner-border spinner-border-sm" role="status" aria-hidden="true"></span>
|
||||||
|
Loading...
|
||||||
|
</p>
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
<section class="page-section">
|
||||||
|
<EffectiveConfigSection Status="_status" Context="EffectiveConfigContext.Filters" ShowSearchBox="true" OnSaveCompleted="RefreshStatusAsync" />
|
||||||
|
</section>
|
||||||
|
<div class="page-chips" role="group" aria-label="Filter policy category">
|
||||||
|
<span class="page-chip active">All</span>
|
||||||
|
<span class="page-chip">Blocking / sinkhole</span>
|
||||||
|
<span class="page-chip">Split DNS</span>
|
||||||
|
<span class="page-chip">Safety</span>
|
||||||
|
<span class="page-chip">Response shaping</span>
|
||||||
|
<span class="page-chip">Firewall</span>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
|
||||||
|
@code {
|
||||||
|
private string AppTitle => AppOptions!.Value!.EffectiveTitle;
|
||||||
|
private DnsmasqServiceStatus? _status;
|
||||||
|
|
||||||
|
protected override async Task OnInitializedAsync()
|
||||||
|
{
|
||||||
|
await Load();
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task RefreshStatusAsync()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
_status = await StatusClient.GetStatusAsync();
|
||||||
|
await InvokeAsync(StateHasChanged);
|
||||||
|
}
|
||||||
|
catch { /* ignore */ }
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task Load()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
_status = await StatusClient.GetStatusAsync();
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
// Status will stay null; UI shows loading or empty
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -6,7 +6,7 @@
|
|||||||
@inject IOptions<ApplicationOptions> AppOptions
|
@inject IOptions<ApplicationOptions> AppOptions
|
||||||
<PageTitle>Hosts | @AppTitle</PageTitle>
|
<PageTitle>Hosts | @AppTitle</PageTitle>
|
||||||
|
|
||||||
<h1 class="page-title">Hosts file</h1>
|
<h1 class="page-title">Hosts Configuration</h1>
|
||||||
|
|
||||||
@if (_status == null)
|
@if (_status == null)
|
||||||
{
|
{
|
||||||
@@ -18,7 +18,6 @@
|
|||||||
else
|
else
|
||||||
{
|
{
|
||||||
<section class="page-section mb-4">
|
<section class="page-section mb-4">
|
||||||
<h2 class="page-section-title">Config affecting hosts</h2>
|
|
||||||
<EffectiveConfigSection Status="_status" Context="EffectiveConfigContext.Hosts" ShowSearchBox="true" OnSaveCompleted="RefreshStatusAsync" />
|
<EffectiveConfigSection Status="_status" Context="EffectiveConfigContext.Hosts" ShowSearchBox="true" OnSaveCompleted="RefreshStatusAsync" />
|
||||||
</section>
|
</section>
|
||||||
@if (string.IsNullOrEmpty(_status.ManagedHostsFilePath))
|
@if (string.IsNullOrEmpty(_status.ManagedHostsFilePath))
|
||||||
|
|||||||
@@ -20,6 +20,8 @@ public static class EffectiveConfigViews
|
|||||||
EffectiveConfigContext.All => GetAllSections(),
|
EffectiveConfigContext.All => GetAllSections(),
|
||||||
EffectiveConfigContext.Hosts => GetHostsView(),
|
EffectiveConfigContext.Hosts => GetHostsView(),
|
||||||
EffectiveConfigContext.Dhcp => GetDhcpView(),
|
EffectiveConfigContext.Dhcp => GetDhcpView(),
|
||||||
|
EffectiveConfigContext.DnsRecords => GetDnsRecordsView(),
|
||||||
|
EffectiveConfigContext.Filters => GetFiltersView(),
|
||||||
_ => [],
|
_ => [],
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@@ -38,6 +40,7 @@ public static class EffectiveConfigViews
|
|||||||
DnsmasqConfKeys.AddnHosts,
|
DnsmasqConfKeys.AddnHosts,
|
||||||
DnsmasqConfKeys.Hostsdir,
|
DnsmasqConfKeys.Hostsdir,
|
||||||
DnsmasqConfKeys.ReadEthers,
|
DnsmasqConfKeys.ReadEthers,
|
||||||
|
DnsmasqConfKeys.ExpandHosts,
|
||||||
]),
|
]),
|
||||||
];
|
];
|
||||||
|
|
||||||
@@ -47,6 +50,38 @@ public static class EffectiveConfigViews
|
|||||||
new(EffectiveConfigSections.SectionTftpPxe, "TFTP / PXE", null),
|
new(EffectiveConfigSections.SectionTftpPxe, "TFTP / PXE", null),
|
||||||
];
|
];
|
||||||
|
|
||||||
|
private static IReadOnlyList<EffectiveConfigSectionView> GetDnsRecordsView() =>
|
||||||
|
[
|
||||||
|
new(EffectiveConfigSections.SectionDnsRecords, "DNS records", null),
|
||||||
|
];
|
||||||
|
|
||||||
|
private static IReadOnlyList<EffectiveConfigSectionView> GetFiltersView() =>
|
||||||
|
[
|
||||||
|
new(EffectiveConfigSections.SectionResolver, "Resolver / DNS", [
|
||||||
|
DnsmasqConfKeys.DomainNeeded,
|
||||||
|
DnsmasqConfKeys.Local,
|
||||||
|
DnsmasqConfKeys.Server,
|
||||||
|
DnsmasqConfKeys.RevServer,
|
||||||
|
DnsmasqConfKeys.Address,
|
||||||
|
DnsmasqConfKeys.BogusPriv,
|
||||||
|
DnsmasqConfKeys.BogusNxdomain,
|
||||||
|
DnsmasqConfKeys.IgnoreAddress,
|
||||||
|
DnsmasqConfKeys.Alias,
|
||||||
|
DnsmasqConfKeys.Filterwin2k,
|
||||||
|
DnsmasqConfKeys.FilterA,
|
||||||
|
DnsmasqConfKeys.FilterAaaa,
|
||||||
|
DnsmasqConfKeys.FilterRr,
|
||||||
|
DnsmasqConfKeys.StopDnsRebind,
|
||||||
|
DnsmasqConfKeys.RebindLocalhostOk,
|
||||||
|
DnsmasqConfKeys.RebindDomainOk,
|
||||||
|
DnsmasqConfKeys.Ipset,
|
||||||
|
DnsmasqConfKeys.Nftset,
|
||||||
|
DnsmasqConfKeys.ConnmarkAllowlistEnable,
|
||||||
|
DnsmasqConfKeys.ConnmarkAllowlist,
|
||||||
|
DnsmasqConfKeys.NoRoundRobin,
|
||||||
|
]),
|
||||||
|
];
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// For each section in the view, returns the context-visible descriptors for that section.
|
/// 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.
|
/// Does not apply search; component applies search to each section's list and hides sections with no matches.
|
||||||
|
|||||||
@@ -13,4 +13,10 @@ public enum EffectiveConfigContext
|
|||||||
|
|
||||||
/// <summary>DHCP page: DHCP and TFTP/PXE sections.</summary>
|
/// <summary>DHCP page: DHCP and TFTP/PXE sections.</summary>
|
||||||
Dhcp,
|
Dhcp,
|
||||||
|
|
||||||
|
/// <summary>DNS records page: only DNS records section.</summary>
|
||||||
|
DnsRecords,
|
||||||
|
|
||||||
|
/// <summary>Filters page: resolver options for blocking, split DNS, safety, ipset/nftset.</summary>
|
||||||
|
Filters,
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -165,6 +165,41 @@ h1:focus {
|
|||||||
margin-bottom: var(--app-spacing-sm);
|
margin-bottom: var(--app-spacing-sm);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Page intro – short description under the h1 */
|
||||||
|
.page-intro {
|
||||||
|
color: var(--bs-secondary);
|
||||||
|
font-size: 0.9375rem;
|
||||||
|
margin-bottom: var(--app-spacing-md);
|
||||||
|
max-width: var(--app-content-max-width);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Category/record-type chips – visual grouping, optional filter later */
|
||||||
|
.page-chips {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
gap: var(--app-spacing-xs);
|
||||||
|
margin-bottom: var(--app-spacing-md);
|
||||||
|
}
|
||||||
|
|
||||||
|
.page-chip {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
padding: 0.25rem 0.5rem;
|
||||||
|
font-size: 0.8125rem;
|
||||||
|
font-weight: 500;
|
||||||
|
border-radius: 9999px;
|
||||||
|
border: 1px solid var(--bs-border-color);
|
||||||
|
background: var(--bs-body-bg);
|
||||||
|
color: var(--bs-body-color);
|
||||||
|
cursor: default;
|
||||||
|
}
|
||||||
|
|
||||||
|
.page-chip.active {
|
||||||
|
border-color: var(--app-link-color);
|
||||||
|
background: rgba(13, 110, 253, 0.08);
|
||||||
|
color: var(--app-link-color);
|
||||||
|
}
|
||||||
|
|
||||||
/* Wrap text instead of horizontal scroll (WCAG-friendly, better on mobile). pre-wrap keeps newlines so line-by-line is clear. */
|
/* Wrap text instead of horizontal scroll (WCAG-friendly, better on mobile). pre-wrap keeps newlines so line-by-line is clear. */
|
||||||
.pre-block {
|
.pre-block {
|
||||||
font-family: var(--app-font-mono);
|
font-family: var(--app-font-mono);
|
||||||
|
|||||||
Reference in New Issue
Block a user