diff --git a/app/Http/Livewire/Dashboard.php b/app/Http/Livewire/Dashboard.php index 7c0505d8a..d411312d5 100644 --- a/app/Http/Livewire/Dashboard.php +++ b/app/Http/Livewire/Dashboard.php @@ -9,21 +9,21 @@ use Livewire\Component; class Dashboard extends Component { - public int $projects = 0; - public int $servers = 0; + public $projects = []; + public $servers = []; public int $s3s = 0; public int $resources = 0; public function mount() { - $this->servers = Server::ownedByCurrentTeam()->get()->count(); + $this->servers = Server::ownedByCurrentTeam()->get(); $this->s3s = S3Storage::ownedByCurrentTeam()->get()->count(); $projects = Project::ownedByCurrentTeam()->get(); foreach ($projects as $project) { $this->resources += $project->applications->count(); $this->resources += $project->postgresqls->count(); } - $this->projects = $projects->count(); + $this->projects = $projects; } // public function getIptables() // { diff --git a/app/Http/Livewire/Project/Application/General.php b/app/Http/Livewire/Project/Application/General.php index 52212dc9e..afe1b62dd 100644 --- a/app/Http/Livewire/Project/Application/General.php +++ b/app/Http/Livewire/Project/Application/General.php @@ -49,6 +49,9 @@ class General extends Component 'application.ports_exposes' => 'required', 'application.ports_mappings' => 'nullable', 'application.dockerfile' => 'nullable', + 'application.docker_registry_image_name' => 'nullable', + 'application.docker_registry_image_tag' => 'nullable', + 'application.dockerfile_location' => 'nullable', ]; protected $validationAttributes = [ 'application.name' => 'name', @@ -67,6 +70,9 @@ class General extends Component 'application.ports_exposes' => 'Ports exposes', 'application.ports_mappings' => 'Ports mappings', 'application.dockerfile' => 'Dockerfile', + 'application.docker_registry_image_name' => 'Docker registry image name', + 'application.docker_registry_image_tag' => 'Docker registry image tag', + 'application.dockerfile_location' => 'Dockerfile location', ]; diff --git a/app/Http/Livewire/Project/Database/BackupEdit.php b/app/Http/Livewire/Project/Database/BackupEdit.php index a8a0ef055..ea217f526 100644 --- a/app/Http/Livewire/Project/Database/BackupEdit.php +++ b/app/Http/Livewire/Project/Database/BackupEdit.php @@ -8,6 +8,7 @@ class BackupEdit extends Component { public $backup; public $s3s; + public ?string $status = null; public array $parameters; protected $rules = [ diff --git a/app/Http/Livewire/Project/New/DockerImage.php b/app/Http/Livewire/Project/New/DockerImage.php new file mode 100644 index 000000000..1081f3401 --- /dev/null +++ b/app/Http/Livewire/Project/New/DockerImage.php @@ -0,0 +1,78 @@ +parameters = get_route_parameters(); + $this->query = request()->query(); + } + public function submit() + { + $this->validate([ + 'dockerImage' => 'required' + ]); + $image = Str::of($this->dockerImage)->before(':'); + if (Str::of($this->dockerImage)->contains(':')) { + $tag = Str::of($this->dockerImage)->after(':'); + } else { + $tag = 'latest'; + } + $destination_uuid = $this->query['destination']; + $destination = StandaloneDocker::where('uuid', $destination_uuid)->first(); + if (!$destination) { + $destination = SwarmDocker::where('uuid', $destination_uuid)->first(); + } + if (!$destination) { + throw new \Exception('Destination not found. What?!'); + } + $destination_class = $destination->getMorphClass(); + + $project = Project::where('uuid', $this->parameters['project_uuid'])->first(); + $environment = $project->load(['environments'])->environments->where('name', $this->parameters['environment_name'])->first(); + ray($image,$tag); + $application = Application::create([ + 'name' => 'docker-image-' . new Cuid2(7), + 'repository_project_id' => 0, + 'git_repository' => "coollabsio/coolify", + 'git_branch' => 'main', + 'build_pack' => 'dockerimage', + 'ports_exposes' => 80, + 'docker_registry_image_name' => $image, + 'docker_registry_image_tag' => $tag, + 'environment_id' => $environment->id, + 'destination_id' => $destination->id, + 'destination_type' => $destination_class, + 'health_check_enabled' => false, + ]); + + $fqdn = generateFqdn($destination->server, $application->uuid); + $application->update([ + 'name' => 'docker-image-' . $application->uuid, + 'fqdn' => $fqdn + ]); + + redirect()->route('project.application.configuration', [ + 'application_uuid' => $application->uuid, + 'environment_name' => $environment->name, + 'project_uuid' => $project->uuid, + ]); + } + public function render() + { + return view('livewire.project.new.docker-image'); + } +} diff --git a/app/Http/Livewire/Team/Storage/Create.php b/app/Http/Livewire/Team/Storage/Create.php index bcac861f8..4d7fb4f12 100644 --- a/app/Http/Livewire/Team/Storage/Create.php +++ b/app/Http/Livewire/Team/Storage/Create.php @@ -64,7 +64,7 @@ class Create extends Component } $this->storage->team_id = currentTeam()->id; $this->storage->testConnection(); - $this->emit('success', 'Connection is working. Tested with "ListObjectsV2" action.'); + $this->storage->is_usable = true; $this->storage->save(); return redirect()->route('team.storages.show', $this->storage->uuid); } catch (\Throwable $e) { diff --git a/app/Http/Livewire/Team/Storage/Form.php b/app/Http/Livewire/Team/Storage/Form.php index 223c5ac41..573c851d7 100644 --- a/app/Http/Livewire/Team/Storage/Form.php +++ b/app/Http/Livewire/Team/Storage/Form.php @@ -9,6 +9,7 @@ class Form extends Component { public S3Storage $storage; protected $rules = [ + 'storage.is_usable' => 'nullable|boolean', 'storage.name' => 'nullable|min:3|max:255', 'storage.description' => 'nullable|min:3|max:255', 'storage.region' => 'required|max:255', @@ -18,6 +19,7 @@ class Form extends Component 'storage.endpoint' => 'required|url|max:255', ]; protected $validationAttributes = [ + 'storage.is_usable' => 'Is Usable', 'storage.name' => 'Name', 'storage.description' => 'Description', 'storage.region' => 'Region', diff --git a/app/Jobs/ApplicationDeploymentJob.php b/app/Jobs/ApplicationDeploymentJob.php index 57d89d932..f4b839ec3 100644 --- a/app/Jobs/ApplicationDeploymentJob.php +++ b/app/Jobs/ApplicationDeploymentJob.php @@ -45,6 +45,9 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted private string $commit; private bool $force_rebuild; + private ?string $dockerImage = null; + private ?string $dockerImageTag = null; + private GithubApp|GitlabApp|string $source = 'other'; private StandaloneDocker|SwarmDocker $destination; private Server $server; @@ -54,6 +57,7 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted private string|null $currently_running_container_name = null; private string $basedir; private string $workdir; + private ?string $build_pack = null; private string $configuration_dir; private string $build_image_name; private string $production_image_name; @@ -62,6 +66,7 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted private $env_args; private $docker_compose; private $docker_compose_base64; + private string $dockerfile_location = '/Dockerfile'; private $log_model; private Collection $saved_outputs; @@ -73,6 +78,7 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted $this->application_deployment_queue = ApplicationDeploymentQueue::find($application_deployment_queue_id); $this->log_model = $this->application_deployment_queue; $this->application = Application::find($this->application_deployment_queue->application_id); + $this->build_pack = data_get($this->application, 'build_pack'); $this->application_deployment_queue_id = $application_deployment_queue_id; $this->deployment_uuid = $this->application_deployment_queue->deployment_uuid; @@ -135,6 +141,10 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted try { if ($this->application->dockerfile) { $this->deploy_simple_dockerfile(); + } else if ($this->application->build_pack === 'dockerimage') { + $this->deploy_dockerimage(); + } else if ($this->application->build_pack === 'dockerfile') { + $this->deploy_dockerfile(); } else { if ($this->pull_request_id !== 0) { $this->deploy_pull_request(); @@ -173,6 +183,7 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted ); } } + private function deploy_docker_compose() { $dockercompose_base64 = base64_encode($this->application->dockercompose); @@ -245,6 +256,50 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted $this->rolling_update(); } + private function deploy_dockerimage() + { + $this->dockerImage = $this->application->docker_registry_image_name; + $this->dockerImageTag = $this->application->docker_registry_image_tag; + ray("echo 'Starting deployment of {$this->dockerImage}:{$this->dockerImageTag}.'"); + $this->execute_remote_command( + [ + "echo 'Starting deployment of {$this->dockerImage}:{$this->dockerImageTag}.'" + ], + ); + $this->production_image_name = Str::lower("{$this->dockerImage}:{$this->dockerImageTag}"); + $this->prepare_builder_image(); + $this->generate_compose_file(); + $this->rolling_update(); + } + + private function deploy_dockerfile() + { + if (data_get($this->application, 'dockerfile_location')) { + $this->dockerfile_location = $this->application->dockerfile_location; + } + $this->execute_remote_command( + [ + "echo 'Starting deployment of {$this->application->git_repository}:{$this->application->git_branch}.'" + ], + ); + $this->prepare_builder_image(); + $this->clone_repository(); + $this->set_base_dir(); + $tag = Str::of("{$this->commit}-{$this->application->id}-{$this->pull_request_id}"); + if (strlen($tag) > 128) { + $tag = $tag->substr(0, 128); + } + + $this->build_image_name = Str::lower("{$this->application->git_repository}:{$tag}-build"); + $this->production_image_name = Str::lower("{$this->application->uuid}:{$tag}"); + // ray('Build Image Name: ' . $this->build_image_name . ' & Production Image Name: ' . $this->production_image_name)->green(); + $this->cleanup_git(); + $this->generate_compose_file(); + $this->generate_build_env_variables(); + $this->add_build_env_variables_to_dockerfile(); + // $this->build_image(); + $this->rolling_update(); + } private function deploy() { $this->execute_remote_command( @@ -398,7 +453,8 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted ); } - private function set_base_dir() { + private function set_base_dir() + { $this->execute_remote_command( [ "echo -n 'Setting base directory to {$this->workdir}.'" @@ -608,6 +664,12 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted if (count($volume_names) > 0) { $docker_compose['volumes'] = $volume_names; } + if ($this->build_pack === 'dockerfile') { + $docker_compose['services'][$this->container_name]['build'] = [ + 'context' => $this->workdir, + 'dockerfile' => $this->workdir . $this->dockerfile_location, + ]; + } $this->docker_compose = Yaml::dump($docker_compose, 10); $this->docker_compose_base64 = base64_encode($this->docker_compose); $this->execute_remote_command([executeInDocker($this->deployment_uuid, "echo '{$this->docker_compose_base64}' | base64 -d > {$this->workdir}/docker-compose.yml"), "hidden" => true]); @@ -671,7 +733,7 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted private function generate_healthcheck_commands() { - if ($this->application->dockerfile || $this->application->build_pack === 'dockerfile') { + if ($this->application->dockerfile || $this->application->build_pack === 'dockerfile' || $this->application->build_pack === 'dockerimage') { // TODO: disabled HC because there are several ways to hc a simple docker image, hard to figure out a good way. Like some docker images (pocketbase) does not have curl. return 'exit 0'; } @@ -764,7 +826,7 @@ COPY ./nginx.conf /etc/nginx/conf.d/default.conf"); { $this->execute_remote_command( ["echo -n 'Starting application (could take a while).'"], - [executeInDocker($this->deployment_uuid, "docker compose --project-directory {$this->workdir} up -d >/dev/null"), "hidden" => true], + [executeInDocker($this->deployment_uuid, "docker compose --project-directory {$this->workdir} up --build -d >/dev/null"), "hidden" => true], ); } diff --git a/app/Jobs/ContainerStatusJob.php b/app/Jobs/ContainerStatusJob.php index 4f104f2ea..e175aeb8f 100644 --- a/app/Jobs/ContainerStatusJob.php +++ b/app/Jobs/ContainerStatusJob.php @@ -44,12 +44,12 @@ class ContainerStatusJob implements ShouldQueue, ShouldBeEncrypted public function handle() { try { - ray("checking server status for {$this->server->name}"); + // ray("checking server status for {$this->server->name}"); // ray()->clearAll(); $serverUptimeCheckNumber = $this->server->unreachable_count; $serverUptimeCheckNumberMax = 3; - ray('checking # ' . $serverUptimeCheckNumber); + // ray('checking # ' . $serverUptimeCheckNumber); if ($serverUptimeCheckNumber >= $serverUptimeCheckNumberMax) { if ($this->server->unreachable_email_sent === false) { ray('Server unreachable, sending notification...'); diff --git a/app/Jobs/DatabaseBackupJob.php b/app/Jobs/DatabaseBackupJob.php index a59c6494a..cc65cea9b 100644 --- a/app/Jobs/DatabaseBackupJob.php +++ b/app/Jobs/DatabaseBackupJob.php @@ -31,7 +31,7 @@ class DatabaseBackupJob implements ShouldQueue, ShouldBeEncrypted public ?string $container_name = null; public ?ScheduledDatabaseBackupExecution $backup_log = null; - public string $backup_status; + public string $backup_status = 'failed'; public ?string $backup_location = null; public string $backup_dir; public string $backup_file; @@ -74,7 +74,7 @@ class DatabaseBackupJob implements ShouldQueue, ShouldBeEncrypted $ip = Str::slug($this->server->ip); $this->backup_dir = backup_dir() . "/coolify" . "/coolify-db-$ip"; } - $this->backup_file = "/pg_dump-" . Carbon::now()->timestamp . ".dump"; + $this->backup_file = "/pg-backup-customformat-" . Carbon::now()->timestamp . ".backup"; $this->backup_location = $this->backup_dir . $this->backup_file; $this->backup_log = ScheduledDatabaseBackupExecution::create([ @@ -90,10 +90,17 @@ class DatabaseBackupJob implements ShouldQueue, ShouldBeEncrypted $this->upload_to_s3(); } $this->save_backup_logs(); + $this->team->notify(new BackupSuccess($this->backup, $this->database)); + $this->backup_status = 'success'; } catch (\Throwable $e) { - ray($e->getMessage()); + $this->backup_status = 'failed'; send_internal_notification('DatabaseBackupJob failed with: ' . $e->getMessage()); + $this->team->notify(new BackupFailed($this->backup, $this->database, $this->backup_output)); throw $e; + } finally { + $this->backup_log->update([ + 'status' => $this->backup_status, + ]); } } @@ -103,28 +110,15 @@ class DatabaseBackupJob implements ShouldQueue, ShouldBeEncrypted ray($this->backup_dir); $commands[] = "mkdir -p " . $this->backup_dir; $commands[] = "docker exec $this->container_name pg_dump -Fc -U {$this->database->postgres_user} > $this->backup_location"; - $this->backup_output = instant_remote_process($commands, $this->server); - $this->backup_output = trim($this->backup_output); - if ($this->backup_output === '') { $this->backup_output = null; } - ray('Backup done for ' . $this->container_name . ' at ' . $this->server->name . ':' . $this->backup_location); - - $this->backup_status = 'success'; - $this->team->notify(new BackupSuccess($this->backup, $this->database)); } catch (\Throwable $e) { - $this->backup_status = 'failed'; $this->add_to_backup_output($e->getMessage()); ray('Backup failed for ' . $this->container_name . ' at ' . $this->server->name . ':' . $this->backup_location . '\n\nError:' . $e->getMessage()); - $this->team->notify(new BackupFailed($this->backup, $this->database, $this->backup_output)); - } finally { - $this->backup_log->update([ - 'status' => $this->backup_status, - ]); } } @@ -163,11 +157,16 @@ class DatabaseBackupJob implements ShouldQueue, ShouldBeEncrypted } $key = $this->s3->key; $secret = $this->s3->secret; - // $region = $this->s3->region; + // $region = $this->s3->region; $bucket = $this->s3->bucket; $endpoint = $this->s3->endpoint; + $this->s3->testConnection(); + if (isDev()) { + $commands[] = "docker run --pull=always -d --network {$this->database->destination->network} --name backup-of-{$this->backup->uuid} --rm -v coolify_coolify-data-dev:/data/coolify:ro ghcr.io/coollabsio/coolify-helper >/dev/null 2>&1"; + } else { + $commands[] = "docker run --pull=always -d --network {$this->database->destination->network} --name backup-of-{$this->backup->uuid} --rm -v $this->backup_location:$this->backup_location:ro ghcr.io/coollabsio/coolify-helper >/dev/null 2>&1"; + } - $commands[] = "docker run --pull=always -d --network {$this->database->destination->network} --name backup-of-{$this->backup->uuid} --rm -v $this->backup_location:$this->backup_location:ro ghcr.io/coollabsio/coolify-helper"; $commands[] = "docker exec backup-of-{$this->backup->uuid} mc config host add temporary {$endpoint} $key $secret"; $commands[] = "docker exec backup-of-{$this->backup->uuid} mc cp $this->backup_location temporary/$bucket{$this->backup_dir}/"; instant_remote_process($commands, $this->server); @@ -175,7 +174,7 @@ class DatabaseBackupJob implements ShouldQueue, ShouldBeEncrypted ray('Uploaded to S3. ' . $this->backup_location . ' to s3://' . $bucket . $this->backup_dir); } catch (\Throwable $e) { $this->add_to_backup_output($e->getMessage()); - ray($e->getMessage()); + throw $e; } finally { $command = "docker rm -f backup-of-{$this->backup->uuid}"; instant_remote_process([$command], $this->server); diff --git a/app/Models/Application.php b/app/Models/Application.php index 32eb227ca..634569c0f 100644 --- a/app/Models/Application.php +++ b/app/Models/Application.php @@ -101,7 +101,21 @@ class Application extends BaseModel } ); } - + public function dockerfileLocation(): Attribute + { + return Attribute::make( + set: function ($value) { + if (is_null($value) || $value === '') { + return '/Dockerfile'; + } else { + if ($value !== '/') { + return Str::start(Str::replaceEnd('/', '', $value), '/'); + } + return Str::start($value, '/'); + } + } + ); + } public function baseDirectory(): Attribute { return Attribute::make( @@ -259,13 +273,14 @@ class Application extends BaseModel if ($this->dockerfile) { return false; } - + if ($this->build_pack === 'dockerimage') { + return false; + } return true; } public function isHealthcheckDisabled(): bool { - if (data_get($this, 'dockerfile') || data_get($this, 'build_pack') === 'dockerfile' || data_get($this, 'health_check_enabled') === false) { - ray('dockerfile'); + if (data_get($this, 'health_check_enabled') === false) { return true; } return false; diff --git a/app/Models/S3Storage.php b/app/Models/S3Storage.php index cbcdb97a9..03953d319 100644 --- a/app/Models/S3Storage.php +++ b/app/Models/S3Storage.php @@ -3,6 +3,8 @@ namespace App\Models; use Illuminate\Database\Eloquent\Factories\HasFactory; +use Illuminate\Notifications\Messages\MailMessage; +use Illuminate\Support\Facades\Storage; class S3Storage extends BaseModel { @@ -10,6 +12,7 @@ class S3Storage extends BaseModel protected $guarded = []; protected $casts = [ + 'is_usable' => 'boolean', 'key' => 'encrypted', 'secret' => 'encrypted', ]; @@ -19,7 +22,15 @@ class S3Storage extends BaseModel $selectArray = collect($select)->concat(['id']); return S3Storage::whereTeamId(currentTeam()->id)->select($selectArray->all())->orderBy('name'); } + public function isUsable() + { + return $this->is_usable; + } + public function team() + { + return $this->belongsTo(Team::class); + } public function awsUrl() { return "{$this->endpoint}/{$this->bucket}"; @@ -27,7 +38,34 @@ class S3Storage extends BaseModel public function testConnection() { - set_s3_target($this); - return \Storage::disk('custom-s3')->files(); + try { + set_s3_target($this); + Storage::disk('custom-s3')->files(); + $this->unusable_email_sent = false; + $this->is_usable = true; + return; + } catch (\Throwable $e) { + $this->is_usable = false; + if ($this->unusable_email_sent === false) { + $mail = new MailMessage(); + $mail->subject('Coolify: S3 Storage Connection Error'); + $mail->view('emails.s3-connection-error', ['name' => $this->name, 'reason' => $e->getMessage(), 'url' => route('team.storages.show', ['storage_uuid' => $this->uuid])]); + $users = collect([]); + $members = $this->team->members()->get(); + foreach ($members as $user) { + if ($user->isAdmin()) { + $users->push($user); + } + } + foreach ($users as $user) { + send_user_an_email($mail, $user->email); + } + $this->unusable_email_sent = true; + } + + throw $e; + } finally { + $this->save(); + } } } diff --git a/app/Models/Server.php b/app/Models/Server.php index 5cbd6deb5..a53d6ffe5 100644 --- a/app/Models/Server.php +++ b/app/Models/Server.php @@ -17,10 +17,14 @@ class Server extends BaseModel protected static function booted() { static::saving(function ($server) { - $server->forceFill([ - 'ip' => Str::of($server->ip)->trim(), - 'user' => Str::of($server->user)->trim(), - ]); + $payload = []; + if ($server->user) { + $payload['user'] = Str::of($server->user)->trim(); + } + if ($server->ip) { + $payload['ip'] = Str::of($server->ip)->trim(); + } + $server->forceFill($payload); }); static::created(function ($server) { diff --git a/app/Models/Team.php b/app/Models/Team.php index 6ae949d08..8021b1e97 100644 --- a/app/Models/Team.php +++ b/app/Models/Team.php @@ -48,6 +48,7 @@ class Team extends Model implements SendsDiscord, SendsEmail } return explode(',', $recipients); } + public function limits(): Attribute { return Attribute::make( @@ -125,7 +126,7 @@ class Team extends Model implements SendsDiscord, SendsEmail public function s3s() { - return $this->hasMany(S3Storage::class); + return $this->hasMany(S3Storage::class)->where('is_usable', true); } public function trialEnded() { foreach ($this->servers as $server) { diff --git a/app/Notifications/Application/DeploymentFailed.php b/app/Notifications/Application/DeploymentFailed.php index f97fbc77e..a379cd46f 100644 --- a/app/Notifications/Application/DeploymentFailed.php +++ b/app/Notifications/Application/DeploymentFailed.php @@ -52,10 +52,10 @@ class DeploymentFailed extends Notification implements ShouldQueue $pull_request_id = data_get($this->preview, 'pull_request_id', 0); $fqdn = $this->fqdn; if ($pull_request_id === 0) { - $mail->subject('❌ Deployment failed of ' . $this->application_name . '.'); + $mail->subject('Coolify: Deployment failed of ' . $this->application_name . '.'); } else { $fqdn = $this->preview->fqdn; - $mail->subject('❌ Deployment failed of pull request #' . $this->preview->pull_request_id . ' of ' . $this->application_name . '.'); + $mail->subject('Coolify: Deployment failed of pull request #' . $this->preview->pull_request_id . ' of ' . $this->application_name . '.'); } $mail->view('emails.application-deployment-failed', [ 'name' => $this->application_name, @@ -69,10 +69,10 @@ class DeploymentFailed extends Notification implements ShouldQueue public function toDiscord(): string { if ($this->preview) { - $message = '❌ Pull request #' . $this->preview->pull_request_id . ' of **' . $this->application_name . '** (' . $this->preview->fqdn . ') deployment failed: '; + $message = 'Coolify: Pull request #' . $this->preview->pull_request_id . ' of **' . $this->application_name . '** (' . $this->preview->fqdn . ') deployment failed: '; $message .= '[View Deployment Logs](' . $this->deployment_url . ')'; } else { - $message = '❌ Deployment failed of **' . $this->application_name . '** (' . $this->fqdn . '): '; + $message = 'Coolify: Deployment failed of **' . $this->application_name . '** (' . $this->fqdn . '): '; $message .= '[View Deployment Logs](' . $this->deployment_url . ')'; } return $message; @@ -80,9 +80,9 @@ class DeploymentFailed extends Notification implements ShouldQueue public function toTelegram(): array { if ($this->preview) { - $message = '❌ Pull request #' . $this->preview->pull_request_id . ' of **' . $this->application_name . '** (' . $this->preview->fqdn . ') deployment failed: '; + $message = 'Coolify: Pull request #' . $this->preview->pull_request_id . ' of **' . $this->application_name . '** (' . $this->preview->fqdn . ') deployment failed: '; } else { - $message = '❌ Deployment failed of **' . $this->application_name . '** (' . $this->fqdn . '): '; + $message = 'Coolify: Deployment failed of **' . $this->application_name . '** (' . $this->fqdn . '): '; } return [ "message" => $message, diff --git a/app/Notifications/Application/DeploymentSuccess.php b/app/Notifications/Application/DeploymentSuccess.php index 053ae7a2b..1fe01a5d7 100644 --- a/app/Notifications/Application/DeploymentSuccess.php +++ b/app/Notifications/Application/DeploymentSuccess.php @@ -52,10 +52,10 @@ class DeploymentSuccess extends Notification implements ShouldQueue $pull_request_id = data_get($this->preview, 'pull_request_id', 0); $fqdn = $this->fqdn; if ($pull_request_id === 0) { - $mail->subject("✅ New version is deployed of {$this->application_name}"); + $mail->subject("Coolify: New version is deployed of {$this->application_name}"); } else { $fqdn = $this->preview->fqdn; - $mail->subject("✅ Pull request #{$pull_request_id} of {$this->application_name} deployed successfully"); + $mail->subject("Coolify: Pull request #{$pull_request_id} of {$this->application_name} deployed successfully"); } $mail->view('emails.application-deployment-success', [ 'name' => $this->application_name, @@ -69,7 +69,7 @@ class DeploymentSuccess extends Notification implements ShouldQueue public function toDiscord(): string { if ($this->preview) { - $message = '✅ New PR' . $this->preview->pull_request_id . ' version successfully deployed of ' . $this->application_name . ' + $message = 'Coolify: New PR' . $this->preview->pull_request_id . ' version successfully deployed of ' . $this->application_name . ' '; if ($this->preview->fqdn) { @@ -77,7 +77,7 @@ class DeploymentSuccess extends Notification implements ShouldQueue } $message .= '[Deployment logs](' . $this->deployment_url . ')'; } else { - $message = '✅ New version successfully deployed of ' . $this->application_name . ' + $message = 'Coolify: New version successfully deployed of ' . $this->application_name . ' '; if ($this->fqdn) { @@ -90,7 +90,7 @@ class DeploymentSuccess extends Notification implements ShouldQueue public function toTelegram(): array { if ($this->preview) { - $message = '✅ New PR' . $this->preview->pull_request_id . ' version successfully deployed of ' . $this->application_name . ''; + $message = 'Coolify: New PR' . $this->preview->pull_request_id . ' version successfully deployed of ' . $this->application_name . ''; if ($this->preview->fqdn) { $buttons[] = [ "text" => "Open Application", diff --git a/app/Notifications/Application/StatusChanged.php b/app/Notifications/Application/StatusChanged.php index 064c347d0..df12f2b0a 100644 --- a/app/Notifications/Application/StatusChanged.php +++ b/app/Notifications/Application/StatusChanged.php @@ -45,7 +45,7 @@ class StatusChanged extends Notification implements ShouldQueue { $mail = new MailMessage(); $fqdn = $this->fqdn; - $mail->subject("⛔ {$this->application_name} has been stopped"); + $mail->subject("Coolify: {$this->application_name} has been stopped"); $mail->view('emails.application-status-changes', [ 'name' => $this->application_name, 'fqdn' => $fqdn, @@ -56,7 +56,7 @@ class StatusChanged extends Notification implements ShouldQueue public function toDiscord(): string { - $message = '⛔ ' . $this->application_name . ' has been stopped. + $message = 'Coolify: ' . $this->application_name . ' has been stopped. '; $message .= '[Open Application in Coolify](' . $this->application_url . ')'; @@ -64,7 +64,7 @@ class StatusChanged extends Notification implements ShouldQueue } public function toTelegram(): array { - $message = '⛔ ' . $this->application_name . ' has been stopped.'; + $message = 'Coolify: ' . $this->application_name . ' has been stopped.'; return [ "message" => $message, "buttons" => [ diff --git a/app/Notifications/Container/ContainerRestarted.php b/app/Notifications/Container/ContainerRestarted.php index 25fd08ff8..dc55c05d2 100644 --- a/app/Notifications/Container/ContainerRestarted.php +++ b/app/Notifications/Container/ContainerRestarted.php @@ -27,7 +27,7 @@ class ContainerRestarted extends Notification implements ShouldQueue public function toMail(): MailMessage { $mail = new MailMessage(); - $mail->subject("✅ Container ({$this->name}) has been restarted automatically on {$this->server->name}"); + $mail->subject("Coolify: Container ({$this->name}) has been restarted automatically on {$this->server->name}"); $mail->view('emails.container-restarted', [ 'containerName' => $this->name, 'serverName' => $this->server->name, @@ -38,12 +38,12 @@ class ContainerRestarted extends Notification implements ShouldQueue public function toDiscord(): string { - $message = "✅ Container ({$this->name}) has been restarted automatically on {$this->server->name}"; + $message = "Coolify: Container ({$this->name}) has been restarted automatically on {$this->server->name}"; return $message; } public function toTelegram(): array { - $message = "✅ Container ({$this->name}) has been restarted automatically on {$this->server->name}"; + $message = "Coolify: Container ({$this->name}) has been restarted automatically on {$this->server->name}"; $payload = [ "message" => $message, ]; diff --git a/app/Notifications/Container/ContainerStopped.php b/app/Notifications/Container/ContainerStopped.php index 390c9552b..4518698fb 100644 --- a/app/Notifications/Container/ContainerStopped.php +++ b/app/Notifications/Container/ContainerStopped.php @@ -26,7 +26,7 @@ class ContainerStopped extends Notification implements ShouldQueue public function toMail(): MailMessage { $mail = new MailMessage(); - $mail->subject("⛔ Container {$this->name} has been stopped on {$this->server->name}"); + $mail->subject("Coolify: Container ({$this->name}) has been stopped on {$this->server->name}"); $mail->view('emails.container-stopped', [ 'containerName' => $this->name, 'serverName' => $this->server->name, @@ -37,12 +37,12 @@ class ContainerStopped extends Notification implements ShouldQueue public function toDiscord(): string { - $message = "⛔ Container {$this->name} has been stopped on {$this->server->name}"; + $message = "Coolify: Container ({$this->name}) has been stopped on {$this->server->name}"; return $message; } public function toTelegram(): array { - $message = "⛔ Container ($this->name} has been stopped on {$this->server->name}"; + $message = "Coolify: Container ($this->name} has been stopped on {$this->server->name}"; $payload = [ "message" => $message, ]; diff --git a/app/Notifications/Database/BackupFailed.php b/app/Notifications/Database/BackupFailed.php index 482b27977..dddc1240b 100644 --- a/app/Notifications/Database/BackupFailed.php +++ b/app/Notifications/Database/BackupFailed.php @@ -30,7 +30,7 @@ class BackupFailed extends Notification implements ShouldQueue public function toMail(): MailMessage { $mail = new MailMessage(); - $mail->subject("❌ [ACTION REQUIRED] Backup FAILED for {$this->database->name}"); + $mail->subject("Coolify: [ACTION REQUIRED] Backup FAILED for {$this->database->name}"); $mail->view('emails.backup-failed', [ 'name' => $this->name, 'frequency' => $this->frequency, @@ -41,11 +41,11 @@ class BackupFailed extends Notification implements ShouldQueue public function toDiscord(): string { - return "❌ Database backup for {$this->name} with frequency of {$this->frequency} was FAILED.\n\nReason: {$this->output}"; + return "Coolify: Database backup for {$this->name} with frequency of {$this->frequency} was FAILED.\n\nReason: {$this->output}"; } public function toTelegram(): array { - $message = "❌ Database backup for {$this->name} with frequency of {$this->frequency} was FAILED.\n\nReason: {$this->output}"; + $message = "Coolify: Database backup for {$this->name} with frequency of {$this->frequency} was FAILED.\n\nReason: {$this->output}"; return [ "message" => $message, ]; diff --git a/app/Notifications/Database/BackupSuccess.php b/app/Notifications/Database/BackupSuccess.php index 0378baf96..bbe0bc6d3 100644 --- a/app/Notifications/Database/BackupSuccess.php +++ b/app/Notifications/Database/BackupSuccess.php @@ -30,7 +30,7 @@ class BackupSuccess extends Notification implements ShouldQueue public function toMail(): MailMessage { $mail = new MailMessage(); - $mail->subject("✅ Backup successfully done for {$this->database->name}"); + $mail->subject("Coolify: Backup successfully done for {$this->database->name}"); $mail->view('emails.backup-success', [ 'name' => $this->name, 'frequency' => $this->frequency, @@ -40,11 +40,11 @@ class BackupSuccess extends Notification implements ShouldQueue public function toDiscord(): string { - return "✅ Database backup for {$this->name} with frequency of {$this->frequency} was successful."; + return "Coolify: Database backup for {$this->name} with frequency of {$this->frequency} was successful."; } public function toTelegram(): array { - $message = "✅ Database backup for {$this->name} with frequency of {$this->frequency} was successful."; + $message = "Coolify: Database backup for {$this->name} with frequency of {$this->frequency} was successful."; return [ "message" => $message, ]; diff --git a/app/Notifications/Server/Revived.php b/app/Notifications/Server/Revived.php index 07771a287..21fe6d40d 100644 --- a/app/Notifications/Server/Revived.php +++ b/app/Notifications/Server/Revived.php @@ -45,7 +45,7 @@ class Revived extends Notification implements ShouldQueue public function toMail(): MailMessage { $mail = new MailMessage(); - $mail->subject("✅ Server ({$this->server->name}) revived."); + $mail->subject("Coolify: Server ({$this->server->name}) revived."); $mail->view('emails.server-revived', [ 'name' => $this->server->name, ]); @@ -54,13 +54,13 @@ class Revived extends Notification implements ShouldQueue public function toDiscord(): string { - $message = "✅ Server '{$this->server->name}' revived. All automations & integrations are turned on again!"; + $message = "Coolify: Server '{$this->server->name}' revived. All automations & integrations are turned on again!"; return $message; } public function toTelegram(): array { return [ - "message" => "✅ Server '{$this->server->name}' revived. All automations & integrations are turned on again!" + "message" => "Coolify: Server '{$this->server->name}' revived. All automations & integrations are turned on again!" ]; } } diff --git a/app/Notifications/Server/Unreachable.php b/app/Notifications/Server/Unreachable.php index 705988e31..ae100b804 100644 --- a/app/Notifications/Server/Unreachable.php +++ b/app/Notifications/Server/Unreachable.php @@ -43,7 +43,7 @@ class Unreachable extends Notification implements ShouldQueue public function toMail(): MailMessage { $mail = new MailMessage(); - $mail->subject("⛔ Server ({$this->server->name}) is unreachable after trying to connect to it 5 times"); + $mail->subject("Coolify: Server ({$this->server->name}) is unreachable after trying to connect to it 5 times"); $mail->view('emails.server-lost-connection', [ 'name' => $this->server->name, ]); @@ -52,13 +52,13 @@ class Unreachable extends Notification implements ShouldQueue public function toDiscord(): string { - $message = "⛔ Server '{$this->server->name}' is unreachable after trying to connect to it 5 times. All automations & integrations are turned off! Please check your server! IMPORTANT: We automatically try to revive your server. If your server is back online, we will automatically turn on all automations & integrations."; + $message = "Coolify: Server '{$this->server->name}' is unreachable after trying to connect to it 5 times. All automations & integrations are turned off! Please check your server! IMPORTANT: We automatically try to revive your server. If your server is back online, we will automatically turn on all automations & integrations."; return $message; } public function toTelegram(): array { return [ - "message" => "⛔ Server '{$this->server->name}' is unreachable after trying to connect to it 5 times. All automations & integrations are turned off! Please check your server! IMPORTANT: We automatically try to revive your server. If your server is back online, we will automatically turn on all automations & integrations." + "message" => "Coolify: Server '{$this->server->name}' is unreachable after trying to connect to it 5 times. All automations & integrations are turned off! Please check your server! IMPORTANT: We automatically try to revive your server. If your server is back online, we will automatically turn on all automations & integrations." ]; } } diff --git a/app/Notifications/Test.php b/app/Notifications/Test.php index 098a94920..06e3adbaa 100644 --- a/app/Notifications/Test.php +++ b/app/Notifications/Test.php @@ -24,14 +24,14 @@ class Test extends Notification implements ShouldQueue public function toMail(): MailMessage { $mail = new MailMessage(); - $mail->subject("Test Email"); + $mail->subject("Coolify: Test Email"); $mail->view('emails.test'); return $mail; } public function toDiscord(): string { - $message = 'This is a test Discord notification from Coolify.'; + $message = 'Coolify: This is a test Discord notification from Coolify.'; $message .= "\n\n"; $message .= '[Go to your dashboard](' . base_url() . ')'; return $message; @@ -39,7 +39,7 @@ class Test extends Notification implements ShouldQueue public function toTelegram(): array { return [ - "message" => 'This is a test Telegram notification from Coolify.', + "message" => 'Coolify: This is a test Telegram notification from Coolify.', "buttons" => [ [ "text" => "Go to your dashboard", diff --git a/app/Notifications/TransactionalEmails/InvitationLink.php b/app/Notifications/TransactionalEmails/InvitationLink.php index ab7cfb122..dd1275c2d 100644 --- a/app/Notifications/TransactionalEmails/InvitationLink.php +++ b/app/Notifications/TransactionalEmails/InvitationLink.php @@ -30,7 +30,7 @@ class InvitationLink extends Notification implements ShouldQueue $invitation_team = Team::find($invitation->team->id); $mail = new MailMessage(); - $mail->subject('Invitation for ' . $invitation_team->name); + $mail->subject('Coolify: Invitation for ' . $invitation_team->name); $mail->view('emails.invitation-link', [ 'team' => $invitation_team->name, 'email' => $this->user->email, diff --git a/app/Notifications/TransactionalEmails/ResetPassword.php b/app/Notifications/TransactionalEmails/ResetPassword.php index 2a11051b3..cde6190e2 100644 --- a/app/Notifications/TransactionalEmails/ResetPassword.php +++ b/app/Notifications/TransactionalEmails/ResetPassword.php @@ -50,7 +50,7 @@ class ResetPassword extends Notification protected function buildMailMessage($url) { $mail = new MailMessage(); - $mail->subject('Reset Password'); + $mail->subject('Coolify: Reset Password'); $mail->view('emails.reset-password', ['url' => $url, 'count' => config('auth.passwords.' . config('auth.defaults.passwords') . '.expire')]); return $mail; } diff --git a/app/Notifications/TransactionalEmails/Test.php b/app/Notifications/TransactionalEmails/Test.php index 21cf87470..6a4e5533f 100644 --- a/app/Notifications/TransactionalEmails/Test.php +++ b/app/Notifications/TransactionalEmails/Test.php @@ -25,7 +25,7 @@ class Test extends Notification implements ShouldQueue public function toMail(): MailMessage { $mail = new MailMessage(); - $mail->subject('Test Email'); + $mail->subject('Coolify: Test Email'); $mail->view('emails.test'); return $mail; } diff --git a/bootstrap/helpers/proxy.php b/bootstrap/helpers/proxy.php index f3ee1fa77..86034282e 100644 --- a/bootstrap/helpers/proxy.php +++ b/bootstrap/helpers/proxy.php @@ -204,17 +204,23 @@ stream { proxy_pass $database->uuid:5432; } } +EOF; + $dockerfile = <<< EOF +FROM nginx:stable-alpine + +COPY nginx.conf /etc/nginx/nginx.conf EOF; $docker_compose = [ 'version' => '3.8', 'services' => [ $containerName => [ + 'build' => [ + 'context' => $configuration_dir, + 'dockerfile' => 'Dockerfile', + ], 'image' => "nginx:stable-alpine", 'container_name' => $containerName, 'restart' => RESTART_MODE, - 'volumes' => [ - "$configuration_dir/nginx.conf:/etc/nginx/nginx.conf:ro", - ], 'ports' => [ "$database->public_port:$database->public_port", ], @@ -243,13 +249,13 @@ EOF; ]; $dockercompose_base64 = base64_encode(Yaml::dump($docker_compose, 4, 2)); $nginxconf_base64 = base64_encode($nginxconf); + $dockerfile_base64 = base64_encode($dockerfile); instant_remote_process([ "mkdir -p $configuration_dir", + "echo '{$dockerfile_base64}' | base64 -d > $configuration_dir/Dockerfile", "echo '{$nginxconf_base64}' | base64 -d > $configuration_dir/nginx.conf", "echo '{$dockercompose_base64}' | base64 -d > $configuration_dir/docker-compose.yaml", - "docker compose --project-directory {$configuration_dir} up -d >/dev/null", - - + "docker compose --project-directory {$configuration_dir} up --build -d >/dev/null", ], $database->destination->server); } function stopPostgresProxy(StandalonePostgresql $database) diff --git a/config/sentry.php b/config/sentry.php index 747522787..a2621713d 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.71', + 'release' => '4.0.0-beta.72', // 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 b255224ee..4f50ad680 100644 --- a/config/version.php +++ b/config/version.php @@ -1,3 +1,3 @@ boolean('is_usable')->default(false); + $table->boolean('unusable_email_sent')->default(false); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::table('s3_storages', function (Blueprint $table) { + $table->dropColumn('is_usable'); + $table->dropColumn('unusable_email_sent'); + }); + } +}; diff --git a/database/migrations/2023_10_10_113144_add_dockerfile_location_applications_table.php b/database/migrations/2023_10_10_113144_add_dockerfile_location_applications_table.php new file mode 100644 index 000000000..39956a9ed --- /dev/null +++ b/database/migrations/2023_10_10_113144_add_dockerfile_location_applications_table.php @@ -0,0 +1,28 @@ +string('dockerfile_location')->nullable(); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::table('applications', function (Blueprint $table) { + $table->dropColumn('dockerfile_location'); + }); + } +}; diff --git a/resources/views/components/applications/navbar.blade.php b/resources/views/components/applications/navbar.blade.php index b84b0bf13..d38790789 100644 --- a/resources/views/components/applications/navbar.blade.php +++ b/resources/views/components/applications/navbar.blade.php @@ -16,7 +16,7 @@ @if ($application->status !== 'exited') - + @@ -25,7 +25,7 @@ - Restart + Redeploy + Connection could not be establised with one of your S3 Storage ({{ $name }}). Please fix it + [here]({{ $url }}). + + {{ $reason }} + diff --git a/resources/views/livewire/dashboard.blade.php b/resources/views/livewire/dashboard.blade.php index c61f48d65..c0d4a3575 100644 --- a/resources/views/livewire/dashboard.blade.php +++ b/resources/views/livewire/dashboard.blade.php @@ -3,7 +3,7 @@ @endif Dashboard - Something useful will be here. + Your self-hosted environment @if (request()->query->get('success')) Servers - {{ $servers }} + {{ $servers->count() }} Projects - {{ $projects }} + {{ $projects->count() }} @@ -35,6 +35,84 @@ {{ $s3s }} - - {{-- Get IPTABLES --}} + Projects + + @if ($projects->count() === 1) + + @else + + @endif + @foreach ($projects as $project) + + @if (data_get($project, 'environments.0.name')) + + {{ $project->name }} + + {{ $project->description }} + + @else + + {{ $project->name }} + + {{ $project->description }} + + @endif + + + + + + + + + @endforeach + +Servers +@if ($servers->count() === 1) + + @else + +@endif +@foreach ($servers as $server) + $server->settings->is_reachable, + 'border-red-500' => !$server->settings->is_reachable, + ])> + + + {{ $server->name }} + + + {{ $server->description }} + + @if (!$server->settings->is_reachable) + Not reachable + @endif + @if (!$server->settings->is_reachable && !$server->settings->is_usable) + & + @endif + @if (!$server->settings->is_usable) + Not usable by Coolify + @endif + + + + +@endforeach + + +{{-- Get IPTABLES --}} diff --git a/resources/views/livewire/project/application/general.blade.php b/resources/views/livewire/project/application/general.blade.php index fb525a8dd..4492c8192 100644 --- a/resources/views/livewire/project/application/general.blade.php +++ b/resources/views/livewire/project/application/general.blade.php @@ -24,6 +24,7 @@ Nixpacks Dockerfile + Docker Image @if ($application->settings->is_static) @@ -41,28 +42,42 @@ @endif - Build - @if ($application->could_set_build_commands()) + @if ($application->build_pack !== 'dockerimage') + Build + @if ($application->could_set_build_commands()) + + + + + + @endif + - - - + + @if ($application->build_pack === 'dockerfile') + + @endif + @if ($application->could_set_build_commands()) + @if ($application->settings->is_static) + + @else + + @endif + @endif + + @else + + + @endif - - - @if ($application->could_set_build_commands()) - @if ($application->settings->is_static) - - @else - - @endif - @endif - @if ($application->dockerfile) @endif @@ -81,7 +96,6 @@ Advanced - diff --git a/resources/views/livewire/project/database/backup-edit.blade.php b/resources/views/livewire/project/database/backup-edit.blade.php index f7f7afa8e..412595958 100644 --- a/resources/views/livewire/project/database/backup-edit.blade.php +++ b/resources/views/livewire/project/database/backup-edit.blade.php @@ -4,7 +4,9 @@ Save - + @if (Str::of($status)->startsWith('running')) + + @endif @if ($backup->database_id !== 0) Delete @endif @@ -16,7 +18,7 @@ @if ($backup->save_s3) - Select a S3 storage + Select a S3 storage @foreach ($s3s as $s3) {{ $s3->name }} @endforeach diff --git a/resources/views/livewire/project/new/docker-image.blade.php b/resources/views/livewire/project/new/docker-image.blade.php new file mode 100644 index 000000000..55ad3d6ac --- /dev/null +++ b/resources/views/livewire/project/new/docker-image.blade.php @@ -0,0 +1,11 @@ + + Create a new Application + You can deploy an existing Docker Image from any Registry. + + + Docker Image + Save + + + + diff --git a/resources/views/livewire/project/new/select.blade.php b/resources/views/livewire/project/new/select.blade.php index 376f143dc..4e96cdf76 100644 --- a/resources/views/livewire/project/new/select.blade.php +++ b/resources/views/livewire/project/new/select.blade.php @@ -62,6 +62,16 @@ + + + + Based on an existing Docker Image + + + You can deploy an existing Docker Image form any Registry. + + + Databases diff --git a/resources/views/livewire/server/all.blade.php b/resources/views/livewire/server/all.blade.php index 8ad9c82a3..8195eb2af 100644 --- a/resources/views/livewire/server/all.blade.php +++ b/resources/views/livewire/server/all.blade.php @@ -15,7 +15,7 @@ 'border-red-500' => !$server->settings->is_reachable, ])> - + {{ $server->name }} diff --git a/resources/views/livewire/settings/backup.blade.php b/resources/views/livewire/settings/backup.blade.php index f2bb18fbe..3b050bfb5 100644 --- a/resources/views/livewire/settings/backup.blade.php +++ b/resources/views/livewire/settings/backup.blade.php @@ -22,7 +22,7 @@ - + @else To configure automatic backup for your Coolify instance, you first need to add as a database resource into Coolify. diff --git a/resources/views/livewire/team/storage/form.blade.php b/resources/views/livewire/team/storage/form.blade.php index 5c824e984..5e7c6a7d8 100644 --- a/resources/views/livewire/team/storage/form.blade.php +++ b/resources/views/livewire/team/storage/form.blade.php @@ -10,12 +10,17 @@ Storage Details {{ $storage->name }} + @if ($storage->is_usable) + Usable + @else + Not Usable + @endif Save - Test Connection + Validate Connection Delete diff --git a/resources/views/project/database/backups/executions.blade.php b/resources/views/project/database/backups/executions.blade.php index afcd0c061..26d4aabd0 100644 --- a/resources/views/project/database/backups/executions.blade.php +++ b/resources/views/project/database/backups/executions.blade.php @@ -12,7 +12,7 @@ - + Executions diff --git a/resources/views/project/new.blade.php b/resources/views/project/new.blade.php index 8d69f95e0..5b6ee1029 100644 --- a/resources/views/project/new.blade.php +++ b/resources/views/project/new.blade.php @@ -9,6 +9,8 @@ @elseif ($type === 'docker-compose-empty') + @elseif ($type === 'docker-image') + @else @endif diff --git a/resources/views/project/resources.blade.php b/resources/views/project/resources.blade.php index 213fa04cd..ac728fc0d 100644 --- a/resources/views/project/resources.blade.php +++ b/resources/views/project/resources.blade.php @@ -2,11 +2,12 @@ Resources - + - Add @if ($environment->can_delete_environment()) + @else + + + New @endif @@ -32,14 +33,15 @@ @if ($environment->can_delete_environment()) - No resources found. + + Add New Resource @endif @foreach ($environment->applications->sortBy('name') as $application) - {{ $application->name }} + {{ $application->name }} {{ $application->description }} @@ -48,19 +50,19 @@ - {{ $databases->name }} + {{ $databases->name }} {{ $databases->description }} @endforeach @foreach ($environment->services->sortBy('name') as $service) - - - {{ $service->name }} - {{ $service->description }} - - - @endforeach + + + {{ $service->name }} + {{ $service->description }} + + + @endforeach diff --git a/resources/views/projects.blade.php b/resources/views/projects.blade.php index a76607b9d..6de72d451 100644 --- a/resources/views/projects.blade.php +++ b/resources/views/projects.blade.php @@ -17,17 +17,17 @@ @forelse ($projects as $project) - - {{ $project->name }} - + + {{ $project->name }} + {{ $project->description }} - + - + diff --git a/versions.json b/versions.json index 008b4208e..862e20304 100644 --- a/versions.json +++ b/versions.json @@ -4,7 +4,7 @@ "version": "3.12.36" }, "v4": { - "version": "4.0.0-beta.70" + "version": "4.0.0-beta.72" } } }
No resources found.