feat: Cloudflare DNS management with automatic record sync

Add flexible DNS management to Docker Watcher. By default, wildcard DNS
is assumed (current behavior). When disabled, users can configure a
Cloudflare DNS provider with API token and zone selection. DNS A records
are automatically created/updated/deleted in sync with proxy consumers
(deployed instances and standalone proxies).

- Settings: wildcard_dns toggle, dns_provider, cloudflare credentials
- Cloudflare client: Provider interface with EnsureRecord/DeleteRecord/ListRecords
- DNS lifecycle hooks in deployer and proxy manager (best-effort)
- Settings UI: DNS config section with provider picker, zone selector, test button
- DNS Records page at /dns with filtering, sync status, reconciliation
- Records visible in both wildcard and managed modes
- Cleanup on provider change: removes old records when switching modes
This commit is contained in:
2026-04-02 14:49:21 +03:00
parent c9d4895ee3
commit c730cfaa45
46 changed files with 2429 additions and 1260 deletions
+26 -1
View File
@@ -22,7 +22,9 @@ import type {
ValidationResult,
Volume,
VolumeScopeInfo,
BrowseResult
BrowseResult,
DnsZone,
DnsRecordView
} from './types';
// ── Helpers ─────────────────────────────────────────────────────────
@@ -268,6 +270,29 @@ export function listNpmCertificates(): Promise<NpmCertificate[]> {
return get<NpmCertificate[]>('/api/settings/npm-certificates');
}
// ── DNS ────────────────────────────────────────────────────────────
export function testDnsConnection(provider: string, token: string, zoneId: string): Promise<{ success: boolean; error?: string }> {
return post<{ success: boolean; error?: string }>('/api/settings/dns/test', { provider, token, zone_id: zoneId });
}
export function listDnsZones(token?: string): Promise<DnsZone[]> {
const params = token ? `?token=${encodeURIComponent(token)}` : '';
return get<DnsZone[]>(`/api/settings/dns/zones${params}`);
}
export function getDnsRecords(): Promise<DnsRecordView[]> {
return get<DnsRecordView[]>('/api/dns/records');
}
export function syncDnsRecords(): Promise<{ created: number; deleted: number; already_synced: number }> {
return post<{ created: number; deleted: number; already_synced: number }>('/api/dns/sync');
}
export function deleteDnsRecord(fqdn: string): Promise<void> {
return del<void>(`/api/dns/records/${encodeURIComponent(fqdn)}`);
}
// ── Health ──────────────────────────────────────────────────────────
export function getHealth(): Promise<{ docker: DockerHealth }> {