diff --git a/app/Actions/Proxy/StartProxy.php b/app/Actions/Proxy/StartProxy.php index daee357d5..99206606d 100644 --- a/app/Actions/Proxy/StartProxy.php +++ b/app/Actions/Proxy/StartProxy.php @@ -27,7 +27,7 @@ class StartProxy $server->save(); if ($server->isSwarm()) { $commands = $commands->merge([ - "mkdir -p $proxy_path && cd $proxy_path", + "mkdir -p $proxy_path/dynamic && cd $proxy_path", "echo 'Creating required Docker Compose file.'", "echo 'Starting coolify-proxy.'", "cd $proxy_path && docker stack deploy -c docker-compose.yml coolify-proxy", @@ -35,7 +35,7 @@ class StartProxy ]); } else { $commands = $commands->merge([ - "mkdir -p $proxy_path && cd $proxy_path", + "mkdir -p $proxy_path/dynamic && cd $proxy_path", "echo 'Creating required Docker Compose file.'", "echo 'Pulling docker image.'", 'docker compose pull', diff --git a/app/Jobs/ApplicationDeploymentJob.php b/app/Jobs/ApplicationDeploymentJob.php index 457cc04ad..93dad8e8a 100644 --- a/app/Jobs/ApplicationDeploymentJob.php +++ b/app/Jobs/ApplicationDeploymentJob.php @@ -488,7 +488,6 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted } else { $this->application_deployment_queue->addLogEntry("Starting pull request (#{$this->pull_request_id}) deployment of {$this->customRepository}:{$this->application->git_branch}."); } - ray('asddf'); $this->prepare_builder_image(); $this->check_git_if_build_needed(); $this->clone_repository(); diff --git a/app/Jobs/ScheduledTaskJob.php b/app/Jobs/ScheduledTaskJob.php index 227807b41..4a38a005b 100644 --- a/app/Jobs/ScheduledTaskJob.php +++ b/app/Jobs/ScheduledTaskJob.php @@ -39,7 +39,7 @@ class ScheduledTaskJob implements ShouldQueue } else if ($application = $task->application()->first()) { $this->resource = $application; } else { - throw new \Exception('ScheduledTaskJob failed: No resource found.'); + throw new \RuntimeException('ScheduledTaskJob failed: No resource found.'); } $this->team = Team::find($task->team_id); } diff --git a/app/Livewire/Project/DeleteEnvironment.php b/app/Livewire/Project/DeleteEnvironment.php index 0f2b59c93..c64ebd4b2 100644 --- a/app/Livewire/Project/DeleteEnvironment.php +++ b/app/Livewire/Project/DeleteEnvironment.php @@ -9,6 +9,7 @@ class DeleteEnvironment extends Component { public array $parameters; public int $environment_id; + public bool $disabled = false; public function mount() { diff --git a/app/Livewire/Project/DeleteProject.php b/app/Livewire/Project/DeleteProject.php index 7ac4aa281..543b45784 100644 --- a/app/Livewire/Project/DeleteProject.php +++ b/app/Livewire/Project/DeleteProject.php @@ -9,6 +9,7 @@ class DeleteProject extends Component { public array $parameters; public int $project_id; + public bool $disabled = false; public function mount() { diff --git a/app/Livewire/Project/Service/Configuration.php b/app/Livewire/Project/Service/Configuration.php index 32c174520..afbdd47bc 100644 --- a/app/Livewire/Project/Service/Configuration.php +++ b/app/Livewire/Project/Service/Configuration.php @@ -8,7 +8,7 @@ use Livewire\Component; class Configuration extends Component { - public Service $service; + public ?Service $service = null; public $applications; public $databases; public array $parameters; diff --git a/app/Livewire/Project/Shared/Danger.php b/app/Livewire/Project/Shared/Danger.php index b0f1c97c7..f19cf8d53 100644 --- a/app/Livewire/Project/Shared/Danger.php +++ b/app/Livewire/Project/Shared/Danger.php @@ -17,8 +17,8 @@ class Danger extends Component { $this->modalId = new Cuid2(7); $parameters = get_route_parameters(); - $this->projectUuid = $parameters['project_uuid']; - $this->environmentName = $parameters['environment_name']; + $this->projectUuid = data_get($parameters, 'project_uuid'); + $this->environmentName = data_get($parameters, 'environment_name'); } public function delete() diff --git a/app/Livewire/Project/Shared/EnvironmentVariable/Add.php b/app/Livewire/Project/Shared/EnvironmentVariable/Add.php index 78ed3c780..923f0d455 100644 --- a/app/Livewire/Project/Shared/EnvironmentVariable/Add.php +++ b/app/Livewire/Project/Shared/EnvironmentVariable/Add.php @@ -32,6 +32,13 @@ class Add extends Component public function submit() { $this->validate(); + if (str($this->value)->startsWith('{{') && str($this->value)->endsWith('}}')) { + $type = str($this->value)->after("{{")->before(".")->value; + if (!collect(SHARED_VARIABLE_TYPES)->contains($type)) { + $this->dispatch('error', 'Invalid shared variable type.', "Valid types are: team, project, environment."); + return; + } + } $this->dispatch('saveKey', [ 'key' => $this->key, 'value' => $this->value, diff --git a/app/Livewire/Project/Shared/EnvironmentVariable/All.php b/app/Livewire/Project/Shared/EnvironmentVariable/All.php index 77644519a..c30011a4a 100644 --- a/app/Livewire/Project/Shared/EnvironmentVariable/All.php +++ b/app/Livewire/Project/Shared/EnvironmentVariable/All.php @@ -71,12 +71,26 @@ class All extends Component continue; } $found->value = $variable; + if (str($found->value)->startsWith('{{') && str($found->value)->endsWith('}}')) { + $type = str($found->value)->after("{{")->before(".")->value; + if (!collect(SHARED_VARIABLE_TYPES)->contains($type)) { + $this->dispatch('error', 'Invalid shared variable type.', "Valid types are: team, project, environment."); + return; + } + } $found->save(); continue; } else { $environment = new EnvironmentVariable(); $environment->key = $key; $environment->value = $variable; + if (str($environment->value)->startsWith('{{') && str($environment->value)->endsWith('}}')) { + $type = str($environment->value)->after("{{")->before(".")->value; + if (!collect(SHARED_VARIABLE_TYPES)->contains($type)) { + $this->dispatch('error', 'Invalid shared variable type.', "Valid types are: team, project, environment."); + return; + } + } $environment->is_build_time = false; $environment->is_preview = $isPreview ? true : false; switch ($this->resource->type()) { diff --git a/app/Livewire/Project/Shared/EnvironmentVariable/Show.php b/app/Livewire/Project/Shared/EnvironmentVariable/Show.php index f8709afd8..1494707e7 100644 --- a/app/Livewire/Project/Shared/EnvironmentVariable/Show.php +++ b/app/Livewire/Project/Shared/EnvironmentVariable/Show.php @@ -50,7 +50,8 @@ class Show extends Component $this->isLocked = true; } } - public function serialize() { + public function serialize() + { data_forget($this->env, 'real_value'); if ($this->env->getMorphClass() === 'App\Models\SharedEnvironmentVariable') { data_forget($this->env, 'is_build_time'); @@ -80,11 +81,18 @@ class Show extends Component } else { $this->validate(); } + if (str($this->env->value)->startsWith('{{') && str($this->env->value)->endsWith('}}')) { + $type = str($this->env->value)->after("{{")->before(".")->value; + if (!collect(SHARED_VARIABLE_TYPES)->contains($type)) { + $this->dispatch('error', 'Invalid shared variable type.', "Valid types are: team, project, environment."); + return; + } + } $this->serialize(); $this->env->save(); $this->dispatch('success', 'Environment variable updated successfully.'); $this->dispatch('refreshEnvs'); - } catch(\Exception $e) { + } catch (\Exception $e) { return handleError($e); } } diff --git a/app/Livewire/Project/Shared/ResourceOperations.php b/app/Livewire/Project/Shared/ResourceOperations.php index 125bf2fb4..e2037991a 100644 --- a/app/Livewire/Project/Shared/ResourceOperations.php +++ b/app/Livewire/Project/Shared/ResourceOperations.php @@ -20,8 +20,8 @@ class ResourceOperations extends Component public function mount() { $parameters = get_route_parameters(); - $this->projectUuid = $parameters['project_uuid']; - $this->environmentName = $parameters['environment_name']; + $this->projectUuid = data_get($parameters, 'project_uuid'); + $this->environmentName = data_get($parameters, 'environment_name'); $this->projects = Project::ownedByCurrentTeam()->get(); $this->servers = currentTeam()->servers; } diff --git a/app/Livewire/Team/Invitations.php b/app/Livewire/Team/Invitations.php index c51723572..436c0778d 100644 --- a/app/Livewire/Team/Invitations.php +++ b/app/Livewire/Team/Invitations.php @@ -12,7 +12,11 @@ class Invitations extends Component public function deleteInvitation(int $invitation_id) { - TeamInvitation::find($invitation_id)->delete(); + $initiation_found = TeamInvitation::find($invitation_id); + if (!$initiation_found) { + return $this->dispatch('error', 'Invitation not found.'); + } + $initiation_found->delete(); $this->refreshInvitations(); $this->dispatch('success', 'Invitation revoked.'); } diff --git a/app/Livewire/Team/Storage/Create.php b/app/Livewire/Team/Storage/Create.php index 82f93320b..a25fc9821 100644 --- a/app/Livewire/Team/Storage/Create.php +++ b/app/Livewire/Team/Storage/Create.php @@ -67,7 +67,8 @@ class Create extends Component $this->storage->save(); return redirect()->route('team.storage.show', $this->storage->uuid); } catch (\Throwable $e) { - return handleError($e, $this); + $this->dispatch('error', 'Failed to create storage.', $e->getMessage()); + // return handleError($e, $this); } } } diff --git a/app/Livewire/Team/Storage/Form.php b/app/Livewire/Team/Storage/Form.php index 8a26a3471..1fd0d470b 100644 --- a/app/Livewire/Team/Storage/Form.php +++ b/app/Livewire/Team/Storage/Form.php @@ -33,9 +33,9 @@ class Form extends Component { try { $this->storage->testConnection(shouldSave: true); - return $this->dispatch('success', 'Connection is working. Tested with "ListObjectsV2" action.'); + return $this->dispatch('success', 'Connection is working.', 'Tested with "ListObjectsV2" action.'); } catch (\Throwable $e) { - return handleError($e, $this); + $this->dispatch('error', 'Failed to create storage.', $e->getMessage()); } } diff --git a/app/Models/EnvironmentVariable.php b/app/Models/EnvironmentVariable.php index 4ecc6699c..a3611a0c6 100644 --- a/app/Models/EnvironmentVariable.php +++ b/app/Models/EnvironmentVariable.php @@ -91,7 +91,7 @@ class EnvironmentVariable extends Model } private function get_real_environment_variables(?string $environment_variable = null, $resource = null): string|null { - if (!$environment_variable) { + if (!$environment_variable || !$resource) { return null; } $environment_variable = trim($environment_variable); @@ -100,6 +100,9 @@ class EnvironmentVariable extends Model $variable = Str::after($environment_variable, "{$type}."); $variable = Str::before($variable, '}}'); $variable = Str::of($variable)->trim()->value; + if (!collect(SHARED_VARIABLE_TYPES)->contains($type)) { + return $variable; + } if ($type === 'environment') { $id = $resource->environment->id; } else if ($type === 'project') { diff --git a/app/Models/Project.php b/app/Models/Project.php index d5f1bdd54..b9afc7426 100644 --- a/app/Models/Project.php +++ b/app/Models/Project.php @@ -64,10 +64,13 @@ class Project extends BaseModel } public function mysqls() { - return $this->hasMany(StandaloneMysql::class, Environment::class); + return $this->hasManyThrough(StandaloneMysql::class, Environment::class); } public function mariadbs() { - return $this->hasMany(StandaloneMariadb::class, Environment::class); + return $this->hasManyThrough(StandaloneMariadb::class, Environment::class); + } + public function resource_count() { + return $this->applications()->count() + $this->postgresqls()->count() + $this->redis()->count() + $this->mongodbs()->count() + $this->mysqls()->count() + $this->mariadbs()->count(); } } diff --git a/bootstrap/helpers/constants.php b/bootstrap/helpers/constants.php index 380168005..9b16ebfed 100644 --- a/bootstrap/helpers/constants.php +++ b/bootstrap/helpers/constants.php @@ -34,3 +34,5 @@ const SUPPORTED_OS = [ 'centos fedora rhel ol rocky', 'sles opensuse-leap opensuse-tumbleweed' ]; + +const SHARED_VARIABLE_TYPES = ['team', 'project', 'environment']; diff --git a/bootstrap/helpers/github.php b/bootstrap/helpers/github.php index 11cc5a3f8..16633168f 100644 --- a/bootstrap/helpers/github.php +++ b/bootstrap/helpers/github.php @@ -29,7 +29,7 @@ function generate_github_installation_token(GithubApp $source) 'Accept' => 'application/vnd.github.machine-man-preview+json' ])->post("{$source->api_url}/app/installations/{$source->installation_id}/access_tokens"); if ($token->failed()) { - throw new \Exception("Failed to get access token for " . $source->name . " with error: " . $token->json()['message']); + throw new RuntimeException("Failed to get access token for " . $source->name . " with error: " . $token->json()['message']); } return $token->json()['token']; } diff --git a/bootstrap/helpers/shared.php b/bootstrap/helpers/shared.php index c3b6f9f01..2a6ce9647 100644 --- a/bootstrap/helpers/shared.php +++ b/bootstrap/helpers/shared.php @@ -937,7 +937,7 @@ function parseDockerComposeFile(Service|Application $resource, bool $isNew = fal 'service_id' => $resource->id, ])->first(); ['command' => $command, 'forService' => $forService, 'generatedValue' => $generatedValue, 'port' => $port] = parseEnvVariable($value); - if ($command->value() === 'FQDN' || $command->value() === 'URL') { + if ($command?->value() === 'FQDN' || $command?->value() === 'URL') { if (Str::lower($forService) === $serviceName) { $fqdn = generateFqdn($resource->server, $containerName); } else { @@ -1357,7 +1357,7 @@ function parseDockerComposeFile(Service|Application $resource, bool $isNew = fal 'application_id' => $resource->id, ])->first(); ['command' => $command, 'forService' => $forService, 'generatedValue' => $generatedValue, 'port' => $port] = parseEnvVariable($value); - if ($command->value() === 'FQDN' || $command->value() === 'URL') { + if ($command?->value() === 'FQDN' || $command?->value() === 'URL') { if (Str::lower($forService) === $serviceName) { $fqdn = generateFqdn($server, $containerName); } else { @@ -1670,15 +1670,21 @@ function ip_match($ip, $cidrs, &$match = null) function check_fqdn_usage(ServiceApplication|Application $own_resource) { $domains = collect($own_resource->fqdns)->map(function ($domain) { - return Url::fromString($domain)->getHost(); + if (str($domain)->endsWith('/')) { + $domain = str($domain)->beforeLast('/'); + } + return str($domain)->replace('http://', '')->replace('https://', ''); }); $apps = Application::all(); foreach ($apps as $app) { $list_of_domains = collect(explode(',', $app->fqdn))->filter(fn ($fqdn) => $fqdn !== ''); foreach ($list_of_domains as $domain) { - $naked_domain = Url::fromString($domain)->getHost(); + if (str($domain)->endsWith('/')) { + $domain = str($domain)->beforeLast('/'); + } + $naked_domain = str($domain)->replace('http://', '')->replace('https://', '')->value(); if ($domains->contains($naked_domain)) { - if ($app->uuid !== $own_resource->uuid ) { + if ($app->uuid !== $own_resource->uuid) { throw new \RuntimeException("Domain $naked_domain is already in use by another resource."); } } @@ -1688,7 +1694,10 @@ function check_fqdn_usage(ServiceApplication|Application $own_resource) foreach ($apps as $app) { $list_of_domains = collect(explode(',', $app->fqdn))->filter(fn ($fqdn) => $fqdn !== ''); foreach ($list_of_domains as $domain) { - $naked_domain = Url::fromString($domain)->getHost(); + if (str($domain)->endsWith('/')) { + $domain = str($domain)->beforeLast('/'); + } + $naked_domain = str($domain)->replace('http://', '')->replace('https://', '')->value(); if ($domains->contains($naked_domain)) { if ($app->uuid !== $own_resource->uuid) { throw new \RuntimeException("Domain $naked_domain is already in use by another resource."); diff --git a/config/sentry.php b/config/sentry.php index 050405d5e..cb35fa610 100644 --- a/config/sentry.php +++ b/config/sentry.php @@ -7,7 +7,7 @@ return [ // The release version of your application // Example with dynamic git hash: trim(exec('git --git-dir ' . base_path('.git') . ' log --pretty="%h" -n1 HEAD')) - 'release' => '4.0.0-beta.203', + 'release' => '4.0.0-beta.204', // When left empty or `null` the Laravel environment will be used 'environment' => config('app.env'), diff --git a/config/version.php b/config/version.php index c1ca7dc2b..0cbf5f9b7 100644 --- a/config/version.php +++ b/config/version.php @@ -1,3 +1,3 @@ 'Are you sure?', + 'buttonTitle' => 'Open Modal', + 'isErrorButton' => false, + 'disabled' => false, + 'action' => 'delete', +]) +
+ @if ($disabled) + {{ $buttonTitle }} + @elseif ($isErrorButton) + {{ $buttonTitle }} + @else + {{ $buttonTitle }} + @endif + +
diff --git a/resources/views/components/toast.blade.php b/resources/views/components/toast.blade.php index f28bbc5eb..4be15e201 100644 --- a/resources/views/components/toast.blade.php +++ b/resources/views/components/toast.blade.php @@ -369,10 +369,10 @@ window.customToastHTML = ` }, 5); }, 4000);" @mouseover="toastHovered=true" @mouseout="toastHovered=false" - class="absolute w-full duration-200 ease-out select-none sm:max-w-xs" + class="absolute w-full duration-100 ease-out sm:max-w-xs" :class="{ 'toast-no-description': !toast.description }">