mirror of
https://github.com/alexhopeoconnor/dnsmasq-webui.git
synced 2026-10-03 22:12:00 +10:00
- 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
209 lines
7.8 KiB
Plaintext
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();
|
|
}
|
|
}
|