From b44dde2c7731ccd68491e622811cbabb4ea66dc2 Mon Sep 17 00:00:00 2001 From: Alex Hope-O'Connor Date: Thu, 5 Feb 2026 23:08:40 +1000 Subject: [PATCH] Refactor: reorganise namespaces and move service DTOs to Models/Contracts - Move Client/Http, Services, Parsers under Infrastructure/ (Client, Services, Parsers) - Move config helpers (DnsmasqConfKeys, DnsmasqOptionTooltips, DnsmasqFileEncoding, EffectiveConfigFieldBuilder) to Infrastructure/Helpers/Config; ApiJsonOptions to Helpers/Http - Move Configuration types (ApplicationOptions, DnsmasqOptions, DnsmasqOptionsValidator) to Models/Config - Merge Models/Status and Models/EffectiveConfig into Models/Dnsmasq and Models/Dnsmasq/EffectiveConfig - Move Client/Models and Client/Services to Models/Client and Infrastructure/Services - Move ConfigSetSnapshot, HostsSnapshot, ProcessRunResult, ManagedConfigContent from Infrastructure/Services/Abstractions to Models/Contracts - Update all namespaces and usings; tests pass --- .../ConfigValueSourceTests.cs | 2 +- .../DnsmasqConfDhcpHostLineParserTests.cs | 2 +- .../DnsmasqConfDirectiveParserTests.cs | 4 +- .../DnsmasqConfFileLineParserTests.cs | 2 +- .../DnsmasqConfIncludeParserTests.cs | 63 ++++++++++++++++--- .../DnsmasqConfigSetServiceTests.cs | 7 ++- .../DnsmasqLeasesFileLineParserTests.cs | 2 +- .../EffectiveDnsmasqConfigTests.cs | 2 +- .../HostsFileLineParserTests.cs | 2 +- src/DnsmasqWebUI/Client/Http/StatusClient.cs | 24 ------- .../CustomDisplays/FlagValueDisplay.razor | 2 +- .../CustomDisplays/PortValueDisplay.razor | 2 +- .../EffectiveConfigFieldDisplay.razor | 4 +- .../EffectiveConfigMultiValueItem.razor | 2 +- .../EffectiveConfigMultiValueRow.razor | 2 +- .../EffectiveConfigOptionItem.razor | 2 +- .../EffectiveConfigSection.razor | 3 +- .../EffectiveConfigSectionPanel.razor | 2 +- .../Components/Dnsmasq/LogsSection.razor.cs | 4 +- .../Components/Dnsmasq/StatusSection.razor.cs | 4 +- .../Components/Modals/SettingsModal.razor.cs | 4 +- .../Components/Pages/Dnsmasq.razor | 7 ++- src/DnsmasqWebUI/Components/_Imports.razor | 15 ++--- .../Controllers/ConfigController.cs | 6 +- .../Controllers/DhcpController.cs | 4 +- .../Controllers/HostsController.cs | 4 +- .../Controllers/LeasesController.cs | 2 +- .../Controllers/ReloadController.cs | 2 +- .../Controllers/StatusController.cs | 10 +-- .../Extensions/ServiceCollectionExtensions.cs | 6 +- .../Client}/Abstractions/IConfigSetClient.cs | 4 +- .../Client}/Abstractions/IDhcpHostsClient.cs | 5 +- .../Client}/Abstractions/IHostsClient.cs | 5 +- .../Client}/Abstractions/ILeasesClient.cs | 2 +- .../Client}/Abstractions/IReloadClient.cs | 4 +- .../Client}/Abstractions/IStatusClient.cs | 4 +- .../Client}/ConfigSetClient.cs | 10 +-- .../Client}/DhcpHostsClient.cs | 16 ++--- .../Client}/HostsClient.cs | 16 ++--- .../Client}/LeasesClient.cs | 7 ++- .../Client}/ReloadClient.cs | 9 +-- .../Client}/SameHostBaseAddressHandler.cs | 4 +- .../Infrastructure/Client/StatusClient.cs | 18 ++++++ .../Infrastructure/DnsmasqFileEncoding.cs | 9 --- .../Helpers}/Config/DnsmasqConfKeys.cs | 2 +- .../Helpers/Config/DnsmasqFileEncoding.cs | 28 +++++++++ .../Helpers}/Config/DnsmasqOptionTooltips.cs | 2 +- .../Config}/EffectiveConfigFieldBuilder.cs | 6 +- .../Helpers/Http/ApiJsonOptions.cs | 26 ++++++++ .../Parsers/ConfParserHelpers.cs | 2 +- .../Parsers/DnsmasqConfDhcpHostLineParser.cs | 2 +- .../Parsers/DnsmasqConfDirectiveParser.cs | 2 +- .../Parsers/DnsmasqConfFileLineParser.cs | 4 +- .../Parsers/DnsmasqConfIncludeParser.cs | 13 ++-- .../Parsers/DnsmasqLeasesFileLineParser.cs | 2 +- .../Parsers/HostsFileLineParser.cs | 2 +- .../Abstractions/IApplicationHostedService.cs | 2 +- .../IApplicationScopedConcrete.cs | 2 +- .../Abstractions/IApplicationScopedService.cs | 2 +- .../Abstractions/IApplicationSingleton.cs | 2 +- .../Abstractions/IClientSettingsService.cs | 5 +- .../Services/Abstractions/IConfigSetCache.cs | 4 +- .../Abstractions/IDnsmasqConfigService.cs | 9 ++- .../Abstractions/IDnsmasqConfigSetService.cs | 5 +- .../IEffectiveConfigRenderFragmentRegistry.cs | 5 +- .../Services/Abstractions/IHostsCache.cs | 3 +- .../Abstractions/IHostsFileService.cs | 2 +- .../Services/Abstractions/ILeasesCache.cs | 2 +- .../Abstractions/ILeasesFileService.cs | 2 +- .../Services/Abstractions/IProcessRunner.cs | 4 +- .../Services/Abstractions/IReloadService.cs | 2 +- .../Services/ClientSettingsService.cs | 6 +- .../Services/ConfigSetCache.cs | 15 ++--- .../Services/DnsmasqConfigService.cs | 29 +++------ .../Services/DnsmasqConfigSetService.cs | 8 ++- .../EffectiveConfigRenderFragmentRegistry.cs | 9 +-- .../EnsureManagedConfigHostedService.cs | 22 ++----- .../Services/HostsCache.cs | 9 +-- .../Services/HostsFileService.cs | 8 +-- .../Services/LeasesCache.cs | 6 +- .../Services/LeasesFileService.cs | 4 +- .../Services/ProcessRunner.cs | 5 +- .../Services/ReloadService.cs | 6 +- .../Client}/ClientSettings.cs | 2 +- .../Client}/SettingsModalContext.cs | 2 +- .../Config}/ApplicationOptions.cs | 2 +- .../Config}/DnsmasqOptions.cs | 2 +- .../Config}/DnsmasqOptionsValidator.cs | 2 +- .../Contracts}/ConfigSetSnapshot.cs | 5 +- .../Contracts}/HostsSnapshot.cs | 2 +- .../Models/Contracts/ManagedConfigContent.cs | 6 ++ .../Contracts}/ProcessRunResult.cs | 4 +- .../DnsmasqServiceStatus.cs | 6 +- .../EffectiveConfig/ConfigValueSource.cs | 2 +- .../EffectiveConfig/DnsmasqConfFileSource.cs | 2 +- .../EffectiveConfig/DnsmasqConfigSet.cs | 2 +- .../EffectiveConfig/DnsmasqConfigSetEntry.cs | 2 +- .../EffectiveConfigDisplayMode.cs | 2 +- .../EffectiveConfigFieldDescriptor.cs | 4 +- .../EffectiveConfig/EffectiveConfigSources.cs | 2 +- .../EffectiveConfig/EffectiveDnsmasqConfig.cs | 2 +- .../SaveWithReloadResult.cs | 4 +- src/DnsmasqWebUI/Program.cs | 9 +-- testdata/README | 4 +- testdata/zz-dnsmasq-webui.conf | 1 + testdata/zz-dnsmasq-webui.hosts | 0 106 files changed, 377 insertions(+), 271 deletions(-) delete mode 100644 src/DnsmasqWebUI/Client/Http/StatusClient.cs rename src/DnsmasqWebUI/{Client/Http => Infrastructure/Client}/Abstractions/IConfigSetClient.cs (61%) rename src/DnsmasqWebUI/{Client/Http => Infrastructure/Client}/Abstractions/IDhcpHostsClient.cs (70%) rename src/DnsmasqWebUI/{Client/Http => Infrastructure/Client}/Abstractions/IHostsClient.cs (74%) rename src/DnsmasqWebUI/{Client/Http => Infrastructure/Client}/Abstractions/ILeasesClient.cs (86%) rename src/DnsmasqWebUI/{Client/Http => Infrastructure/Client}/Abstractions/IReloadClient.cs (58%) rename src/DnsmasqWebUI/{Client/Http => Infrastructure/Client}/Abstractions/IStatusClient.cs (64%) rename src/DnsmasqWebUI/{Client/Http => Infrastructure/Client}/ConfigSetClient.cs (61%) rename src/DnsmasqWebUI/{Client/Http => Infrastructure/Client}/DhcpHostsClient.cs (78%) rename src/DnsmasqWebUI/{Client/Http => Infrastructure/Client}/HostsClient.cs (70%) rename src/DnsmasqWebUI/{Client/Http => Infrastructure/Client}/LeasesClient.cs (66%) rename src/DnsmasqWebUI/{Client/Http => Infrastructure/Client}/ReloadClient.cs (68%) rename src/DnsmasqWebUI/{Client/Http => Infrastructure/Client}/SameHostBaseAddressHandler.cs (92%) create mode 100644 src/DnsmasqWebUI/Infrastructure/Client/StatusClient.cs delete mode 100644 src/DnsmasqWebUI/Infrastructure/DnsmasqFileEncoding.cs rename src/DnsmasqWebUI/{Models => Infrastructure/Helpers}/Config/DnsmasqConfKeys.cs (98%) create mode 100644 src/DnsmasqWebUI/Infrastructure/Helpers/Config/DnsmasqFileEncoding.cs rename src/DnsmasqWebUI/{Models => Infrastructure/Helpers}/Config/DnsmasqOptionTooltips.cs (98%) rename src/DnsmasqWebUI/{Models/EffectiveConfig => Infrastructure/Helpers/Config}/EffectiveConfigFieldBuilder.cs (98%) create mode 100644 src/DnsmasqWebUI/Infrastructure/Helpers/Http/ApiJsonOptions.cs rename src/DnsmasqWebUI/{ => Infrastructure}/Parsers/ConfParserHelpers.cs (98%) rename src/DnsmasqWebUI/{ => Infrastructure}/Parsers/DnsmasqConfDhcpHostLineParser.cs (99%) rename src/DnsmasqWebUI/{ => Infrastructure}/Parsers/DnsmasqConfDirectiveParser.cs (98%) rename src/DnsmasqWebUI/{ => Infrastructure}/Parsers/DnsmasqConfFileLineParser.cs (97%) rename src/DnsmasqWebUI/{ => Infrastructure}/Parsers/DnsmasqConfIncludeParser.cs (99%) rename src/DnsmasqWebUI/{ => Infrastructure}/Parsers/DnsmasqLeasesFileLineParser.cs (97%) rename src/DnsmasqWebUI/{ => Infrastructure}/Parsers/HostsFileLineParser.cs (98%) rename src/DnsmasqWebUI/{ => Infrastructure}/Services/Abstractions/IApplicationHostedService.cs (84%) rename src/DnsmasqWebUI/{ => Infrastructure}/Services/Abstractions/IApplicationScopedConcrete.cs (79%) rename src/DnsmasqWebUI/{ => Infrastructure}/Services/Abstractions/IApplicationScopedService.cs (77%) rename src/DnsmasqWebUI/{ => Infrastructure}/Services/Abstractions/IApplicationSingleton.cs (77%) rename src/DnsmasqWebUI/{Client => Infrastructure}/Services/Abstractions/IClientSettingsService.cs (81%) rename src/DnsmasqWebUI/{ => Infrastructure}/Services/Abstractions/IConfigSetCache.cs (91%) rename src/DnsmasqWebUI/{ => Infrastructure}/Services/Abstractions/IDnsmasqConfigService.cs (61%) rename src/DnsmasqWebUI/{ => Infrastructure}/Services/Abstractions/IDnsmasqConfigSetService.cs (89%) rename src/DnsmasqWebUI/{ => Infrastructure}/Services/Abstractions/IEffectiveConfigRenderFragmentRegistry.cs (82%) rename src/DnsmasqWebUI/{ => Infrastructure}/Services/Abstractions/IHostsCache.cs (85%) rename src/DnsmasqWebUI/{ => Infrastructure}/Services/Abstractions/IHostsFileService.cs (81%) rename src/DnsmasqWebUI/{ => Infrastructure}/Services/Abstractions/ILeasesCache.cs (89%) rename src/DnsmasqWebUI/{ => Infrastructure}/Services/Abstractions/ILeasesFileService.cs (82%) rename src/DnsmasqWebUI/{ => Infrastructure}/Services/Abstractions/IProcessRunner.cs (83%) rename src/DnsmasqWebUI/{ => Infrastructure}/Services/Abstractions/IReloadService.cs (78%) rename src/DnsmasqWebUI/{Client => Infrastructure}/Services/ClientSettingsService.cs (93%) rename src/DnsmasqWebUI/{ => Infrastructure}/Services/ConfigSetCache.cs (98%) rename src/DnsmasqWebUI/{ => Infrastructure}/Services/DnsmasqConfigService.cs (93%) rename src/DnsmasqWebUI/{ => Infrastructure}/Services/DnsmasqConfigSetService.cs (92%) rename src/DnsmasqWebUI/{ => Infrastructure}/Services/EffectiveConfigRenderFragmentRegistry.cs (91%) rename src/DnsmasqWebUI/{ => Infrastructure}/Services/EnsureManagedConfigHostedService.cs (88%) rename src/DnsmasqWebUI/{ => Infrastructure}/Services/HostsCache.cs (97%) rename src/DnsmasqWebUI/{ => Infrastructure}/Services/HostsFileService.cs (89%) rename src/DnsmasqWebUI/{ => Infrastructure}/Services/LeasesCache.cs (96%) rename src/DnsmasqWebUI/{ => Infrastructure}/Services/LeasesFileService.cs (85%) rename src/DnsmasqWebUI/{ => Infrastructure}/Services/ProcessRunner.cs (95%) rename src/DnsmasqWebUI/{ => Infrastructure}/Services/ReloadService.cs (94%) rename src/DnsmasqWebUI/{Client/Models => Models/Client}/ClientSettings.cs (93%) rename src/DnsmasqWebUI/{Client/Models => Models/Client}/SettingsModalContext.cs (92%) rename src/DnsmasqWebUI/{Configuration => Models/Config}/ApplicationOptions.cs (95%) rename src/DnsmasqWebUI/{Configuration => Models/Config}/DnsmasqOptions.cs (98%) rename src/DnsmasqWebUI/{Configuration => Models/Config}/DnsmasqOptionsValidator.cs (98%) rename src/DnsmasqWebUI/{Services/Abstractions => Models/Contracts}/ConfigSetSnapshot.cs (77%) rename src/DnsmasqWebUI/{Services/Abstractions => Models/Contracts}/HostsSnapshot.cs (86%) create mode 100644 src/DnsmasqWebUI/Models/Contracts/ManagedConfigContent.cs rename src/DnsmasqWebUI/{Services/Abstractions => Models/Contracts}/ProcessRunResult.cs (79%) rename src/DnsmasqWebUI/Models/{Status => Dnsmasq}/DnsmasqServiceStatus.cs (93%) rename src/DnsmasqWebUI/Models/{ => Dnsmasq}/EffectiveConfig/ConfigValueSource.cs (97%) rename src/DnsmasqWebUI/Models/{ => Dnsmasq}/EffectiveConfig/DnsmasqConfFileSource.cs (76%) rename src/DnsmasqWebUI/Models/{ => Dnsmasq}/EffectiveConfig/DnsmasqConfigSet.cs (87%) rename src/DnsmasqWebUI/Models/{ => Dnsmasq}/EffectiveConfig/DnsmasqConfigSetEntry.cs (81%) rename src/DnsmasqWebUI/Models/{ => Dnsmasq}/EffectiveConfig/EffectiveConfigDisplayMode.cs (88%) rename src/DnsmasqWebUI/Models/{ => Dnsmasq}/EffectiveConfig/EffectiveConfigFieldDescriptor.cs (89%) rename src/DnsmasqWebUI/Models/{ => Dnsmasq}/EffectiveConfig/EffectiveConfigSources.cs (97%) rename src/DnsmasqWebUI/Models/{ => Dnsmasq}/EffectiveConfig/EffectiveDnsmasqConfig.cs (98%) rename src/DnsmasqWebUI/Models/{Status => Dnsmasq}/SaveWithReloadResult.cs (66%) create mode 100644 testdata/zz-dnsmasq-webui.conf create mode 100644 testdata/zz-dnsmasq-webui.hosts diff --git a/src/DnsmasqWebUI.Tests/ConfigValueSourceTests.cs b/src/DnsmasqWebUI.Tests/ConfigValueSourceTests.cs index 0c92e60..263ebba 100644 --- a/src/DnsmasqWebUI.Tests/ConfigValueSourceTests.cs +++ b/src/DnsmasqWebUI.Tests/ConfigValueSourceTests.cs @@ -1,4 +1,4 @@ -using DnsmasqWebUI.Models.EffectiveConfig; +using DnsmasqWebUI.Models.Dnsmasq.EffectiveConfig; namespace DnsmasqWebUI.Tests; diff --git a/src/DnsmasqWebUI.Tests/DnsmasqConfDhcpHostLineParserTests.cs b/src/DnsmasqWebUI.Tests/DnsmasqConfDhcpHostLineParserTests.cs index a23d85f..43f4494 100644 --- a/src/DnsmasqWebUI.Tests/DnsmasqConfDhcpHostLineParserTests.cs +++ b/src/DnsmasqWebUI.Tests/DnsmasqConfDhcpHostLineParserTests.cs @@ -1,5 +1,5 @@ using DnsmasqWebUI.Models.Dhcp; -using DnsmasqWebUI.Parsers; +using DnsmasqWebUI.Infrastructure.Parsers; namespace DnsmasqWebUI.Tests; diff --git a/src/DnsmasqWebUI.Tests/DnsmasqConfDirectiveParserTests.cs b/src/DnsmasqWebUI.Tests/DnsmasqConfDirectiveParserTests.cs index 91083de..2651dbd 100644 --- a/src/DnsmasqWebUI.Tests/DnsmasqConfDirectiveParserTests.cs +++ b/src/DnsmasqWebUI.Tests/DnsmasqConfDirectiveParserTests.cs @@ -1,5 +1,5 @@ -using DnsmasqWebUI.Models.Config; -using DnsmasqWebUI.Parsers; +using DnsmasqWebUI.Infrastructure.Helpers.Config; +using DnsmasqWebUI.Infrastructure.Parsers; namespace DnsmasqWebUI.Tests; diff --git a/src/DnsmasqWebUI.Tests/DnsmasqConfFileLineParserTests.cs b/src/DnsmasqWebUI.Tests/DnsmasqConfFileLineParserTests.cs index 1766955..02bf3fa 100644 --- a/src/DnsmasqWebUI.Tests/DnsmasqConfFileLineParserTests.cs +++ b/src/DnsmasqWebUI.Tests/DnsmasqConfFileLineParserTests.cs @@ -1,6 +1,6 @@ using DnsmasqWebUI.Models.Config; using DnsmasqWebUI.Models.Dhcp; -using DnsmasqWebUI.Parsers; +using DnsmasqWebUI.Infrastructure.Parsers; namespace DnsmasqWebUI.Tests; diff --git a/src/DnsmasqWebUI.Tests/DnsmasqConfIncludeParserTests.cs b/src/DnsmasqWebUI.Tests/DnsmasqConfIncludeParserTests.cs index 04df989..44d0efe 100644 --- a/src/DnsmasqWebUI.Tests/DnsmasqConfIncludeParserTests.cs +++ b/src/DnsmasqWebUI.Tests/DnsmasqConfIncludeParserTests.cs @@ -1,5 +1,7 @@ -using DnsmasqWebUI.Models.Config; -using DnsmasqWebUI.Parsers; +using DnsmasqWebUI.Infrastructure.Helpers.Config; +using DnsmasqWebUI.Models.Dnsmasq; +using DnsmasqWebUI.Models.Dnsmasq.EffectiveConfig; +using DnsmasqWebUI.Infrastructure.Parsers; namespace DnsmasqWebUI.Tests; @@ -145,14 +147,57 @@ public class DnsmasqConfIncludeParserTests } [Fact] - public void GetIncludedPaths_testdata_dnsmasq_conf_ReturnsMainOnlyWhenConfDirMissing() + public void GetIncludedPaths_ConfDirMissing_ReturnsMainOnly() { - var mainPath = TestDataHelper.GetPath("dnsmasq.conf"); - if (!File.Exists(mainPath)) - return; // testdata not copied - var paths = DnsmasqConfIncludeParser.GetIncludedPaths(mainPath); - Assert.Single(paths); - Assert.Equal(Path.GetFullPath(mainPath), paths[0]); + var baseDir = Path.Combine(Path.GetTempPath(), "dnsmasq-confdir-missing-" + Guid.NewGuid().ToString("N")); + Directory.CreateDirectory(baseDir); + try + { + var main = Path.Combine(baseDir, "dnsmasq.conf"); + File.WriteAllText(main, "conf-dir=nonexistent-subdir\n"); + var paths = DnsmasqConfIncludeParser.GetIncludedPaths(main); + Assert.Single(paths); + Assert.Equal(Path.GetFullPath(main), paths[0]); + } + finally + { + Directory.Delete(baseDir, recursive: true); + } + } + + /// Mirrors harness layout: main + conf-dir with multiple files (including lan-dns.conf) + conf-file. Asserts all appear in load order with correct sources. + [Fact] + public void GetIncludedPathsWithSource_ConfDirAndConfFile_ReturnsAllFilesInLoadOrderWithSources() + { + var baseDir = Path.Combine(Path.GetTempPath(), "dnsmasq-harness-layout-" + Guid.NewGuid().ToString("N")); + Directory.CreateDirectory(baseDir); + var confDir = Path.Combine(baseDir, "dnsmasq.d"); + Directory.CreateDirectory(confDir); + try + { + File.WriteAllText(Path.Combine(confDir, "01-other.conf"), "# other\n"); + File.WriteAllText(Path.Combine(confDir, "02-servers.conf"), "# servers\n"); + File.WriteAllText(Path.Combine(confDir, "lan-dns.conf"), "# lan dns\n"); + File.WriteAllText(Path.Combine(baseDir, "zz-dnsmasq-webui.conf"), "# managed\n"); + var main = Path.Combine(baseDir, "dnsmasq.conf"); + File.WriteAllText(main, "conf-dir=dnsmasq.d\nconf-file=zz-dnsmasq-webui.conf\n"); + var withSource = DnsmasqConfIncludeParser.GetIncludedPathsWithSource(main); + Assert.Equal(5, withSource.Count); + Assert.Equal(Path.GetFullPath(main), withSource[0].Path); + Assert.Equal(DnsmasqConfFileSource.Main, withSource[0].Source); + Assert.Equal(Path.GetFullPath(Path.Combine(confDir, "01-other.conf")), withSource[1].Path); + Assert.Equal(DnsmasqConfFileSource.ConfDir, withSource[1].Source); + Assert.Equal(Path.GetFullPath(Path.Combine(confDir, "02-servers.conf")), withSource[2].Path); + Assert.Equal(DnsmasqConfFileSource.ConfDir, withSource[2].Source); + Assert.Equal(Path.GetFullPath(Path.Combine(confDir, "lan-dns.conf")), withSource[3].Path); + Assert.Equal(DnsmasqConfFileSource.ConfDir, withSource[3].Source); + Assert.Equal(Path.GetFullPath(Path.Combine(baseDir, "zz-dnsmasq-webui.conf")), withSource[4].Path); + Assert.Equal(DnsmasqConfFileSource.ConfFile, withSource[4].Source); + } + finally + { + Directory.Delete(baseDir, recursive: true); + } } [Fact] diff --git a/src/DnsmasqWebUI.Tests/DnsmasqConfigSetServiceTests.cs b/src/DnsmasqWebUI.Tests/DnsmasqConfigSetServiceTests.cs index b33a23c..000686e 100644 --- a/src/DnsmasqWebUI.Tests/DnsmasqConfigSetServiceTests.cs +++ b/src/DnsmasqWebUI.Tests/DnsmasqConfigSetServiceTests.cs @@ -1,6 +1,7 @@ -using DnsmasqWebUI.Configuration; -using DnsmasqWebUI.Models.EffectiveConfig; -using DnsmasqWebUI.Services; +using DnsmasqWebUI.Models.Config; +using DnsmasqWebUI.Models.Dnsmasq; +using DnsmasqWebUI.Models.Dnsmasq.EffectiveConfig; +using DnsmasqWebUI.Infrastructure.Services; using Microsoft.Extensions.Logging.Abstractions; using Microsoft.Extensions.Options; diff --git a/src/DnsmasqWebUI.Tests/DnsmasqLeasesFileLineParserTests.cs b/src/DnsmasqWebUI.Tests/DnsmasqLeasesFileLineParserTests.cs index 40acf91..dbfc44d 100644 --- a/src/DnsmasqWebUI.Tests/DnsmasqLeasesFileLineParserTests.cs +++ b/src/DnsmasqWebUI.Tests/DnsmasqLeasesFileLineParserTests.cs @@ -1,5 +1,5 @@ using DnsmasqWebUI.Models.Dhcp; -using DnsmasqWebUI.Parsers; +using DnsmasqWebUI.Infrastructure.Parsers; namespace DnsmasqWebUI.Tests; diff --git a/src/DnsmasqWebUI.Tests/EffectiveDnsmasqConfigTests.cs b/src/DnsmasqWebUI.Tests/EffectiveDnsmasqConfigTests.cs index 0e778b1..d8ee717 100644 --- a/src/DnsmasqWebUI.Tests/EffectiveDnsmasqConfigTests.cs +++ b/src/DnsmasqWebUI.Tests/EffectiveDnsmasqConfigTests.cs @@ -1,4 +1,4 @@ -using DnsmasqWebUI.Models.EffectiveConfig; +using DnsmasqWebUI.Models.Dnsmasq.EffectiveConfig; namespace DnsmasqWebUI.Tests; diff --git a/src/DnsmasqWebUI.Tests/HostsFileLineParserTests.cs b/src/DnsmasqWebUI.Tests/HostsFileLineParserTests.cs index 3e359a4..a4a94f2 100644 --- a/src/DnsmasqWebUI.Tests/HostsFileLineParserTests.cs +++ b/src/DnsmasqWebUI.Tests/HostsFileLineParserTests.cs @@ -1,5 +1,5 @@ using DnsmasqWebUI.Models.Hosts; -using DnsmasqWebUI.Parsers; +using DnsmasqWebUI.Infrastructure.Parsers; namespace DnsmasqWebUI.Tests; diff --git a/src/DnsmasqWebUI/Client/Http/StatusClient.cs b/src/DnsmasqWebUI/Client/Http/StatusClient.cs deleted file mode 100644 index e5fa151..0000000 --- a/src/DnsmasqWebUI/Client/Http/StatusClient.cs +++ /dev/null @@ -1,24 +0,0 @@ -using System.Text.Json; -using DnsmasqWebUI.Client.Http.Abstractions; -using DnsmasqWebUI.Models.Status; -using System.Net.Http.Json; - -namespace DnsmasqWebUI.Client.Http; - -public sealed class StatusClient : IStatusClient -{ - private readonly HttpClient _http; - - /// Matches API serialization (camelCase) so nested EffectiveConfigSources and tuple (value, source) deserialize correctly. - private static readonly JsonSerializerOptions JsonOptions = new() - { - PropertyNamingPolicy = JsonNamingPolicy.CamelCase, - PropertyNameCaseInsensitive = true - }; - - public StatusClient(HttpClient http) => _http = http; - - public async Task GetStatusAsync(CancellationToken ct = default) => - await _http.GetFromJsonAsync("api/status", JsonOptions, ct) - ?? throw new InvalidOperationException("Unexpected null from api/status."); -} diff --git a/src/DnsmasqWebUI/Components/Dnsmasq/EffectiveConfig/CustomDisplays/FlagValueDisplay.razor b/src/DnsmasqWebUI/Components/Dnsmasq/EffectiveConfig/CustomDisplays/FlagValueDisplay.razor index d1e2def..898aef2 100644 --- a/src/DnsmasqWebUI/Components/Dnsmasq/EffectiveConfig/CustomDisplays/FlagValueDisplay.razor +++ b/src/DnsmasqWebUI/Components/Dnsmasq/EffectiveConfig/CustomDisplays/FlagValueDisplay.razor @@ -1,5 +1,5 @@ @namespace DnsmasqWebUI.Components.Dnsmasq.EffectiveConfig.CustomDisplays -@using DnsmasqWebUI.Models.EffectiveConfig +@using DnsmasqWebUI.Models.Dnsmasq @* Reusable value-only display for boolean/flag options. Edit mode: checkbox (unless readonly); View: "Enabled" / "Disabled". *@ @if (DisplayMode == EffectiveConfigDisplayMode.Edit && Descriptor.GetSource()?.IsReadOnly != true) diff --git a/src/DnsmasqWebUI/Components/Dnsmasq/EffectiveConfig/CustomDisplays/PortValueDisplay.razor b/src/DnsmasqWebUI/Components/Dnsmasq/EffectiveConfig/CustomDisplays/PortValueDisplay.razor index 93340d4..1a49576 100644 --- a/src/DnsmasqWebUI/Components/Dnsmasq/EffectiveConfig/CustomDisplays/PortValueDisplay.razor +++ b/src/DnsmasqWebUI/Components/Dnsmasq/EffectiveConfig/CustomDisplays/PortValueDisplay.razor @@ -1,5 +1,5 @@ @namespace DnsmasqWebUI.Components.Dnsmasq.EffectiveConfig.CustomDisplays -@using DnsmasqWebUI.Models.EffectiveConfig +@using DnsmasqWebUI.Models.Dnsmasq @* Value-only display for the port option. Edit mode: number input (unless readonly); View: text with "(default DNS port)". *@ @if (DisplayMode == EffectiveConfigDisplayMode.Edit && Descriptor.GetSource()?.IsReadOnly != true) diff --git a/src/DnsmasqWebUI/Components/Dnsmasq/EffectiveConfig/EffectiveConfigFieldDisplay.razor b/src/DnsmasqWebUI/Components/Dnsmasq/EffectiveConfig/EffectiveConfigFieldDisplay.razor index 8d2154c..9edf1ce 100644 --- a/src/DnsmasqWebUI/Components/Dnsmasq/EffectiveConfig/EffectiveConfigFieldDisplay.razor +++ b/src/DnsmasqWebUI/Components/Dnsmasq/EffectiveConfig/EffectiveConfigFieldDisplay.razor @@ -1,7 +1,7 @@ @namespace DnsmasqWebUI.Components.Dnsmasq.EffectiveConfig -@using DnsmasqWebUI.Models.EffectiveConfig +@using DnsmasqWebUI.Models.Dnsmasq @using DnsmasqWebUI.Models.Config -@using DnsmasqWebUI.Services.Abstractions +@using DnsmasqWebUI.Infrastructure.Services.Abstractions @inject IEffectiveConfigRenderFragmentRegistry RenderFragmentRegistry @* Custom fragment renders only the value; we keep the row shell (label + value slot + badge) here for consistency. *@ diff --git a/src/DnsmasqWebUI/Components/Dnsmasq/EffectiveConfig/EffectiveConfigMultiValueItem.razor b/src/DnsmasqWebUI/Components/Dnsmasq/EffectiveConfig/EffectiveConfigMultiValueItem.razor index 4568059..68faee1 100644 --- a/src/DnsmasqWebUI/Components/Dnsmasq/EffectiveConfig/EffectiveConfigMultiValueItem.razor +++ b/src/DnsmasqWebUI/Components/Dnsmasq/EffectiveConfig/EffectiveConfigMultiValueItem.razor @@ -1,5 +1,5 @@ @namespace DnsmasqWebUI.Components.Dnsmasq.EffectiveConfig -@using DnsmasqWebUI.Models.EffectiveConfig +@using DnsmasqWebUI.Models.Dnsmasq @Value diff --git a/src/DnsmasqWebUI/Components/Dnsmasq/EffectiveConfig/EffectiveConfigMultiValueRow.razor b/src/DnsmasqWebUI/Components/Dnsmasq/EffectiveConfig/EffectiveConfigMultiValueRow.razor index 7eb6ac5..25937a3 100644 --- a/src/DnsmasqWebUI/Components/Dnsmasq/EffectiveConfig/EffectiveConfigMultiValueRow.razor +++ b/src/DnsmasqWebUI/Components/Dnsmasq/EffectiveConfig/EffectiveConfigMultiValueRow.razor @@ -1,5 +1,5 @@ @namespace DnsmasqWebUI.Components.Dnsmasq.EffectiveConfig -@using DnsmasqWebUI.Models.EffectiveConfig +@using DnsmasqWebUI.Models.Dnsmasq
  • @Label: diff --git a/src/DnsmasqWebUI/Components/Dnsmasq/EffectiveConfig/EffectiveConfigOptionItem.razor b/src/DnsmasqWebUI/Components/Dnsmasq/EffectiveConfig/EffectiveConfigOptionItem.razor index 22f1a48..7958526 100644 --- a/src/DnsmasqWebUI/Components/Dnsmasq/EffectiveConfig/EffectiveConfigOptionItem.razor +++ b/src/DnsmasqWebUI/Components/Dnsmasq/EffectiveConfig/EffectiveConfigOptionItem.razor @@ -1,5 +1,5 @@ @namespace DnsmasqWebUI.Components.Dnsmasq.EffectiveConfig -@using DnsmasqWebUI.Models.EffectiveConfig +@using DnsmasqWebUI.Models.Dnsmasq
  • @Label: diff --git a/src/DnsmasqWebUI/Components/Dnsmasq/EffectiveConfig/EffectiveConfigSection.razor b/src/DnsmasqWebUI/Components/Dnsmasq/EffectiveConfig/EffectiveConfigSection.razor index e818452..b26bd5f 100644 --- a/src/DnsmasqWebUI/Components/Dnsmasq/EffectiveConfig/EffectiveConfigSection.razor +++ b/src/DnsmasqWebUI/Components/Dnsmasq/EffectiveConfig/EffectiveConfigSection.razor @@ -1,6 +1,5 @@ @namespace DnsmasqWebUI.Components.Dnsmasq.EffectiveConfig -@using DnsmasqWebUI.Models.Status -@using DnsmasqWebUI.Models.EffectiveConfig +@using DnsmasqWebUI.Models.Dnsmasq @if (Status != null) { diff --git a/src/DnsmasqWebUI/Components/Dnsmasq/EffectiveConfig/EffectiveConfigSectionPanel.razor b/src/DnsmasqWebUI/Components/Dnsmasq/EffectiveConfig/EffectiveConfigSectionPanel.razor index f7e524b..cbbe97b 100644 --- a/src/DnsmasqWebUI/Components/Dnsmasq/EffectiveConfig/EffectiveConfigSectionPanel.razor +++ b/src/DnsmasqWebUI/Components/Dnsmasq/EffectiveConfig/EffectiveConfigSectionPanel.razor @@ -1,5 +1,5 @@ @namespace DnsmasqWebUI.Components.Dnsmasq.EffectiveConfig -@using DnsmasqWebUI.Models.EffectiveConfig +@using DnsmasqWebUI.Models.Dnsmasq
    diff --git a/src/DnsmasqWebUI/Components/Dnsmasq/LogsSection.razor.cs b/src/DnsmasqWebUI/Components/Dnsmasq/LogsSection.razor.cs index a06729e..93f7e11 100644 --- a/src/DnsmasqWebUI/Components/Dnsmasq/LogsSection.razor.cs +++ b/src/DnsmasqWebUI/Components/Dnsmasq/LogsSection.razor.cs @@ -1,5 +1,5 @@ -using DnsmasqWebUI.Client.Http.Abstractions; -using DnsmasqWebUI.Models.Status; +using DnsmasqWebUI.Infrastructure.Client.Abstractions; +using DnsmasqWebUI.Models.Dnsmasq; using Microsoft.AspNetCore.Components; namespace DnsmasqWebUI.Components.Dnsmasq; diff --git a/src/DnsmasqWebUI/Components/Dnsmasq/StatusSection.razor.cs b/src/DnsmasqWebUI/Components/Dnsmasq/StatusSection.razor.cs index 662b23a..5b57d69 100644 --- a/src/DnsmasqWebUI/Components/Dnsmasq/StatusSection.razor.cs +++ b/src/DnsmasqWebUI/Components/Dnsmasq/StatusSection.razor.cs @@ -1,5 +1,5 @@ -using DnsmasqWebUI.Client.Http.Abstractions; -using DnsmasqWebUI.Models.Status; +using DnsmasqWebUI.Infrastructure.Client.Abstractions; +using DnsmasqWebUI.Models.Dnsmasq; using Microsoft.AspNetCore.Components; namespace DnsmasqWebUI.Components.Dnsmasq; diff --git a/src/DnsmasqWebUI/Components/Modals/SettingsModal.razor.cs b/src/DnsmasqWebUI/Components/Modals/SettingsModal.razor.cs index d204954..be77fca 100644 --- a/src/DnsmasqWebUI/Components/Modals/SettingsModal.razor.cs +++ b/src/DnsmasqWebUI/Components/Modals/SettingsModal.razor.cs @@ -1,5 +1,5 @@ -using DnsmasqWebUI.Client.Models; -using DnsmasqWebUI.Client.Services.Abstractions; +using DnsmasqWebUI.Models.Client; +using DnsmasqWebUI.Infrastructure.Services.Abstractions; using Microsoft.AspNetCore.Components; using Microsoft.AspNetCore.Components.Web; using Microsoft.JSInterop; diff --git a/src/DnsmasqWebUI/Components/Pages/Dnsmasq.razor b/src/DnsmasqWebUI/Components/Pages/Dnsmasq.razor index e93e238..b3a0dbb 100644 --- a/src/DnsmasqWebUI/Components/Pages/Dnsmasq.razor +++ b/src/DnsmasqWebUI/Components/Pages/Dnsmasq.razor @@ -24,9 +24,12 @@ else if (_status != null)

    Service configuration

    Config files (load order):

      - @if (_configSet != null && _configSet.Files?.Count > 0) + @{ + var configFileList = _status.ConfigFiles?.Count > 0 ? _status.ConfigFiles : _configSet?.Files; + } + @if (configFileList != null && configFileList.Count > 0) { - @foreach (var f in _configSet.Files) + @foreach (var f in configFileList) { var isMain = string.Equals(f.Path, _status.MainConfigPath, StringComparison.Ordinal); var isManaged = f.IsManaged; diff --git a/src/DnsmasqWebUI/Components/_Imports.razor b/src/DnsmasqWebUI/Components/_Imports.razor index e9fa274..a32a2d2 100644 --- a/src/DnsmasqWebUI/Components/_Imports.razor +++ b/src/DnsmasqWebUI/Components/_Imports.razor @@ -7,19 +7,20 @@ @using Microsoft.AspNetCore.Components.Web.Virtualization @using Microsoft.JSInterop @using DnsmasqWebUI -@using DnsmasqWebUI.Client.Models -@using DnsmasqWebUI.Client.Services.Abstractions +@using DnsmasqWebUI.Models.Client +@using DnsmasqWebUI.Infrastructure.Services.Abstractions @using DnsmasqWebUI.Components @using DnsmasqWebUI.Components.Modals @using DnsmasqWebUI.Components.Dnsmasq @using DnsmasqWebUI.Components.Dnsmasq.EffectiveConfig @using DnsmasqWebUI.Components.Hosts +@using DnsmasqWebUI.Infrastructure +@using DnsmasqWebUI.Infrastructure.Helpers.Config @using DnsmasqWebUI.Models -@using DnsmasqWebUI.Models.EffectiveConfig +@using DnsmasqWebUI.Models.Dnsmasq +@using DnsmasqWebUI.Models.Dnsmasq.EffectiveConfig @using DnsmasqWebUI.Models.Hosts @using DnsmasqWebUI.Models.Dhcp -@using DnsmasqWebUI.Models.Status -@using DnsmasqWebUI.Configuration -@using DnsmasqWebUI.Client.Http.Abstractions -@using DnsmasqWebUI.Services.Abstractions +@using DnsmasqWebUI.Models.Config +@using DnsmasqWebUI.Infrastructure.Client.Abstractions @using Microsoft.Extensions.Options diff --git a/src/DnsmasqWebUI/Controllers/ConfigController.cs b/src/DnsmasqWebUI/Controllers/ConfigController.cs index 6532090..1494750 100644 --- a/src/DnsmasqWebUI/Controllers/ConfigController.cs +++ b/src/DnsmasqWebUI/Controllers/ConfigController.cs @@ -1,6 +1,8 @@ using DnsmasqWebUI.Models.Config; -using DnsmasqWebUI.Models.EffectiveConfig; -using DnsmasqWebUI.Services.Abstractions; +using DnsmasqWebUI.Models.Dnsmasq; +using DnsmasqWebUI.Models.Dnsmasq.EffectiveConfig; +using DnsmasqWebUI.Models.Contracts; +using DnsmasqWebUI.Infrastructure.Services.Abstractions; using Microsoft.AspNetCore.Mvc; namespace DnsmasqWebUI.Controllers; diff --git a/src/DnsmasqWebUI/Controllers/DhcpController.cs b/src/DnsmasqWebUI/Controllers/DhcpController.cs index 3b72b58..a008e6f 100644 --- a/src/DnsmasqWebUI/Controllers/DhcpController.cs +++ b/src/DnsmasqWebUI/Controllers/DhcpController.cs @@ -1,6 +1,6 @@ using DnsmasqWebUI.Models.Dhcp; -using DnsmasqWebUI.Models.Status; -using DnsmasqWebUI.Services.Abstractions; +using DnsmasqWebUI.Models.Dnsmasq; +using DnsmasqWebUI.Infrastructure.Services.Abstractions; using Microsoft.AspNetCore.Mvc; namespace DnsmasqWebUI.Controllers; diff --git a/src/DnsmasqWebUI/Controllers/HostsController.cs b/src/DnsmasqWebUI/Controllers/HostsController.cs index 72f7dda..532b53e 100644 --- a/src/DnsmasqWebUI/Controllers/HostsController.cs +++ b/src/DnsmasqWebUI/Controllers/HostsController.cs @@ -1,6 +1,6 @@ using DnsmasqWebUI.Models.Hosts; -using DnsmasqWebUI.Models.Status; -using DnsmasqWebUI.Services.Abstractions; +using DnsmasqWebUI.Models.Dnsmasq; +using DnsmasqWebUI.Infrastructure.Services.Abstractions; using Microsoft.AspNetCore.Mvc; namespace DnsmasqWebUI.Controllers; diff --git a/src/DnsmasqWebUI/Controllers/LeasesController.cs b/src/DnsmasqWebUI/Controllers/LeasesController.cs index 27e2f07..6c0e5f5 100644 --- a/src/DnsmasqWebUI/Controllers/LeasesController.cs +++ b/src/DnsmasqWebUI/Controllers/LeasesController.cs @@ -1,5 +1,5 @@ using DnsmasqWebUI.Models.Dhcp; -using DnsmasqWebUI.Services.Abstractions; +using DnsmasqWebUI.Infrastructure.Services.Abstractions; using Microsoft.AspNetCore.Mvc; namespace DnsmasqWebUI.Controllers; diff --git a/src/DnsmasqWebUI/Controllers/ReloadController.cs b/src/DnsmasqWebUI/Controllers/ReloadController.cs index 184ee8d..2fb2d83 100644 --- a/src/DnsmasqWebUI/Controllers/ReloadController.cs +++ b/src/DnsmasqWebUI/Controllers/ReloadController.cs @@ -1,4 +1,4 @@ -using DnsmasqWebUI.Services.Abstractions; +using DnsmasqWebUI.Infrastructure.Services.Abstractions; using Microsoft.AspNetCore.Mvc; namespace DnsmasqWebUI.Controllers; diff --git a/src/DnsmasqWebUI/Controllers/StatusController.cs b/src/DnsmasqWebUI/Controllers/StatusController.cs index 5e3e7e1..179398f 100644 --- a/src/DnsmasqWebUI/Controllers/StatusController.cs +++ b/src/DnsmasqWebUI/Controllers/StatusController.cs @@ -1,7 +1,8 @@ -using DnsmasqWebUI.Models.EffectiveConfig; -using DnsmasqWebUI.Models.Status; -using DnsmasqWebUI.Configuration; -using DnsmasqWebUI.Services.Abstractions; +using DnsmasqWebUI.Models.Dnsmasq; +using DnsmasqWebUI.Models.Dnsmasq.EffectiveConfig; +using DnsmasqWebUI.Models.Config; +using DnsmasqWebUI.Models.Contracts; +using DnsmasqWebUI.Infrastructure.Services.Abstractions; using Microsoft.AspNetCore.Mvc; using Microsoft.Extensions.Options; @@ -76,6 +77,7 @@ public class StatusController : ControllerBase ManagedFilePathExists: !string.IsNullOrEmpty(set.ManagedFilePath) && System.IO.File.Exists(set.ManagedFilePath), LeasesPathConfigured: !string.IsNullOrEmpty(effectiveLeasesPath), LeasesPathExists: !string.IsNullOrEmpty(effectiveLeasesPath) && System.IO.File.Exists(effectiveLeasesPath), + ConfigFiles: set.Files.Count > 0 ? set.Files : null, ReloadCommandConfigured: !string.IsNullOrWhiteSpace(_options.ReloadCommand), StatusCommandConfigured: !string.IsNullOrWhiteSpace(_options.StatusCommand), StatusShowConfigured: !string.IsNullOrWhiteSpace(_options.StatusShowCommand), diff --git a/src/DnsmasqWebUI/Extensions/ServiceCollectionExtensions.cs b/src/DnsmasqWebUI/Extensions/ServiceCollectionExtensions.cs index f4d5812..ac756b1 100644 --- a/src/DnsmasqWebUI/Extensions/ServiceCollectionExtensions.cs +++ b/src/DnsmasqWebUI/Extensions/ServiceCollectionExtensions.cs @@ -1,7 +1,7 @@ using System.Reflection; -using DnsmasqWebUI.Client.Http; -using DnsmasqWebUI.Client.Http.Abstractions; -using DnsmasqWebUI.Services.Abstractions; +using DnsmasqWebUI.Infrastructure.Client; +using DnsmasqWebUI.Infrastructure.Client.Abstractions; +using DnsmasqWebUI.Infrastructure.Services.Abstractions; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; diff --git a/src/DnsmasqWebUI/Client/Http/Abstractions/IConfigSetClient.cs b/src/DnsmasqWebUI/Infrastructure/Client/Abstractions/IConfigSetClient.cs similarity index 61% rename from src/DnsmasqWebUI/Client/Http/Abstractions/IConfigSetClient.cs rename to src/DnsmasqWebUI/Infrastructure/Client/Abstractions/IConfigSetClient.cs index 0c54917..38154d2 100644 --- a/src/DnsmasqWebUI/Client/Http/Abstractions/IConfigSetClient.cs +++ b/src/DnsmasqWebUI/Infrastructure/Client/Abstractions/IConfigSetClient.cs @@ -1,6 +1,6 @@ -using DnsmasqWebUI.Models.EffectiveConfig; +using DnsmasqWebUI.Models.Dnsmasq.EffectiveConfig; -namespace DnsmasqWebUI.Client.Http.Abstractions; +namespace DnsmasqWebUI.Infrastructure.Client.Abstractions; /// Typed client for GET api/config/set. public interface IConfigSetClient diff --git a/src/DnsmasqWebUI/Client/Http/Abstractions/IDhcpHostsClient.cs b/src/DnsmasqWebUI/Infrastructure/Client/Abstractions/IDhcpHostsClient.cs similarity index 70% rename from src/DnsmasqWebUI/Client/Http/Abstractions/IDhcpHostsClient.cs rename to src/DnsmasqWebUI/Infrastructure/Client/Abstractions/IDhcpHostsClient.cs index b0c7214..9012d78 100644 --- a/src/DnsmasqWebUI/Client/Http/Abstractions/IDhcpHostsClient.cs +++ b/src/DnsmasqWebUI/Infrastructure/Client/Abstractions/IDhcpHostsClient.cs @@ -1,7 +1,8 @@ using DnsmasqWebUI.Models.Dhcp; -using DnsmasqWebUI.Models.Status; +using DnsmasqWebUI.Models.Dnsmasq; +using DnsmasqWebUI.Models.Dnsmasq.EffectiveConfig; -namespace DnsmasqWebUI.Client.Http.Abstractions; +namespace DnsmasqWebUI.Infrastructure.Client.Abstractions; /// Typed client for GET/PUT api/dhcp/hosts. public interface IDhcpHostsClient diff --git a/src/DnsmasqWebUI/Client/Http/Abstractions/IHostsClient.cs b/src/DnsmasqWebUI/Infrastructure/Client/Abstractions/IHostsClient.cs similarity index 74% rename from src/DnsmasqWebUI/Client/Http/Abstractions/IHostsClient.cs rename to src/DnsmasqWebUI/Infrastructure/Client/Abstractions/IHostsClient.cs index d438886..847c405 100644 --- a/src/DnsmasqWebUI/Client/Http/Abstractions/IHostsClient.cs +++ b/src/DnsmasqWebUI/Infrastructure/Client/Abstractions/IHostsClient.cs @@ -1,7 +1,8 @@ using DnsmasqWebUI.Models.Hosts; -using DnsmasqWebUI.Models.Status; +using DnsmasqWebUI.Models.Dnsmasq; +using DnsmasqWebUI.Models.Dnsmasq.EffectiveConfig; -namespace DnsmasqWebUI.Client.Http.Abstractions; +namespace DnsmasqWebUI.Infrastructure.Client.Abstractions; /// Typed client for GET/PUT api/hosts. public interface IHostsClient diff --git a/src/DnsmasqWebUI/Client/Http/Abstractions/ILeasesClient.cs b/src/DnsmasqWebUI/Infrastructure/Client/Abstractions/ILeasesClient.cs similarity index 86% rename from src/DnsmasqWebUI/Client/Http/Abstractions/ILeasesClient.cs rename to src/DnsmasqWebUI/Infrastructure/Client/Abstractions/ILeasesClient.cs index 6482daf..989f0f3 100644 --- a/src/DnsmasqWebUI/Client/Http/Abstractions/ILeasesClient.cs +++ b/src/DnsmasqWebUI/Infrastructure/Client/Abstractions/ILeasesClient.cs @@ -1,6 +1,6 @@ using DnsmasqWebUI.Models.Dhcp; -namespace DnsmasqWebUI.Client.Http.Abstractions; +namespace DnsmasqWebUI.Infrastructure.Client.Abstractions; /// Typed client for GET api/leases. public interface ILeasesClient diff --git a/src/DnsmasqWebUI/Client/Http/Abstractions/IReloadClient.cs b/src/DnsmasqWebUI/Infrastructure/Client/Abstractions/IReloadClient.cs similarity index 58% rename from src/DnsmasqWebUI/Client/Http/Abstractions/IReloadClient.cs rename to src/DnsmasqWebUI/Infrastructure/Client/Abstractions/IReloadClient.cs index d324505..502786f 100644 --- a/src/DnsmasqWebUI/Client/Http/Abstractions/IReloadClient.cs +++ b/src/DnsmasqWebUI/Infrastructure/Client/Abstractions/IReloadClient.cs @@ -1,6 +1,6 @@ -using DnsmasqWebUI.Services.Abstractions; +using DnsmasqWebUI.Infrastructure.Services.Abstractions; -namespace DnsmasqWebUI.Client.Http.Abstractions; +namespace DnsmasqWebUI.Infrastructure.Client.Abstractions; /// Typed client for POST api/reload. public interface IReloadClient diff --git a/src/DnsmasqWebUI/Client/Http/Abstractions/IStatusClient.cs b/src/DnsmasqWebUI/Infrastructure/Client/Abstractions/IStatusClient.cs similarity index 64% rename from src/DnsmasqWebUI/Client/Http/Abstractions/IStatusClient.cs rename to src/DnsmasqWebUI/Infrastructure/Client/Abstractions/IStatusClient.cs index a5ca13e..2c527b2 100644 --- a/src/DnsmasqWebUI/Client/Http/Abstractions/IStatusClient.cs +++ b/src/DnsmasqWebUI/Infrastructure/Client/Abstractions/IStatusClient.cs @@ -1,6 +1,6 @@ -using DnsmasqWebUI.Models.Status; +using DnsmasqWebUI.Models.Dnsmasq; -namespace DnsmasqWebUI.Client.Http.Abstractions; +namespace DnsmasqWebUI.Infrastructure.Client.Abstractions; /// Typed client for GET api/status. public interface IStatusClient diff --git a/src/DnsmasqWebUI/Client/Http/ConfigSetClient.cs b/src/DnsmasqWebUI/Infrastructure/Client/ConfigSetClient.cs similarity index 61% rename from src/DnsmasqWebUI/Client/Http/ConfigSetClient.cs rename to src/DnsmasqWebUI/Infrastructure/Client/ConfigSetClient.cs index 3113ab4..ca731e4 100644 --- a/src/DnsmasqWebUI/Client/Http/ConfigSetClient.cs +++ b/src/DnsmasqWebUI/Infrastructure/Client/ConfigSetClient.cs @@ -1,8 +1,10 @@ -using DnsmasqWebUI.Client.Http.Abstractions; -using DnsmasqWebUI.Models.EffectiveConfig; +using DnsmasqWebUI.Infrastructure.Client.Abstractions; +using DnsmasqWebUI.Infrastructure.Helpers.Http; +using DnsmasqWebUI.Models.Dnsmasq; +using DnsmasqWebUI.Models.Dnsmasq.EffectiveConfig; using System.Net.Http.Json; -namespace DnsmasqWebUI.Client.Http; +namespace DnsmasqWebUI.Infrastructure.Client; public sealed class ConfigSetClient : IConfigSetClient { @@ -11,6 +13,6 @@ public sealed class ConfigSetClient : IConfigSetClient public ConfigSetClient(HttpClient http) => _http = http; public async Task GetConfigSetAsync(CancellationToken ct = default) => - await _http.GetFromJsonAsync("api/config/set", ct) + await _http.GetFromJsonAsync("api/config/set", ApiJsonOptions.ClientOptions, ct) ?? throw new InvalidOperationException("Unexpected null from api/config/set."); } diff --git a/src/DnsmasqWebUI/Client/Http/DhcpHostsClient.cs b/src/DnsmasqWebUI/Infrastructure/Client/DhcpHostsClient.cs similarity index 78% rename from src/DnsmasqWebUI/Client/Http/DhcpHostsClient.cs rename to src/DnsmasqWebUI/Infrastructure/Client/DhcpHostsClient.cs index 366a5c1..7471624 100644 --- a/src/DnsmasqWebUI/Client/Http/DhcpHostsClient.cs +++ b/src/DnsmasqWebUI/Infrastructure/Client/DhcpHostsClient.cs @@ -1,10 +1,12 @@ using System.Net.Http.Json; using System.Text.Json.Serialization; -using DnsmasqWebUI.Client.Http.Abstractions; +using DnsmasqWebUI.Infrastructure.Client.Abstractions; +using DnsmasqWebUI.Infrastructure.Helpers.Http; using DnsmasqWebUI.Models.Dhcp; -using DnsmasqWebUI.Models.Status; +using DnsmasqWebUI.Models.Dnsmasq; +using DnsmasqWebUI.Models.Dnsmasq.EffectiveConfig; -namespace DnsmasqWebUI.Client.Http; +namespace DnsmasqWebUI.Infrastructure.Client; public sealed class DhcpHostsClient : IDhcpHostsClient { @@ -14,13 +16,13 @@ public sealed class DhcpHostsClient : IDhcpHostsClient public async Task> GetDhcpHostsAsync(CancellationToken ct = default) { - var list = await _http.GetFromJsonAsync>("api/dhcp/hosts", ct); + var list = await _http.GetFromJsonAsync>("api/dhcp/hosts", ApiJsonOptions.ClientOptions, ct); return list ?? new List(); } public async Task SaveDhcpHostsAsync(IReadOnlyList entries, CancellationToken ct = default) { - var response = await _http.PutAsJsonAsync("api/dhcp/hosts", entries, ct); + var response = await _http.PutAsJsonAsync("api/dhcp/hosts", entries, ApiJsonOptions.ClientOptions, ct); if (!response.IsSuccessStatusCode) { var body = await response.Content.ReadAsStringAsync(ct); @@ -29,7 +31,7 @@ public sealed class DhcpHostsClient : IDhcpHostsClient { try { - var err = System.Text.Json.JsonSerializer.Deserialize(body); + var err = System.Text.Json.JsonSerializer.Deserialize(body, ApiJsonOptions.ClientOptions); if (!string.IsNullOrEmpty(err?.Error)) msg = err.Error; } @@ -37,7 +39,7 @@ public sealed class DhcpHostsClient : IDhcpHostsClient } throw new HttpRequestException(msg); } - return await response.Content.ReadFromJsonAsync(ct) + return await response.Content.ReadFromJsonAsync(ApiJsonOptions.ClientOptions, ct) ?? throw new InvalidOperationException("Unexpected null from api/dhcp/hosts."); } diff --git a/src/DnsmasqWebUI/Client/Http/HostsClient.cs b/src/DnsmasqWebUI/Infrastructure/Client/HostsClient.cs similarity index 70% rename from src/DnsmasqWebUI/Client/Http/HostsClient.cs rename to src/DnsmasqWebUI/Infrastructure/Client/HostsClient.cs index 7b23fde..54a6c18 100644 --- a/src/DnsmasqWebUI/Client/Http/HostsClient.cs +++ b/src/DnsmasqWebUI/Infrastructure/Client/HostsClient.cs @@ -1,9 +1,11 @@ -using DnsmasqWebUI.Client.Http.Abstractions; +using DnsmasqWebUI.Infrastructure.Client.Abstractions; +using DnsmasqWebUI.Infrastructure.Helpers.Http; using DnsmasqWebUI.Models.Hosts; -using DnsmasqWebUI.Models.Status; +using DnsmasqWebUI.Models.Dnsmasq; +using DnsmasqWebUI.Models.Dnsmasq.EffectiveConfig; using System.Net.Http.Json; -namespace DnsmasqWebUI.Client.Http; +namespace DnsmasqWebUI.Infrastructure.Client; public sealed class HostsClient : IHostsClient { @@ -13,21 +15,21 @@ public sealed class HostsClient : IHostsClient public async Task> GetHostsAsync(CancellationToken ct = default) { - var list = await _http.GetFromJsonAsync>("api/hosts", ct); + var list = await _http.GetFromJsonAsync>("api/hosts", ApiJsonOptions.ClientOptions, ct); return list ?? new List(); } public async Task> GetReadOnlyHostsAsync(CancellationToken ct = default) { - var list = await _http.GetFromJsonAsync>("api/hosts/readonly", ct); + var list = await _http.GetFromJsonAsync>("api/hosts/readonly", ApiJsonOptions.ClientOptions, ct); return list ?? new List(); } public async Task SaveHostsAsync(IReadOnlyList entries, CancellationToken ct = default) { - var response = await _http.PutAsJsonAsync("api/hosts", entries, ct); + var response = await _http.PutAsJsonAsync("api/hosts", entries, ApiJsonOptions.ClientOptions, ct); response.EnsureSuccessStatusCode(); - return await response.Content.ReadFromJsonAsync(ct) + return await response.Content.ReadFromJsonAsync(ApiJsonOptions.ClientOptions, ct) ?? throw new InvalidOperationException("Unexpected null from api/hosts."); } } diff --git a/src/DnsmasqWebUI/Client/Http/LeasesClient.cs b/src/DnsmasqWebUI/Infrastructure/Client/LeasesClient.cs similarity index 66% rename from src/DnsmasqWebUI/Client/Http/LeasesClient.cs rename to src/DnsmasqWebUI/Infrastructure/Client/LeasesClient.cs index 1a7df25..e763bfe 100644 --- a/src/DnsmasqWebUI/Client/Http/LeasesClient.cs +++ b/src/DnsmasqWebUI/Infrastructure/Client/LeasesClient.cs @@ -1,8 +1,9 @@ -using DnsmasqWebUI.Client.Http.Abstractions; +using DnsmasqWebUI.Infrastructure.Client.Abstractions; +using DnsmasqWebUI.Infrastructure.Helpers.Http; using DnsmasqWebUI.Models.Dhcp; using System.Net.Http.Json; -namespace DnsmasqWebUI.Client.Http; +namespace DnsmasqWebUI.Infrastructure.Client; public sealed class LeasesClient : ILeasesClient { @@ -13,7 +14,7 @@ public sealed class LeasesClient : ILeasesClient public async Task GetLeasesAsync(bool forceRefresh = false, CancellationToken ct = default) { var url = forceRefresh ? "api/leases?refresh=true" : "api/leases"; - return await _http.GetFromJsonAsync(url, ct) + return await _http.GetFromJsonAsync(url, ApiJsonOptions.ClientOptions, ct) ?? throw new InvalidOperationException("Unexpected null from api/leases."); } } diff --git a/src/DnsmasqWebUI/Client/Http/ReloadClient.cs b/src/DnsmasqWebUI/Infrastructure/Client/ReloadClient.cs similarity index 68% rename from src/DnsmasqWebUI/Client/Http/ReloadClient.cs rename to src/DnsmasqWebUI/Infrastructure/Client/ReloadClient.cs index d866e89..c5d2d6b 100644 --- a/src/DnsmasqWebUI/Client/Http/ReloadClient.cs +++ b/src/DnsmasqWebUI/Infrastructure/Client/ReloadClient.cs @@ -1,8 +1,9 @@ -using DnsmasqWebUI.Client.Http.Abstractions; -using DnsmasqWebUI.Services.Abstractions; +using DnsmasqWebUI.Infrastructure.Client.Abstractions; +using DnsmasqWebUI.Infrastructure.Helpers.Http; +using DnsmasqWebUI.Infrastructure.Services.Abstractions; using System.Net.Http.Json; -namespace DnsmasqWebUI.Client.Http; +namespace DnsmasqWebUI.Infrastructure.Client; public sealed class ReloadClient : IReloadClient { @@ -14,7 +15,7 @@ public sealed class ReloadClient : IReloadClient { var response = await _http.PostAsync("api/reload", null, ct); response.EnsureSuccessStatusCode(); - return await response.Content.ReadFromJsonAsync(ct) + return await response.Content.ReadFromJsonAsync(ApiJsonOptions.ClientOptions, ct) ?? throw new InvalidOperationException("Unexpected null from api/reload."); } } diff --git a/src/DnsmasqWebUI/Client/Http/SameHostBaseAddressHandler.cs b/src/DnsmasqWebUI/Infrastructure/Client/SameHostBaseAddressHandler.cs similarity index 92% rename from src/DnsmasqWebUI/Client/Http/SameHostBaseAddressHandler.cs rename to src/DnsmasqWebUI/Infrastructure/Client/SameHostBaseAddressHandler.cs index 16820e2..1dbfcf8 100644 --- a/src/DnsmasqWebUI/Client/Http/SameHostBaseAddressHandler.cs +++ b/src/DnsmasqWebUI/Infrastructure/Client/SameHostBaseAddressHandler.cs @@ -1,7 +1,7 @@ using System.Net; -using DnsmasqWebUI.Services.Abstractions; +using DnsmasqWebUI.Infrastructure.Services.Abstractions; -namespace DnsmasqWebUI.Client.Http; +namespace DnsmasqWebUI.Infrastructure.Client; /// /// DelegatingHandler that rewrites relative request URIs to the current request's scheme, host, and path base. diff --git a/src/DnsmasqWebUI/Infrastructure/Client/StatusClient.cs b/src/DnsmasqWebUI/Infrastructure/Client/StatusClient.cs new file mode 100644 index 0000000..60c8637 --- /dev/null +++ b/src/DnsmasqWebUI/Infrastructure/Client/StatusClient.cs @@ -0,0 +1,18 @@ +using DnsmasqWebUI.Infrastructure.Client.Abstractions; +using DnsmasqWebUI.Infrastructure.Helpers.Http; +using DnsmasqWebUI.Models.Dnsmasq; +using DnsmasqWebUI.Models.Dnsmasq.EffectiveConfig; +using System.Net.Http.Json; + +namespace DnsmasqWebUI.Infrastructure.Client; + +public sealed class StatusClient : IStatusClient +{ + private readonly HttpClient _http; + + public StatusClient(HttpClient http) => _http = http; + + public async Task GetStatusAsync(CancellationToken ct = default) => + await _http.GetFromJsonAsync("api/status", ApiJsonOptions.ClientOptions, ct) + ?? throw new InvalidOperationException("Unexpected null from api/status."); +} diff --git a/src/DnsmasqWebUI/Infrastructure/DnsmasqFileEncoding.cs b/src/DnsmasqWebUI/Infrastructure/DnsmasqFileEncoding.cs deleted file mode 100644 index c9886cd..0000000 --- a/src/DnsmasqWebUI/Infrastructure/DnsmasqFileEncoding.cs +++ /dev/null @@ -1,9 +0,0 @@ -using System.Text; - -namespace DnsmasqWebUI.Infrastructure; - -/// Encoding for dnsmasq config and hosts files. UTF-8 without BOM so dnsmasq does not see a BOM as "bad option" on line 1. -public static class DnsmasqFileEncoding -{ - public static readonly Encoding Utf8NoBom = new UTF8Encoding(false); -} diff --git a/src/DnsmasqWebUI/Models/Config/DnsmasqConfKeys.cs b/src/DnsmasqWebUI/Infrastructure/Helpers/Config/DnsmasqConfKeys.cs similarity index 98% rename from src/DnsmasqWebUI/Models/Config/DnsmasqConfKeys.cs rename to src/DnsmasqWebUI/Infrastructure/Helpers/Config/DnsmasqConfKeys.cs index 7471be8..0287c55 100644 --- a/src/DnsmasqWebUI/Models/Config/DnsmasqConfKeys.cs +++ b/src/DnsmasqWebUI/Infrastructure/Helpers/Config/DnsmasqConfKeys.cs @@ -1,4 +1,4 @@ -namespace DnsmasqWebUI.Models.Config; +namespace DnsmasqWebUI.Infrastructure.Helpers.Config; /// /// Literal dnsmasq .conf option names (config keys). Use these everywhere we reference option names diff --git a/src/DnsmasqWebUI/Infrastructure/Helpers/Config/DnsmasqFileEncoding.cs b/src/DnsmasqWebUI/Infrastructure/Helpers/Config/DnsmasqFileEncoding.cs new file mode 100644 index 0000000..45aa110 --- /dev/null +++ b/src/DnsmasqWebUI/Infrastructure/Helpers/Config/DnsmasqFileEncoding.cs @@ -0,0 +1,28 @@ +using System.Text; + +namespace DnsmasqWebUI.Infrastructure.Helpers.Config; + +/// Encoding and reading helpers for dnsmasq config and hosts files. UTF-8 without BOM so dnsmasq does not see a BOM as "bad option" on line 1. +public static class DnsmasqFileEncoding +{ + private const char Utf8BomChar = '\uFEFF'; + + public static readonly Encoding Utf8NoBom = new UTF8Encoding(false); + + /// Read config file as UTF-8 and strip UTF-8 BOM from first line if present, so conf-dir= etc. are recognized. + public static string[] ReadConfigLines(string path) + { + var lines = File.ReadAllLines(path, Encoding.UTF8); + StripBomFromFirstLine(lines); + return lines; + } + + /// Strip UTF-8 BOM from first line if present (mutates list). Use so we do not perpetuate BOM when writing. + public static void StripBomFromFirstLine(IList lines) + { + if (lines.Count == 0) return; + var first = lines[0]; + if (first.Length > 0 && first[0] == Utf8BomChar) + lines[0] = first[1..]; + } +} diff --git a/src/DnsmasqWebUI/Models/Config/DnsmasqOptionTooltips.cs b/src/DnsmasqWebUI/Infrastructure/Helpers/Config/DnsmasqOptionTooltips.cs similarity index 98% rename from src/DnsmasqWebUI/Models/Config/DnsmasqOptionTooltips.cs rename to src/DnsmasqWebUI/Infrastructure/Helpers/Config/DnsmasqOptionTooltips.cs index 0ec3c66..4d57ea8 100644 --- a/src/DnsmasqWebUI/Models/Config/DnsmasqOptionTooltips.cs +++ b/src/DnsmasqWebUI/Infrastructure/Helpers/Config/DnsmasqOptionTooltips.cs @@ -1,6 +1,6 @@ using System.Collections.Frozen; -namespace DnsmasqWebUI.Models.Config; +namespace DnsmasqWebUI.Infrastructure.Helpers.Config; /// /// Short explainer tooltips for dnsmasq option names. Shown on hover over the field label. diff --git a/src/DnsmasqWebUI/Models/EffectiveConfig/EffectiveConfigFieldBuilder.cs b/src/DnsmasqWebUI/Infrastructure/Helpers/Config/EffectiveConfigFieldBuilder.cs similarity index 98% rename from src/DnsmasqWebUI/Models/EffectiveConfig/EffectiveConfigFieldBuilder.cs rename to src/DnsmasqWebUI/Infrastructure/Helpers/Config/EffectiveConfigFieldBuilder.cs index 97e9892..e80b2d5 100644 --- a/src/DnsmasqWebUI/Models/EffectiveConfig/EffectiveConfigFieldBuilder.cs +++ b/src/DnsmasqWebUI/Infrastructure/Helpers/Config/EffectiveConfigFieldBuilder.cs @@ -1,7 +1,7 @@ -using DnsmasqWebUI.Models.Config; -using DnsmasqWebUI.Models.Status; +using DnsmasqWebUI.Models.Dnsmasq; +using DnsmasqWebUI.Models.Dnsmasq.EffectiveConfig; -namespace DnsmasqWebUI.Models.EffectiveConfig; +namespace DnsmasqWebUI.Infrastructure.Helpers.Config; /// /// Builds field descriptors for viewing and editing. Mapping is done once here via delegates passed to each descriptor. diff --git a/src/DnsmasqWebUI/Infrastructure/Helpers/Http/ApiJsonOptions.cs b/src/DnsmasqWebUI/Infrastructure/Helpers/Http/ApiJsonOptions.cs new file mode 100644 index 0000000..40d4a9e --- /dev/null +++ b/src/DnsmasqWebUI/Infrastructure/Helpers/Http/ApiJsonOptions.cs @@ -0,0 +1,26 @@ +using System.Text.Json; +using System.Text.Json.Serialization; + +namespace DnsmasqWebUI.Infrastructure.Helpers.Http; + +/// +/// Shared JSON options for API serialization (Controllers) and client deserialization (HTTP clients). +/// Keeps camelCase and enum-as-string in one place so server and client stay in sync. +/// +public static class ApiJsonOptions +{ + /// Options for Controllers (AddJsonOptions). Apply via ConfigureServer(JsonSerializerOptions). + public static void ConfigureServer(JsonSerializerOptions options) + { + options.PropertyNamingPolicy = JsonNamingPolicy.CamelCase; + options.Converters.Add(new JsonStringEnumConverter()); + } + + /// Options for HTTP clients when deserializing API responses (camelCase, case-insensitive, enum as string). + public static JsonSerializerOptions ClientOptions { get; } = new() + { + PropertyNamingPolicy = JsonNamingPolicy.CamelCase, + PropertyNameCaseInsensitive = true, + Converters = { new JsonStringEnumConverter() } + }; +} diff --git a/src/DnsmasqWebUI/Parsers/ConfParserHelpers.cs b/src/DnsmasqWebUI/Infrastructure/Parsers/ConfParserHelpers.cs similarity index 98% rename from src/DnsmasqWebUI/Parsers/ConfParserHelpers.cs rename to src/DnsmasqWebUI/Infrastructure/Parsers/ConfParserHelpers.cs index bde42ef..93b8a72 100644 --- a/src/DnsmasqWebUI/Parsers/ConfParserHelpers.cs +++ b/src/DnsmasqWebUI/Infrastructure/Parsers/ConfParserHelpers.cs @@ -2,7 +2,7 @@ using Superpower; using Superpower.Model; using Superpower.Parsers; -namespace DnsmasqWebUI.Parsers; +namespace DnsmasqWebUI.Infrastructure.Parsers; /// /// Shared Superpower text-parser helpers used by .conf and related parsers. diff --git a/src/DnsmasqWebUI/Parsers/DnsmasqConfDhcpHostLineParser.cs b/src/DnsmasqWebUI/Infrastructure/Parsers/DnsmasqConfDhcpHostLineParser.cs similarity index 99% rename from src/DnsmasqWebUI/Parsers/DnsmasqConfDhcpHostLineParser.cs rename to src/DnsmasqWebUI/Infrastructure/Parsers/DnsmasqConfDhcpHostLineParser.cs index 47c76c5..d3f668d 100644 --- a/src/DnsmasqWebUI/Parsers/DnsmasqConfDhcpHostLineParser.cs +++ b/src/DnsmasqWebUI/Infrastructure/Parsers/DnsmasqConfDhcpHostLineParser.cs @@ -3,7 +3,7 @@ using Superpower; using Superpower.Model; using Superpower.Parsers; -namespace DnsmasqWebUI.Parsers; +namespace DnsmasqWebUI.Infrastructure.Parsers; /// /// Parses a single dhcp-host= line from a dnsmasq .conf file. Same format as long option without --. diff --git a/src/DnsmasqWebUI/Parsers/DnsmasqConfDirectiveParser.cs b/src/DnsmasqWebUI/Infrastructure/Parsers/DnsmasqConfDirectiveParser.cs similarity index 98% rename from src/DnsmasqWebUI/Parsers/DnsmasqConfDirectiveParser.cs rename to src/DnsmasqWebUI/Infrastructure/Parsers/DnsmasqConfDirectiveParser.cs index 7368016..b03b78f 100644 --- a/src/DnsmasqWebUI/Parsers/DnsmasqConfDirectiveParser.cs +++ b/src/DnsmasqWebUI/Infrastructure/Parsers/DnsmasqConfDirectiveParser.cs @@ -2,7 +2,7 @@ using Superpower; using Superpower.Model; using Superpower.Parsers; -namespace DnsmasqWebUI.Parsers; +namespace DnsmasqWebUI.Infrastructure.Parsers; /// /// Key/value parsing for dnsmasq .conf lines. Used by to build effective config. diff --git a/src/DnsmasqWebUI/Parsers/DnsmasqConfFileLineParser.cs b/src/DnsmasqWebUI/Infrastructure/Parsers/DnsmasqConfFileLineParser.cs similarity index 97% rename from src/DnsmasqWebUI/Parsers/DnsmasqConfFileLineParser.cs rename to src/DnsmasqWebUI/Infrastructure/Parsers/DnsmasqConfFileLineParser.cs index 383fc71..ea6c1c7 100644 --- a/src/DnsmasqWebUI/Parsers/DnsmasqConfFileLineParser.cs +++ b/src/DnsmasqWebUI/Infrastructure/Parsers/DnsmasqConfFileLineParser.cs @@ -1,10 +1,10 @@ using DnsmasqWebUI.Models.Config; -using DnsmasqWebUI.Models.EffectiveConfig; +using DnsmasqWebUI.Models.Dnsmasq.EffectiveConfig; using Superpower; using Superpower.Model; using Superpower.Parsers; -namespace DnsmasqWebUI.Parsers; +namespace DnsmasqWebUI.Infrastructure.Parsers; /// /// Parses a dnsmasq .conf file line-by-line into variants (BlankLine, CommentLine, DhcpHostLine, OtherLine). diff --git a/src/DnsmasqWebUI/Parsers/DnsmasqConfIncludeParser.cs b/src/DnsmasqWebUI/Infrastructure/Parsers/DnsmasqConfIncludeParser.cs similarity index 99% rename from src/DnsmasqWebUI/Parsers/DnsmasqConfIncludeParser.cs rename to src/DnsmasqWebUI/Infrastructure/Parsers/DnsmasqConfIncludeParser.cs index 1bace32..1d54498 100644 --- a/src/DnsmasqWebUI/Parsers/DnsmasqConfIncludeParser.cs +++ b/src/DnsmasqWebUI/Infrastructure/Parsers/DnsmasqConfIncludeParser.cs @@ -1,10 +1,11 @@ -using DnsmasqWebUI.Models.Config; -using DnsmasqWebUI.Models.EffectiveConfig; +using DnsmasqWebUI.Infrastructure.Helpers.Config; +using DnsmasqWebUI.Models.Dnsmasq; +using DnsmasqWebUI.Models.Dnsmasq.EffectiveConfig; using Superpower; using Superpower.Model; using Superpower.Parsers; -namespace DnsmasqWebUI.Parsers; +namespace DnsmasqWebUI.Infrastructure.Parsers; /// /// Parses the main dnsmasq .conf file only for conf-file= and conf-dir= directives @@ -21,6 +22,7 @@ public static class DnsmasqConfIncludeParser private static string[] GetLines(string path, IReadOnlyDictionary pathToLines) => pathToLines.TryGetValue(path, out var L) ? L : Array.Empty(); + /// /// Returns the ordered list of absolute config file paths dnsmasq loads, in the exact order /// dnsmasq reads them (main interleaved with conf-file and conf-dir). @@ -71,7 +73,7 @@ public static class DnsmasqConfIncludeParser if (!seenPaths.Add(canonicalPath)) yield break; // dnsmasq skips re-reading same file (by inode; we use path) - var lines = File.ReadAllLines(currentFilePath); + var lines = DnsmasqFileEncoding.ReadConfigLines(currentFilePath); var dir = Path.GetDirectoryName(canonicalPath) ?? ""; // Yield "file started" so callers (e.g. GetIncludedPathsWithSource) can add this file even if it only has conf-file/conf-dir lines. @@ -100,6 +102,7 @@ public static class DnsmasqConfIncludeParser var (directory, matchSuffix, ignoreSuffix) = ParseConfDirValue(value); if (string.IsNullOrEmpty(directory)) continue; var resolvedDir = ResolvePath(directory, dir) ?? directory; + resolvedDir = Path.GetFullPath(resolvedDir); if (!Directory.Exists(resolvedDir)) continue; var files = GetConfDirFilesSorted(resolvedDir, matchSuffix, ignoreSuffix); foreach (var f in files) @@ -191,7 +194,7 @@ public static class DnsmasqConfIncludeParser if (!File.Exists(mainFull)) return null; - foreach (var line in File.ReadAllLines(mainFull)) + foreach (var line in DnsmasqFileEncoding.ReadConfigLines(mainFull)) { var kv = DnsmasqConfDirectiveParser.TryParseKeyValue(line); if (kv == null) diff --git a/src/DnsmasqWebUI/Parsers/DnsmasqLeasesFileLineParser.cs b/src/DnsmasqWebUI/Infrastructure/Parsers/DnsmasqLeasesFileLineParser.cs similarity index 97% rename from src/DnsmasqWebUI/Parsers/DnsmasqLeasesFileLineParser.cs rename to src/DnsmasqWebUI/Infrastructure/Parsers/DnsmasqLeasesFileLineParser.cs index ce7ae85..dafeb83 100644 --- a/src/DnsmasqWebUI/Parsers/DnsmasqLeasesFileLineParser.cs +++ b/src/DnsmasqWebUI/Infrastructure/Parsers/DnsmasqLeasesFileLineParser.cs @@ -3,7 +3,7 @@ using Superpower; using Superpower.Model; using Superpower.Parsers; -namespace DnsmasqWebUI.Parsers; +namespace DnsmasqWebUI.Infrastructure.Parsers; /// /// Parses a single line of the dnsmasq DHCPv4 lease file (e.g. /var/lib/misc/dnsmasq.leases). diff --git a/src/DnsmasqWebUI/Parsers/HostsFileLineParser.cs b/src/DnsmasqWebUI/Infrastructure/Parsers/HostsFileLineParser.cs similarity index 98% rename from src/DnsmasqWebUI/Parsers/HostsFileLineParser.cs rename to src/DnsmasqWebUI/Infrastructure/Parsers/HostsFileLineParser.cs index ebdba59..07c07f9 100644 --- a/src/DnsmasqWebUI/Parsers/HostsFileLineParser.cs +++ b/src/DnsmasqWebUI/Infrastructure/Parsers/HostsFileLineParser.cs @@ -3,7 +3,7 @@ using Superpower; using Superpower.Model; using Superpower.Parsers; -namespace DnsmasqWebUI.Parsers; +namespace DnsmasqWebUI.Infrastructure.Parsers; /// /// Parses a single line of an /etc/hosts-style file. Format: IP_address canonical_hostname [aliases...] diff --git a/src/DnsmasqWebUI/Services/Abstractions/IApplicationHostedService.cs b/src/DnsmasqWebUI/Infrastructure/Services/Abstractions/IApplicationHostedService.cs similarity index 84% rename from src/DnsmasqWebUI/Services/Abstractions/IApplicationHostedService.cs rename to src/DnsmasqWebUI/Infrastructure/Services/Abstractions/IApplicationHostedService.cs index 2fcec5e..b3e06d5 100644 --- a/src/DnsmasqWebUI/Services/Abstractions/IApplicationHostedService.cs +++ b/src/DnsmasqWebUI/Infrastructure/Services/Abstractions/IApplicationHostedService.cs @@ -1,6 +1,6 @@ using Microsoft.Extensions.Hosting; -namespace DnsmasqWebUI.Services.Abstractions; +namespace DnsmasqWebUI.Infrastructure.Services.Abstractions; /// /// Marker interface for application hosted services that are registered via assembly scanning. diff --git a/src/DnsmasqWebUI/Services/Abstractions/IApplicationScopedConcrete.cs b/src/DnsmasqWebUI/Infrastructure/Services/Abstractions/IApplicationScopedConcrete.cs similarity index 79% rename from src/DnsmasqWebUI/Services/Abstractions/IApplicationScopedConcrete.cs rename to src/DnsmasqWebUI/Infrastructure/Services/Abstractions/IApplicationScopedConcrete.cs index ffe1125..e4e367f 100644 --- a/src/DnsmasqWebUI/Services/Abstractions/IApplicationScopedConcrete.cs +++ b/src/DnsmasqWebUI/Infrastructure/Services/Abstractions/IApplicationScopedConcrete.cs @@ -1,4 +1,4 @@ -namespace DnsmasqWebUI.Services.Abstractions; +namespace DnsmasqWebUI.Infrastructure.Services.Abstractions; /// /// Marker for types that are registered as their own concrete type with scoped lifetime. diff --git a/src/DnsmasqWebUI/Services/Abstractions/IApplicationScopedService.cs b/src/DnsmasqWebUI/Infrastructure/Services/Abstractions/IApplicationScopedService.cs similarity index 77% rename from src/DnsmasqWebUI/Services/Abstractions/IApplicationScopedService.cs rename to src/DnsmasqWebUI/Infrastructure/Services/Abstractions/IApplicationScopedService.cs index 59c1d38..cfc92be 100644 --- a/src/DnsmasqWebUI/Services/Abstractions/IApplicationScopedService.cs +++ b/src/DnsmasqWebUI/Infrastructure/Services/Abstractions/IApplicationScopedService.cs @@ -1,4 +1,4 @@ -namespace DnsmasqWebUI.Services.Abstractions; +namespace DnsmasqWebUI.Infrastructure.Services.Abstractions; /// /// Marker interface for application services that are registered as scoped. diff --git a/src/DnsmasqWebUI/Services/Abstractions/IApplicationSingleton.cs b/src/DnsmasqWebUI/Infrastructure/Services/Abstractions/IApplicationSingleton.cs similarity index 77% rename from src/DnsmasqWebUI/Services/Abstractions/IApplicationSingleton.cs rename to src/DnsmasqWebUI/Infrastructure/Services/Abstractions/IApplicationSingleton.cs index 8c6d485..0cbf4aa 100644 --- a/src/DnsmasqWebUI/Services/Abstractions/IApplicationSingleton.cs +++ b/src/DnsmasqWebUI/Infrastructure/Services/Abstractions/IApplicationSingleton.cs @@ -1,4 +1,4 @@ -namespace DnsmasqWebUI.Services.Abstractions; +namespace DnsmasqWebUI.Infrastructure.Services.Abstractions; /// /// Marker interface for application services that are registered as singleton. diff --git a/src/DnsmasqWebUI/Client/Services/Abstractions/IClientSettingsService.cs b/src/DnsmasqWebUI/Infrastructure/Services/Abstractions/IClientSettingsService.cs similarity index 81% rename from src/DnsmasqWebUI/Client/Services/Abstractions/IClientSettingsService.cs rename to src/DnsmasqWebUI/Infrastructure/Services/Abstractions/IClientSettingsService.cs index a34ec08..76e6484 100644 --- a/src/DnsmasqWebUI/Client/Services/Abstractions/IClientSettingsService.cs +++ b/src/DnsmasqWebUI/Infrastructure/Services/Abstractions/IClientSettingsService.cs @@ -1,7 +1,6 @@ -using DnsmasqWebUI.Client.Models; -using DnsmasqWebUI.Services.Abstractions; +using DnsmasqWebUI.Models.Client; -namespace DnsmasqWebUI.Client.Services.Abstractions; +namespace DnsmasqWebUI.Infrastructure.Services.Abstractions; /// /// Loads and saves client-side preferences (e.g. polling intervals) to browser local storage. diff --git a/src/DnsmasqWebUI/Services/Abstractions/IConfigSetCache.cs b/src/DnsmasqWebUI/Infrastructure/Services/Abstractions/IConfigSetCache.cs similarity index 91% rename from src/DnsmasqWebUI/Services/Abstractions/IConfigSetCache.cs rename to src/DnsmasqWebUI/Infrastructure/Services/Abstractions/IConfigSetCache.cs index 0e1adf5..fe80d32 100644 --- a/src/DnsmasqWebUI/Services/Abstractions/IConfigSetCache.cs +++ b/src/DnsmasqWebUI/Infrastructure/Services/Abstractions/IConfigSetCache.cs @@ -1,4 +1,6 @@ -namespace DnsmasqWebUI.Services.Abstractions; +using DnsmasqWebUI.Models.Contracts; + +namespace DnsmasqWebUI.Infrastructure.Services.Abstractions; /// /// Singleton cache for the config set (main + conf-dir + managed file). Invalidates on file watchers, staleness, or manual Invalidate. diff --git a/src/DnsmasqWebUI/Services/Abstractions/IDnsmasqConfigService.cs b/src/DnsmasqWebUI/Infrastructure/Services/Abstractions/IDnsmasqConfigService.cs similarity index 61% rename from src/DnsmasqWebUI/Services/Abstractions/IDnsmasqConfigService.cs rename to src/DnsmasqWebUI/Infrastructure/Services/Abstractions/IDnsmasqConfigService.cs index 1e20303..6205c02 100644 --- a/src/DnsmasqWebUI/Services/Abstractions/IDnsmasqConfigService.cs +++ b/src/DnsmasqWebUI/Infrastructure/Services/Abstractions/IDnsmasqConfigService.cs @@ -1,8 +1,10 @@ using DnsmasqWebUI.Models.Config; +using DnsmasqWebUI.Models.Contracts; using DnsmasqWebUI.Models.Dhcp; -using DnsmasqWebUI.Models.EffectiveConfig; +using DnsmasqWebUI.Models.Dnsmasq; +using DnsmasqWebUI.Models.Dnsmasq.EffectiveConfig; -namespace DnsmasqWebUI.Services.Abstractions; +namespace DnsmasqWebUI.Infrastructure.Services.Abstractions; public interface IDnsmasqConfigService : IApplicationScopedService { @@ -11,6 +13,3 @@ public interface IDnsmasqConfigService : IApplicationScopedService Task ReadManagedConfigAsync(CancellationToken ct = default); Task WriteManagedConfigAsync(IReadOnlyList lines, CancellationToken ct = default); } - -/// Full managed file content. EffectiveHostsPathInFile is parsed from AddnHosts line if present (for display only). -public record ManagedConfigContent(IReadOnlyList Lines, string EffectiveHostsPathInFile); diff --git a/src/DnsmasqWebUI/Services/Abstractions/IDnsmasqConfigSetService.cs b/src/DnsmasqWebUI/Infrastructure/Services/Abstractions/IDnsmasqConfigSetService.cs similarity index 89% rename from src/DnsmasqWebUI/Services/Abstractions/IDnsmasqConfigSetService.cs rename to src/DnsmasqWebUI/Infrastructure/Services/Abstractions/IDnsmasqConfigSetService.cs index 0c321e8..0266518 100644 --- a/src/DnsmasqWebUI/Services/Abstractions/IDnsmasqConfigSetService.cs +++ b/src/DnsmasqWebUI/Infrastructure/Services/Abstractions/IDnsmasqConfigSetService.cs @@ -1,6 +1,7 @@ -using DnsmasqWebUI.Models.EffectiveConfig; +using DnsmasqWebUI.Models.Dnsmasq; +using DnsmasqWebUI.Models.Dnsmasq.EffectiveConfig; -namespace DnsmasqWebUI.Services.Abstractions; +namespace DnsmasqWebUI.Infrastructure.Services.Abstractions; /// Discovers the dnsmasq config set (main + conf-file/conf-dir) and the managed file path. public interface IDnsmasqConfigSetService : IApplicationSingleton diff --git a/src/DnsmasqWebUI/Services/Abstractions/IEffectiveConfigRenderFragmentRegistry.cs b/src/DnsmasqWebUI/Infrastructure/Services/Abstractions/IEffectiveConfigRenderFragmentRegistry.cs similarity index 82% rename from src/DnsmasqWebUI/Services/Abstractions/IEffectiveConfigRenderFragmentRegistry.cs rename to src/DnsmasqWebUI/Infrastructure/Services/Abstractions/IEffectiveConfigRenderFragmentRegistry.cs index f3a5f78..f47c024 100644 --- a/src/DnsmasqWebUI/Services/Abstractions/IEffectiveConfigRenderFragmentRegistry.cs +++ b/src/DnsmasqWebUI/Infrastructure/Services/Abstractions/IEffectiveConfigRenderFragmentRegistry.cs @@ -1,7 +1,8 @@ using Microsoft.AspNetCore.Components; -using DnsmasqWebUI.Models.EffectiveConfig; +using DnsmasqWebUI.Models.Dnsmasq; +using DnsmasqWebUI.Models.Dnsmasq.EffectiveConfig; -namespace DnsmasqWebUI.Services.Abstractions; +namespace DnsmasqWebUI.Infrastructure.Services.Abstractions; /// /// Lookup for custom display fragments per effective-config field (section + option). diff --git a/src/DnsmasqWebUI/Services/Abstractions/IHostsCache.cs b/src/DnsmasqWebUI/Infrastructure/Services/Abstractions/IHostsCache.cs similarity index 85% rename from src/DnsmasqWebUI/Services/Abstractions/IHostsCache.cs rename to src/DnsmasqWebUI/Infrastructure/Services/Abstractions/IHostsCache.cs index 25912ed..46b8f92 100644 --- a/src/DnsmasqWebUI/Services/Abstractions/IHostsCache.cs +++ b/src/DnsmasqWebUI/Infrastructure/Services/Abstractions/IHostsCache.cs @@ -1,6 +1,7 @@ +using DnsmasqWebUI.Models.Contracts; using DnsmasqWebUI.Models.Hosts; -namespace DnsmasqWebUI.Services.Abstractions; +namespace DnsmasqWebUI.Infrastructure.Services.Abstractions; /// /// Singleton cache for hosts: managed hosts file + read-only (system + addn-hosts). Invalidates on watchers, staleness, or Invalidate. diff --git a/src/DnsmasqWebUI/Services/Abstractions/IHostsFileService.cs b/src/DnsmasqWebUI/Infrastructure/Services/Abstractions/IHostsFileService.cs similarity index 81% rename from src/DnsmasqWebUI/Services/Abstractions/IHostsFileService.cs rename to src/DnsmasqWebUI/Infrastructure/Services/Abstractions/IHostsFileService.cs index 5417bcf..de8ce7e 100644 --- a/src/DnsmasqWebUI/Services/Abstractions/IHostsFileService.cs +++ b/src/DnsmasqWebUI/Infrastructure/Services/Abstractions/IHostsFileService.cs @@ -1,6 +1,6 @@ using DnsmasqWebUI.Models.Hosts; -namespace DnsmasqWebUI.Services.Abstractions; +namespace DnsmasqWebUI.Infrastructure.Services.Abstractions; public interface IHostsFileService : IApplicationScopedService { diff --git a/src/DnsmasqWebUI/Services/Abstractions/ILeasesCache.cs b/src/DnsmasqWebUI/Infrastructure/Services/Abstractions/ILeasesCache.cs similarity index 89% rename from src/DnsmasqWebUI/Services/Abstractions/ILeasesCache.cs rename to src/DnsmasqWebUI/Infrastructure/Services/Abstractions/ILeasesCache.cs index 2c54f3e..d70da22 100644 --- a/src/DnsmasqWebUI/Services/Abstractions/ILeasesCache.cs +++ b/src/DnsmasqWebUI/Infrastructure/Services/Abstractions/ILeasesCache.cs @@ -1,6 +1,6 @@ using DnsmasqWebUI.Models.Dhcp; -namespace DnsmasqWebUI.Services.Abstractions; +namespace DnsmasqWebUI.Infrastructure.Services.Abstractions; /// /// Singleton cache for the leases file; invalidates when the file changes (e.g. via file watcher). diff --git a/src/DnsmasqWebUI/Services/Abstractions/ILeasesFileService.cs b/src/DnsmasqWebUI/Infrastructure/Services/Abstractions/ILeasesFileService.cs similarity index 82% rename from src/DnsmasqWebUI/Services/Abstractions/ILeasesFileService.cs rename to src/DnsmasqWebUI/Infrastructure/Services/Abstractions/ILeasesFileService.cs index 0d2ed9e..9b6f45d 100644 --- a/src/DnsmasqWebUI/Services/Abstractions/ILeasesFileService.cs +++ b/src/DnsmasqWebUI/Infrastructure/Services/Abstractions/ILeasesFileService.cs @@ -1,6 +1,6 @@ using DnsmasqWebUI.Models.Dhcp; -namespace DnsmasqWebUI.Services.Abstractions; +namespace DnsmasqWebUI.Infrastructure.Services.Abstractions; public interface ILeasesFileService : IApplicationScopedService { diff --git a/src/DnsmasqWebUI/Services/Abstractions/IProcessRunner.cs b/src/DnsmasqWebUI/Infrastructure/Services/Abstractions/IProcessRunner.cs similarity index 83% rename from src/DnsmasqWebUI/Services/Abstractions/IProcessRunner.cs rename to src/DnsmasqWebUI/Infrastructure/Services/Abstractions/IProcessRunner.cs index aaee890..26b539a 100644 --- a/src/DnsmasqWebUI/Services/Abstractions/IProcessRunner.cs +++ b/src/DnsmasqWebUI/Infrastructure/Services/Abstractions/IProcessRunner.cs @@ -1,4 +1,6 @@ -namespace DnsmasqWebUI.Services.Abstractions; +using DnsmasqWebUI.Models.Contracts; + +namespace DnsmasqWebUI.Infrastructure.Services.Abstractions; /// Runs a shell command with timeout and cancellation. Used for status, reload, status show, and logs commands. public interface IProcessRunner : IApplicationScopedService diff --git a/src/DnsmasqWebUI/Services/Abstractions/IReloadService.cs b/src/DnsmasqWebUI/Infrastructure/Services/Abstractions/IReloadService.cs similarity index 78% rename from src/DnsmasqWebUI/Services/Abstractions/IReloadService.cs rename to src/DnsmasqWebUI/Infrastructure/Services/Abstractions/IReloadService.cs index a61ca70..b1d8bbd 100644 --- a/src/DnsmasqWebUI/Services/Abstractions/IReloadService.cs +++ b/src/DnsmasqWebUI/Infrastructure/Services/Abstractions/IReloadService.cs @@ -1,4 +1,4 @@ -namespace DnsmasqWebUI.Services.Abstractions; +namespace DnsmasqWebUI.Infrastructure.Services.Abstractions; public interface IReloadService : IApplicationScopedService { diff --git a/src/DnsmasqWebUI/Client/Services/ClientSettingsService.cs b/src/DnsmasqWebUI/Infrastructure/Services/ClientSettingsService.cs similarity index 93% rename from src/DnsmasqWebUI/Client/Services/ClientSettingsService.cs rename to src/DnsmasqWebUI/Infrastructure/Services/ClientSettingsService.cs index 528b6be..2d42405 100644 --- a/src/DnsmasqWebUI/Client/Services/ClientSettingsService.cs +++ b/src/DnsmasqWebUI/Infrastructure/Services/ClientSettingsService.cs @@ -1,9 +1,9 @@ using System.Text.Json; -using DnsmasqWebUI.Client.Models; -using DnsmasqWebUI.Client.Services.Abstractions; +using DnsmasqWebUI.Models.Client; +using DnsmasqWebUI.Infrastructure.Services.Abstractions; using Microsoft.JSInterop; -namespace DnsmasqWebUI.Client.Services; +namespace DnsmasqWebUI.Infrastructure.Services; /// /// Loads and saves client-side settings to browser local storage via JS interop. diff --git a/src/DnsmasqWebUI/Services/ConfigSetCache.cs b/src/DnsmasqWebUI/Infrastructure/Services/ConfigSetCache.cs similarity index 98% rename from src/DnsmasqWebUI/Services/ConfigSetCache.cs rename to src/DnsmasqWebUI/Infrastructure/Services/ConfigSetCache.cs index 72615ca..51802d4 100644 --- a/src/DnsmasqWebUI/Services/ConfigSetCache.cs +++ b/src/DnsmasqWebUI/Infrastructure/Services/ConfigSetCache.cs @@ -1,13 +1,14 @@ -using System.Text; -using DnsmasqWebUI.Configuration; using DnsmasqWebUI.Models.Config; +using DnsmasqWebUI.Models.Contracts; +using DnsmasqWebUI.Infrastructure.Helpers.Config; using DnsmasqWebUI.Models.Dhcp; -using DnsmasqWebUI.Models.EffectiveConfig; -using DnsmasqWebUI.Parsers; -using DnsmasqWebUI.Services.Abstractions; +using DnsmasqWebUI.Models.Dnsmasq; +using DnsmasqWebUI.Models.Dnsmasq.EffectiveConfig; +using DnsmasqWebUI.Infrastructure.Parsers; +using DnsmasqWebUI.Infrastructure.Services.Abstractions; using Microsoft.Extensions.Options; -namespace DnsmasqWebUI.Services; +namespace DnsmasqWebUI.Infrastructure.Services; /// /// Singleton cache for the config set (main + conf-dir + managed file). Reads all files once per refresh, @@ -203,7 +204,7 @@ public sealed class ConfigSetCache : IConfigSetCache, IDisposable } try { - dict[canonical] = File.ReadAllLines(path, Encoding.UTF8); + dict[canonical] = DnsmasqFileEncoding.ReadConfigLines(path); } catch { diff --git a/src/DnsmasqWebUI/Services/DnsmasqConfigService.cs b/src/DnsmasqWebUI/Infrastructure/Services/DnsmasqConfigService.cs similarity index 93% rename from src/DnsmasqWebUI/Services/DnsmasqConfigService.cs rename to src/DnsmasqWebUI/Infrastructure/Services/DnsmasqConfigService.cs index 0bfae0f..f19bd53 100644 --- a/src/DnsmasqWebUI/Services/DnsmasqConfigService.cs +++ b/src/DnsmasqWebUI/Infrastructure/Services/DnsmasqConfigService.cs @@ -1,12 +1,14 @@ using System.Text; -using DnsmasqWebUI.Infrastructure; +using DnsmasqWebUI.Infrastructure.Helpers.Config; using DnsmasqWebUI.Models.Config; +using DnsmasqWebUI.Models.Contracts; using DnsmasqWebUI.Models.Dhcp; -using DnsmasqWebUI.Models.EffectiveConfig; -using DnsmasqWebUI.Parsers; -using DnsmasqWebUI.Services.Abstractions; +using DnsmasqWebUI.Models.Dnsmasq; +using DnsmasqWebUI.Models.Dnsmasq.EffectiveConfig; +using DnsmasqWebUI.Infrastructure.Parsers; +using DnsmasqWebUI.Infrastructure.Services.Abstractions; -namespace DnsmasqWebUI.Services; +namespace DnsmasqWebUI.Infrastructure.Services; public class DnsmasqConfigService : IDnsmasqConfigService { @@ -113,7 +115,9 @@ public class DnsmasqConfigService : IDnsmasqConfigService if (File.Exists(path)) { var read = await File.ReadAllLinesAsync(path, DnsmasqFileEncoding.Utf8NoBom, ct); - rawLines = StripBomFromFirstLine(read); + var list = read.ToList(); + DnsmasqFileEncoding.StripBomFromFirstLine(list); + rawLines = list; } else rawLines = Array.Empty(); @@ -203,17 +207,4 @@ public class DnsmasqConfigService : IDnsmasqConfigService _logger.LogInformation("Wrote managed config file: {Path}", path); } - /// Strip UTF-8 BOM from first line if present. - private static IReadOnlyList StripBomFromFirstLine(IReadOnlyList lines) - { - if (lines.Count == 0) return lines; - var first = lines[0]; - if (first.Length > 0 && first[0] == '\uFEFF') - { - var list = lines.ToList(); - list[0] = first[1..]; - return list; - } - return lines; - } } diff --git a/src/DnsmasqWebUI/Services/DnsmasqConfigSetService.cs b/src/DnsmasqWebUI/Infrastructure/Services/DnsmasqConfigSetService.cs similarity index 92% rename from src/DnsmasqWebUI/Services/DnsmasqConfigSetService.cs rename to src/DnsmasqWebUI/Infrastructure/Services/DnsmasqConfigSetService.cs index 259e2d1..f7b4ea3 100644 --- a/src/DnsmasqWebUI/Services/DnsmasqConfigSetService.cs +++ b/src/DnsmasqWebUI/Infrastructure/Services/DnsmasqConfigSetService.cs @@ -1,7 +1,9 @@ -using DnsmasqWebUI.Models.EffectiveConfig; -using DnsmasqWebUI.Services.Abstractions; +using DnsmasqWebUI.Models.Dnsmasq; +using DnsmasqWebUI.Models.Dnsmasq.EffectiveConfig; +using DnsmasqWebUI.Models.Contracts; +using DnsmasqWebUI.Infrastructure.Services.Abstractions; -namespace DnsmasqWebUI.Services; +namespace DnsmasqWebUI.Infrastructure.Services; public class DnsmasqConfigSetService : IDnsmasqConfigSetService { diff --git a/src/DnsmasqWebUI/Services/EffectiveConfigRenderFragmentRegistry.cs b/src/DnsmasqWebUI/Infrastructure/Services/EffectiveConfigRenderFragmentRegistry.cs similarity index 91% rename from src/DnsmasqWebUI/Services/EffectiveConfigRenderFragmentRegistry.cs rename to src/DnsmasqWebUI/Infrastructure/Services/EffectiveConfigRenderFragmentRegistry.cs index 05d8ee4..80b24fc 100644 --- a/src/DnsmasqWebUI/Services/EffectiveConfigRenderFragmentRegistry.cs +++ b/src/DnsmasqWebUI/Infrastructure/Services/EffectiveConfigRenderFragmentRegistry.cs @@ -1,10 +1,11 @@ using Microsoft.AspNetCore.Components; -using DnsmasqWebUI.Models.Config; -using DnsmasqWebUI.Models.EffectiveConfig; -using DnsmasqWebUI.Services.Abstractions; +using DnsmasqWebUI.Infrastructure.Helpers.Config; +using DnsmasqWebUI.Models.Dnsmasq; +using DnsmasqWebUI.Models.Dnsmasq.EffectiveConfig; +using DnsmasqWebUI.Infrastructure.Services.Abstractions; using DnsmasqWebUI.Components.Dnsmasq.EffectiveConfig.CustomDisplays; -namespace DnsmasqWebUI.Services; +namespace DnsmasqWebUI.Infrastructure.Services; /// /// Maps (sectionId, optionName) to custom effective-config value display component types and builds diff --git a/src/DnsmasqWebUI/Services/EnsureManagedConfigHostedService.cs b/src/DnsmasqWebUI/Infrastructure/Services/EnsureManagedConfigHostedService.cs similarity index 88% rename from src/DnsmasqWebUI/Services/EnsureManagedConfigHostedService.cs rename to src/DnsmasqWebUI/Infrastructure/Services/EnsureManagedConfigHostedService.cs index e949376..a564722 100644 --- a/src/DnsmasqWebUI/Services/EnsureManagedConfigHostedService.cs +++ b/src/DnsmasqWebUI/Infrastructure/Services/EnsureManagedConfigHostedService.cs @@ -1,13 +1,12 @@ using System.Text; -using DnsmasqWebUI.Infrastructure; +using DnsmasqWebUI.Infrastructure.Helpers.Config; using DnsmasqWebUI.Models.Config; -using DnsmasqWebUI.Models.EffectiveConfig; -using DnsmasqWebUI.Configuration; -using DnsmasqWebUI.Parsers; -using DnsmasqWebUI.Services.Abstractions; +using DnsmasqWebUI.Models.Dnsmasq; +using DnsmasqWebUI.Infrastructure.Parsers; +using DnsmasqWebUI.Infrastructure.Services.Abstractions; using Microsoft.Extensions.Options; -namespace DnsmasqWebUI.Services; +namespace DnsmasqWebUI.Infrastructure.Services; /// /// Runs once at startup: ensures the managed file is included only by a conf-file= directive (never conf-dir=), @@ -52,7 +51,7 @@ public class EnsureManagedConfigHostedService : IApplicationHostedService var lines = File.Exists(mainFull) ? (await File.ReadAllLinesAsync(mainFull, DnsmasqFileEncoding.Utf8NoBom, cancellationToken)).ToList() : new List(); - StripBomFromFirstLine(lines); + DnsmasqFileEncoding.StripBomFromFirstLine(lines); // Remove any conf-file= line that points to our managed path (we will add it as the last line). for (var i = lines.Count - 1; i >= 0; i--) @@ -96,14 +95,5 @@ public class EnsureManagedConfigHostedService : IApplicationHostedService await configService.WriteManagedConfigAsync(Array.Empty(), cancellationToken); } - /// Strip UTF-8 BOM from first line if present, so we do not perpetuate it when writing. - private static void StripBomFromFirstLine(List lines) - { - if (lines.Count == 0) return; - var first = lines[0]; - if (first.Length > 0 && first[0] == '\uFEFF') - lines[0] = first[1..]; - } - public Task StopAsync(CancellationToken cancellationToken) => Task.CompletedTask; } diff --git a/src/DnsmasqWebUI/Services/HostsCache.cs b/src/DnsmasqWebUI/Infrastructure/Services/HostsCache.cs similarity index 97% rename from src/DnsmasqWebUI/Services/HostsCache.cs rename to src/DnsmasqWebUI/Infrastructure/Services/HostsCache.cs index 5a6c72f..be7d69b 100644 --- a/src/DnsmasqWebUI/Services/HostsCache.cs +++ b/src/DnsmasqWebUI/Infrastructure/Services/HostsCache.cs @@ -1,11 +1,12 @@ using System.Text; -using DnsmasqWebUI.Configuration; +using DnsmasqWebUI.Models.Config; +using DnsmasqWebUI.Models.Contracts; using DnsmasqWebUI.Models.Hosts; -using DnsmasqWebUI.Parsers; -using DnsmasqWebUI.Services.Abstractions; +using DnsmasqWebUI.Infrastructure.Parsers; +using DnsmasqWebUI.Infrastructure.Services.Abstractions; using Microsoft.Extensions.Options; -namespace DnsmasqWebUI.Services; +namespace DnsmasqWebUI.Infrastructure.Services; /// /// Singleton cache for hosts: managed hosts file + read-only (system + addn-hosts). Invalidates on watchers (managed + system path), staleness, or Invalidate. diff --git a/src/DnsmasqWebUI/Services/HostsFileService.cs b/src/DnsmasqWebUI/Infrastructure/Services/HostsFileService.cs similarity index 89% rename from src/DnsmasqWebUI/Services/HostsFileService.cs rename to src/DnsmasqWebUI/Infrastructure/Services/HostsFileService.cs index 78fe5e4..2ac3487 100644 --- a/src/DnsmasqWebUI/Services/HostsFileService.cs +++ b/src/DnsmasqWebUI/Infrastructure/Services/HostsFileService.cs @@ -1,9 +1,9 @@ -using DnsmasqWebUI.Infrastructure; +using DnsmasqWebUI.Infrastructure.Helpers.Config; using DnsmasqWebUI.Models.Hosts; -using DnsmasqWebUI.Parsers; -using DnsmasqWebUI.Services.Abstractions; +using DnsmasqWebUI.Infrastructure.Parsers; +using DnsmasqWebUI.Infrastructure.Services.Abstractions; -namespace DnsmasqWebUI.Services; +namespace DnsmasqWebUI.Infrastructure.Services; public class HostsFileService : IHostsFileService { diff --git a/src/DnsmasqWebUI/Services/LeasesCache.cs b/src/DnsmasqWebUI/Infrastructure/Services/LeasesCache.cs similarity index 96% rename from src/DnsmasqWebUI/Services/LeasesCache.cs rename to src/DnsmasqWebUI/Infrastructure/Services/LeasesCache.cs index 2545149..197dcc9 100644 --- a/src/DnsmasqWebUI/Services/LeasesCache.cs +++ b/src/DnsmasqWebUI/Infrastructure/Services/LeasesCache.cs @@ -1,9 +1,9 @@ using System.Text; using DnsmasqWebUI.Models.Dhcp; -using DnsmasqWebUI.Parsers; -using DnsmasqWebUI.Services.Abstractions; +using DnsmasqWebUI.Infrastructure.Parsers; +using DnsmasqWebUI.Infrastructure.Services.Abstractions; -namespace DnsmasqWebUI.Services; +namespace DnsmasqWebUI.Infrastructure.Services; /// /// Singleton cache for the leases file with a so we re-read only when the file changes. diff --git a/src/DnsmasqWebUI/Services/LeasesFileService.cs b/src/DnsmasqWebUI/Infrastructure/Services/LeasesFileService.cs similarity index 85% rename from src/DnsmasqWebUI/Services/LeasesFileService.cs rename to src/DnsmasqWebUI/Infrastructure/Services/LeasesFileService.cs index f8538a4..9e76972 100644 --- a/src/DnsmasqWebUI/Services/LeasesFileService.cs +++ b/src/DnsmasqWebUI/Infrastructure/Services/LeasesFileService.cs @@ -1,7 +1,7 @@ using DnsmasqWebUI.Models.Dhcp; -using DnsmasqWebUI.Services.Abstractions; +using DnsmasqWebUI.Infrastructure.Services.Abstractions; -namespace DnsmasqWebUI.Services; +namespace DnsmasqWebUI.Infrastructure.Services; public class LeasesFileService : ILeasesFileService { diff --git a/src/DnsmasqWebUI/Services/ProcessRunner.cs b/src/DnsmasqWebUI/Infrastructure/Services/ProcessRunner.cs similarity index 95% rename from src/DnsmasqWebUI/Services/ProcessRunner.cs rename to src/DnsmasqWebUI/Infrastructure/Services/ProcessRunner.cs index 3c7436f..b39c0ac 100644 --- a/src/DnsmasqWebUI/Services/ProcessRunner.cs +++ b/src/DnsmasqWebUI/Infrastructure/Services/ProcessRunner.cs @@ -1,9 +1,10 @@ using System.Diagnostics; using System.Text; -using DnsmasqWebUI.Services.Abstractions; +using DnsmasqWebUI.Models.Contracts; +using DnsmasqWebUI.Infrastructure.Services.Abstractions; using Microsoft.Extensions.Logging; -namespace DnsmasqWebUI.Services; +namespace DnsmasqWebUI.Infrastructure.Services; /// Runs shell commands via /bin/sh with async output capture and timeout. Used by StatusController and ReloadService. public sealed class ProcessRunner : IProcessRunner diff --git a/src/DnsmasqWebUI/Services/ReloadService.cs b/src/DnsmasqWebUI/Infrastructure/Services/ReloadService.cs similarity index 94% rename from src/DnsmasqWebUI/Services/ReloadService.cs rename to src/DnsmasqWebUI/Infrastructure/Services/ReloadService.cs index 211589d..977088b 100644 --- a/src/DnsmasqWebUI/Services/ReloadService.cs +++ b/src/DnsmasqWebUI/Infrastructure/Services/ReloadService.cs @@ -1,8 +1,8 @@ -using DnsmasqWebUI.Configuration; -using DnsmasqWebUI.Services.Abstractions; +using DnsmasqWebUI.Models.Config; +using DnsmasqWebUI.Infrastructure.Services.Abstractions; using Microsoft.Extensions.Options; -namespace DnsmasqWebUI.Services; +namespace DnsmasqWebUI.Infrastructure.Services; public class ReloadService : IReloadService { diff --git a/src/DnsmasqWebUI/Client/Models/ClientSettings.cs b/src/DnsmasqWebUI/Models/Client/ClientSettings.cs similarity index 93% rename from src/DnsmasqWebUI/Client/Models/ClientSettings.cs rename to src/DnsmasqWebUI/Models/Client/ClientSettings.cs index 6b4b59d..e355ede 100644 --- a/src/DnsmasqWebUI/Client/Models/ClientSettings.cs +++ b/src/DnsmasqWebUI/Models/Client/ClientSettings.cs @@ -1,4 +1,4 @@ -namespace DnsmasqWebUI.Client.Models; +namespace DnsmasqWebUI.Models.Client; /// /// Client-side preferences persisted in browser local storage. diff --git a/src/DnsmasqWebUI/Client/Models/SettingsModalContext.cs b/src/DnsmasqWebUI/Models/Client/SettingsModalContext.cs similarity index 92% rename from src/DnsmasqWebUI/Client/Models/SettingsModalContext.cs rename to src/DnsmasqWebUI/Models/Client/SettingsModalContext.cs index 9dcd389..a500a9c 100644 --- a/src/DnsmasqWebUI/Client/Models/SettingsModalContext.cs +++ b/src/DnsmasqWebUI/Models/Client/SettingsModalContext.cs @@ -1,4 +1,4 @@ -namespace DnsmasqWebUI.Client.Models; +namespace DnsmasqWebUI.Models.Client; /// /// Which subset of client settings to show in the settings modal. diff --git a/src/DnsmasqWebUI/Configuration/ApplicationOptions.cs b/src/DnsmasqWebUI/Models/Config/ApplicationOptions.cs similarity index 95% rename from src/DnsmasqWebUI/Configuration/ApplicationOptions.cs rename to src/DnsmasqWebUI/Models/Config/ApplicationOptions.cs index 4714f2a..af898e0 100644 --- a/src/DnsmasqWebUI/Configuration/ApplicationOptions.cs +++ b/src/DnsmasqWebUI/Models/Config/ApplicationOptions.cs @@ -1,4 +1,4 @@ -namespace DnsmasqWebUI.Configuration; +namespace DnsmasqWebUI.Models.Config; /// /// Application-level settings (title, etc.). Bound from "Application" config section diff --git a/src/DnsmasqWebUI/Configuration/DnsmasqOptions.cs b/src/DnsmasqWebUI/Models/Config/DnsmasqOptions.cs similarity index 98% rename from src/DnsmasqWebUI/Configuration/DnsmasqOptions.cs rename to src/DnsmasqWebUI/Models/Config/DnsmasqOptions.cs index 05d5c58..05c6314 100644 --- a/src/DnsmasqWebUI/Configuration/DnsmasqOptions.cs +++ b/src/DnsmasqWebUI/Models/Config/DnsmasqOptions.cs @@ -1,4 +1,4 @@ -namespace DnsmasqWebUI.Configuration; +namespace DnsmasqWebUI.Models.Config; /// /// Configuration for dnsmasq paths and reload/status commands. diff --git a/src/DnsmasqWebUI/Configuration/DnsmasqOptionsValidator.cs b/src/DnsmasqWebUI/Models/Config/DnsmasqOptionsValidator.cs similarity index 98% rename from src/DnsmasqWebUI/Configuration/DnsmasqOptionsValidator.cs rename to src/DnsmasqWebUI/Models/Config/DnsmasqOptionsValidator.cs index 3c2a31e..28805b7 100644 --- a/src/DnsmasqWebUI/Configuration/DnsmasqOptionsValidator.cs +++ b/src/DnsmasqWebUI/Models/Config/DnsmasqOptionsValidator.cs @@ -1,6 +1,6 @@ using Microsoft.Extensions.Options; -namespace DnsmasqWebUI.Configuration; +namespace DnsmasqWebUI.Models.Config; /// /// Validates required dnsmasq options at startup. If config is missing or default paths don't point at existing files, diff --git a/src/DnsmasqWebUI/Services/Abstractions/ConfigSetSnapshot.cs b/src/DnsmasqWebUI/Models/Contracts/ConfigSetSnapshot.cs similarity index 77% rename from src/DnsmasqWebUI/Services/Abstractions/ConfigSetSnapshot.cs rename to src/DnsmasqWebUI/Models/Contracts/ConfigSetSnapshot.cs index a5d946d..18fbc4a 100644 --- a/src/DnsmasqWebUI/Services/Abstractions/ConfigSetSnapshot.cs +++ b/src/DnsmasqWebUI/Models/Contracts/ConfigSetSnapshot.cs @@ -1,7 +1,8 @@ using DnsmasqWebUI.Models.Dhcp; -using DnsmasqWebUI.Models.EffectiveConfig; +using DnsmasqWebUI.Models.Dnsmasq; +using DnsmasqWebUI.Models.Dnsmasq.EffectiveConfig; -namespace DnsmasqWebUI.Services.Abstractions; +namespace DnsmasqWebUI.Models.Contracts; /// Immutable snapshot from the config set cache: config set, effective config, sources, managed file content, and DHCP host entries (one read per refresh). public record ConfigSetSnapshot( diff --git a/src/DnsmasqWebUI/Services/Abstractions/HostsSnapshot.cs b/src/DnsmasqWebUI/Models/Contracts/HostsSnapshot.cs similarity index 86% rename from src/DnsmasqWebUI/Services/Abstractions/HostsSnapshot.cs rename to src/DnsmasqWebUI/Models/Contracts/HostsSnapshot.cs index 08b8441..7a6a4c6 100644 --- a/src/DnsmasqWebUI/Services/Abstractions/HostsSnapshot.cs +++ b/src/DnsmasqWebUI/Models/Contracts/HostsSnapshot.cs @@ -1,6 +1,6 @@ using DnsmasqWebUI.Models.Hosts; -namespace DnsmasqWebUI.Services.Abstractions; +namespace DnsmasqWebUI.Models.Contracts; /// Immutable snapshot from the hosts cache: managed hosts file entries and read-only hosts files (system + addn-hosts). public record HostsSnapshot( diff --git a/src/DnsmasqWebUI/Models/Contracts/ManagedConfigContent.cs b/src/DnsmasqWebUI/Models/Contracts/ManagedConfigContent.cs new file mode 100644 index 0000000..8961628 --- /dev/null +++ b/src/DnsmasqWebUI/Models/Contracts/ManagedConfigContent.cs @@ -0,0 +1,6 @@ +using DnsmasqWebUI.Models.Config; + +namespace DnsmasqWebUI.Models.Contracts; + +/// Full managed file content. EffectiveHostsPathInFile is parsed from AddnHosts line if present (for display only). +public record ManagedConfigContent(IReadOnlyList Lines, string EffectiveHostsPathInFile); diff --git a/src/DnsmasqWebUI/Services/Abstractions/ProcessRunResult.cs b/src/DnsmasqWebUI/Models/Contracts/ProcessRunResult.cs similarity index 79% rename from src/DnsmasqWebUI/Services/Abstractions/ProcessRunResult.cs rename to src/DnsmasqWebUI/Models/Contracts/ProcessRunResult.cs index 9aef926..5b7754a 100644 --- a/src/DnsmasqWebUI/Services/Abstractions/ProcessRunResult.cs +++ b/src/DnsmasqWebUI/Models/Contracts/ProcessRunResult.cs @@ -1,6 +1,6 @@ -namespace DnsmasqWebUI.Services.Abstractions; +namespace DnsmasqWebUI.Models.Contracts; -/// Result of running a shell command via . +/// Result of running a shell command via IProcessRunner. /// Process exit code, or null if timed out or failed to start. /// Standard output. /// Standard error. diff --git a/src/DnsmasqWebUI/Models/Status/DnsmasqServiceStatus.cs b/src/DnsmasqWebUI/Models/Dnsmasq/DnsmasqServiceStatus.cs similarity index 93% rename from src/DnsmasqWebUI/Models/Status/DnsmasqServiceStatus.cs rename to src/DnsmasqWebUI/Models/Dnsmasq/DnsmasqServiceStatus.cs index 6bbd4b2..487a05e 100644 --- a/src/DnsmasqWebUI/Models/Status/DnsmasqServiceStatus.cs +++ b/src/DnsmasqWebUI/Models/Dnsmasq/DnsmasqServiceStatus.cs @@ -1,6 +1,6 @@ -using DnsmasqWebUI.Models.EffectiveConfig; +using DnsmasqWebUI.Models.Dnsmasq.EffectiveConfig; -namespace DnsmasqWebUI.Models.Status; +namespace DnsmasqWebUI.Models.Dnsmasq; /// /// Dnsmasq service and config status returned by GET api/status. @@ -21,6 +21,7 @@ namespace DnsmasqWebUI.Models.Status; /// True if exists on disk. /// True when dhcp-leasefile is set in effective config. /// True if is set and the file exists on disk. +/// Ordered list of config files (main + conf-file + conf-dir) for "Config files (load order)" in the UI. Null or empty when config set is unavailable. /// True when ReloadCommand is set in app config (reload after save). /// True when StatusCommand is set (used to determine dnsmasq active/inactive). /// True when StatusShowCommand is set (full service status output on Dnsmasq page). @@ -52,6 +53,7 @@ public record DnsmasqServiceStatus( bool ManagedFilePathExists, bool LeasesPathConfigured, bool LeasesPathExists, + IReadOnlyList? ConfigFiles, bool ReloadCommandConfigured, bool StatusCommandConfigured, bool StatusShowConfigured, diff --git a/src/DnsmasqWebUI/Models/EffectiveConfig/ConfigValueSource.cs b/src/DnsmasqWebUI/Models/Dnsmasq/EffectiveConfig/ConfigValueSource.cs similarity index 97% rename from src/DnsmasqWebUI/Models/EffectiveConfig/ConfigValueSource.cs rename to src/DnsmasqWebUI/Models/Dnsmasq/EffectiveConfig/ConfigValueSource.cs index 94357cc..34a31b6 100644 --- a/src/DnsmasqWebUI/Models/EffectiveConfig/ConfigValueSource.cs +++ b/src/DnsmasqWebUI/Models/Dnsmasq/EffectiveConfig/ConfigValueSource.cs @@ -1,4 +1,4 @@ -namespace DnsmasqWebUI.Models.EffectiveConfig; +namespace DnsmasqWebUI.Models.Dnsmasq.EffectiveConfig; /// /// Where an effective config value came from (which file and line). Used so the UI can show exactly which file diff --git a/src/DnsmasqWebUI/Models/EffectiveConfig/DnsmasqConfFileSource.cs b/src/DnsmasqWebUI/Models/Dnsmasq/EffectiveConfig/DnsmasqConfFileSource.cs similarity index 76% rename from src/DnsmasqWebUI/Models/EffectiveConfig/DnsmasqConfFileSource.cs rename to src/DnsmasqWebUI/Models/Dnsmasq/EffectiveConfig/DnsmasqConfFileSource.cs index 13921bd..923fbb0 100644 --- a/src/DnsmasqWebUI/Models/EffectiveConfig/DnsmasqConfFileSource.cs +++ b/src/DnsmasqWebUI/Models/Dnsmasq/EffectiveConfig/DnsmasqConfFileSource.cs @@ -1,4 +1,4 @@ -namespace DnsmasqWebUI.Models.EffectiveConfig; +namespace DnsmasqWebUI.Models.Dnsmasq.EffectiveConfig; /// Source of a file in the dnsmasq config set: main config, conf-file=, or conf-dir=. public enum DnsmasqConfFileSource diff --git a/src/DnsmasqWebUI/Models/EffectiveConfig/DnsmasqConfigSet.cs b/src/DnsmasqWebUI/Models/Dnsmasq/EffectiveConfig/DnsmasqConfigSet.cs similarity index 87% rename from src/DnsmasqWebUI/Models/EffectiveConfig/DnsmasqConfigSet.cs rename to src/DnsmasqWebUI/Models/Dnsmasq/EffectiveConfig/DnsmasqConfigSet.cs index 80babae..fbb6834 100644 --- a/src/DnsmasqWebUI/Models/EffectiveConfig/DnsmasqConfigSet.cs +++ b/src/DnsmasqWebUI/Models/Dnsmasq/EffectiveConfig/DnsmasqConfigSet.cs @@ -1,4 +1,4 @@ -namespace DnsmasqWebUI.Models.EffectiveConfig; +namespace DnsmasqWebUI.Models.Dnsmasq.EffectiveConfig; /// Ordered set of dnsmasq config files (main + conf-file + conf-dir). ManagedFilePath is the single config file we read/write; ManagedHostsFilePath is the single hosts file we read/write. public record DnsmasqConfigSet( diff --git a/src/DnsmasqWebUI/Models/EffectiveConfig/DnsmasqConfigSetEntry.cs b/src/DnsmasqWebUI/Models/Dnsmasq/EffectiveConfig/DnsmasqConfigSetEntry.cs similarity index 81% rename from src/DnsmasqWebUI/Models/EffectiveConfig/DnsmasqConfigSetEntry.cs rename to src/DnsmasqWebUI/Models/Dnsmasq/EffectiveConfig/DnsmasqConfigSetEntry.cs index d198ccd..3fb7113 100644 --- a/src/DnsmasqWebUI/Models/EffectiveConfig/DnsmasqConfigSetEntry.cs +++ b/src/DnsmasqWebUI/Models/Dnsmasq/EffectiveConfig/DnsmasqConfigSetEntry.cs @@ -1,4 +1,4 @@ -namespace DnsmasqWebUI.Models.EffectiveConfig; +namespace DnsmasqWebUI.Models.Dnsmasq.EffectiveConfig; /// One file in the dnsmasq config set (main or included). IsManaged is true only for the app-managed file. public record DnsmasqConfigSetEntry(string Path, string FileName, DnsmasqConfFileSource Source, bool IsManaged); diff --git a/src/DnsmasqWebUI/Models/EffectiveConfig/EffectiveConfigDisplayMode.cs b/src/DnsmasqWebUI/Models/Dnsmasq/EffectiveConfig/EffectiveConfigDisplayMode.cs similarity index 88% rename from src/DnsmasqWebUI/Models/EffectiveConfig/EffectiveConfigDisplayMode.cs rename to src/DnsmasqWebUI/Models/Dnsmasq/EffectiveConfig/EffectiveConfigDisplayMode.cs index 71f7a0d..a5a38f4 100644 --- a/src/DnsmasqWebUI/Models/EffectiveConfig/EffectiveConfigDisplayMode.cs +++ b/src/DnsmasqWebUI/Models/Dnsmasq/EffectiveConfig/EffectiveConfigDisplayMode.cs @@ -1,4 +1,4 @@ -namespace DnsmasqWebUI.Models.EffectiveConfig; +namespace DnsmasqWebUI.Models.Dnsmasq.EffectiveConfig; /// /// Display mode for effective-config fields. Cascaded from the parent so that diff --git a/src/DnsmasqWebUI/Models/EffectiveConfig/EffectiveConfigFieldDescriptor.cs b/src/DnsmasqWebUI/Models/Dnsmasq/EffectiveConfig/EffectiveConfigFieldDescriptor.cs similarity index 89% rename from src/DnsmasqWebUI/Models/EffectiveConfig/EffectiveConfigFieldDescriptor.cs rename to src/DnsmasqWebUI/Models/Dnsmasq/EffectiveConfig/EffectiveConfigFieldDescriptor.cs index 3824096..00d6c6a 100644 --- a/src/DnsmasqWebUI/Models/EffectiveConfig/EffectiveConfigFieldDescriptor.cs +++ b/src/DnsmasqWebUI/Models/Dnsmasq/EffectiveConfig/EffectiveConfigFieldDescriptor.cs @@ -1,6 +1,6 @@ -using DnsmasqWebUI.Models.Status; +using DnsmasqWebUI.Models.Dnsmasq; -namespace DnsmasqWebUI.Models.EffectiveConfig; +namespace DnsmasqWebUI.Models.Dnsmasq.EffectiveConfig; /// /// Glue for rendering one effective-config field: metadata (section, option name) plus delegates that resolve value, source, and items from Status at render time. diff --git a/src/DnsmasqWebUI/Models/EffectiveConfig/EffectiveConfigSources.cs b/src/DnsmasqWebUI/Models/Dnsmasq/EffectiveConfig/EffectiveConfigSources.cs similarity index 97% rename from src/DnsmasqWebUI/Models/EffectiveConfig/EffectiveConfigSources.cs rename to src/DnsmasqWebUI/Models/Dnsmasq/EffectiveConfig/EffectiveConfigSources.cs index 8ee3392..b715fe6 100644 --- a/src/DnsmasqWebUI/Models/EffectiveConfig/EffectiveConfigSources.cs +++ b/src/DnsmasqWebUI/Models/Dnsmasq/EffectiveConfig/EffectiveConfigSources.cs @@ -1,4 +1,4 @@ -namespace DnsmasqWebUI.Models.EffectiveConfig; +namespace DnsmasqWebUI.Models.Dnsmasq.EffectiveConfig; /// /// Source file for each field in . diff --git a/src/DnsmasqWebUI/Models/EffectiveConfig/EffectiveDnsmasqConfig.cs b/src/DnsmasqWebUI/Models/Dnsmasq/EffectiveConfig/EffectiveDnsmasqConfig.cs similarity index 98% rename from src/DnsmasqWebUI/Models/EffectiveConfig/EffectiveDnsmasqConfig.cs rename to src/DnsmasqWebUI/Models/Dnsmasq/EffectiveConfig/EffectiveDnsmasqConfig.cs index c369f05..5846d0d 100644 --- a/src/DnsmasqWebUI/Models/EffectiveConfig/EffectiveDnsmasqConfig.cs +++ b/src/DnsmasqWebUI/Models/Dnsmasq/EffectiveConfig/EffectiveDnsmasqConfig.cs @@ -1,4 +1,4 @@ -namespace DnsmasqWebUI.Models.EffectiveConfig; +namespace DnsmasqWebUI.Models.Dnsmasq.EffectiveConfig; /// /// Effective dnsmasq config after reading all config files (main + conf-file + conf-dir) diff --git a/src/DnsmasqWebUI/Models/Status/SaveWithReloadResult.cs b/src/DnsmasqWebUI/Models/Dnsmasq/SaveWithReloadResult.cs similarity index 66% rename from src/DnsmasqWebUI/Models/Status/SaveWithReloadResult.cs rename to src/DnsmasqWebUI/Models/Dnsmasq/SaveWithReloadResult.cs index 39b14e3..4caadda 100644 --- a/src/DnsmasqWebUI/Models/Status/SaveWithReloadResult.cs +++ b/src/DnsmasqWebUI/Models/Dnsmasq/SaveWithReloadResult.cs @@ -1,6 +1,6 @@ -using DnsmasqWebUI.Services.Abstractions; +using DnsmasqWebUI.Infrastructure.Services.Abstractions; -namespace DnsmasqWebUI.Models.Status; +namespace DnsmasqWebUI.Models.Dnsmasq; /// Result of a save operation that triggers a dnsmasq reload (e.g. PUT api/hosts, PUT api/dhcp/hosts). public record SaveWithReloadResult(bool Saved, ReloadResult Reload); diff --git a/src/DnsmasqWebUI/Program.cs b/src/DnsmasqWebUI/Program.cs index 82b5e03..045eaf1 100644 --- a/src/DnsmasqWebUI/Program.cs +++ b/src/DnsmasqWebUI/Program.cs @@ -1,6 +1,7 @@ using DnsmasqWebUI.Components; +using DnsmasqWebUI.Models.Config; using DnsmasqWebUI.Extensions; -using DnsmasqWebUI.Configuration; +using DnsmasqWebUI.Infrastructure.Helpers.Http; using Microsoft.Extensions.Options; // When not in Development, use the app's directory (not CWD) so static assets work when run via symlink or from any CWD. @@ -25,11 +26,7 @@ builder.Services.AddHttpContextAccessor(); builder.Services.AddDnsmasqApiHttpClients(); builder.Services.AddControllers() - .AddJsonOptions(o => - { - o.JsonSerializerOptions.PropertyNamingPolicy = System.Text.Json.JsonNamingPolicy.CamelCase; - o.JsonSerializerOptions.Converters.Add(new System.Text.Json.Serialization.JsonStringEnumConverter()); - }); + .AddJsonOptions(o => ApiJsonOptions.ConfigureServer(o.JsonSerializerOptions)); builder.Services.AddRazorComponents() .AddInteractiveServerComponents(); diff --git a/testdata/README b/testdata/README index 18f3567..89fbcc8 100644 --- a/testdata/README +++ b/testdata/README @@ -9,7 +9,7 @@ Source for the Docker test harness: `scripts/prepare-test-mount.sh` syncs this d | **dnsmasq-test.conf** | Harness main config (`/data/dnsmasq-test.conf`). DNS + DHCP; `conf-dir=/data/dnsmasq.d`, multiple server/addn-hosts/address/dhcp-range. | | **dnsmasq.conf** | Minimal main (conf-dir only). Used by unit tests when conf-dir is absent. | | **dnsmasq.conf.example** | Commented example; not used by harness or tests. | -| **dnsmasq.d/** | Included configs (alphabetical order). | +| **dnsmasq.d/** | Included configs (conf-dir; alphabetical order). In the harness, "Config files (load order)" should list main, then these, then zz-dnsmasq-webui.conf. | | **dnsmasq.d/01-other.conf** | `domain=local`. Not managed. | | **dnsmasq.d/02-servers.conf** | Extra `server=` / `local=` (readonly in UI). | | **dnsmasq.d/03-more.conf** | `resolv-file=`, `dhcp-option=`. Not managed. | @@ -20,3 +20,5 @@ Source for the Docker test harness: `scripts/prepare-test-mount.sh` syncs this d | **leases** | Sample DHCP leases for unit tests only; not synced to the harness. The harness uses the path from config (`dhcp-leasefile=` in dnsmasq-test.conf); the app discovers and monitors that file; dnsmasq creates it when DHCP clients obtain addresses. | Harness usage (start/stop/tidy): see **agents.md** in the repo root. + +**Manual check for conf-dir fix:** After starting the harness, open the app and confirm "Config files (load order)" shows: main (`/data/dnsmasq-test.conf`), then conf-dir files (`01-other.conf`, `02-servers.conf`, `03-more.conf`, `dhcp.conf`), then managed (`zz-dnsmasq-webui.conf`). Unit test `GetIncludedPathsWithSource_ConfDirAndConfFile_ReturnsAllFilesInLoadOrderWithSources` asserts the same layout (main + conf-dir + conf-file) with sources. diff --git a/testdata/zz-dnsmasq-webui.conf b/testdata/zz-dnsmasq-webui.conf new file mode 100644 index 0000000..acc2481 --- /dev/null +++ b/testdata/zz-dnsmasq-webui.conf @@ -0,0 +1 @@ +addn-hosts=/home/alex/Documents/Projects/servers/dnsmasq-webui/testdata/zz-dnsmasq-webui.hosts diff --git a/testdata/zz-dnsmasq-webui.hosts b/testdata/zz-dnsmasq-webui.hosts new file mode 100644 index 0000000..e69de29