From 4c1c19b792bc83e56ebdbe5486f1579706d0205f Mon Sep 17 00:00:00 2001 From: Alex Hope-O'Connor Date: Mon, 9 Mar 2026 23:38:47 +1000 Subject: [PATCH] EffectiveConfig section CSS/panel updates; add ConfigFileList components --- .../Components/Dnsmasq/ConfigFileList.razor | 72 ++++++++++++++ .../Dnsmasq/ConfigFileList.razor.css | 98 +++++++++++++++++++ .../Dnsmasq/ConfigFileListItem.razor | 30 ++++++ .../EffectiveConfigSection.razor.css | 16 +++ .../EffectiveConfigSectionPanel.razor | 6 +- 5 files changed, 220 insertions(+), 2 deletions(-) create mode 100644 src/DnsmasqWebUI/Components/Dnsmasq/ConfigFileList.razor create mode 100644 src/DnsmasqWebUI/Components/Dnsmasq/ConfigFileList.razor.css create mode 100644 src/DnsmasqWebUI/Components/Dnsmasq/ConfigFileListItem.razor diff --git a/src/DnsmasqWebUI/Components/Dnsmasq/ConfigFileList.razor b/src/DnsmasqWebUI/Components/Dnsmasq/ConfigFileList.razor new file mode 100644 index 0000000..a391cf1 --- /dev/null +++ b/src/DnsmasqWebUI/Components/Dnsmasq/ConfigFileList.razor @@ -0,0 +1,72 @@ +@namespace DnsmasqWebUI.Components.Dnsmasq +@using DnsmasqWebUI.Models.Dnsmasq +@using DnsmasqWebUI.Models.Dnsmasq.EffectiveConfig + +
+ @{ + var configFileList = Status.ConfigFiles?.Count > 0 ? Status.ConfigFiles : ConfigSet?.Files; + var hasList = configFileList != null && configFileList.Count > 0; + var mainEntry = hasList ? configFileList![0] : null; + var hasMoreThanOne = hasList && configFileList!.Count > 1; + var fileCount = hasList ? configFileList!.Count : 2; + var showToggle = hasMoreThanOne || (!hasList && Status.ManagedFilePath != null); + } +

Config files (load order)

+
+
+
    + @if (hasList) + { + + } + else + { +
  • Main config: @(Status.MainConfigPath ?? "—") @(Status.MainConfigPathExists ? "✓" : "(missing)")
  • + } +
+ @if (showToggle) + { +
+
+
    + @if (hasList) + { + @foreach (var f in configFileList!.Skip(1)) + { + + } + } + else + { +
  • Managed file: @(Status.ManagedFilePath ?? "—") @(Status.ManagedFilePathExists ? "✓" : "(not created yet)")
  • + } +
+
+
+ } +
+ @if (showToggle) + { + + } +
+
+ +@code { + [Parameter, EditorRequired] + public DnsmasqServiceStatus Status { get; set; } = null!; + + [Parameter] + public DnsmasqConfigSet? ConfigSet { get; set; } + + private bool _expanded; + + private void ToggleExpanded() => _expanded = !_expanded; +} diff --git a/src/DnsmasqWebUI/Components/Dnsmasq/ConfigFileList.razor.css b/src/DnsmasqWebUI/Components/Dnsmasq/ConfigFileList.razor.css new file mode 100644 index 0000000..c4c0efd --- /dev/null +++ b/src/DnsmasqWebUI/Components/Dnsmasq/ConfigFileList.razor.css @@ -0,0 +1,98 @@ +/* Config file list: card-style block with expandable list */ +.config-file-list { + --cfl-radius: var(--app-radius, 0.375rem); + --cfl-spacing: var(--app-spacing-md, 1rem); + --cfl-border: 1px solid #e9ecef; + --cfl-bg: #f8f9fa; + --cfl-bg-hover: #e9ecef; +} + +.config-file-list { + border-radius: var(--cfl-radius); + border: var(--cfl-border); + background: var(--cfl-bg); + padding: var(--cfl-spacing); + margin-top: 0.5rem; +} + +.config-file-list .config-file-list-title { + font-size: 0.9375rem; + font-weight: 600; + color: #212529; + margin-bottom: 0.5rem; +} + +.config-file-list ul { + margin: 0; + padding-left: 1.25rem; + font-size: 0.875rem; + line-height: 1.6; + color: #495057; +} + +.config-file-list ul li { + padding: 0.2rem 0; +} + +/* Content and toggle on one row: list grows, toggle at bottom-right */ +.config-file-list-content-row { + display: flex; + align-items: flex-end; + gap: 0.75rem; +} + +.config-file-list-list { + flex: 1; + min-width: 0; +} + +.config-file-list .config-file-list-toggle { + display: inline-flex; + align-items: center; + gap: 0.35rem; + flex-shrink: 0; + padding: 0.35rem 0.5rem; + font-size: 0.8125rem; + font-weight: 500; + color: #0d6efd; + background: transparent; + border: none; + border-radius: var(--cfl-radius); + cursor: pointer; + transition: background-color 0.15s ease, color 0.15s ease; +} + +.config-file-list .config-file-list-toggle:hover { + background: var(--cfl-bg-hover); + color: #0a58ca; +} + +.config-file-list .config-file-list-toggle:focus-visible { + outline: 0; + box-shadow: 0 0 0 0.2rem rgba(13, 110, 253, 0.25); +} + +.config-file-list .config-file-list-toggle .bi { + font-size: 0.875rem; + transition: transform 0.2s ease; +} + +/* Expandable section: animate height with grid 0fr → 1fr */ +.config-file-list-expandable { + display: grid; + grid-template-rows: 0fr; + transition: grid-template-rows 0.25s ease-out; +} + +.config-file-list-expandable.expanded { + grid-template-rows: 1fr; +} + +.config-file-list-expandable-inner { + min-height: 0; + overflow: hidden; +} + +.config-file-list-expandable-inner ul { + padding-top: 0.15rem; +} diff --git a/src/DnsmasqWebUI/Components/Dnsmasq/ConfigFileListItem.razor b/src/DnsmasqWebUI/Components/Dnsmasq/ConfigFileListItem.razor new file mode 100644 index 0000000..8fc7b41 --- /dev/null +++ b/src/DnsmasqWebUI/Components/Dnsmasq/ConfigFileListItem.razor @@ -0,0 +1,30 @@ +@namespace DnsmasqWebUI.Components.Dnsmasq +@using DnsmasqWebUI.Models.Dnsmasq +@using DnsmasqWebUI.Models.Dnsmasq.EffectiveConfig + +
  • + @if (Entry.IsManaged) + { + + } + else + { + + } + @Entry.Path + @{ + var isMain = string.Equals(Entry.Path, Status.MainConfigPath, StringComparison.Ordinal); + var isManaged = Entry.IsManaged; + var exists = isMain ? Status.MainConfigPathExists : (isManaged ? Status.ManagedFilePathExists : (bool?)null); + } + @if (exists == true) { ✓ } + @if (exists == false) { @(isManaged ? "(not created yet)" : "(missing)") } +
  • + +@code { + [Parameter, EditorRequired] + public DnsmasqConfigSetEntry Entry { get; set; } = null!; + + [Parameter, EditorRequired] + public DnsmasqServiceStatus Status { get; set; } = null!; +} diff --git a/src/DnsmasqWebUI/Components/EffectiveConfig/Composition/EffectiveConfigSection.razor.css b/src/DnsmasqWebUI/Components/EffectiveConfig/Composition/EffectiveConfigSection.razor.css index f38ce53..f931d6d 100644 --- a/src/DnsmasqWebUI/Components/EffectiveConfig/Composition/EffectiveConfigSection.razor.css +++ b/src/DnsmasqWebUI/Components/EffectiveConfig/Composition/EffectiveConfigSection.razor.css @@ -125,6 +125,22 @@ opacity: 1; } +/* Animated expand/collapse: grid 0fr → 1fr */ +::deep .ec-section-collapse { + display: grid; + grid-template-rows: 0fr; + transition: grid-template-rows 0.25s ease-out; +} + +::deep .ec-section-collapse-open { + grid-template-rows: 1fr; +} + +::deep .ec-section-collapse-inner { + min-height: 0; + overflow: hidden; +} + ::deep .ec-section-body { padding: 0.25rem 1rem 0.5rem 1rem; padding-left: 1rem; diff --git a/src/DnsmasqWebUI/Components/EffectiveConfig/Composition/EffectiveConfigSectionPanel.razor b/src/DnsmasqWebUI/Components/EffectiveConfig/Composition/EffectiveConfigSectionPanel.razor index 9b2c732..3075f51 100644 --- a/src/DnsmasqWebUI/Components/EffectiveConfig/Composition/EffectiveConfigSectionPanel.razor +++ b/src/DnsmasqWebUI/Components/EffectiveConfig/Composition/EffectiveConfigSectionPanel.razor @@ -8,8 +8,9 @@ @Title -
    -
    +
    +
    +
      @foreach (var f in Descriptors) { @@ -18,6 +19,7 @@ IsEditMode="@IsEditMode" IsActive="@(ActiveFieldKey == fieldKey)" /> }
    +