DHCP: read-only hosts from other configs, combobox inputs, polling, UI fixes

- 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
This commit is contained in:
2026-02-02 00:40:53 +10:00
parent 92dfa2b170
commit a60d0e8db8
26 changed files with 803 additions and 106 deletions
@@ -38,6 +38,15 @@
@bind="_editingSettings.RecentLogsPollingIntervalSeconds" />
</section>
}
@if (ShouldShowSection("Leases"))
{
<section class="mb-4">
<h6 class="text-muted">DHCP leases</h6>
<label for="leases-interval" class="form-label">Refresh interval (seconds)</label>
<input id="leases-interval" type="number" class="form-control" min="5" step="1"
@bind="_editingSettings.LeasesPollingIntervalSeconds" />
</section>
}
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" @onclick="Close">Cancel</button>
@@ -42,7 +42,8 @@ public partial class SettingsModal : IAsyncDisposable
_editingSettings = new ClientSettings
{
ServiceStatusPollingIntervalSeconds = _editingSettings.ServiceStatusPollingIntervalSeconds,
RecentLogsPollingIntervalSeconds = _editingSettings.RecentLogsPollingIntervalSeconds
RecentLogsPollingIntervalSeconds = _editingSettings.RecentLogsPollingIntervalSeconds,
LeasesPollingIntervalSeconds = _editingSettings.LeasesPollingIntervalSeconds
};
}
}
@@ -73,6 +74,8 @@ public partial class SettingsModal : IAsyncDisposable
return key == "ServiceStatus";
if (SettingsContext == SettingsModalContext.LogsPolling)
return key == "Logs";
if (SettingsContext == SettingsModalContext.LeasesPolling)
return key == "Leases";
if (SettingsContext == SettingsModalContext.All)
{
if (string.IsNullOrWhiteSpace(_searchTerm)) return true;
@@ -81,6 +84,7 @@ public partial class SettingsModal : IAsyncDisposable
{
"ServiceStatus" => "service status polling".Contains(term, StringComparison.OrdinalIgnoreCase),
"Logs" => "recent logs polling".Contains(term, StringComparison.OrdinalIgnoreCase),
"Leases" => "dhcp leases refresh polling".Contains(term, StringComparison.OrdinalIgnoreCase),
_ => false
};
}
@@ -93,6 +97,8 @@ public partial class SettingsModal : IAsyncDisposable
_editingSettings.ServiceStatusPollingIntervalSeconds, 5, 300);
_editingSettings.RecentLogsPollingIntervalSeconds = Math.Clamp(
_editingSettings.RecentLogsPollingIntervalSeconds, 5, 300);
_editingSettings.LeasesPollingIntervalSeconds = Math.Clamp(
_editingSettings.LeasesPollingIntervalSeconds, 5, 300);
await ClientSettingsService.SaveSettingsAsync(_editingSettings);
if (_jsModule != null)