Initial commit: dnsmasq web UI

- ASP.NET Core 9 Blazor Web App + Web API
- Hosts file management (read/write, hosts(5) format)
- DHCP static hosts (dhcp-host=, dnsmasq config)
- DHCP leases (read-only, dnsmasq lease file format)
- Reload and status commands (configurable)
- Docker: standalone app image + Dockerfile.dnsmasq (app + dnsmasq)
- docker-compose.test.yml: app + dhcp-client, testdata mount
- Unit tests (Hosts, DhcpHost, Leases, DnsmasqConfig parsers) using testdata
This commit is contained in:
2026-01-31 03:06:46 +10:00
commit e99b2d0453
105 changed files with 62565 additions and 0 deletions
@@ -0,0 +1,76 @@
using System.Diagnostics;
using DnsmasqWebUI.Options;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Options;
namespace DnsmasqWebUI.Controllers;
[ApiController]
[Route("api/[controller]")]
public class StatusController : ControllerBase
{
private readonly DnsmasqOptions _options;
public StatusController(IOptions<DnsmasqOptions> options)
{
_options = options.Value;
}
[HttpGet]
public IActionResult Get()
{
try
{
var dnsmasqStatus = GetDnsmasqServiceStatus();
return Ok(new
{
hostsPath = _options.HostsPath,
configPath = _options.ConfigPath,
leasesPath = _options.LeasesPath,
hostsPathExists = !string.IsNullOrEmpty(_options.HostsPath) && System.IO.File.Exists(_options.HostsPath),
configPathExists = !string.IsNullOrEmpty(_options.ConfigPath) && System.IO.File.Exists(_options.ConfigPath),
leasesPathConfigured = !string.IsNullOrEmpty(_options.LeasesPath),
leasesPathExists = !string.IsNullOrEmpty(_options.LeasesPath) && System.IO.File.Exists(_options.LeasesPath),
reloadCommandConfigured = !string.IsNullOrWhiteSpace(_options.ReloadCommand),
statusCommandConfigured = !string.IsNullOrWhiteSpace(_options.StatusCommand),
dnsmasqStatus
});
}
catch (Exception ex)
{
return StatusCode(500, new { error = ex.Message });
}
}
/// <summary>Runs StatusCommand if configured; returns "active" | "inactive" | "unknown" | "notConfigured".</summary>
private string GetDnsmasqServiceStatus()
{
if (string.IsNullOrWhiteSpace(_options.StatusCommand))
return "notConfigured";
try
{
using var process = new Process
{
StartInfo = new ProcessStartInfo
{
FileName = "/bin/sh",
Arguments = "-c \"" + _options.StatusCommand.Replace("\"", "\\\"") + "\"",
RedirectStandardOutput = true,
RedirectStandardError = true,
UseShellExecute = false,
CreateNoWindow = true
}
};
process.Start();
process.StandardOutput.ReadToEnd();
process.StandardError.ReadToEnd();
process.WaitForExit(TimeSpan.FromSeconds(5));
return process.ExitCode == 0 ? "active" : "inactive";
}
catch
{
return "unknown";
}
}
}