- Register ISettingsModalService, INotificationService, IUpdateCheckService via assembly scanning (marker interfaces)
- Add IApplicationOptionsValidator<TOptions> marker; scan only those for IValidateOptions registration
- Fix ApplicationLifecycleLoggingHostedService: log RestartCommand/ReloadCommand/StatusCommand (no ApplyCommand)
- NavMenu: spin animation for update-check button icon when checking; single icon with conditional nav-footer-spin class
- Add NotificationHost/NotificationToast components and NotificationService for toasts
- Effective config save flow, restore result, save service; various EffectiveConfig and DnsmasqOptions/validator updates
- Test and script updates; docker-compose.test.yml, appsettings, DnsmasqConfKeys/tooltips/sections
- Move registration marker interfaces to Registration/Abstractions
- Add feature folders with Abstractions subfolders: EffectiveConfig,
Dnsmasq/Config, Dnsmasq/Hosts, Dnsmasq/Leases, Dnsmasq/Reload,
Logs, Updates, UI/Settings, Common/Process
- Place interfaces in each feature's Abstractions namespace; align
implementation namespaces to folder paths
- Update Program, extensions, controllers, hubs, helpers, components,
and tests to use new namespaces
- Remove old flat Services/Abstractions and root-level service files
- Add Components/Shared/StatusBadge + StatusBadgeKind; centralize badge
styles and map Kind to Bootstrap classes. Refactor OptionStateBadge to
compose StatusBadge; add OK/Cancel/Revert when active with
OnConfirm/OnCancel/OnRevert callbacks and exit-action flag in
EffectiveConfigFieldDisplay.
- Fix _valueAtActivation to use Descriptor.GetValue() (persisted value)
so save modal shows correct before/after and no phantom pending on
revert-to-original.
- OptionHelpController: add CancelCloseFromLabelEnter() and call when
opening/switching help so pending 300ms close does not dismiss new help.
- Migrate Dnsmasq.razor and HostsFileSection to StatusBadge; save modal
option chip uses NeutralPill. Centralize badge CSS in StatusBadge.
- Badge styling: font-size/weight/padding, hover lift+shadow, active
press with visible release animation.
- Toolbar: single expand/collapse toggle; change count after Save/Exit.
- Save modal: selective revert per change; modern list styling, empty state;
Session + OnReverted; Save disabled when no changes.
- Cancel: prevent blur-before-click (mousedown preventDefault on cancel
badge); _cancelRequested so cancel never commits.
- DeactivateFieldAsync in UiContext + Section; OptionStateBadge cancel
clears draft and deactivates.
Replace separate Expand all / Collapse all buttons with one toggle button.
Icon and label switch based on AllSectionsExpanded (expand when any
section collapsed, collapse when all expanded).
- Use pending != null ? pending.NewValue : Descriptor.GetValue() so clearing
a value (port, log-queries, local-service) shows '(not set)' instead of
reverting to the old value; fix effective value and commit logic in
EffectiveConfigFieldDisplay.
- Rename ReadonlyBadge to OptionStateBadge; add editable/pending state and
use it from EffectiveConfigOptionItem, EffectiveConfigFieldDisplay,
EffectiveConfigMultiValueItem.
- OptionHelpLabel: default CssClass from HelpKey (EffectiveCssClass).
Add readonly badge/popover guidance, pending change tracking, and save confirmation flow for effective config editing. Update field renderers and config services to commit staged changes, and remove bundled Bootstrap dist assets from wwwroot.
- Add EffectiveConfigOptionKindMap (Single/Multi kind + LastWins/Flag/Multi parser
behavior) and drive field builder + ConfigSetCache from it.
- Single AddDescriptor() in list extensions that dispatches via kind map to
AddSingleDescriptor/AddMultiDescriptor.
- ConfigSetCache: ParseOptionValue/ParseOptionWithSource dispatch by option
name; BuildEffectiveConfig and BuildEffectiveConfigSources use them with
casts; special cases (NoHosts, AddnHosts, server/local, DhcpLeaseFilePath)
remain explicit.
- Add IntegerValueDisplay, LogQueriesDisplay, LocalServiceDisplay; register
integers with bounds/units in EffectiveConfigRenderFragmentRegistry.
- Add EffectiveConfigDescriptorFactory, EffectiveIntegerConfigFieldDescriptor,
IEffectiveConfigFieldDescriptor for integer descriptor metadata.
- NavMenu: show short date in update-check tooltip when last check was not today
- Settings modal: expand/collapse all sections with bi-arrows-expand/collapse icons
- Effective config: add Expand all button (bi-arrows-expand), same icon style as toolbar
- Rename settings-modal.js to dialog.js; add config-option-help.js, JS extension helpers
- Config option help modal and client settings/dialog wiring updates
- Add DnsmasqConfKeys for DHCP advanced, DHCPv6/RA, TFTP/PXE, DNSSEC, Other
- Add EffectiveDnsmasqConfig + EffectiveConfigSources for new options
- Wire ConfigSetCache BuildEffectiveConfig/Sources/CreateDefault
- Add EffectiveConfigFieldBuilder sections (TFTP/PXE, DNSSEC), tooltips, registry
- Update EffectiveDnsmasqConfigTests CreateConfig with all new params
- Add one parser test per conf option with documented example (200 tests)
- Replace FieldBounds with generic ClientSettingsField<T> abstraction
- Add IClientSettingsField, ClientSettingsMinMaxField, ClientSettingsBoolField
- Put abstractions in Models/Client/Abstractions namespace
- Fields hold values; CoerceValue for clamping on set
- ClientSettingsService hydrates fields on load; ToDto for save
- Settings modal binds directly to field values, collapsible only for All context
- Add LogsPreView with ShouldRender freeze to prevent Blazor overwriting JS-owned logs DOM
- Add left margin to static group heading in single-section mode
- Add app version to sidebar footer with link to GitHub release
- CSS cache busting: append version query string to static assets
- Remove LogEvents; improve startup logging with version, env, paths, uptime
- Settings modal: collapsible groups with search-aware expand/collapse
- Release workflow and publish script: pass version from tag for builds
GitHub can return release body with unescaped control characters (e.g. from
generated release notes); jq rejects them. Sanitize response in fetch_release
and list_releases before piping to jq (keep \n and \r).
- 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
- Use set +e around env/grep/read so grep (no match), read (EOF), and
[ -f ]/[ -s ] do not exit the script when no DNSMASQ_WEBUI_* or --set
- Write env vars to temp file .env then read; only process when file
exists and is non-empty
- Add || true after [ -s CONFIG_SET_FILE ] && cat so empty CONFIG_SET_FILE
does not trigger exit
Fixes install/update with no config (e.g. plain --version/--update --dir)
failing silently under sh (dash).
- Echo 'Installing dnsmasq-webui...' at start of do_install so output is visible
- Use </dev/null on all curl calls so they do not consume stdin; when run
as 'curl | sh' the script was being eaten and exited before any echo
- EnsureManagedConfigHostedService: write conf-file=/absolute/path so
dnsmasq finds the managed file regardless of CWD (e.g. systemd with CWD=/)
- Dnsmasq.razor: show Config files (load order) as single list with all
paths (main, conf-dir, managed); fallback to Main/Managed when set unavailable
- Add DnsmasqFileEncoding.Utf8NoBom for all config and hosts file writes
- Strip BOM from first line when reading main config and managed config
- Prevents 'bad option at line 1' when dnsmasq loads files written by the app
- Add --set KEY=VALUE and env vars DNSMASQ_WEBUI_* to configure app at install
- Write env file for systemd (system: /etc/default/dnsmasq-webui; user: install-dir/dnsmasq-webui.env)
- Default ASPNETCORE_URLS=http://0.0.0.0:8080 so service listens on 8080
- Preserve existing env file on update (only overwrite when new config or first install)
- README: configure-at-install section, --set in options table, update preserves env note
- README: use master branch in raw GitHub URL (repo default branch)
- install.sh: trim repo and asset URLs (fix malformed URL when script has CRLF or trailing whitespace)
- Config set cache: ConfigSetCache, IConfigSetCache, ConfigSetSnapshot. One read of
main + conf-dir + managed config, build snapshot via parser pathToLines overloads.
Watchers (main + managed file), 120s staleness, Invalidate(), NotifyWeWroteManagedConfig.
- Parser: use DnsmasqConfKeys in DnsmasqConfIncludeParser and ConfigSetCache; add
DnsmasqConfKeys.cs and use in DnsmasqConfOptionRegistry.
- ConfigSetSnapshot: add DhcpHostEntries (built from pathToLines). ReadDhcpHostsAsync
and ReadManagedConfigAsync use cache; writes call NotifyWeWroteManagedConfig.
- DnsmasqConfigSetService: use IConfigSetCache only; all data from snapshot.
- Hosts cache: HostsCache, IHostsCache, HostsSnapshot (managed + read-only). Watchers
on managed and system hosts path, staleness, NotifyWeWroteManagedHosts.
- HostsFileService and HostsController: use IHostsCache for ReadAsync and GetReadOnly.
- Tests: DnsmasqConfigSetServiceTests use ConfigSetCache with test options.
- Managed hosts file (zz-dnsmasq-webui.hosts): app reads/writes only this;
addn-hosts in managed config loads it. SystemHostsPath is read-only display.
- Hosts page: editable managed file; then 'Other hosts (read-only)' (system
hosts when configured and !no-hosts, plus addn-hosts excluding managed).
- GET api/hosts/readonly for read-only files; no-hosts hides system hosts.
- HostsFileSection.razor component; fix space before read-only badge.
- Test harness: show container /etc/hosts (default SystemHostsPath).
- Config parsing/sources, tests and client updates for new hosts model.
- Static hosts: read dhcp-host from all config files; show external as read-only
with Source column; IsEditable + SourcePath on DhcpHostEntry; reject save if
managed entry duplicates MAC from another file (400 + message)
- Lease field: single input + datalist (type or select), remove Other dropdown
- MAC field: single input + datalist from current leases, TryFillFromLeaseByMac
- Add host: default Name=*, suggest next free IP from DHCP range
- Leases: configurable refresh polling; Settings modal for interval (client-side)
- DhcpHostsClient: surface 400 error body message on save failure
- App title config (ApplicationOptions), nav/layout/error/hosts tweaks