Files
dnsmasq-webui/src/DnsmasqWebUI/Components/Pages/Dnsmasq.razor
T
alex e7c2560d07 Release v0.0.5: option help modal, update check, effective config sections
- Config option help: modal with dnsmasq docs, label click/mouseleave
- Update check: background + manual check, nav footer tooltip with last check time
- Effective config: sectioned views (DNS, DHCP, etc.), section toggle
- Version bump to 0.0.5
2026-02-13 00:11:02 +10:00

209 lines
7.8 KiB
Plaintext

@page "/"
@rendermode InteractiveServer
@namespace DnsmasqWebUI.Components.Pages
@inject IStatusClient StatusClient
@inject IConfigSetClient ConfigSetClient
@inject IReloadClient ReloadClient
@inject IClientSettingsService ClientSettingsService
@inject ISettingsModalService SettingsModalService
@inject IOptions<ApplicationOptions> AppOptions
@implements IDisposable
<PageTitle>Dnsmasq | @AppTitle</PageTitle>
@if (_loading)
{
<p class="text-muted"><em>Loading…</em></p>
}
else if (_error != null)
{
<div class="alert alert-danger">@_error</div>
}
else if (_status != null)
{
<section class="page-section">
<h2 class="page-section-title">Service configuration</h2>
<p class="mb-1"><strong>Config files (load order):</strong></p>
<ul class="list-unstyled small ms-2 mb-0">
@{
var configFileList = _status.ConfigFiles?.Count > 0 ? _status.ConfigFiles : _configSet?.Files;
}
@if (configFileList != null && configFileList.Count > 0)
{
@foreach (var f in configFileList)
{
var isMain = string.Equals(f.Path, _status.MainConfigPath, StringComparison.Ordinal);
var isManaged = f.IsManaged;
var exists = isMain ? _status.MainConfigPathExists : (isManaged ? _status.ManagedFilePathExists : (bool?)null);
<li>
@if (f.IsManaged)
{
<span class="badge bg-primary me-1">managed</span>
}
else
{
<span class="badge bg-secondary me-1">@(f.Source.ToString().ToLowerInvariant())</span>
}
@f.Path
@if (exists == true) { <text> ✓</text> }
@if (exists == false) { <text> @(isManaged ? "(not created yet)" : "(missing)")</text> }
</li>
}
}
else
{
<li><strong>Main config:</strong> @(_status.MainConfigPath ?? "—") @(_status.MainConfigPathExists ? "✓" : "(missing)")</li>
<li><strong>Managed file:</strong> @(_status.ManagedFilePath ?? "—") @(_status.ManagedFilePathExists ? "✓" : "(not created yet)")</li>
}
</ul>
<EffectiveConfigSection Status="_status" Context="EffectiveConfigContext.All" ShowSearchBox="true" />
@if (_status.ReloadCommandConfigured)
{
<div class="d-flex align-items-center gap-2 flex-wrap mt-3">
<button class="btn btn-primary btn-sm" @onclick="ReloadDnsmasq" disabled="@_reloading">
@if (_reloading)
{
<span class="spinner-border spinner-border-sm me-1" role="status" aria-hidden="true"></span>
}
@(_reloading ? "Reloading…" : "Reload config")
</button>
@if (!string.IsNullOrEmpty(_reloadMessage))
{
<span class="@(_reloadSuccess ? "text-success" : "text-danger") small">@_reloadMessage</span>
}
@if (!_reloadSuccess && _reloadDetail != null)
{
<pre class="pre-block mt-2 mb-0" style="max-height: 10rem;">@_reloadDetail</pre>
}
</div>
}
<StatusSection RefreshIntervalSeconds="@ClientSettingsFields.ServiceStatusPollingInterval.Value" IsReloading="_reloading" OnOpenSettings="OpenServiceStatusSettings" />
<LogsSection Status="_status" RefreshIntervalSeconds="@ClientSettingsFields.RecentLogsPollingInterval.Value" LogsMaxLines="@ClientSettingsFields.RecentLogsMaxLines.Value" LogsAutoScroll="@ClientSettingsFields.RecentLogsAutoScroll.Value" OnOpenSettings="OpenRecentLogsSettings" />
<AppLogsSection RefreshIntervalSeconds="@ClientSettingsFields.AppLogsPollingInterval.Value" LogsMaxLines="@ClientSettingsFields.AppLogsMaxLines.Value" LogsAutoScroll="@ClientSettingsFields.AppLogsAutoScroll.Value" OnOpenSettings="OpenAppLogsSettings" />
</section>
}
@code {
private string AppTitle => AppOptions!.Value!.EffectiveTitle;
private DnsmasqServiceStatus? _status;
private DnsmasqConfigSet? _configSet;
private string? _error;
private bool _loading = true;
private bool _reloading;
private string? _reloadMessage;
private string? _reloadDetail;
private bool _reloadSuccess;
private readonly CancellationTokenSource _cts = new();
protected override void OnInitialized()
{
SettingsModalService.SettingsChanged += OnSettingsChanged;
}
protected override async Task OnAfterRenderAsync(bool firstRender)
{
if (!firstRender) return;
_loading = true;
try
{
await Task.Delay(1, _cts.Token);
var token = _cts.Token;
await ClientSettingsService.LoadSettingsAsync();
_status = await StatusClient.GetStatusAsync(token);
if (_status != null)
{
try
{
_configSet = await ConfigSetClient.GetConfigSetAsync(token);
}
catch
{
// Config set is optional for Dnsmasq page
}
}
}
catch (OperationCanceledException)
{
// Component disposed or navigated away; ignore
}
catch (Exception ex)
{
_error = ex.Message;
}
finally
{
_loading = false;
StateHasChanged();
}
}
private void OpenServiceStatusSettings() => SettingsModalService.Open(SettingsModalContext.ServicePolling, "Service status polling");
private void OpenRecentLogsSettings() => SettingsModalService.Open(SettingsModalContext.LogsPolling, "Recent logs polling");
private void OpenAppLogsSettings() => SettingsModalService.Open(SettingsModalContext.AppLogsPolling, "App logs polling");
private async void OnSettingsChanged()
{
await InvokeAsync(StateHasChanged);
}
private async Task ReloadDnsmasq()
{
if (_status == null || !_status.ReloadCommandConfigured || _reloading) return;
_reloading = true;
_reloadMessage = null;
_reloadDetail = null;
try
{
var token = _cts.Token;
var result = await ReloadClient.ReloadAsync(token);
if (result.Success)
{
_reloadMessage = "Reloaded.";
_reloadSuccess = true;
}
else
{
_reloadMessage = "Reload failed" + (result.ExitCode != 0 ? " (exit " + result.ExitCode + ")" : "") + ".";
_reloadDetail = BuildReloadDetail(result);
_reloadSuccess = false;
}
try
{
_status = await StatusClient.GetStatusAsync(token);
}
catch
{
// Keep _reloadMessage; status will refresh on next page load
}
}
catch (OperationCanceledException) { }
catch (Exception ex)
{
if (_reloadMessage == null) { _reloadMessage = ex.Message; _reloadSuccess = false; }
}
finally
{
_reloading = false;
}
}
private static string? BuildReloadDetail(ReloadResult r)
{
var parts = new List<string>();
if (!string.IsNullOrEmpty(r.StdOut)) parts.Add("stdout:\n" + r.StdOut);
if (!string.IsNullOrEmpty(r.StdErr)) parts.Add("stderr:\n" + r.StdErr);
return parts.Count > 0 ? string.Join("\n\n", parts) : null;
}
public void Dispose()
{
SettingsModalService.SettingsChanged -= OnSettingsChanged;
_cts.Cancel();
_cts.Dispose();
}
}