feat(domains): implement domain conflict detection and user confirmation modal across application components
This commit is contained in:
		@@ -51,9 +51,16 @@ class General extends Component
 | 
			
		||||
 | 
			
		||||
    public $parsedServiceDomains = [];
 | 
			
		||||
 | 
			
		||||
    public $domainConflicts = [];
 | 
			
		||||
 | 
			
		||||
    public $showDomainConflictModal = false;
 | 
			
		||||
 | 
			
		||||
    public $forceSaveDomains = false;
 | 
			
		||||
 | 
			
		||||
    protected $listeners = [
 | 
			
		||||
        'resetDefaultLabels',
 | 
			
		||||
        'configurationChanged' => '$refresh',
 | 
			
		||||
        'confirmDomainUsage',
 | 
			
		||||
    ];
 | 
			
		||||
 | 
			
		||||
    protected function rules(): array
 | 
			
		||||
@@ -485,10 +492,33 @@ class General extends Component
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
            checkDomainUsage(resource: $this->application);
 | 
			
		||||
 | 
			
		||||
            // Check for domain conflicts if not forcing save
 | 
			
		||||
            if (! $this->forceSaveDomains) {
 | 
			
		||||
                $result = checkDomainUsage(resource: $this->application);
 | 
			
		||||
                if ($result['hasConflicts']) {
 | 
			
		||||
                    $this->domainConflicts = $result['conflicts'];
 | 
			
		||||
                    $this->showDomainConflictModal = true;
 | 
			
		||||
 | 
			
		||||
                    return false;
 | 
			
		||||
                }
 | 
			
		||||
            } else {
 | 
			
		||||
                // Reset the force flag after using it
 | 
			
		||||
                $this->forceSaveDomains = false;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            $this->application->fqdn = $domains->implode(',');
 | 
			
		||||
            $this->resetDefaultLabels(false);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        return true;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function confirmDomainUsage()
 | 
			
		||||
    {
 | 
			
		||||
        $this->forceSaveDomains = true;
 | 
			
		||||
        $this->showDomainConflictModal = false;
 | 
			
		||||
        $this->submit();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function setRedirect()
 | 
			
		||||
@@ -536,7 +566,9 @@ class General extends Component
 | 
			
		||||
                $this->application->parseHealthcheckFromDockerfile($this->application->dockerfile);
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            $this->checkFqdns();
 | 
			
		||||
            if (! $this->checkFqdns()) {
 | 
			
		||||
                return; // Stop if there are conflicts and user hasn't confirmed
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            $this->application->save();
 | 
			
		||||
            if (! $this->customLabels && $this->application->destination->server->proxyType() !== 'NONE' && ! $this->application->settings->is_container_label_readonly_enabled) {
 | 
			
		||||
@@ -588,7 +620,20 @@ class General extends Component
 | 
			
		||||
                            }
 | 
			
		||||
                        }
 | 
			
		||||
                    }
 | 
			
		||||
                    checkDomainUsage(resource: $this->application);
 | 
			
		||||
                    // Check for domain conflicts if not forcing save
 | 
			
		||||
                    if (! $this->forceSaveDomains) {
 | 
			
		||||
                        $result = checkDomainUsage(resource: $this->application);
 | 
			
		||||
                        if ($result['hasConflicts']) {
 | 
			
		||||
                            $this->domainConflicts = $result['conflicts'];
 | 
			
		||||
                            $this->showDomainConflictModal = true;
 | 
			
		||||
 | 
			
		||||
                            return;
 | 
			
		||||
                        }
 | 
			
		||||
                    } else {
 | 
			
		||||
                        // Reset the force flag after using it
 | 
			
		||||
                        $this->forceSaveDomains = false;
 | 
			
		||||
                    }
 | 
			
		||||
 | 
			
		||||
                    $this->application->save();
 | 
			
		||||
                    $this->resetDefaultLabels();
 | 
			
		||||
                }
 | 
			
		||||
 
 | 
			
		||||
@@ -25,6 +25,14 @@ class Previews extends Component
 | 
			
		||||
 | 
			
		||||
    public int $rate_limit_remaining;
 | 
			
		||||
 | 
			
		||||
    public $domainConflicts = [];
 | 
			
		||||
 | 
			
		||||
    public $showDomainConflictModal = false;
 | 
			
		||||
 | 
			
		||||
    public $forceSaveDomains = false;
 | 
			
		||||
 | 
			
		||||
    public $pendingPreviewId = null;
 | 
			
		||||
 | 
			
		||||
    protected $rules = [
 | 
			
		||||
        'application.previews.*.fqdn' => 'string|nullable',
 | 
			
		||||
    ];
 | 
			
		||||
