Skip to content

Build DomainManager.vue, DnsRecordEditor.vue, and ApplicationDomainBinding.vue #201

@johnproblems

Description

@johnproblems

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:

  1. 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.

  2. 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).

  3. 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`

Metadata

Metadata

Assignees

No one assigned

    Labels

    epic:topgunTasks for topguntaskIndividual task

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions