-
Notifications
You must be signed in to change notification settings - Fork 0
Description
Task: Build DomainManager.vue, DnsRecordEditor.vue, and ApplicationDomainBinding.vue
Description
Build a comprehensive suite of Vue.js 3 components for domain management and DNS configuration within the Coolify Enterprise platform. These components provide organization administrators with a powerful, intuitive interface for managing custom domains, editing DNS records, and binding domains to applications—all while maintaining the enterprise's white-label branding.
This task creates three interconnected Vue.js components that work together to provide complete domain management functionality:
-
DomainManager.vue - The primary domain management interface where users can view all organization domains, check availability, register new domains, renew existing domains, transfer domains from other registrars, and configure DNS settings. This component acts as the central hub for all domain-related operations.
-
DnsRecordEditor.vue - A specialized component for creating, editing, and deleting DNS records (A, AAAA, CNAME, MX, TXT, NS, SRV). It provides real-time validation, DNS propagation checking, and intelligent suggestions for common configurations (like email setup, CDN integration, and domain verification).
-
ApplicationDomainBinding.vue - A component for associating custom domains with deployed applications. It handles domain verification, automatic DNS record creation, SSL certificate provisioning via Let's Encrypt, and displays binding status with health checks.
Integration with Enterprise Architecture:
These components integrate deeply with the backend domain management system:
- DomainRegistrarService (Task 66) - Handles domain registration, renewal, and transfer operations
- DnsManagementService (Task 67) - Manages DNS record CRUD operations across multiple providers
- WhiteLabelConfig - Displays branding consistently throughout domain management
- Organization Model - Enforces organization-scoped domain ownership
- Application Model - Links domains to deployed applications with automatic proxy configuration
Why This Task Is Critical:
Domain management is a cornerstone of professional application deployment. These components transform Coolify from a tool that requires users to manually configure DNS elsewhere into a comprehensive platform where domains are managed alongside infrastructure and deployments. For enterprise customers, this means:
- Reduced Friction: Register and configure domains without leaving the platform
- Automatic Configuration: DNS records created automatically when binding domains to applications
- SSL Automation: Let's Encrypt integration provides automatic HTTPS for all custom domains
- White-Label Consistency: Domain management respects organization branding throughout the UI
- Audit Trail: All domain operations logged for compliance and debugging
Acceptance Criteria
DomainManager.vue Component
- Display paginated list of organization domains with search and filtering
- Domain availability checker with real-time validation
- Domain registration flow with WHOIS privacy, auto-renewal, and contact management
- Domain renewal functionality with expiration warnings (30/60/90 day alerts)
- Domain transfer flow with authorization code handling
- Domain deletion with confirmation and safety checks
- DNS settings quick access with link to DnsRecordEditor
- Domain verification status display (verified, pending, failed)
- Integration with multiple registrars (Namecheap, Route53, Cloudflare)
- Responsive design working on mobile, tablet, desktop
- Loading states for async operations
- Error handling with user-friendly messages
- Accessibility compliance (ARIA labels, keyboard navigation, screen reader support)
DnsRecordEditor.vue Component
- Display all DNS records for a domain in a structured table
- Create new DNS records with record type selection (A, AAAA, CNAME, MX, TXT, NS, SRV)
- Edit existing DNS records with validation
- Delete DNS records with confirmation
- Bulk import DNS records from JSON/CSV
- DNS record templates for common configurations (email, CDN, verification)
- Real-time validation for record values (IP addresses, domains, priorities)
- DNS propagation checker with global location testing
- TTL configuration with intelligent defaults
- Record conflict detection (e.g., CNAME + A record on same subdomain)
- Export DNS records to JSON/CSV
- Integration with DNS providers (Cloudflare, Route53, DigitalOcean DNS)
ApplicationDomainBinding.vue Component
- Display all domains bound to an application
- Add new domain binding with availability check
- Domain verification workflow (DNS TXT record method, file upload method)
- Automatic DNS record creation (A/AAAA pointing to application server)
- SSL certificate provisioning status with Let's Encrypt integration
- Certificate renewal countdown and auto-renewal status
- Domain health checks (DNS resolution, SSL validity, HTTP response)
- Remove domain binding with cleanup (DNS records, SSL certificates)
- Primary domain designation for redirects
- WWW vs non-WWW configuration
- Force HTTPS configuration
- Custom error pages for domain-specific 404/500 errors
General Requirements (All Components)
- Vue 3 Composition API with TypeScript-style prop definitions
- Inertia.js integration for server communication
- Real-time form validation with error display
- Optimistic UI updates with rollback on failure
- WebSocket integration for long-running operations (DNS propagation, SSL provisioning)
- Dark mode support matching Coolify's theme system
- Comprehensive unit tests with Vitest/Vue Test Utils (>85% coverage)
- Integration with organization white-label branding
- Performance: initial render < 500ms, interactions < 100ms
Technical Details
File Paths
Vue Components:
/home/topgun/topgun/resources/js/Components/Enterprise/Domain/DomainManager.vue/home/topgun/topgun/resources/js/Components/Enterprise/Domain/DnsRecordEditor.vue/home/topgun/topgun/resources/js/Components/Enterprise/Domain/ApplicationDomainBinding.vue
Supporting Components:
/home/topgun/topgun/resources/js/Components/Enterprise/Domain/DomainAvailabilityChecker.vue/home/topgun/topgun/resources/js/Components/Enterprise/Domain/DomainRegistrationForm.vue/home/topgun/topgun/resources/js/Components/Enterprise/Domain/DnsRecordForm.vue/home/topgun/topgun/resources/js/Components/Enterprise/Domain/DnsPropagationStatus.vue/home/topgun/topgun/resources/js/Components/Enterprise/Domain/SslCertificateStatus.vue
Backend Integration:
/home/topgun/topgun/app/Http/Controllers/Enterprise/DomainController.php(existing, from Task 66)/home/topgun/topgun/app/Http/Controllers/Enterprise/DnsRecordController.php(existing, from Task 67)/home/topgun/topgun/app/Http/Controllers/Enterprise/ApplicationDomainController.php(new)
Routes:
/home/topgun/topgun/routes/web.php(domain management routes)
Test Files:
/home/topgun/topgun/resources/js/Components/Enterprise/Domain/__tests__/DomainManager.spec.js/home/topgun/topgun/resources/js/Components/Enterprise/Domain/__tests__/DnsRecordEditor.spec.js/home/topgun/topgun/resources/js/Components/Enterprise/Domain/__tests__/ApplicationDomainBinding.spec.js
Component 1: DomainManager.vue
File: resources/js/Components/Enterprise/Domain/DomainManager.vue
<script setup>
import { ref, computed, onMounted } from 'vue'
import { router, useForm, usePage } from '@inertiajs/vue3'
import DomainAvailabilityChecker from './DomainAvailabilityChecker.vue'
import DomainRegistrationForm from './DomainRegistrationForm.vue'
import DnsRecordEditor from './DnsRecordEditor.vue'
const props = defineProps({
organizationId: {
type: Number,
required: true,
},
domains: {
type: Array,
default: () => [],
},
registrars: {
type: Array,
default: () => [],
},
pagination: {
type: Object,
default: () => ({}),
},
})
const emit = defineEmits(['domain-registered', 'domain-renewed', 'domain-deleted'])
// State
const showRegistrationModal = ref(false)
const showDnsEditorModal = ref(false)
const selectedDomain = ref(null)
const searchQuery = ref('')
const filterStatus = ref('all') // all, active, expiring, expired
const sortBy = ref('name') // name, expires_at, created_at
const sortDirection = ref('asc')
// Forms
const renewForm = useForm({
domain_id: null,
years: 1,
})
const deleteForm = useForm({
domain_id: null,
})
// Computed
const filteredDomains = computed(() => {
let filtered = props.domains
// Search filter
if (searchQuery.value) {
filtered = filtered.filter(domain =>
domain.name.toLowerCase().includes(searchQuery.value.toLowerCase())
)
}
// Status filter
if (filterStatus.value !== 'all') {
const now = new Date()
filtered = filtered.filter(domain => {
const expiresAt = new Date(domain.expires_at)
const daysUntilExpiry = Math.ceil((expiresAt - now) / (1000 * 60 * 60 * 24))
switch (filterStatus.value) {
case 'active':
return daysUntilExpiry > 30
case 'expiring':
return daysUntilExpiry <= 30 && daysUntilExpiry > 0
case 'expired':
return daysUntilExpiry <= 0
default:
return true
}
})
}
// Sorting
filtered.sort((a, b) => {
let aVal = a[sortBy.value]
let bVal = b[sortBy.value]
if (sortBy.value === 'expires_at' || sortBy.value === 'created_at') {
aVal = new Date(aVal)
bVal = new Date(bVal)
}
if (sortDirection.value === 'asc') {
return aVal > bVal ? 1 : -1
} else {
return aVal < bVal ? 1 : -1
}
})
return filtered
})
const expiringDomains = computed(() => {
const now = new Date()
return props.domains.filter(domain => {
const expiresAt = new Date(domain.expires_at)
const daysUntilExpiry = Math.ceil((expiresAt - now) / (1000 * 60 * 60 * 24))
return daysUntilExpiry <= 30 && daysUntilExpiry > 0
})
})
// Methods
const openRegistrationModal = () => {
showRegistrationModal.value = true
}
const openDnsEditor = (domain) => {
selectedDomain.value = domain
showDnsEditorModal.value = true
}
const renewDomain = (domain) => {
if (!confirm(`Renew ${domain.name} for 1 year?`)) return
renewForm.domain_id = domain.id
renewForm.post(route('enterprise.domains.renew', {
organization: props.organizationId,
domain: domain.id,
}), {
onSuccess: () => {
emit('domain-renewed', domain)
renewForm.reset()
},
onError: (errors) => {
alert(errors.message || 'Failed to renew domain')
},
})
}
const deleteDomain = (domain) => {
if (!confirm(`Delete ${domain.name}? This action cannot be undone.`)) return
deleteForm.domain_id = domain.id
deleteForm.delete(route('enterprise.domains.destroy', {
organization: props.organizationId,
domain: domain.id,
}), {
onSuccess: () => {
emit('domain-deleted', domain)
},
onError: (errors) => {
alert(errors.message || 'Failed to delete domain')
},
})
}
const getExpiryClass = (domain) => {
const now = new Date()
const expiresAt = new Date(domain.expires_at)
const daysUntilExpiry = Math.ceil((expiresAt - now) / (1000 * 60 * 60 * 24))
if (daysUntilExpiry <= 0) return 'text-red-600 dark:text-red-400'
if (daysUntilExpiry <= 30) return 'text-yellow-600 dark:text-yellow-400'
return 'text-gray-600 dark:text-gray-400'
}
const formatDate = (dateString) => {
return new Date(dateString).toLocaleDateString('en-US', {
year: 'numeric',
month: 'short',
day: 'numeric',
})
}
const toggleSort = (field) => {
if (sortBy.value === field) {
sortDirection.value = sortDirection.value === 'asc' ? 'desc' : 'asc'
} else {
---
**Note:** Full task details in repository at `.claude/epics/topgun/70.md`