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:
+26
-1
@@ -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 }> {
|
||||
|
||||
@@ -16,7 +16,8 @@
|
||||
"proxies": "Proxies",
|
||||
"events": "Events",
|
||||
"settings": "Settings",
|
||||
"logout": "Log out"
|
||||
"logout": "Log out",
|
||||
"dns": "DNS Records"
|
||||
},
|
||||
"dashboard": {
|
||||
"title": "Dashboard",
|
||||
@@ -243,7 +244,26 @@
|
||||
"noCertificate": "None (no SSL)",
|
||||
"clearCertificate": "Clear",
|
||||
"loadingCertificates": "Loading certificates...",
|
||||
"noCertificatesFound": "No wildcard certificates found in NPM"
|
||||
"noCertificatesFound": "No wildcard certificates found in NPM",
|
||||
"dnsConfig": "DNS Configuration",
|
||||
"wildcardDns": "Wildcard DNS is configured",
|
||||
"wildcardDnsHelp": "When enabled, all subdomains resolve to your server via a wildcard DNS rule. Disable to manage DNS records per service.",
|
||||
"dnsProvider": "DNS Provider",
|
||||
"dnsProviderHelp": "Select a DNS provider for automatic record management",
|
||||
"cloudflareApiToken": "Cloudflare API Token",
|
||||
"cloudflareApiTokenHelp": "API token with DNS edit permissions for your zone",
|
||||
"cloudflareApiTokenPlaceholder": "Enter Cloudflare API token",
|
||||
"cloudflareApiTokenConfigured": "API token is configured",
|
||||
"cloudflareZone": "Cloudflare Zone",
|
||||
"cloudflareZoneHelp": "Select the DNS zone to manage records in",
|
||||
"selectZone": "Select Zone",
|
||||
"noZone": "No zone selected",
|
||||
"loadingZones": "Loading zones...",
|
||||
"noZonesFound": "No zones found for this token",
|
||||
"testConnection": "Test Connection",
|
||||
"testingConnection": "Testing...",
|
||||
"connectionSuccess": "Connection successful",
|
||||
"connectionFailed": "Connection failed"
|
||||
},
|
||||
"settingsRegistries": {
|
||||
"title": "Container Registries",
|
||||
@@ -540,6 +560,43 @@
|
||||
"proxies": "Proxies",
|
||||
"recentErrors": "Recent Errors"
|
||||
},
|
||||
"dns": {
|
||||
"title": "DNS Records",
|
||||
"description": "View and manage DNS records created by Docker Watcher.",
|
||||
"wildcardActive": "Wildcard DNS Mode Active",
|
||||
"wildcardActiveDesc": "DNS records are managed externally via wildcard DNS. Disable wildcard DNS in Settings to manage records individually.",
|
||||
"refresh": "Refresh",
|
||||
"syncNow": "Sync Now",
|
||||
"syncing": "Syncing...",
|
||||
"syncComplete": "Sync complete: {created} created, {deleted} deleted, {synced} already synced",
|
||||
"syncFailed": "DNS sync failed",
|
||||
"searchPlaceholder": "Search by FQDN...",
|
||||
"allConsumers": "All consumers",
|
||||
"managed": "Managed (instances)",
|
||||
"standalone": "Standalone proxies",
|
||||
"orphaned": "Orphaned",
|
||||
"allStatuses": "All statuses",
|
||||
"statusSynced": "Synced",
|
||||
"statusMissing": "Missing",
|
||||
"statusOrphaned": "Orphaned",
|
||||
"columnFqdn": "FQDN",
|
||||
"columnType": "Type",
|
||||
"columnValue": "Value",
|
||||
"columnConsumer": "Consumer",
|
||||
"columnStatus": "Status",
|
||||
"columnActions": "Actions",
|
||||
"noConsumer": "No consumer",
|
||||
"noRecords": "No DNS records found. Records will appear here when services are deployed.",
|
||||
"noMatchingRecords": "No records match the current filters.",
|
||||
"deleteRecord": "Delete record",
|
||||
"recordDeleted": "DNS record {fqdn} deleted",
|
||||
"deleteFailed": "Failed to delete DNS record",
|
||||
"loadFailed": "Failed to load DNS records",
|
||||
"totalRecords": "Total: {count}",
|
||||
"syncedCount": "Synced: {count}",
|
||||
"missingCount": "Missing: {count}",
|
||||
"orphanedCount": "Orphaned: {count}"
|
||||
},
|
||||
"language": {
|
||||
"en": "English",
|
||||
"ru": "Russian"
|
||||
|
||||
@@ -16,7 +16,8 @@
|
||||
"proxies": "Прокси",
|
||||
"events": "События",
|
||||
"settings": "Настройки",
|
||||
"logout": "Выйти"
|
||||
"logout": "Выйти",
|
||||
"dns": "DNS-записи"
|
||||
},
|
||||
"dashboard": {
|
||||
"title": "Панель управления",
|
||||
@@ -243,7 +244,26 @@
|
||||
"noCertificate": "Нет (без SSL)",
|
||||
"clearCertificate": "Очистить",
|
||||
"loadingCertificates": "Загрузка сертификатов...",
|
||||
"noCertificatesFound": "Wildcard-сертификаты в NPM не найдены"
|
||||
"noCertificatesFound": "Wildcard-сертификаты в NPM не найдены",
|
||||
"dnsConfig": "Настройки DNS",
|
||||
"wildcardDns": "Wildcard DNS настроен",
|
||||
"wildcardDnsHelp": "Когда включено, все поддомены разрешаются на ваш сервер через wildcard DNS правило. Отключите для управления DNS-записями для каждого сервиса.",
|
||||
"dnsProvider": "DNS-провайдер",
|
||||
"dnsProviderHelp": "Выберите DNS-провайдера для автоматического управления записями",
|
||||
"cloudflareApiToken": "API-токен Cloudflare",
|
||||
"cloudflareApiTokenHelp": "API-токен с правами редактирования DNS для вашей зоны",
|
||||
"cloudflareApiTokenPlaceholder": "Введите API-токен Cloudflare",
|
||||
"cloudflareApiTokenConfigured": "API-токен настроен",
|
||||
"cloudflareZone": "Зона Cloudflare",
|
||||
"cloudflareZoneHelp": "Выберите DNS-зону для управления записями",
|
||||
"selectZone": "Выбрать зону",
|
||||
"noZone": "Зона не выбрана",
|
||||
"loadingZones": "Загрузка зон...",
|
||||
"noZonesFound": "Зоны для этого токена не найдены",
|
||||
"testConnection": "Проверить соединение",
|
||||
"testingConnection": "Проверка...",
|
||||
"connectionSuccess": "Соединение успешно",
|
||||
"connectionFailed": "Ошибка соединения"
|
||||
},
|
||||
"settingsRegistries": {
|
||||
"title": "Реестры контейнеров",
|
||||
@@ -540,6 +560,43 @@
|
||||
"proxies": "Прокси",
|
||||
"recentErrors": "Недавние ошибки"
|
||||
},
|
||||
"dns": {
|
||||
"title": "DNS-записи",
|
||||
"description": "Просмотр и управление DNS-записями, созданными Docker Watcher.",
|
||||
"wildcardActive": "Режим Wildcard DNS активен",
|
||||
"wildcardActiveDesc": "DNS-записи управляются внешне через wildcard DNS. Отключите wildcard DNS в настройках для индивидуального управления записями.",
|
||||
"refresh": "Обновить",
|
||||
"syncNow": "Синхронизировать",
|
||||
"syncing": "Синхронизация...",
|
||||
"syncComplete": "Синхронизация завершена: {created} создано, {deleted} удалено, {synced} уже синхронизировано",
|
||||
"syncFailed": "Ошибка синхронизации DNS",
|
||||
"searchPlaceholder": "Поиск по FQDN...",
|
||||
"allConsumers": "Все потребители",
|
||||
"managed": "Управляемые (инстансы)",
|
||||
"standalone": "Автономные прокси",
|
||||
"orphaned": "Осиротевшие",
|
||||
"allStatuses": "Все статусы",
|
||||
"statusSynced": "Синхронизировано",
|
||||
"statusMissing": "Отсутствует",
|
||||
"statusOrphaned": "Осиротевшее",
|
||||
"columnFqdn": "FQDN",
|
||||
"columnType": "Тип",
|
||||
"columnValue": "Значение",
|
||||
"columnConsumer": "Потребитель",
|
||||
"columnStatus": "Статус",
|
||||
"columnActions": "Действия",
|
||||
"noConsumer": "Нет потребителя",
|
||||
"noRecords": "DNS-записи не найдены. Записи появятся здесь после развёртывания сервисов.",
|
||||
"noMatchingRecords": "Нет записей, соответствующих текущим фильтрам.",
|
||||
"deleteRecord": "Удалить запись",
|
||||
"recordDeleted": "DNS-запись {fqdn} удалена",
|
||||
"deleteFailed": "Не удалось удалить DNS-запись",
|
||||
"loadFailed": "Не удалось загрузить DNS-записи",
|
||||
"totalRecords": "Всего: {count}",
|
||||
"syncedCount": "Синхронизировано: {count}",
|
||||
"missingCount": "Отсутствует: {count}",
|
||||
"orphanedCount": "Осиротевших: {count}"
|
||||
},
|
||||
"language": {
|
||||
"en": "Английский",
|
||||
"ru": "Русский"
|
||||
|
||||
@@ -108,9 +108,30 @@ export interface Settings {
|
||||
ssl_certificate_id: number;
|
||||
stale_threshold_days: number;
|
||||
allowed_volume_paths: string;
|
||||
wildcard_dns: boolean;
|
||||
dns_provider: string;
|
||||
has_cloudflare_api_token: boolean;
|
||||
cloudflare_zone_id: string;
|
||||
updated_at: string;
|
||||
}
|
||||
|
||||
/** A DNS zone from a provider (e.g., Cloudflare). */
|
||||
export interface DnsZone {
|
||||
id: string;
|
||||
name: string;
|
||||
}
|
||||
|
||||
/** A DNS record view for the DNS Records page. */
|
||||
export interface DnsRecordView {
|
||||
fqdn: string;
|
||||
type: string;
|
||||
content: string;
|
||||
consumer_type: string;
|
||||
consumer_name: string;
|
||||
consumer_id: string;
|
||||
status: string;
|
||||
}
|
||||
|
||||
/** An SSL certificate from Nginx Proxy Manager. */
|
||||
export interface NpmCertificate {
|
||||
id: number;
|
||||
|
||||
Reference in New Issue
Block a user