@@ -49,6 +57,16 @@ class Previews extends Component
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function confirmDomainUsage()
 | 
			
		||||
    {
 | 
			
		||||
        $this->forceSaveDomains = true;
 | 
			
		||||
        $this->showDomainConflictModal = false;
 | 
			
		||||
        if ($this->pendingPreviewId) {
 | 
			
		||||
            $this->save_preview($this->pendingPreviewId);
 | 
			
		||||
            $this->pendingPreviewId = null;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function save_preview($preview_id)
 | 
			
		||||
    {
 | 
			
		||||
        try {
 | 
			
		||||
@@ -63,7 +81,20 @@ class Previews extends Component
 | 
			
		||||
                    $this->dispatch('error', 'Validating DNS failed.', "Make sure you have added the DNS records correctly.<br><br>$preview->fqdn->{$this->application->destination->server->ip}<br><br>Check this <a target='_blank' class='underline dark:text-white' href='https://coolify.io/docs/knowledge-base/dns-configuration'>documentation</a> for further help.");
 | 
			
		||||
                    $success = false;
 | 
			
		||||
                }
 | 
			
		||||
                checkDomainUsage(resource: $this->application, domain: $preview->fqdn);
 | 
			
		||||
                // Check for domain conflicts if not forcing save
 | 
			
		||||
                if (! $this->forceSaveDomains) {
 | 
			
		||||
                    $result = checkDomainUsage(resource: $this->application, domain: $preview->fqdn);
 | 
			
		||||
                    if ($result['hasConflicts']) {
 | 
			
		||||
                        $this->domainConflicts = $result['conflicts'];
 | 
			
		||||
                        $this->showDomainConflictModal = true;
 | 
			
		||||
                        $this->pendingPreviewId = $preview_id;
 | 
			
		||||
 | 
			
		||||
                        return;
 | 
			
		||||
                    }
 | 
			
		||||
                } else {
 | 
			
		||||
                    // Reset the force flag after using it
 | 
			
		||||
                    $this->forceSaveDomains = false;
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            if (! $preview) {
 | 
			
		||||
 
 | 
			
		||||
@@ -12,6 +12,12 @@ class EditDomain extends Component
 | 
			
		||||
 | 
			
		||||
    public ServiceApplication $application;
 | 
			
		||||
 | 
			
		||||
    public $domainConflicts = [];
 | 
			
		||||
 | 
			
		||||
    public $showDomainConflictModal = false;
 | 
			
		||||
 | 
			
		||||
    public $forceSaveDomains = false;
 | 
			
		||||
 | 
			
		||||
    protected $rules = [
 | 
			
		||||
        'application.fqdn' => 'nullable',
 | 
			
		||||
        'application.required_fqdn' => 'required|boolean',
 | 
			
		||||
@@ -22,6 +28,13 @@ class EditDomain extends Component
 | 
			
		||||
        $this->application = ServiceApplication::find($this->applicationId);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function confirmDomainUsage()
 | 
			
		||||
    {
 | 
			
		||||
        $this->forceSaveDomains = true;
 | 
			
		||||
        $this->showDomainConflictModal = false;
 | 
			
		||||
        $this->submit();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function submit()
 | 
			
		||||
    {
 | 
			
		||||
        try {
 | 
			
		||||
@@ -37,7 +50,20 @@ class EditDomain extends Component
 | 
			
		||||
            if ($warning) {
 | 
			
		||||
                $this->dispatch('warning', __('warning.sslipdomain'));
 | 
			
		||||
            }
 | 
			
		||||
            checkDomainUsage(resource: $this->application);
 | 
			
		||||
            // Check for domain conflicts if not forcing save
 | 
			
		||||
            if (! $this->forceSaveDomains) {
 | 
			
		||||
                $result = checkDomainUsage(resource: $this->application);
 | 
			
		||||
                if ($result['hasConflicts']) {
 | 
			
		||||
                    $this->domainConflicts = $result['conflicts'];
 | 
			
		||||
                    $this->showDomainConflictModal = true;
 | 
			
		||||
 | 
			
		||||
                    return;
 | 
			
		||||
                }
 | 
			
		||||
            } else {
 | 
			
		||||
                // Reset the force flag after using it
 | 
			
		||||
                $this->forceSaveDomains = false;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            $this->validate();
 | 
			
		||||
            $this->application->save();
 | 
			
		||||
            updateCompose($this->application);
 | 
			
		||||
 
 | 
			
		||||
@@ -23,6 +23,12 @@ class ServiceApplicationView extends Component
 | 
			
		||||
 | 
			
		||||
    public $delete_volumes = true;
 | 
			
		||||
 | 
			
		||||
    public $domainConflicts = [];
 | 
			
		||||
 | 
			
		||||
    public $showDomainConflictModal = false;
 | 
			
		||||
 | 
			
		||||
    public $forceSaveDomains = false;
 | 
			
		||||
 | 
			
		||||
    protected $rules = [
 | 
			
		||||
        'application.human_name' => 'nullable',
 | 
			
		||||
        'application.description' => 'nullable',
 | 
			
		||||
@@ -129,6 +135,13 @@ class ServiceApplicationView extends Component
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function confirmDomainUsage()
 | 
			
		||||
    {
 | 
			
		||||
        $this->forceSaveDomains = true;
 | 
			
		||||
        $this->showDomainConflictModal = false;
 | 
			
		||||
        $this->submit();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function submit()
 | 
			
		||||
    {
 | 
			
		||||
        try {
 | 
			
		||||
@@ -145,7 +158,20 @@ class ServiceApplicationView extends Component
 | 
			
		||||
            if ($warning) {
 | 
			
		||||
                $this->dispatch('warning', __('warning.sslipdomain'));
 | 
			
		||||
            }
 | 
			
		||||
            checkDomainUsage(resource: $this->application);
 | 
			
		||||
            // Check for domain conflicts if not forcing save
 | 
			
		||||
            if (! $this->forceSaveDomains) {
 | 
			
		||||
                $result = checkDomainUsage(resource: $this->application);
 | 
			
		||||
                if ($result['hasConflicts']) {
 | 
			
		||||
                    $this->domainConflicts = $result['conflicts'];
 | 
			
		||||
                    $this->showDomainConflictModal = true;
 | 
			
		||||
 | 
			
		||||
                    return;
 | 
			
		||||
                }
 | 
			
		||||
            } else {
 | 
			
		||||
                // Reset the force flag after using it
 | 
			
		||||
                $this->forceSaveDomains = false;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            $this->validate();
 | 
			
		||||
            $this->application->save();
 | 
			
		||||
            updateCompose($this->application);
 | 
			
		||||
 
 | 
			
		||||
@@ -33,9 +33,6 @@ class ExecuteContainerCommand extends Component
 | 
			
		||||
 | 
			
		||||
    public function mount()
 | 
			
		||||
    {
 | 
			
		||||
        if (! auth()->user()->isAdmin()) {
 | 
			
		||||
            abort(403);
 | 
			
		||||
        }
 | 
			
		||||
        $this->parameters = get_route_parameters();
 | 
			
		||||
        $this->containers = collect();
 | 
			
		||||
        $this->servers = collect();
 | 
			
		||||
 
 | 
			
		||||
@@ -35,6 +35,12 @@ class Index extends Component
 | 
			
		||||
    #[Validate('required|string|timezone')]
 | 
			
		||||
    public string $instance_timezone;
 | 
			
		||||
 | 
			
		||||
    public array $domainConflicts = [];
 | 
			
		||||
 | 
			
		||||
    public bool $showDomainConflictModal = false;
 | 
			
		||||
 | 
			
		||||
    public bool $forceSaveDomains = false;
 | 
			
		||||
 | 
			
		||||
    public function render()
 | 
			
		||||
    {
 | 
			
		||||
        return view('livewire.settings.index');
 | 
			
		||||
@@ -81,6 +87,13 @@ class Index extends Component
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function confirmDomainUsage()
 | 
			
		||||
    {
 | 
			
		||||
        $this->forceSaveDomains = true;
 | 
			
		||||
        $this->showDomainConflictModal = false;
 | 
			
		||||
        $this->submit();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function submit()
 | 
			
		||||
    {
 | 
			
		||||
        try {
 | 
			
		||||
@@ -108,7 +121,18 @@ class Index extends Component
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
            if ($this->fqdn) {
 | 
			
		||||
                checkDomainUsage(domain: $this->fqdn);
 | 
			
		||||
                if (! $this->forceSaveDomains) {
 | 
			
		||||
                    $result = checkDomainUsage(domain: $this->fqdn);
 | 
			
		||||
                    if ($result['hasConflicts']) {
 | 
			
		||||
                        $this->domainConflicts = $result['conflicts'];
 | 
			
		||||
                        $this->showDomainConflictModal = true;
 | 
			
		||||
 | 
			
		||||
                        return;
 | 
			
		||||
                    }
 | 
			
		||||
                } else {
 | 
			
		||||
                    // Reset the force flag after using it
 | 
			
		||||
                    $this->forceSaveDomains = false;
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            $this->instantSave(isSave: false);
 | 
			
		||||
 
 | 
			
		||||
@@ -5,6 +5,14 @@ use App\Models\ServiceApplication;
 | 
			
		||||
 | 
			
		||||
function checkDomainUsage(ServiceApplication|Application|null $resource = null, ?string $domain = null)
 | 
			
		||||
{
 | 
			
		||||
    $conflicts = [];
 | 
			
		||||
 | 
			
		||||
    // Get the current team for filtering
 | 
			
		||||
    $currentTeam = null;
 | 
			
		||||
    if ($resource) {
 | 
			
		||||
        $currentTeam = $resource->team();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if ($resource) {
 | 
			
		||||
        if ($resource->getMorphClass() === Application::class && $resource->build_pack === 'dockercompose') {
 | 
			
		||||
            $domains = data_get(json_decode($resource->docker_compose_domains, true), '*.domain');
 | 
			
		||||
@@ -15,8 +23,9 @@ function checkDomainUsage(ServiceApplication|Application|null $resource = null,
 | 
			
		||||
    } elseif ($domain) {
 | 
			
		||||
        $domains = collect([$domain]);
 | 
			
		||||
    } else {
 | 
			
		||||
        throw new \RuntimeException('No resource or FQDN provided.');
 | 
			
		||||
        return ['conflicts' => [], 'hasConflicts' => false];
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    $domains = $domains->map(function ($domain) {
 | 
			
		||||
        if (str($domain)->endsWith('/')) {
 | 
			
		||||
            $domain = str($domain)->beforeLast('/');
 | 
			
		||||
@@ -24,7 +33,15 @@ function checkDomainUsage(ServiceApplication|Application|null $resource = null,
 | 
			
		||||
 | 
			
		||||
        return str($domain);
 | 
			
		||||
    });
 | 
			
		||||
    $apps = Application::all();
 | 
			
		||||
 | 
			
		||||
    // Filter applications by team if we have a current team
 | 
			
		||||
    $appsQuery = Application::query();
 | 
			
		||||
    if ($currentTeam) {
 | 
			
		||||
        $appsQuery = $appsQuery->whereHas('environment.project', function ($query) use ($currentTeam) {
 | 
			
		||||
            $query->where('team_id', $currentTeam->id);
 | 
			
		||||
        });
 | 
			
		||||
    }
 | 
			
		||||
    $apps = $appsQuery->get();
 | 
			
		||||
    foreach ($apps as $app) {
 | 
			
		||||
        $list_of_domains = collect(explode(',', $app->fqdn))->filter(fn ($fqdn) => $fqdn !== '');
 | 
			
		||||
        foreach ($list_of_domains as $domain) {
 | 
			
		||||
@@ -35,15 +52,35 @@ function checkDomainUsage(ServiceApplication|Application|null $resource = null,
 | 
			
		||||
            if ($domains->contains($naked_domain)) {
 | 
			
		||||
                if (data_get($resource, 'uuid')) {
 | 
			
		||||
                    if ($resource->uuid !== $app->uuid) {
 | 
			
		||||
                        throw new \RuntimeException("Domain $naked_domain is already in use by another resource: <br><br>Link: <a class='underline' target='_blank' href='{$app->link()}'>{$app->name}</a>");
 | 
			
		||||
                        $conflicts[] = [
 | 
			
		||||
                            'domain' => $naked_domain,
 | 
			
		||||
                            'resource_name' => $app->name,
 | 
			
		||||
                            'resource_link' => $app->link(),
 | 
			
		||||
                            'resource_type' => 'application',
 | 
			
		||||
                            'message' => "Domain $naked_domain is already in use by application '{$app->name}'",
 | 
			
		||||
                        ];
 | 
			
		||||
                    }
 | 
			
		||||
                } elseif ($domain) {
 | 
			
		||||
                    throw new \RuntimeException("Domain $naked_domain is already in use by another resource: <br><br>Link: <a class='underline' target='_blank' href='{$app->link()}'>{$app->name}</a>");
 | 
			
		||||
                    $conflicts[] = [
 | 
			
		||||
                        'domain' => $naked_domain,
 | 
			
		||||
                        'resource_name' => $app->name,
 | 
			
		||||
                        'resource_link' => $app->link(),
 | 
			
		||||
                        'resource_type' => 'application',
 | 
			
		||||
                        'message' => "Domain $naked_domain is already in use by application '{$app->name}'",
 | 
			
		||||
                    ];
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
    $apps = ServiceApplication::all();
 | 
			
		||||
 | 
			
		||||
    // Filter service applications by team if we have a current team
 | 
			
		||||
    $serviceAppsQuery = ServiceApplication::query();
 | 
			
		||||
    if ($currentTeam) {
 | 
			
		||||
        $serviceAppsQuery = $serviceAppsQuery->whereHas('service.environment.project', function ($query) use ($currentTeam) {
 | 
			
		||||
            $query->where('team_id', $currentTeam->id);
 | 
			
		||||
        });
 | 
			
		||||
    }
 | 
			
		||||
    $apps = $serviceAppsQuery->get();
 | 
			
		||||
    foreach ($apps as $app) {
 | 
			
		||||
        $list_of_domains = collect(explode(',', $app->fqdn))->filter(fn ($fqdn) => $fqdn !== '');
 | 
			
		||||
        foreach ($list_of_domains as $domain) {
 | 
			
		||||
@@ -54,14 +91,27 @@ function checkDomainUsage(ServiceApplication|Application|null $resource = null,
 | 
			
		||||
            if ($domains->contains($naked_domain)) {
 | 
			
		||||
                if (data_get($resource, 'uuid')) {
 | 
			
		||||
                    if ($resource->uuid !== $app->uuid) {
 | 
			
		||||
                        throw new \RuntimeException("Domain $naked_domain is already in use by another resource: <br><br>Link: <a class='underline' target='_blank' href='{$app->service->link()}'>{$app->service->name}</a>");
 | 
			
		||||
                        $conflicts[] = [
 | 
			
		||||
                            'domain' => $naked_domain,
 | 
			
		||||
                            'resource_name' => $app->service->name,
 | 
			
		||||
                            'resource_link' => $app->service->link(),
 | 
			
		||||
                            'resource_type' => 'service',
 | 
			
		||||
                            'message' => "Domain $naked_domain is already in use by service '{$app->service->name}'",
 | 
			
		||||
                        ];
 | 
			
		||||
                    }
 | 
			
		||||
                } elseif ($domain) {
 | 
			
		||||
                    throw new \RuntimeException("Domain $naked_domain is already in use by another resource: <br><br>Link: <a class='underline' target='_blank' href='{$app->service->link()}'>{$app->service->name}</a>");
 | 
			
		||||
                    $conflicts[] = [
 | 
			
		||||
                        'domain' => $naked_domain,
 | 
			
		||||
                        'resource_name' => $app->service->name,
 | 
			
		||||
                        'resource_link' => $app->service->link(),
 | 
			
		||||
                        'resource_type' => 'service',
 | 
			
		||||
                        'message' => "Domain $naked_domain is already in use by service '{$app->service->name}'",
 | 
			
		||||
                    ];
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if ($resource) {
 | 
			
		||||
        $settings = instanceSettings();
 | 
			
		||||
        if (data_get($settings, 'fqdn')) {
 | 
			
		||||
@@ -71,8 +121,19 @@ function checkDomainUsage(ServiceApplication|Application|null $resource = null,
 | 
			
		||||
            }
 | 
			
		||||
            $naked_domain = str($domain)->value();
 | 
			
		||||
            if ($domains->contains($naked_domain)) {
 | 
			
		||||
                throw new \RuntimeException("Domain $naked_domain is already in use by this Coolify instance.");
 | 
			
		||||
                $conflicts[] = [
 | 
			
		||||
                    'domain' => $naked_domain,
 | 
			
		||||
                    'resource_name' => 'Coolify Instance',
 | 
			
		||||
                    'resource_link' => '#',
 | 
			
		||||
                    'resource_type' => 'instance',
 | 
			
		||||
                    'message' => "Domain $naked_domain is already in use by this Coolify instance",
 | 
			
		||||
                ];
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    return [
 | 
			
		||||
        'conflicts' => $conflicts,
 | 
			
		||||
        'hasConflicts' => count($conflicts) > 0,
 | 
			
		||||
    ];
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										91
									
								
								resources/views/components/domain-conflict-modal.blade.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										91
									
								
								resources/views/components/domain-conflict-modal.blade.php
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,91 @@
 | 
			
		||||
@props([
 | 
			
		||||
    'conflicts' => [],
 | 
			
		||||
    'showModal' => false,
 | 
			
		||||
    'confirmAction' => 'confirmDomainUsage',
 | 
			
		||||
])
 | 
			
		||||
 | 
			
		||||
@if ($showModal && count($conflicts) > 0)
 | 
			
		||||
    <div x-data="{ modalOpen: true }" x-init="$nextTick(() => { modalOpen = true })"
 | 
			
		||||
        @keydown.escape.window="modalOpen = false; $wire.set('showDomainConflictModal', false)"
 | 
			
		||||
        :class="{ 'z-40': modalOpen }" class="relative w-auto h-auto">
 | 
			
		||||
        <template x-teleport="body">
 | 
			
		||||
            <div x-show="modalOpen"
 | 
			
		||||
                class="fixed top-0 lg:pt-10 left-0 z-99 flex items-start justify-center w-screen h-screen" x-cloak>
 | 
			
		||||
                <div x-show="modalOpen" class="absolute inset-0 w-full h-full bg-black/20 backdrop-blur-xs"></div>
 | 
			
		||||
                <div x-show="modalOpen" x-trap.inert.noscroll="modalOpen" x-transition:enter="ease-out duration-100"
 | 
			
		||||
                    x-transition:enter-start="opacity-0 -translate-y-2 sm:scale-95"
 | 
			
		||||
                    x-transition:enter-end="opacity-100 translate-y-0 sm:scale-100"
 | 
			
		||||
                    x-transition:leave="ease-in duration-100"
 | 
			
		||||
                    x-transition:leave-start="opacity-100 translate-y-0 sm:scale-100"
 | 
			
		||||
                    x-transition:leave-end="opacity-0 -translate-y-2 sm:scale-95"
 | 
			
		||||
                    class="relative w-full py-6 border rounded-sm min-w-full lg:min-w-[36rem] max-w-[48rem] bg-neutral-100 border-neutral-400 dark:bg-base px-7 dark:border-coolgray-300">
 | 
			
		||||
                    <div class="flex justify-between items-center pb-3">
 | 
			
		||||
                        <h2 class="pr-8 font-bold">Domain Already In Use</h2>
 | 
			
		||||
                        <button @click="modalOpen = false; $wire.set('showDomainConflictModal', false)"
 | 
			
		||||
                            class="flex absolute top-2 right-2 justify-center items-center w-8 h-8 rounded-full dark:text-white hover:bg-coolgray-300">
 | 
			
		||||
                            <svg class="w-6 h-6" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24"
 | 
			
		||||
                                stroke-width="1.5" stroke="currentColor">
 | 
			
		||||
                                <path stroke-linecap="round" stroke-linejoin="round" d="M6 18L18 6M6 6l12 12" />
 | 
			
		||||
                            </svg>
 | 
			
		||||
                        </button>
 | 
			
		||||
                    </div>
 | 
			
		||||
                    <div class="relative w-auto">
 | 
			
		||||
                        <div class="p-4 mb-4 text-white border-l-4 border-red-500 bg-red-600" role="alert">
 | 
			
		||||
                            <p class="font-bold">Warning: Domain Conflict Detected</p>
 | 
			
		||||
                            <p>{{ $slot ?? 'The following domain(s) are already in use by other resources. Using the same domain for multiple resources can cause routing conflicts and unpredictable behavior.' }}
 | 
			
		||||
                            </p>
 | 
			
		||||
                        </div>
 | 
			
		||||
 | 
			
		||||
                        <div class="mb-4">
 | 
			
		||||
                            <h4 class="mb-2 font-semibold">Conflicting Resources:</h4>
 | 
			
		||||
                            <ul class="space-y-2">
 | 
			
		||||
                                @foreach ($conflicts as $conflict)
 | 
			
		||||
                                    <li class="flex items-start text-red-500">
 | 
			
		||||
                                        <div>
 | 
			
		||||
                                            <strong>{{ $conflict['domain'] }}</strong> is used by
 | 
			
		||||
                                            @if ($conflict['resource_type'] === 'instance')
 | 
			
		||||
                                                <strong>{{ $conflict['resource_name'] }}</strong>
 | 
			
		||||
                                            @else
 | 
			
		||||
                                                <a href="{{ $conflict['resource_link'] }}" target="_blank"
 | 
			
		||||
                                                    class="underline hover:text-red-400">
 | 
			
		||||
                                                    {{ $conflict['resource_name'] }}
 | 
			
		||||
                                                </a>
 | 
			
		||||
                                            @endif
 | 
			
		||||
                                            ({{ $conflict['resource_type'] }})
 | 
			
		||||
                                        </div>
 | 
			
		||||
                                    </li>
 | 
			
		||||
                                @endforeach
 | 
			
		||||
                            </ul>
 | 
			
		||||
                        </div>
 | 
			
		||||
 | 
			
		||||
                        <div class="p-4 mb-4 text-yellow-800 dark:text-yellow-200 border-l-4 bg-yellow-50 dark:bg-yellow-900/20 border border-yellow-200 dark:border-yellow-700 rounded-lg"
 | 
			
		||||
                            role="alert">
 | 
			
		||||
                            <p class="font-bold">What will happen if you continue?</p>
 | 
			
		||||
                            @if (isset($consequences))
 | 
			
		||||
                                {{ $consequences }}
 | 
			
		||||
                            @else
 | 
			
		||||
                                <ul class="mt-2 ml-4 list-disc">
 | 
			
		||||
                                    <li>Only one resource will be accessible at this domain</li>
 | 
			
		||||
                                    <li>The routing behavior will be unpredictable</li>
 | 
			
		||||
                                    <li>You may experience service disruptions</li>
 | 
			
		||||
                                    <li>SSL certificates might not work correctly</li>
 | 
			
		||||
                                </ul>
 | 
			
		||||
                            @endif
 | 
			
		||||
                        </div>
 | 
			
		||||
 | 
			
		||||
                        <div class="flex flex-wrap gap-2 justify-between mt-4">
 | 
			
		||||
                            <x-forms.button @click="modalOpen = false; $wire.set('showDomainConflictModal', false)"
 | 
			
		||||
                                class="w-auto dark:bg-coolgray-200 dark:hover:bg-coolgray-300">
 | 
			
		||||
                                Cancel
 | 
			
		||||
                            </x-forms.button>
 | 
			
		||||
                            <x-forms.button wire:click="{{ $confirmAction }}" @click="modalOpen = false" class="w-auto"
 | 
			
		||||
                                isError>
 | 
			
		||||
                                I understand, proceed anyway
 | 
			
		||||
                            </x-forms.button>
 | 
			
		||||
                        </div>
 | 
			
		||||
                    </div>
 | 
			
		||||
                </div>
 | 
			
		||||
            </div>
 | 
			
		||||
        </template>
 | 
			
		||||
    </div>
 | 
			
		||||
@endif
 | 
			
		||||
@@ -462,6 +462,12 @@
 | 
			
		||||
            </div>
 | 
			
		||||
        </div>
 | 
			
		||||
    </form>
 | 
			
		||||
    
 | 
			
		||||
    <x-domain-conflict-modal 
 | 
			
		||||
        :conflicts="$domainConflicts" 
 | 
			
		||||
        :showModal="$showDomainConflictModal" 
 | 
			
		||||
        confirmAction="confirmDomainUsage" />
 | 
			
		||||
    
 | 
			
		||||
    @script
 | 
			
		||||
        <script>
 | 
			
		||||
            $wire.$on('loadCompose', (isInit = true) => {
 | 
			
		||||
 
 | 
			
		||||
@@ -219,4 +219,19 @@
 | 
			
		||||
            @endforeach
 | 
			
		||||
        </div>
 | 
			
		||||
    @endif
 | 
			
		||||
    
 | 
			
		||||
    <x-domain-conflict-modal 
 | 
			
		||||
        :conflicts="$domainConflicts" 
 | 
			
		||||
        :showModal="$showDomainConflictModal" 
 | 
			
		||||
        confirmAction="confirmDomainUsage">
 | 
			
		||||
        The preview deployment domain is already in use by other resources. Using the same domain for multiple resources can cause routing conflicts and unpredictable behavior.
 | 
			
		||||
        <x-slot:consequences>
 | 
			
		||||
            <ul class="mt-2 ml-4 list-disc">
 | 
			
		||||
                <li>The preview deployment may not be accessible</li>
 | 
			
		||||
                <li>Conflicts with production or other preview deployments</li>
 | 
			
		||||
                <li>SSL certificates might not work correctly</li>
 | 
			
		||||
                <li>Unpredictable routing behavior</li>
 | 
			
		||||
            </ul>
 | 
			
		||||
        </x-slot:consequences>
 | 
			
		||||
    </x-domain-conflict-modal>
 | 
			
		||||
</div>
 | 
			
		||||
 
 | 
			
		||||
@@ -54,7 +54,7 @@
 | 
			
		||||
    @if ($environment->isEmpty())
 | 
			
		||||
        @can('createAnyResource')
 | 
			
		||||
            <a href="{{ route('project.resource.create', ['project_uuid' => data_get($parameters, 'project_uuid'), 'environment_uuid' => data_get($environment, 'uuid')]) }}"
 | 
			
		||||
                class="items-center justify-center box">+ Add New Resource</a>
 | 
			
		||||
                class="items-center justify-center box">+ Add Resource</a>
 | 
			
		||||
        @else
 | 
			
		||||
            <div
 | 
			
		||||
                class="flex flex-col items-center justify-center p-8 text-center border border-dashed border-neutral-300 dark:border-coolgray-300 rounded-lg">
 | 
			
		||||
 
 | 
			
		||||
@@ -1,7 +1,21 @@
 | 
			
		||||
<form wire:submit.prevent='submit' class="flex flex-col w-full gap-2">
 | 
			
		||||
<div class="w-full">
 | 
			
		||||
    <form wire:submit.prevent='submit' class="flex flex-col w-full gap-2">
 | 
			
		||||
        <div class="pb-2">Note: If a service has a defined port, do not delete it. <br>If you want to use your custom
 | 
			
		||||
            domain, you can add it with a port.</div>
 | 
			
		||||
    <x-forms.input canGate="update" :canResource="$application" placeholder="https://app.coolify.io" label="Domains" id="application.fqdn"
 | 
			
		||||
        <x-forms.input canGate="update" :canResource="$application" placeholder="https://app.coolify.io" label="Domains"
 | 
			
		||||
            id="application.fqdn"
 | 
			
		||||
            helper="You can specify one domain with path or more with comma. You can specify a port to bind the domain to.<br><br><span class='text-helper'>Example</span><br>- http://app.coolify.io,https://cloud.coolify.io/dashboard<br>- http://app.coolify.io/api/v3<br>- http://app.coolify.io:3000 -> app.coolify.io will point to port 3000 inside the container. "></x-forms.input>
 | 
			
		||||
        <x-forms.button canGate="update" :canResource="$application" type="submit">Save</x-forms.button>
 | 
			
		||||
</form>
 | 
			
		||||
    </form>
 | 
			
		||||
 | 
			
		||||
    <x-domain-conflict-modal :conflicts="$domainConflicts" :showModal="$showDomainConflictModal" confirmAction="confirmDomainUsage">
 | 
			
		||||
        <x-slot:consequences>
 | 
			
		||||
            <ul class="mt-2 ml-4 list-disc">
 | 
			
		||||
                <li>Only one service will be accessible at this domain</li>
 | 
			
		||||
                <li>The routing behavior will be unpredictable</li>
 | 
			
		||||
                <li>You may experience service disruptions</li>
 | 
			
		||||
                <li>SSL certificates might not work correctly</li>
 | 
			
		||||
            </ul>
 | 
			
		||||
        </x-slot:consequences>
 | 
			
		||||
    </x-domain-conflict-modal>
 | 
			
		||||
</div>
 | 
			
		||||
 
 | 
			
		||||
@@ -67,4 +67,18 @@
 | 
			
		||||
                instantSave="instantSaveAdvanced" id="application.is_log_drain_enabled" label="Drain Logs" />
 | 
			
		||||
        </div>
 | 
			
		||||
    </form>
 | 
			
		||||
    
 | 
			
		||||
    <x-domain-conflict-modal 
 | 
			
		||||
        :conflicts="$domainConflicts" 
 | 
			
		||||
        :showModal="$showDomainConflictModal" 
 | 
			
		||||
        confirmAction="confirmDomainUsage">
 | 
			
		||||
        <x-slot:consequences>
 | 
			
		||||
            <ul class="mt-2 ml-4 list-disc">
 | 
			
		||||
                <li>Only one service will be accessible at this domain</li>
 | 
			
		||||
                <li>The routing behavior will be unpredictable</li>
 | 
			
		||||
                <li>You may experience service disruptions</li>
 | 
			
		||||
                <li>SSL certificates might not work correctly</li>
 | 
			
		||||
            </ul>
 | 
			
		||||
        </x-slot:consequences>
 | 
			
		||||
    </x-domain-conflict-modal>
 | 
			
		||||
</div>
 | 
			
		||||
 
 | 
			
		||||
@@ -79,5 +79,19 @@
 | 
			
		||||
                </div>
 | 
			
		||||
            </div>
 | 
			
		||||
        </form>
 | 
			
		||||
        
 | 
			
		||||
        <x-domain-conflict-modal 
 | 
			
		||||
            :conflicts="$domainConflicts" 
 | 
			
		||||
            :showModal="$showDomainConflictModal" 
 | 
			
		||||
            confirmAction="confirmDomainUsage">
 | 
			
		||||
            <x-slot:consequences>
 | 
			
		||||
                <ul class="mt-2 ml-4 list-disc">
 | 
			
		||||
                    <li>The Coolify instance domain will conflict with existing resources</li>
 | 
			
		||||
                    <li>SSL certificates might not work correctly</li>
 | 
			
		||||
                    <li>Routing behavior will be unpredictable</li>
 | 
			
		||||
                    <li>You may not be able to access the Coolify dashboard properly</li>
 | 
			
		||||
                </ul>
 | 
			
		||||
            </x-slot:consequences>
 | 
			
		||||
        </x-domain-conflict-modal>
 | 
			
		||||
    </div>
 | 
			
		||||
</div>
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user