From a9f8961c32d50632ca8b225df92a248f7d0e3b19 Mon Sep 17 00:00:00 2001 From: Alex Hope-O'Connor Date: Mon, 2 Mar 2026 23:10:39 +1000 Subject: [PATCH] App logs: add Refresh and Download logs buttons; add api/logging/logs/download endpoint --- .../Dnsmasq/AppLogs/AppLogsSection.razor | 4 ++++ .../Dnsmasq/AppLogs/AppLogsSection.razor.cs | 20 +++++++++++++++++++ .../Controllers/LoggingController.cs | 19 ++++++++++++++++++ 3 files changed, 43 insertions(+) diff --git a/src/DnsmasqWebUI/Components/Dnsmasq/AppLogs/AppLogsSection.razor b/src/DnsmasqWebUI/Components/Dnsmasq/AppLogs/AppLogsSection.razor index 26b430f..881126b 100644 --- a/src/DnsmasqWebUI/Components/Dnsmasq/AppLogs/AppLogsSection.razor +++ b/src/DnsmasqWebUI/Components/Dnsmasq/AppLogs/AppLogsSection.razor @@ -31,3 +31,7 @@

+
+ + Download logs +
diff --git a/src/DnsmasqWebUI/Components/Dnsmasq/AppLogs/AppLogsSection.razor.cs b/src/DnsmasqWebUI/Components/Dnsmasq/AppLogs/AppLogsSection.razor.cs index 07040bb..4032232 100644 --- a/src/DnsmasqWebUI/Components/Dnsmasq/AppLogs/AppLogsSection.razor.cs +++ b/src/DnsmasqWebUI/Components/Dnsmasq/AppLogs/AppLogsSection.razor.cs @@ -19,6 +19,7 @@ public partial class AppLogsSection : IAsyncDisposable private bool _justUpdated; private bool _logsContentReceived; + private bool _refreshing; private int _intervalSeconds; private string _logLevel = "Information"; private bool _logLevelDisabled; @@ -91,6 +92,25 @@ public partial class AppLogsSection : IAsyncDisposable private void OpenFiltersModal() => _filtersModalVisible = true; private void CloseFiltersModal() => _filtersModalVisible = false; + private async Task RequestRefreshAsync() + { + if (_hubConnection?.State != HubConnectionState.Connected) + return; + _refreshing = true; + StateHasChanged(); + try + { + await _hubConnection.InvokeAsync("RequestAppLogsSnapshot", _cts.Token); + } + catch (OperationCanceledException) { } + catch (Exception) { /* ignore */ } + finally + { + _refreshing = false; + StateHasChanged(); + } + } + private async Task OnLogLevelChanged() { if (string.IsNullOrEmpty(_logLevel)) return; diff --git a/src/DnsmasqWebUI/Controllers/LoggingController.cs b/src/DnsmasqWebUI/Controllers/LoggingController.cs index c6129ef..37b812f 100644 --- a/src/DnsmasqWebUI/Controllers/LoggingController.cs +++ b/src/DnsmasqWebUI/Controllers/LoggingController.cs @@ -1,6 +1,8 @@ +using System.Text; using System.Text.Json; using System.Text.Json.Nodes; using DnsmasqWebUI.Infrastructure.Logging; +using DnsmasqWebUI.Infrastructure.Services.Logs.Abstractions; using DnsmasqWebUI.Models.Config; using Microsoft.AspNetCore.Mvc; using Microsoft.Extensions.Configuration; @@ -12,22 +14,39 @@ namespace DnsmasqWebUI.Controllers; [Route("api/[controller]")] public class LoggingController : ControllerBase { + private const int AppLogsDownloadMaxLines = 5000; + private readonly IConfiguration _configuration; private readonly IConfigurationRoot _configurationRoot; private readonly RuntimeOverridesOptions _overridesOptions; + private readonly IAppLogsBuffer _appLogsBuffer; private readonly ILogger _logger; public LoggingController( IConfiguration configuration, IOptions overridesOptions, + IAppLogsBuffer appLogsBuffer, ILogger logger) { _configuration = configuration; _configurationRoot = (IConfigurationRoot)configuration; _overridesOptions = overridesOptions.Value; + _appLogsBuffer = appLogsBuffer; _logger = logger; } + /// Download recent app logs from the in-memory buffer (up to AppLogsDownloadMaxLines). + [HttpGet("logs/download")] + public IActionResult GetAppLogsDownload() + { + var lines = _appLogsBuffer.GetRecent(maxLines: AppLogsDownloadMaxLines); + var content = string.Join(Environment.NewLine, lines); + if (lines.Count > 0 && !content.EndsWith(Environment.NewLine)) + content += Environment.NewLine; + _logger.LogDebug("App logs download, lines={Count}", lines.Count); + return File(Encoding.UTF8.GetBytes(content), "text/plain", "app.log"); + } + /// Get the current minimum log level from configuration (Logging:LogLevel:Default). [HttpGet("level")] public ActionResult GetLevel()