mirror of
https://github.com/alexhopeoconnor/dnsmasq-webui.git
synced 2026-10-22 13:57:50 +10:00
App logs, client settings, NavMenu layout
- Add real-time app logs via SignalR (log level, category filters) - Add filters modal with restore defaults; sentinel for empty arrays - Add client settings: max lines and auto-scroll per log panel - Improve NavMenu: align settings + hamburger, smaller mobile logo - LogsService, LogsHub, LoggingController, AppLogsLoggerProvider - Persist log level and filters to appsettings.Overrides.json
This commit is contained in:
@@ -8,9 +8,24 @@ public class ClientSettings
|
||||
/// <summary>Polling interval for the service status block (seconds).</summary>
|
||||
public int ServiceStatusPollingIntervalSeconds { get; set; } = 15;
|
||||
|
||||
/// <summary>Polling interval for the recent logs block (seconds).</summary>
|
||||
/// <summary>Polling interval for the recent dnsmasq logs block (seconds).</summary>
|
||||
public int RecentLogsPollingIntervalSeconds { get; set; } = 5;
|
||||
|
||||
/// <summary>Polling interval for the app logs block fallback (seconds).</summary>
|
||||
public int AppLogsPollingIntervalSeconds { get; set; } = 5;
|
||||
|
||||
/// <summary>Polling interval for the DHCP leases list (seconds).</summary>
|
||||
public int LeasesPollingIntervalSeconds { get; set; } = 5;
|
||||
|
||||
/// <summary>Maximum number of lines in the dnsmasq (recent) logs panel. Older lines are truncated.</summary>
|
||||
public int RecentLogsMaxLines { get; set; } = 500;
|
||||
|
||||
/// <summary>Whether to auto-scroll the dnsmasq logs panel when new lines arrive.</summary>
|
||||
public bool RecentLogsAutoScroll { get; set; } = true;
|
||||
|
||||
/// <summary>Maximum number of lines in the app logs panel. Older lines are truncated.</summary>
|
||||
public int AppLogsMaxLines { get; set; } = 500;
|
||||
|
||||
/// <summary>Whether to auto-scroll the app logs panel when new lines arrive.</summary>
|
||||
public bool AppLogsAutoScroll { get; set; } = true;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,23 @@
|
||||
namespace DnsmasqWebUI.Models.Client;
|
||||
|
||||
/// <summary>
|
||||
/// Per-field metadata for client settings: bounds, display names for validation errors.
|
||||
/// Used by SettingsModal (validation on save) and by pages/components (defensive clamp when loading from storage).
|
||||
/// </summary>
|
||||
public static class ClientSettingsFields
|
||||
{
|
||||
public sealed record FieldBounds(int Min, int Max, string DisplayName)
|
||||
{
|
||||
public bool IsValid(int value) => value >= Min && value <= Max;
|
||||
public int Clamp(int value) => Math.Clamp(value, Min, Max);
|
||||
public string? Validate(int value) =>
|
||||
IsValid(value) ? null : $"{DisplayName} must be between {Min} and {Max} seconds. You entered {value}.";
|
||||
}
|
||||
|
||||
public static readonly FieldBounds ServiceStatusPollingInterval = new(5, 300, "Service status polling interval");
|
||||
public static readonly FieldBounds RecentLogsPollingInterval = new(5, 300, "Recent logs polling interval");
|
||||
public static readonly FieldBounds AppLogsPollingInterval = new(5, 300, "App logs polling interval");
|
||||
public static readonly FieldBounds LeasesPollingInterval = new(5, 300, "DHCP leases refresh interval");
|
||||
public static readonly FieldBounds RecentLogsMaxLines = new(100, 2000, "Recent logs max lines");
|
||||
public static readonly FieldBounds AppLogsMaxLines = new(100, 2000, "App logs max lines");
|
||||
}
|
||||
@@ -11,9 +11,12 @@ public enum SettingsModalContext
|
||||
/// <summary>Show only service status polling interval.</summary>
|
||||
ServicePolling,
|
||||
|
||||
/// <summary>Show only recent logs polling interval.</summary>
|
||||
/// <summary>Show only recent dnsmasq logs polling interval.</summary>
|
||||
LogsPolling,
|
||||
|
||||
/// <summary>Show only app logs polling interval.</summary>
|
||||
AppLogsPolling,
|
||||
|
||||
/// <summary>Show only DHCP leases refresh interval.</summary>
|
||||
LeasesPolling
|
||||
}
|
||||
@@ -25,7 +28,10 @@ public static class SettingsModalSections
|
||||
{
|
||||
public const string ServiceStatus = "ServiceStatus";
|
||||
public const string Logs = "Logs";
|
||||
public const string AppLogs = "AppLogs";
|
||||
public const string Leases = "Leases";
|
||||
public const string RecentLogsDisplay = "RecentLogsDisplay";
|
||||
public const string AppLogsDisplay = "AppLogsDisplay";
|
||||
|
||||
/// <summary>Section metadata: display name (header), field label (for input), and searchable text for filtering.</summary>
|
||||
public sealed record SectionMeta(string DisplayName, string FieldLabel, string SearchableText);
|
||||
@@ -33,19 +39,30 @@ public static class SettingsModalSections
|
||||
public static readonly IReadOnlyDictionary<string, SectionMeta> All = new Dictionary<string, SectionMeta>(StringComparer.OrdinalIgnoreCase)
|
||||
{
|
||||
[ServiceStatus] = new("Service status", "Polling interval (seconds)", "service status polling interval seconds"),
|
||||
[Logs] = new("Recent logs", "Polling interval (seconds)", "recent logs polling interval seconds"),
|
||||
[Logs] = new("Recent logs", "Polling interval (seconds)", "recent dnsmasq logs polling interval seconds"),
|
||||
[AppLogs] = new("App logs", "Polling interval (seconds)", "app logs polling interval seconds"),
|
||||
[Leases] = new("DHCP leases", "Refresh interval (seconds)", "dhcp leases refresh interval seconds"),
|
||||
[RecentLogsDisplay] = new("Recent logs display", "Max lines / Auto-scroll", "recent logs display max lines auto scroll"),
|
||||
[AppLogsDisplay] = new("App logs display", "Max lines / Auto-scroll", "app logs display max lines auto scroll"),
|
||||
};
|
||||
|
||||
/// <summary>Maps a focused context (single-section view) to its section key. Returns null for All.</summary>
|
||||
public static string? GetSectionKeyForContext(SettingsModalContext context) => context switch
|
||||
/// <summary>Maps a focused context (single-section view) to its section keys. Returns null for All (search mode).</summary>
|
||||
public static string[]? GetSectionKeysForContext(SettingsModalContext context) => context switch
|
||||
{
|
||||
SettingsModalContext.ServicePolling => ServiceStatus,
|
||||
SettingsModalContext.LogsPolling => Logs,
|
||||
SettingsModalContext.LeasesPolling => Leases,
|
||||
SettingsModalContext.ServicePolling => [ServiceStatus],
|
||||
SettingsModalContext.LogsPolling => [Logs, RecentLogsDisplay],
|
||||
SettingsModalContext.AppLogsPolling => [AppLogs, AppLogsDisplay],
|
||||
SettingsModalContext.LeasesPolling => [Leases],
|
||||
_ => null
|
||||
};
|
||||
|
||||
/// <summary>Legacy: returns first section key for context. Use GetSectionKeysForContext for multi-section support.</summary>
|
||||
public static string? GetSectionKeyForContext(SettingsModalContext context)
|
||||
{
|
||||
var keys = GetSectionKeysForContext(context);
|
||||
return keys is { Length: > 0 } ? keys[0] : null;
|
||||
}
|
||||
|
||||
public static bool MatchesSearch(string key, string searchTerm)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(searchTerm)) return true;
|
||||
|
||||
Reference in New Issue
Block a user