diff --git a/.github/workflows/coolify-builder.yml b/.github/workflows/coolify-helper.yml similarity index 83% rename from .github/workflows/coolify-builder.yml rename to .github/workflows/coolify-helper.yml index 92e31293b..6a774effc 100644 --- a/.github/workflows/coolify-builder.yml +++ b/.github/workflows/coolify-helper.yml @@ -1,15 +1,15 @@ -name: Coolify Builder (v4) +name: Coolify Helper Image (v4) on: push: - branches: ["main", "v4"] + branches: [ "main", "next" ] paths: - - .github/workflows/coolify-builder.yml - - docker/coolify-builder/Dockerfile + - .github/workflows/coolify-helper.yml + - docker/coolify-helper/Dockerfile env: REGISTRY: ghcr.io - IMAGE_NAME: "coollabsio/coolify-builder" + IMAGE_NAME: "coollabsio/coolify-helper" jobs: amd64: @@ -30,12 +30,12 @@ jobs: with: no-cache: true context: . - file: docker/coolify-builder/Dockerfile + file: docker/coolify-helper/Dockerfile platforms: linux/amd64 push: true tags: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:latest aarch64: - runs-on: [self-hosted, arm64] + runs-on: [ self-hosted, arm64 ] permissions: contents: read packages: write @@ -52,7 +52,7 @@ jobs: with: no-cache: true context: . - file: docker/coolify-builder/Dockerfile + file: docker/coolify-helper/Dockerfile platforms: linux/aarch64 push: true tags: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:aarch64 @@ -61,7 +61,7 @@ jobs: permissions: contents: read packages: write - needs: [amd64, aarch64] + needs: [ amd64, aarch64 ] steps: - name: Checkout uses: actions/checkout@v3 @@ -77,4 +77,4 @@ jobs: password: ${{ secrets.GITHUB_TOKEN }} - name: Create & publish manifest run: | - docker buildx imagetools create --append ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:aarch64 --tag ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:latest \ No newline at end of file + docker buildx imagetools create --append ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:aarch64 --tag ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:latest diff --git a/app/Actions/CoolifyTask/PrepareCoolifyTask.php b/app/Actions/CoolifyTask/PrepareCoolifyTask.php index 263cd329e..3b2f70014 100644 --- a/app/Actions/CoolifyTask/PrepareCoolifyTask.php +++ b/app/Actions/CoolifyTask/PrepareCoolifyTask.php @@ -15,6 +15,7 @@ class PrepareCoolifyTask { protected Activity $activity; protected CoolifyTaskArgs $remoteProcessArgs; + public function __construct(CoolifyTaskArgs $remoteProcessArgs) { $this->remoteProcessArgs = $remoteProcessArgs; diff --git a/app/Actions/CoolifyTask/RunRemoteProcess.php b/app/Actions/CoolifyTask/RunRemoteProcess.php index 2a9fc63a0..192144f8b 100644 --- a/app/Actions/CoolifyTask/RunRemoteProcess.php +++ b/app/Actions/CoolifyTask/RunRemoteProcess.php @@ -49,6 +49,28 @@ public function __construct(Activity $activity, bool $hide_from_output = false, $this->ignore_errors = $ignore_errors; } + public static function decodeOutput(?Activity $activity = null): string + { + if (is_null($activity)) { + return ''; + } + + try { + $decoded = json_decode( + data_get($activity, 'description'), + associative: true, + flags: JSON_THROW_ON_ERROR + ); + } catch (\JsonException $exception) { + return ''; + } + + return collect($decoded) + ->sortBy(fn ($i) => $i['order']) + ->map(fn ($i) => $i['output']) + ->implode(""); + } + public function __invoke(): ProcessResult { $this->time_start = hrtime(true); @@ -83,15 +105,6 @@ public function __invoke(): ProcessResult return $processResult; } - protected function getLatestCounter(): int - { - $description = json_decode($this->activity->description, associative: true, flags: JSON_THROW_ON_ERROR); - if ($description === null || count($description) === 0) { - return 1; - } - return end($description)['order'] + 1; - } - protected function getCommand(): string { $user = $this->activity->getExtraProperty('user'); @@ -120,6 +133,13 @@ protected function handleOutput(string $type, string $output) } } + protected function elapsedTime(): int + { + $timeMs = (hrtime(true) - $this->time_start) / 1_000_000; + + return intval($timeMs); + } + public function encodeOutput($type, $output) { $outputStack = json_decode($this->activity->description, associative: true, flags: JSON_THROW_ON_ERROR); @@ -135,26 +155,13 @@ public function encodeOutput($type, $output) return json_encode($outputStack, flags: JSON_THROW_ON_ERROR); } - public static function decodeOutput(?Activity $activity = null): string + protected function getLatestCounter(): int { - if (is_null($activity)) { - return ''; + $description = json_decode($this->activity->description, associative: true, flags: JSON_THROW_ON_ERROR); + if ($description === null || count($description) === 0) { + return 1; } - - try { - $decoded = json_decode( - data_get($activity, 'description'), - associative: true, - flags: JSON_THROW_ON_ERROR - ); - } catch (\JsonException $exception) { - return ''; - } - - return collect($decoded) - ->sortBy(fn ($i) => $i['order']) - ->map(fn ($i) => $i['output']) - ->implode(""); + return end($description)['order'] + 1; } /** @@ -171,11 +178,4 @@ protected function isAfterLastThrottle() return ($this->current_time - $this->throttle_interval_ms) > $this->last_write_at; } - - protected function elapsedTime(): int - { - $timeMs = (hrtime(true) - $this->time_start) / 1_000_000; - - return intval($timeMs); - } } diff --git a/app/Actions/Database/StartPostgresql.php b/app/Actions/Database/StartPostgresql.php new file mode 100644 index 000000000..1b5d22f44 --- /dev/null +++ b/app/Actions/Database/StartPostgresql.php @@ -0,0 +1,164 @@ +database = $database; + $container_name = generate_container_name($this->database->uuid); + $this->configuration_dir = database_configuration_dir() . '/' . $container_name; + + $this->commands = [ + "mkdir -p $this->configuration_dir", + "mkdir -p $this->configuration_dir/docker-entrypoint-initdb.d/" + ]; + + $persistent_storages = $this->generate_local_persistent_volumes(); + $volume_names = $this->generate_local_persistent_volumes_only_volume_names(); + $environment_variables = $this->generate_environment_variables(); + $this->generate_init_scripts(); + $docker_compose = [ + 'version' => '3.8', + 'services' => [ + $container_name => [ + 'image' => $this->database->image, + 'container_name' => $container_name, + 'environment' => $environment_variables, + 'restart' => 'always', + 'networks' => [ + $this->database->destination->network, + ], + 'healthcheck' => [ + 'test' => [ + 'CMD-SHELL', + 'pg_isready', + '-d', + $this->database->postgres_db, + '-U', + $this->database->postgres_user, + ], + 'interval' => '5s', + 'timeout' => '5s', + 'retries' => 10, + 'start_period' => '5s' + ], + 'mem_limit' => $this->database->limits_memory, + 'memswap_limit' => $this->database->limits_memory_swap, + 'mem_swappiness' => $this->database->limits_memory_swappiness, + 'mem_reservation' => $this->database->limits_memory_reservation, + 'cpus' => $this->database->limits_cpus, + 'cpuset' => $this->database->limits_cpuset, + 'cpu_shares' => $this->database->limits_cpu_shares, + ] + ], + 'networks' => [ + $this->database->destination->network => [ + 'external' => false, + 'name' => $this->database->destination->network, + 'attachable' => true, + ] + ] + ]; + if (count($this->database->ports_mappings_array) > 0) { + $docker_compose['services'][$container_name]['ports'] = $this->database->ports_mappings_array; + } + if (count($persistent_storages) > 0) { + $docker_compose['services'][$container_name]['volumes'] = $persistent_storages; + } + if (count($volume_names) > 0) { + $docker_compose['volumes'] = $volume_names; + } + if (count($this->init_scripts) > 0) { + foreach ($this->init_scripts as $init_script) { + $docker_compose['services'][$container_name]['volumes'][] = [ + 'type' => 'bind', + 'source' => $init_script, + 'target' => '/docker-entrypoint-initdb.d/' . basename($init_script), + 'read_only' => true, + ]; + } + } + $docker_compose = Yaml::dump($docker_compose, 10); + $docker_compose_base64 = base64_encode($docker_compose); + $this->commands[] = "echo '{$docker_compose_base64}' | base64 -d > $this->configuration_dir/docker-compose.yml"; + $readme = generate_readme_file($this->database->name, now()); + $this->commands[] = "echo '{$readme}' > $this->configuration_dir/README.md"; + $this->commands[] = "docker compose -f $this->configuration_dir/docker-compose.yml up -d"; + return remote_process($this->commands, $server); + } + + private function generate_local_persistent_volumes() + { + $local_persistent_volumes = []; + foreach ($this->database->persistentStorages as $persistentStorage) { + $volume_name = $persistentStorage->host_path ?? $persistentStorage->name; + $local_persistent_volumes[] = $volume_name . ':' . $persistentStorage->mount_path; + } + return $local_persistent_volumes; + } + + private function generate_local_persistent_volumes_only_volume_names() + { + $local_persistent_volumes_names = []; + foreach ($this->database->persistentStorages as $persistentStorage) { + if ($persistentStorage->host_path) { + continue; + } + $name = $persistentStorage->name; + $local_persistent_volumes_names[$name] = [ + 'name' => $name, + 'external' => false, + ]; + } + return $local_persistent_volumes_names; + } + + private function generate_environment_variables() + { + $environment_variables = collect(); + ray('Generate Environment Variables')->green(); + ray($this->database->runtime_environment_variables)->green(); + foreach ($this->database->runtime_environment_variables as $env) { + $environment_variables->push("$env->key=$env->value"); + } + + if ($environment_variables->filter(fn ($env) => Str::of($env)->contains('POSTGRES_USER'))->isEmpty()) { + $environment_variables->push("POSTGRES_USER={$this->database->postgres_user}"); + } + + if ($environment_variables->filter(fn ($env) => Str::of($env)->contains('POSTGRES_PASSWORD'))->isEmpty()) { + $environment_variables->push("POSTGRES_PASSWORD={$this->database->postgres_password}"); + } + + if ($environment_variables->filter(fn ($env) => Str::of($env)->contains('POSTGRES_DB'))->isEmpty()) { + $environment_variables->push("POSTGRES_DB={$this->database->postgres_db}"); + } + return $environment_variables->all(); + } + + private function generate_init_scripts() + { + if (is_null($this->database->init_scripts) || count($this->database->init_scripts) === 0) { + return; + } + foreach ($this->database->init_scripts as $init_script) { + $filename = data_get($init_script, 'filename'); + $content = data_get($init_script, 'content'); + $content_base64 = base64_encode($content); + $this->commands[] = "echo '{$content_base64}' | base64 -d > $this->configuration_dir/docker-entrypoint-initdb.d/{$filename}"; + $this->init_scripts[] = "$this->configuration_dir/docker-entrypoint-initdb.d/{$filename}"; + } + } +} diff --git a/app/Actions/Fortify/CreateNewUser.php b/app/Actions/Fortify/CreateNewUser.php index 6ea524147..77ae73fce 100644 --- a/app/Actions/Fortify/CreateNewUser.php +++ b/app/Actions/Fortify/CreateNewUser.php @@ -3,11 +3,8 @@ namespace App\Actions\Fortify; use App\Models\InstanceSettings; -use App\Models\Team; use App\Models\User; -use Illuminate\Support\Facades\DB; use Illuminate\Support\Facades\Hash; -use Illuminate\Support\Facades\Log; use Illuminate\Support\Facades\Validator; use Illuminate\Validation\Rule; use Laravel\Fortify\Contracts\CreatesNewUsers; @@ -19,13 +16,12 @@ class CreateNewUser implements CreatesNewUsers /** * Validate and create a newly registered user. * - * @param array $input + * @param array $input */ public function create(array $input): User { $settings = InstanceSettings::get(); if (!$settings->is_registration_enabled) { - Log::info('Registration is disabled'); abort(403); } Validator::make($input, [ diff --git a/app/Actions/Fortify/ResetUserPassword.php b/app/Actions/Fortify/ResetUserPassword.php index 7a57c5037..58d99b1b2 100644 --- a/app/Actions/Fortify/ResetUserPassword.php +++ b/app/Actions/Fortify/ResetUserPassword.php @@ -14,7 +14,7 @@ class ResetUserPassword implements ResetsUserPasswords /** * Validate and reset the user's forgotten password. * - * @param array $input + * @param array $input */ public function reset(User $user, array $input): void { diff --git a/app/Actions/Fortify/UpdateUserPassword.php b/app/Actions/Fortify/UpdateUserPassword.php index 700563905..5ebf31875 100644 --- a/app/Actions/Fortify/UpdateUserPassword.php +++ b/app/Actions/Fortify/UpdateUserPassword.php @@ -14,7 +14,7 @@ class UpdateUserPassword implements UpdatesUserPasswords /** * Validate and update the user's password. * - * @param array $input + * @param array $input */ public function update(User $user, array $input): void { diff --git a/app/Actions/Fortify/UpdateUserProfileInformation.php b/app/Actions/Fortify/UpdateUserProfileInformation.php index 0930ddf38..85caf943b 100644 --- a/app/Actions/Fortify/UpdateUserProfileInformation.php +++ b/app/Actions/Fortify/UpdateUserProfileInformation.php @@ -13,7 +13,7 @@ class UpdateUserProfileInformation implements UpdatesUserProfileInformation /** * Validate and update the given user's profile information. * - * @param array $input + * @param array $input */ public function update(User $user, array $input): void { @@ -29,8 +29,10 @@ public function update(User $user, array $input): void ], ])->validateWithBag('updateProfileInformation'); - if ($input['email'] !== $user->email && - $user instanceof MustVerifyEmail) { + if ( + $input['email'] !== $user->email && + $user instanceof MustVerifyEmail + ) { $this->updateVerifiedUser($user, $input); } else { $user->forceFill([ @@ -43,7 +45,7 @@ public function update(User $user, array $input): void /** * Update the given verified user's profile information. * - * @param array $input + * @param array $input */ protected function updateVerifiedUser(User $user, array $input): void { diff --git a/app/Actions/License/CheckResaleLicense.php b/app/Actions/License/CheckResaleLicense.php index cef4e62ee..e7dcecdb6 100644 --- a/app/Actions/License/CheckResaleLicense.php +++ b/app/Actions/License/CheckResaleLicense.php @@ -4,7 +4,6 @@ use App\Models\InstanceSettings; use Illuminate\Support\Facades\Http; -use Visus\Cuid2\Cuid2; class CheckResaleLicense { @@ -12,41 +11,52 @@ public function __invoke() { try { $settings = InstanceSettings::get(); - $instance_id = config('app.id'); + $settings->update([ + 'is_resale_license_active' => false, + ]); + if (is_dev()) { + return; + } if (!$settings->resale_license) { return; } - ray('Checking license key'); + $base_url = config('coolify.license_url'); + if (is_dev()) { + $base_url = 'http://host.docker.internal:8787'; + } + $instance_id = config('app.id'); + + ray("Checking license key against $base_url/lemon/validate"); $data = Http::withHeaders([ 'Accept' => 'application/json', - ])->post('https://api.lemonsqueezy.com/v1/licenses/validate', [ + ])->get("$base_url/lemon/validate", [ 'license_key' => $settings->resale_license, - 'instance_name' => $instance_id, - ])->throw()->json(); - $product_id = (int)data_get($data, 'meta.product_id'); - $valid_product_id = (int)config('coolify.lemon_squeezy_product_id'); - if ($product_id !== $valid_product_id) { - throw new \Exception('Invalid product id'); - } - ray('Valid Product Id'); - - ['valid' => $valid, 'license_key' => $license_key] = $data; - - if ($valid) { - if (data_get($license_key, 'status') === 'inactive') { - Http::withHeaders([ - 'Accept' => 'application/json', - ])->post('https://api.lemonsqueezy.com/v1/licenses/activate', [ - 'license_key' => $settings->resale_license, - 'instance_name' => $instance_id, - ])->throw()->json(); - } + 'instance_id' => $instance_id, + ])->json(); + if (data_get($data, 'valid') === true && data_get($data, 'license_key.status') === 'active') { + ray('Valid & active license key'); $settings->update([ 'is_resale_license_active' => true, ]); return; } - throw new \Exception('Invalid license key'); + $data = Http::withHeaders([ + 'Accept' => 'application/json', + ])->get("$base_url/lemon/activate", [ + 'license_key' => $settings->resale_license, + 'instance_id' => $instance_id, + ])->json(); + if (data_get($data, 'activated') === true) { + ray('Activated license key'); + $settings->update([ + 'is_resale_license_active' => true, + ]); + return; + } + if (data_get($data, 'license_key.status') === 'active') { + throw new \Exception('Invalid license key.'); + } + throw new \Exception('Cannot activate license key.'); } catch (\Throwable $th) { ray($th); $settings->update([ diff --git a/app/Actions/Proxy/CheckConfigurationSync.php b/app/Actions/Proxy/CheckConfigurationSync.php new file mode 100644 index 000000000..b6dcb1069 --- /dev/null +++ b/app/Actions/Proxy/CheckConfigurationSync.php @@ -0,0 +1,25 @@ +trim()->value; + resolve(SaveConfigurationSync::class)($server, $proxy_configuration); + return $proxy_configuration; + } + + return $proxy_configuration; + } +} diff --git a/app/Actions/Proxy/CheckProxySettingsInSync.php b/app/Actions/Proxy/CheckProxySettingsInSync.php deleted file mode 100644 index ae0142fec..000000000 --- a/app/Actions/Proxy/CheckProxySettingsInSync.php +++ /dev/null @@ -1,33 +0,0 @@ -trim()->value; - } else { - $final_output = Str::of($output)->trim()->value; - } - $docker_compose_yml_base64 = base64_encode($final_output); - $server->proxy->last_saved_settings = Str::of($docker_compose_yml_base64)->pipe('md5')->value; - $server->save(); - if (is_null($output) || $reset) { - instant_remote_process([ - "mkdir -p $proxy_path", - "echo '$docker_compose_yml_base64' | base64 -d > $proxy_path/docker-compose.yml", - ], $server); - } - return $final_output; - } -} diff --git a/app/Actions/Proxy/SaveConfigurationSync.php b/app/Actions/Proxy/SaveConfigurationSync.php new file mode 100644 index 000000000..c17607516 --- /dev/null +++ b/app/Actions/Proxy/SaveConfigurationSync.php @@ -0,0 +1,23 @@ +proxy->last_saved_settings = Str::of($docker_compose_yml_base64)->pipe('md5')->value; + $server->save(); + + instant_remote_process([ + "mkdir -p $proxy_path", + "echo '$docker_compose_yml_base64' | base64 -d > $proxy_path/docker-compose.yml", + ], $server); + } +} diff --git a/app/Actions/Proxy/StartProxy.php b/app/Actions/Proxy/StartProxy.php index 2050e2ca1..b9d9ab02b 100644 --- a/app/Actions/Proxy/StartProxy.php +++ b/app/Actions/Proxy/StartProxy.php @@ -5,8 +5,8 @@ use App\Enums\ProxyStatus; use App\Enums\ProxyTypes; use App\Models\Server; -use Spatie\Activitylog\Models\Activity; use Illuminate\Support\Str; +use Spatie\Activitylog\Models\Activity; class StartProxy { @@ -18,8 +18,7 @@ public function __invoke(Server $server): Activity $server->proxy->status = ProxyStatus::EXITED->value; $server->save(); } - $proxy_path = config('coolify.proxy_config_path'); - + $proxy_path = get_proxy_path(); $networks = collect($server->standaloneDockers)->map(function ($docker) { return $docker['network']; })->unique(); @@ -30,29 +29,24 @@ public function __invoke(Server $server): Activity return "docker network ls --format '{{.Name}}' | grep '^$network$' >/dev/null 2>&1 || docker network create --attachable $network > /dev/null 2>&1"; }); - $configuration = instant_remote_process([ - "cat $proxy_path/docker-compose.yml", - ], $server, false); - if (is_null($configuration)) { - $configuration = Str::of(getProxyConfiguration($server))->trim()->value; - } else { - $configuration = Str::of($configuration)->trim()->value; - } + $configuration = resolve(CheckConfigurationSync::class)($server); + $docker_compose_yml_base64 = base64_encode($configuration); $server->proxy->last_applied_settings = Str::of($docker_compose_yml_base64)->pipe('md5')->value; $server->save(); + $activity = remote_process([ "echo 'Creating required Docker networks...'", ...$create_networks_command, - "mkdir -p $proxy_path", "cd $proxy_path", - "echo '$docker_compose_yml_base64' | base64 -d > $proxy_path/docker-compose.yml", "echo 'Creating Docker Compose file...'", "echo 'Pulling docker image...'", 'docker compose pull -q', - "echo 'Stopping old proxy...'", + "echo 'Stopping existing proxy...'", 'docker compose down -v --remove-orphans', - "echo 'Starting new proxy...'", + "lsof -nt -i:80 | xargs -r kill -9", + "lsof -nt -i:443 | xargs -r kill -9", + "echo 'Starting proxy...'", 'docker compose up -d --remove-orphans', "echo 'Proxy installed successfully...'" ], $server); diff --git a/app/Actions/Server/UpdateCoolify.php b/app/Actions/Server/UpdateCoolify.php index 71231cd36..8f81852d8 100644 --- a/app/Actions/Server/UpdateCoolify.php +++ b/app/Actions/Server/UpdateCoolify.php @@ -17,13 +17,13 @@ public function __invoke(bool $force) $settings = InstanceSettings::get(); ray('Running InstanceAutoUpdateJob'); $localhost_name = 'localhost'; - if (isDev()) { + if (is_dev()) { $localhost_name = 'testing-local-docker-container'; } $this->server = Server::where('name', $localhost_name)->firstOrFail(); $this->latest_version = get_latest_version_of_coolify(); $this->current_version = config('version'); - ray('latest version:' . $this->latest_version . " current version: " . $this->current_version . ' force: ' . $force); + ray('latest version:' . $this->latest_version . " current version: " . $this->current_version . ' force: ' . $force); if ($settings->next_channel) { ray('next channel enabled'); $this->latest_version = 'next'; @@ -49,9 +49,10 @@ public function __invoke(bool $force) return; } } + private function update() { - if (isDev()) { + if (is_dev()) { ray("Running update on local docker container. Updating to $this->latest_version"); remote_process([ "sleep 10" diff --git a/app/Console/Commands/Init.php b/app/Console/Commands/Init.php index 32ff31827..ae682e682 100644 --- a/app/Console/Commands/Init.php +++ b/app/Console/Commands/Init.php @@ -10,10 +10,12 @@ class Init extends Command { protected $signature = 'app:init'; protected $description = 'Cleanup instance related stuffs'; + public function handle() { $this->cleanup_in_progress_application_deployments(); } + private function cleanup_in_progress_application_deployments() { // Cleanup any failed deployments diff --git a/app/Console/Commands/NotifyDemo.php b/app/Console/Commands/NotifyDemo.php index 1d5906bd0..72e4a37e6 100644 --- a/app/Console/Commands/NotifyDemo.php +++ b/app/Console/Commands/NotifyDemo.php @@ -43,15 +43,16 @@ private function showHelp() style('coolify')->color('#9333EA'); style('title-box')->apply('mt-1 px-2 py-1 bg-coolify'); - render(<<<'HTML' + render( + <<<'HTML'
Coolify
-

+

Demo Notify => Send a demo notification to a given channel.

-

+

php artisan app:demo-notify {channel}

@@ -64,7 +65,8 @@ private function showHelp()
- HTML); + HTML + ); ask(<<<'HTML'
diff --git a/app/Console/Commands/SyncBunny.php b/app/Console/Commands/SyncBunny.php index dec92dd6e..3f4bc412a 100644 --- a/app/Console/Commands/SyncBunny.php +++ b/app/Console/Commands/SyncBunny.php @@ -4,8 +4,8 @@ use Illuminate\Console\Command; use Illuminate\Http\Client\PendingRequest; -use Illuminate\Support\Facades\Http; use Illuminate\Http\Client\Pool; +use Illuminate\Support\Facades\Http; class SyncBunny extends Command { diff --git a/app/Console/Kernel.php b/app/Console/Kernel.php index 4e52dbb81..21c4de1d0 100644 --- a/app/Console/Kernel.php +++ b/app/Console/Kernel.php @@ -3,10 +3,14 @@ namespace App\Console; use App\Jobs\CheckResaleLicenseJob; +use App\Jobs\CheckResaleLicenseKeys; +use App\Jobs\CleanupInstanceStuffsJob; +use App\Jobs\DatabaseBackupJob; +use App\Jobs\DockerCleanupJob; +use App\Jobs\InstanceApplicationsStatusJob; use App\Jobs\InstanceAutoUpdateJob; use App\Jobs\ProxyCheckJob; -use App\Jobs\DockerCleanupJob; -use App\Jobs\CheckResaleLicenseKeys; +use App\Models\ScheduledDatabaseBackup; use Illuminate\Console\Scheduling\Schedule; use Illuminate\Foundation\Console\Kernel as ConsoleKernel; @@ -14,20 +18,47 @@ class Kernel extends ConsoleKernel { protected function schedule(Schedule $schedule): void { - if (isDev()) { + // $schedule->call(fn() => $this->check_scheduled_backups($schedule))->everyTenSeconds(); + if (is_dev()) { $schedule->command('horizon:snapshot')->everyMinute(); + $schedule->job(new InstanceApplicationsStatusJob)->everyMinute(); $schedule->job(new ProxyCheckJob)->everyFiveMinutes(); + $schedule->job(new CleanupInstanceStuffsJob)->everyMinute(); + // $schedule->job(new CheckResaleLicenseJob)->hourly(); // $schedule->job(new DockerCleanupJob)->everyOddHour(); // $schedule->job(new InstanceAutoUpdateJob(true))->everyMinute(); } else { $schedule->command('horizon:snapshot')->everyFiveMinutes(); + $schedule->job(new CleanupInstanceStuffsJob)->everyMinute(); + $schedule->job(new InstanceApplicationsStatusJob)->everyMinute(); $schedule->job(new CheckResaleLicenseJob)->hourly(); $schedule->job(new ProxyCheckJob)->everyFiveMinutes(); $schedule->job(new DockerCleanupJob)->everyTenMinutes(); $schedule->job(new InstanceAutoUpdateJob)->everyTenMinutes(); } + $this->check_scheduled_backups($schedule); } + + private function check_scheduled_backups($schedule) + { + ray('check_scheduled_backups'); + $scheduled_backups = ScheduledDatabaseBackup::all(); + if ($scheduled_backups->isEmpty()) { + ray('no scheduled backups'); + return; + } + foreach ($scheduled_backups as $scheduled_backup) { + if (!$scheduled_backup->enabled) continue; + if (isset(VALID_CRON_STRINGS[$scheduled_backup->frequency])) { + $scheduled_backup->frequency = VALID_CRON_STRINGS[$scheduled_backup->frequency]; + } + $schedule->job(new DatabaseBackupJob( + backup: $scheduled_backup + ))->cron($scheduled_backup->frequency); + } + } + protected function commands(): void { $this->load(__DIR__ . '/Commands'); diff --git a/app/Data/ServerMetadata.php b/app/Data/ServerMetadata.php index b96efa622..b18ddab8e 100644 --- a/app/Data/ServerMetadata.php +++ b/app/Data/ServerMetadata.php @@ -9,7 +9,7 @@ class ServerMetadata extends Data { public function __construct( - public ?ProxyTypes $type, + public ?ProxyTypes $type, public ?ProxyStatus $status ) { } diff --git a/app/Enums/ProxyTypes.php b/app/Enums/ProxyTypes.php index e94792188..dfbf65c64 100644 --- a/app/Enums/ProxyTypes.php +++ b/app/Enums/ProxyTypes.php @@ -8,6 +8,7 @@ enum ProxyTypes: string case NGINX = 'NGINX'; case CADDY = 'CADDY'; } + enum ProxyStatus: string { case EXITED = 'exited'; diff --git a/app/Exceptions/Handler.php b/app/Exceptions/Handler.php index 3fc20bd25..b08c07012 100644 --- a/app/Exceptions/Handler.php +++ b/app/Exceptions/Handler.php @@ -4,13 +4,12 @@ use App\Models\InstanceSettings; use Illuminate\Foundation\Exceptions\Handler as ExceptionHandler; -use Throwable; use Sentry\Laravel\Integration; +use Throwable; class Handler extends ExceptionHandler { - private InstanceSettings $settings; /** * A list of exception types with their corresponding custom log levels. * @@ -19,7 +18,6 @@ class Handler extends ExceptionHandler protected $levels = [ // ]; - /** * A list of the exception types that are not reported. * @@ -28,7 +26,6 @@ class Handler extends ExceptionHandler protected $dontReport = [ // ]; - /** * A list of the inputs that are never flashed to the session on validation exceptions. * @@ -39,6 +36,7 @@ class Handler extends ExceptionHandler 'password', 'password_confirmation', ]; + private InstanceSettings $settings; /** * Register the exception handling callbacks for the application. @@ -47,7 +45,7 @@ public function register(): void { $this->reportable(function (Throwable $e) { $this->settings = InstanceSettings::get(); - if ($this->settings->do_not_track || isDev()) { + if ($this->settings->do_not_track || is_dev()) { return; } Integration::captureUnhandledException($e); diff --git a/app/Http/Controllers/ApplicationController.php b/app/Http/Controllers/ApplicationController.php index 9d49f6c52..9b87492e4 100644 --- a/app/Http/Controllers/ApplicationController.php +++ b/app/Http/Controllers/ApplicationController.php @@ -5,15 +5,14 @@ use App\Models\ApplicationDeploymentQueue; use Illuminate\Foundation\Auth\Access\AuthorizesRequests; use Illuminate\Foundation\Validation\ValidatesRequests; -use Illuminate\Http\Request; -use Spatie\Activitylog\Models\Activity; class ApplicationController extends Controller { use AuthorizesRequests, ValidatesRequests; + public function configuration() { - $project = session('currentTeam')->load(['projects'])->projects->where('uuid', request()->route('project_uuid'))->first(); + $project = auth()->user()->currentTeam()->load(['projects'])->projects->where('uuid', request()->route('project_uuid'))->first(); if (!$project) { return redirect()->route('dashboard'); } @@ -27,9 +26,10 @@ public function configuration() } return view('project.application.configuration', ['application' => $application]); } + public function deployments() { - $project = session('currentTeam')->load(['projects'])->projects->where('uuid', request()->route('project_uuid'))->first(); + $project = auth()->user()->currentTeam()->load(['projects'])->projects->where('uuid', request()->route('project_uuid'))->first(); if (!$project) { return redirect()->route('dashboard'); } @@ -49,7 +49,7 @@ public function deployment() { $deploymentUuid = request()->route('deployment_uuid'); - $project = session('currentTeam')->load(['projects'])->projects->where('uuid', request()->route('project_uuid'))->first(); + $project = auth()->user()->currentTeam()->load(['projects'])->projects->where('uuid', request()->route('project_uuid'))->first(); if (!$project) { return redirect()->route('dashboard'); } diff --git a/app/Http/Controllers/Controller.php b/app/Http/Controllers/Controller.php index c9620f4bd..84da5de2e 100644 --- a/app/Http/Controllers/Controller.php +++ b/app/Http/Controllers/Controller.php @@ -2,77 +2,89 @@ namespace App\Http\Controllers; -use App\Http\Livewire\Team\Invitations; use App\Models\InstanceSettings; use App\Models\Project; +use App\Models\S3Storage; use App\Models\Server; +use App\Models\StandalonePostgresql; use App\Models\TeamInvitation; use App\Models\User; +use App\Models\Waitlist; use Illuminate\Foundation\Auth\Access\AuthorizesRequests; use Illuminate\Foundation\Validation\ValidatesRequests; use Illuminate\Routing\Controller as BaseController; -use Illuminate\Support\Facades\DB; +use Throwable; class Controller extends BaseController { use AuthorizesRequests, ValidatesRequests; + public function waitlist() { + $waiting_in_line = Waitlist::whereVerified(true)->count(); + return view('auth.waitlist', [ + 'waiting_in_line' => $waiting_in_line, + ]); + } public function subscription() { - if (!isCloud()) { + if (!is_cloud()) { abort(404); } return view('subscription', [ - 'settings' => InstanceSettings::get() + 'settings' => InstanceSettings::get(), ]); } + public function license() { - if (!isCloud()) { + if (!is_cloud()) { abort(404); } return view('settings.license', [ - 'settings' => InstanceSettings::get() + 'settings' => InstanceSettings::get(), ]); } + + public function force_passoword_reset() { + return view('auth.force-password-reset'); + } public function dashboard() { $projects = Project::ownedByCurrentTeam()->get(); $servers = Server::ownedByCurrentTeam()->get(); - + $s3s = S3Storage::ownedByCurrentTeam()->get(); $resources = 0; foreach ($projects as $project) { $resources += $project->applications->count(); + $resources += $project->postgresqls->count(); } return view('dashboard', [ 'servers' => $servers->count(), 'projects' => $projects->count(), 'resources' => $resources, + 's3s' => $s3s, ]); } + public function settings() { - if (auth()->user()->isInstanceAdmin()) { + if (is_instance_admin()) { $settings = InstanceSettings::get(); + $database = StandalonePostgresql::whereName('coolify-db')->first(); + if ($database) { + $s3s = S3Storage::whereTeamId(0)->get(); + } return view('settings.configuration', [ - 'settings' => $settings - ]); - } else { - return redirect()->route('dashboard'); - } - } - public function emails() - { - if (auth()->user()->isInstanceAdmin()) { - $settings = InstanceSettings::get(); - return view('settings.emails', [ - 'settings' => $settings + 'settings' => $settings, + 'database' => $database, + 's3s' => $s3s ?? [], ]); } else { return redirect()->route('dashboard'); } } + public function team() { $invitations = []; @@ -83,6 +95,23 @@ public function team() 'invitations' => $invitations, ]); } + + public function storages() + { + $s3 = S3Storage::ownedByCurrentTeam()->get(); + return view('team.storages.all', [ + 's3' => $s3, + ]); + } + + public function storages_show() + { + $storage = S3Storage::ownedByCurrentTeam()->whereUuid(request()->storage_uuid)->firstOrFail(); + return view('team.storages.show', [ + 'storage' => $storage, + ]); + } + public function members() { $invitations = []; @@ -93,6 +122,7 @@ public function members() 'invitations' => $invitations, ]); } + public function acceptInvitation() { try { @@ -115,10 +145,11 @@ public function acceptInvitation() $invitation->delete(); abort(401); } - } catch (\Throwable $th) { + } catch (Throwable $th) { throw $th; } } + public function revokeInvitation() { try { @@ -132,7 +163,7 @@ public function revokeInvitation() } $invitation->delete(); return redirect()->route('team.show'); - } catch (\Throwable $th) { + } catch (Throwable $th) { throw $th; } } diff --git a/app/Http/Controllers/DatabaseController.php b/app/Http/Controllers/DatabaseController.php new file mode 100644 index 000000000..bb6baf5ec --- /dev/null +++ b/app/Http/Controllers/DatabaseController.php @@ -0,0 +1,76 @@ +user()->currentTeam()->load(['projects'])->projects->where('uuid', request()->route('project_uuid'))->first(); + if (!$project) { + return redirect()->route('dashboard'); + } + $environment = $project->load(['environments'])->environments->where('name', request()->route('environment_name'))->first()->load(['applications']); + if (!$environment) { + return redirect()->route('dashboard'); + } + $database = $environment->databases->where('uuid', request()->route('database_uuid'))->first(); + if (!$database) { + return redirect()->route('dashboard'); + } + return view('project.database.configuration', ['database' => $database]); + } + + public function executions() + { + $backup_uuid = request()->route('backup_uuid'); + $project = auth()->user()->currentTeam()->load(['projects'])->projects->where('uuid', request()->route('project_uuid'))->first(); + if (!$project) { + return redirect()->route('dashboard'); + } + $environment = $project->load(['environments'])->environments->where('name', request()->route('environment_name'))->first()->load(['applications']); + if (!$environment) { + return redirect()->route('dashboard'); + } + $database = $environment->databases->where('uuid', request()->route('database_uuid'))->first(); + if (!$database) { + return redirect()->route('dashboard'); + } + $backup = $database->scheduledBackups->where('uuid', $backup_uuid)->first(); + if (!$backup) { + return redirect()->route('dashboard'); + } + $executions = collect($backup->executions)->sortByDesc('created_at'); + return view('project.database.backups.executions', [ + 'database' => $database, + 'backup' => $backup, + 'executions' => $executions, + 's3s' => auth()->user()->currentTeam()->s3s, + ]); + } + + public function backups() + { + $project = auth()->user()->currentTeam()->load(['projects'])->projects->where('uuid', request()->route('project_uuid'))->first(); + if (!$project) { + return redirect()->route('dashboard'); + } + $environment = $project->load(['environments'])->environments->where('name', request()->route('environment_name'))->first()->load(['applications']); + if (!$environment) { + return redirect()->route('dashboard'); + } + $database = $environment->databases->where('uuid', request()->route('database_uuid'))->first(); + if (!$database) { + return redirect()->route('dashboard'); + } + return view('project.database.backups.all', [ + 'database' => $database, + 's3s' => auth()->user()->currentTeam()->s3s, + ]); + } +} diff --git a/app/Http/Controllers/MagicController.php b/app/Http/Controllers/MagicController.php index c4f77805a..515bc14d1 100644 --- a/app/Http/Controllers/MagicController.php +++ b/app/Http/Controllers/MagicController.php @@ -2,7 +2,6 @@ namespace App\Http\Controllers; -use App\Http\Livewire\Server\PrivateKey; use App\Models\Environment; use App\Models\Project; use App\Models\Server; @@ -16,34 +15,39 @@ public function servers() 'servers' => Server::isUsable()->get() ]); } + public function destinations() { return response()->json([ 'destinations' => Server::destinationsByServer(request()->query('server_id'))->sortBy('name') ]); } + public function projects() { return response()->json([ 'projects' => Project::ownedByCurrentTeam()->get() ]); } + public function environments() { return response()->json([ 'environments' => Project::ownedByCurrentTeam()->whereUuid(request()->query('project_uuid'))->first()->environments ]); } + public function newProject() { $project = Project::firstOrCreate( ['name' => request()->query('name') ?? generate_random_name()], - ['team_id' => session('currentTeam')->id] + ['team_id' => auth()->user()->currentTeam()->id] ); return response()->json([ 'project_uuid' => $project->uuid ]); } + public function newEnvironment() { $environment = Environment::firstOrCreate( @@ -54,6 +58,7 @@ public function newEnvironment() 'environment_name' => $environment->name, ]); } + public function newTeam() { $team = Team::create( diff --git a/app/Http/Controllers/ProjectController.php b/app/Http/Controllers/ProjectController.php index 432e72f39..2ad941e92 100644 --- a/app/Http/Controllers/ProjectController.php +++ b/app/Http/Controllers/ProjectController.php @@ -3,46 +3,48 @@ namespace App\Http\Controllers; use App\Models\Project; +use App\Models\Server; class ProjectController extends Controller { public function all() { - $teamId = session('currentTeam')->id; - - $projects = Project::where('team_id', $teamId)->get(); - return view('projects', ['projects' => $projects]); + return view('projects', [ + 'projects' => Project::ownedByCurrentTeam()->get(), + 'servers' => Server::ownedByCurrentTeam()->count(), + ]); } public function edit() { $projectUuid = request()->route('project_uuid'); - $teamId = session('currentTeam')->id; + $teamId = auth()->user()->currentTeam()->id; $project = Project::where('team_id', $teamId)->where('uuid', $projectUuid)->first(); if (!$project) { return redirect()->route('dashboard'); } return view('project.edit', ['project' => $project]); } + public function show() { $projectUuid = request()->route('project_uuid'); - $teamId = session('currentTeam')->id; + $teamId = auth()->user()->currentTeam()->id; $project = Project::where('team_id', $teamId)->where('uuid', $projectUuid)->first(); if (!$project) { return redirect()->route('dashboard'); } $project->load(['environments']); - if (count($project->environments) == 1) { - return redirect()->route('project.resources', ['project_uuid' => $project->uuid, 'environment_name' => $project->environments->first()->name]); - } return view('project.show', ['project' => $project]); } public function new() { - $project = session('currentTeam')->load(['projects'])->projects->where('uuid', request()->route('project_uuid'))->first(); + $type = request()->query('type'); + $destination_uuid = request()->query('destination'); + + $project = auth()->user()->currentTeam()->load(['projects'])->projects->where('uuid', request()->route('project_uuid'))->first(); if (!$project) { return redirect()->route('dashboard'); } @@ -50,16 +52,22 @@ public function new() if (!$environment) { return redirect()->route('dashboard'); } - - $type = request()->query('type'); - + if (in_array($type, DATABASE_TYPES)) { + $standalone_postgresql = create_standalone_postgresql($environment->id, $destination_uuid); + return redirect()->route('project.database.configuration', [ + 'project_uuid' => $project->uuid, + 'environment_name' => $environment->name, + 'database_uuid' => $standalone_postgresql->uuid, + ]); + } return view('project.new', [ 'type' => $type ]); } + public function resources() { - $project = session('currentTeam')->load(['projects'])->projects->where('uuid', request()->route('project_uuid'))->first(); + $project = auth()->user()->currentTeam()->load(['projects'])->projects->where('uuid', request()->route('project_uuid'))->first(); if (!$project) { return redirect()->route('dashboard'); } diff --git a/app/Http/Controllers/ServerController.php b/app/Http/Controllers/ServerController.php new file mode 100644 index 000000000..e7f9feb57 --- /dev/null +++ b/app/Http/Controllers/ServerController.php @@ -0,0 +1,31 @@ + false, + 'private_keys' => PrivateKey::ownedByCurrentTeam()->get(), + ]); + } + $servers = auth()->user()->currentTeam()->servers->count(); + $subscription = auth()->user()->currentTeam()?->subscription->type(); + $limits = config('constants.limits.server')[strtolower($subscription)]; + $limit_reached = true ?? $servers >= $limits[$subscription]; + + return view('server.create', [ + 'limit_reached' => $limit_reached, + 'private_keys' => PrivateKey::ownedByCurrentTeam()->get(), + ]); + } +} diff --git a/app/Http/Kernel.php b/app/Http/Kernel.php index 06777812f..57b5edfec 100644 --- a/app/Http/Kernel.php +++ b/app/Http/Kernel.php @@ -37,6 +37,7 @@ class Kernel extends HttpKernel \Illuminate\View\Middleware\ShareErrorsFromSession::class, \App\Http\Middleware\VerifyCsrfToken::class, \Illuminate\Routing\Middleware\SubstituteBindings::class, + \App\Http\Middleware\CheckForcePasswordReset::class, \App\Http\Middleware\SubscriptionValid::class, ], diff --git a/app/Http/Livewire/ActivityMonitor.php b/app/Http/Livewire/ActivityMonitor.php index 4352b9dc9..9ce37f9b3 100644 --- a/app/Http/Livewire/ActivityMonitor.php +++ b/app/Http/Livewire/ActivityMonitor.php @@ -8,19 +8,13 @@ class ActivityMonitor extends Component { - public bool $header = false; + public string|null $header = null; public $activityId; public $isPollingActive = false; protected $activity; protected $listeners = ['newMonitorActivity']; - public function hydrateActivity() - { - $this->activity = Activity::query() - ->find($this->activityId); - } - public function newMonitorActivity($activityId) { $this->activityId = $activityId; @@ -30,6 +24,12 @@ public function newMonitorActivity($activityId) $this->isPollingActive = true; } + public function hydrateActivity() + { + $this->activity = Activity::query() + ->find($this->activityId); + } + public function polling() { $this->hydrateActivity(); @@ -42,8 +42,10 @@ public function polling() $this->setStatus(ProcessStatus::ERROR); } $this->isPollingActive = false; + $this->emit('activityFinished'); } } + protected function setStatus($status) { $this->activity->properties = $this->activity->properties->merge([ diff --git a/app/Http/Livewire/CheckLicense.php b/app/Http/Livewire/CheckLicense.php index 7e6f07e26..3afefed34 100644 --- a/app/Http/Livewire/CheckLicense.php +++ b/app/Http/Livewire/CheckLicense.php @@ -19,11 +19,13 @@ class CheckLicense extends Component 'instance_id' => 'Instance Id (Do not change this)', 'settings.is_resale_license_active' => 'Is License Active', ]; + public function mount() { $this->instance_id = config('app.id'); $this->settings = InstanceSettings::get(); } + public function submit() { $this->validate(); diff --git a/app/Http/Livewire/Destination/Form.php b/app/Http/Livewire/Destination/Form.php index 4f400a670..56c7ba5ed 100644 --- a/app/Http/Livewire/Destination/Form.php +++ b/app/Http/Livewire/Destination/Form.php @@ -18,11 +18,13 @@ class Form extends Component 'destination.network' => 'network', 'destination.server.ip' => 'IP Address', ]; + public function submit() { $this->validate(); $this->destination->save(); } + public function delete() { try { diff --git a/app/Http/Livewire/Destination/New/StandaloneDocker.php b/app/Http/Livewire/Destination/New/StandaloneDocker.php index 8e189c912..fa884ec37 100644 --- a/app/Http/Livewire/Destination/New/StandaloneDocker.php +++ b/app/Http/Livewire/Destination/New/StandaloneDocker.php @@ -5,6 +5,7 @@ use App\Models\Server; use App\Models\StandaloneDocker as ModelsStandaloneDocker; use Illuminate\Database\Eloquent\Collection; +use Illuminate\Support\Str; use Livewire\Component; use Visus\Cuid2\Cuid2; @@ -27,6 +28,7 @@ class StandaloneDocker extends Component 'network' => 'network', 'server_id' => 'server' ]; + public function mount() { if (request()->query('server_id')) { @@ -41,16 +43,17 @@ public function mount() } else { $this->network = new Cuid2(7); } - $this->name = generate_random_name(); + $this->name = Str::kebab("{$this->servers->first()->name}-{$this->network}"); } - private function createNetworkAndAttachToProxy() + + public function generate_name() { - instant_remote_process(['docker network create --attachable ' . $this->network], $this->server, throwError: false); - instant_remote_process(["docker network connect $this->network coolify-proxy"], $this->server, throwError: false); + $this->server = Server::find($this->server_id); + $this->name = Str::kebab("{$this->server->name}-{$this->network}"); } + public function submit() { - $this->validate(); try { $this->server = Server::find($this->server_id); @@ -64,7 +67,7 @@ public function submit() 'name' => $this->name, 'network' => $this->network, 'server_id' => $this->server_id, - 'team_id' => session('currentTeam')->id + 'team_id' => auth()->user()->currentTeam()->id ]); } $this->createNetworkAndAttachToProxy(); @@ -73,4 +76,10 @@ public function submit() return general_error_handler(err: $e); } } + + private function createNetworkAndAttachToProxy() + { + instant_remote_process(['docker network create --attachable ' . $this->network], $this->server, throwError: false); + instant_remote_process(["docker network connect $this->network coolify-proxy"], $this->server, throwError: false); + } } diff --git a/app/Http/Livewire/Destination/Show.php b/app/Http/Livewire/Destination/Show.php index df27e31df..d68537ac4 100644 --- a/app/Http/Livewire/Destination/Show.php +++ b/app/Http/Livewire/Destination/Show.php @@ -10,6 +10,7 @@ class Show extends Component { public Server $server; public Collection|array $networks = []; + public function scan() { $alreadyAddedNetworks = $this->server->standaloneDockers; diff --git a/app/Http/Livewire/Dev/S3Test.php b/app/Http/Livewire/Dev/S3Test.php new file mode 100644 index 000000000..3a20224cd --- /dev/null +++ b/app/Http/Livewire/Dev/S3Test.php @@ -0,0 +1,41 @@ +s3 = S3Storage::first(); + } + + public function save() + { + try { + $this->validate([ + 'file' => 'required|max:150', // 1MB Max + ]); + set_s3_target($this->s3); + $this->file->storeAs('files', $this->file->getClientOriginalName(), 'custom-s3'); + $this->emit('success', 'File uploaded successfully.'); + } catch (\Throwable $th) { + return general_error_handler($th, $this, false); + } + } + + public function get_files() + { + set_s3_target($this->s3); + dd(Storage::disk('custom-s3')->files('files')); + } +} diff --git a/app/Http/Livewire/ForcePasswordReset.php b/app/Http/Livewire/ForcePasswordReset.php new file mode 100644 index 000000000..96a273e80 --- /dev/null +++ b/app/Http/Livewire/ForcePasswordReset.php @@ -0,0 +1,39 @@ + 'required|email', + 'password' => 'required|min:8', + 'password_confirmation' => 'required|same:password', + ]; + public function mount() { + $this->email = auth()->user()->email; + } + public function submit() { + try { + $this->rateLimit(10); + $this->validate(); + auth()->user()->forceFill([ + 'password' => Hash::make($this->password), + 'force_password_reset' => false, + ])->save(); + auth()->logout(); + return redirect()->route('login')->with('status', 'Your initial password has been set.'); + } catch(\Exception $e) { + return general_error_handler(err:$e, that:$this); + } + } + +} diff --git a/app/Http/Livewire/License.php b/app/Http/Livewire/License.php deleted file mode 100644 index 14c147a87..000000000 --- a/app/Http/Livewire/License.php +++ /dev/null @@ -1,26 +0,0 @@ -validate([ - 'license' => 'required' - ]); - // Pretend we're checking the license - // if ($this->license === '123') { - // ray('license is valid'); - // Cache::put('license_key', '123'); - // return redirect()->to('/'); - // } else { - // ray('license is invalid'); - // } - } -} diff --git a/app/Http/Livewire/Notifications/DiscordSettings.php b/app/Http/Livewire/Notifications/DiscordSettings.php index c3c114d3b..03cac4753 100644 --- a/app/Http/Livewire/Notifications/DiscordSettings.php +++ b/app/Http/Livewire/Notifications/DiscordSettings.php @@ -3,48 +3,55 @@ namespace App\Http\Livewire\Notifications; use App\Models\Team; -use App\Notifications\Notifications\TestNotification; +use App\Notifications\Test; use Livewire\Component; class DiscordSettings extends Component { public Team $model; protected $rules = [ - 'model.discord.enabled' => 'nullable|boolean', - 'model.discord.webhook_url' => 'required|url', - 'model.discord_notifications.test' => 'nullable|boolean', - 'model.discord_notifications.deployments' => 'nullable|boolean', - + 'model.discord_enabled' => 'nullable|boolean', + 'model.discord_webhook_url' => 'required|url', + 'model.discord_notifications_test' => 'nullable|boolean', + 'model.discord_notifications_deployments' => 'nullable|boolean', + 'model.discord_notifications_status_changes' => 'nullable|boolean', + 'model.discord_notifications_database_backups' => 'nullable|boolean', ]; protected $validationAttributes = [ - 'model.discord.webhook_url' => 'Discord Webhook', + 'model.discord_webhook_url' => 'Discord Webhook', ]; + public function instantSave() { try { $this->submit(); } catch (\Exception $e) { - $this->model->discord->enabled = false; + ray($e->getMessage()); + $this->model->discord_enabled = false; $this->validate(); } } - public function saveModel() - { - $this->model->save(); - if (is_a($this->model, Team::class)) { - session(['currentTeam' => $this->model]); - } - $this->emit('success', 'Settings saved.'); - } + public function submit() { $this->resetErrorBag(); $this->validate(); $this->saveModel(); } + + public function saveModel() + { + ray($this->model); + $this->model->save(); + if (is_a($this->model, Team::class)) { + session(['currentTeam' => $this->model]); + } + $this->emit('success', 'Settings saved.'); + } + public function sendTestNotification() { - $this->model->notify(new TestNotification('discord')); + $this->model->notify(new Test); $this->emit('success', 'Test notification sent.'); } } diff --git a/app/Http/Livewire/Notifications/EmailSettings.php b/app/Http/Livewire/Notifications/EmailSettings.php index bc8d69240..eceb4e88b 100644 --- a/app/Http/Livewire/Notifications/EmailSettings.php +++ b/app/Http/Livewire/Notifications/EmailSettings.php @@ -4,89 +4,116 @@ use App\Models\InstanceSettings; use App\Models\Team; -use App\Notifications\Notifications\TestNotification; +use App\Notifications\Test; use Livewire\Component; class EmailSettings extends Component { public Team $model; + public string $emails; protected $rules = [ - 'model.smtp.enabled' => 'nullable|boolean', - 'model.smtp.from_address' => 'required|email', - 'model.smtp.from_name' => 'required', - 'model.smtp.recipients' => 'nullable', - 'model.smtp.host' => 'required', - 'model.smtp.port' => 'required', - 'model.smtp.encryption' => 'nullable', - 'model.smtp.username' => 'nullable', - 'model.smtp.password' => 'nullable', - 'model.smtp.timeout' => 'nullable', - 'model.smtp.test_recipients' => 'nullable', - 'model.smtp_notifications.test' => 'nullable|boolean', - 'model.smtp_notifications.deployments' => 'nullable|boolean', - 'model.discord_notifications.test' => 'nullable|boolean', - 'model.discord_notifications.deployments' => 'nullable|boolean', + 'model.smtp_enabled' => 'nullable|boolean', + 'model.smtp_from_address' => 'required|email', + 'model.smtp_from_name' => 'required', + 'model.smtp_recipients' => 'nullable', + 'model.smtp_host' => 'required', + 'model.smtp_port' => 'required', + 'model.smtp_encryption' => 'nullable', + 'model.smtp_username' => 'nullable', + 'model.smtp_password' => 'nullable', + 'model.smtp_timeout' => 'nullable', + 'model.smtp_notifications_test' => 'nullable|boolean', + 'model.smtp_notifications_deployments' => 'nullable|boolean', + 'model.smtp_notifications_status_changes' => 'nullable|boolean', + 'model.smtp_notifications_database_backups' => 'nullable|boolean', ]; protected $validationAttributes = [ - 'model.smtp.from_address' => 'From Address', - 'model.smtp.from_name' => 'From Name', - 'model.smtp.recipients' => 'Recipients', - 'model.smtp.host' => 'Host', - 'model.smtp.port' => 'Port', - 'model.smtp.encryption' => 'Encryption', - 'model.smtp.username' => 'Username', - 'model.smtp.password' => 'Password', - 'model.smtp.test_recipients' => 'Test Recipients', + 'model.smtp_from_address' => 'From Address', + 'model.smtp_from_name' => 'From Name', + 'model.smtp_recipients' => 'Recipients', + 'model.smtp_host' => 'Host', + 'model.smtp_port' => 'Port', + 'model.smtp_encryption' => 'Encryption', + 'model.smtp_username' => 'Username', + 'model.smtp_password' => 'Password', ]; + + public function mount() + { + $this->decrypt(); + $this->emails = auth()->user()->email; + } + private function decrypt() { - if (data_get($this->model, 'smtp.password')) { + if (data_get($this->model, 'smtp_password')) { try { - $this->model->smtp->password = decrypt($this->model->smtp->password); + $this->model->smtp_password = decrypt($this->model->smtp_password); } catch (\Exception $e) { } } } - public function mount() - { - $this->decrypt(); - } + public function copyFromInstanceSettings() { $settings = InstanceSettings::get(); - if ($settings->smtp->enabled) { - $this->model->smtp->enabled = true; - $this->model->smtp->from_address = $settings->smtp->from_address; - $this->model->smtp->from_name = $settings->smtp->from_name; - $this->model->smtp->recipients = $settings->smtp->recipients; - $this->model->smtp->host = $settings->smtp->host; - $this->model->smtp->port = $settings->smtp->port; - $this->model->smtp->encryption = $settings->smtp->encryption; - $this->model->smtp->username = $settings->smtp->username; - $this->model->smtp->password = $settings->smtp->password; - $this->model->smtp->timeout = $settings->smtp->timeout; - $this->model->smtp->test_recipients = $settings->smtp->test_recipients; - $this->saveModel(); + if ($settings->smtp_enabled) { + $team = auth()->user()->currentTeam(); + $team->update([ + 'smtp_enabled' => $settings->smtp_enabled, + 'smtp_from_address' => $settings->smtp_from_address, + 'smtp_from_name' => $settings->smtp_from_name, + 'smtp_recipients' => $settings->smtp_recipients, + 'smtp_host' => $settings->smtp_host, + 'smtp_port' => $settings->smtp_port, + 'smtp_encryption' => $settings->smtp_encryption, + 'smtp_username' => $settings->smtp_username, + 'smtp_password' => $settings->smtp_password, + 'smtp_timeout' => $settings->smtp_timeout, + ]); + $this->decrypt(); + if (is_a($team, Team::class)) { + session(['currentTeam' => $team]); + } + $this->model = $team; + $this->emit('success', 'Settings saved.'); } else { $this->emit('error', 'Instance SMTP settings are not enabled.'); } } + + public function sendTestNotification() + { + $this->model->notify(new Test($this->emails)); + $this->emit('success', 'Test Email sent successfully.'); + } + + public function instantSave() + { + try { + $this->submit(); + } catch (\Exception $e) { + $this->model->smtp_enabled = false; + $this->validate(); + } + } + public function submit() { $this->resetErrorBag(); $this->validate(); - if ($this->model->smtp->password) { - $this->model->smtp->password = encrypt($this->model->smtp->password); + if ($this->model->smtp_password) { + $this->model->smtp_password = encrypt($this->model->smtp_password); } else { - $this->model->smtp->password = null; + $this->model->smtp_password = null; } - $this->model->smtp->recipients = str_replace(' ', '', $this->model->smtp->recipients); - $this->model->smtp->test_recipients = str_replace(' ', '', $this->model->smtp->test_recipients); + $this->model->smtp_recipients = str_replace(' ', '', $this->model->smtp_recipients); $this->saveModel(); } + public function saveModel() { $this->model->save(); @@ -96,18 +123,4 @@ public function saveModel() } $this->emit('success', 'Settings saved.'); } - public function sendTestNotification() - { - $this->model->notify(new TestNotification('smtp')); - $this->emit('success', 'Test notification sent.'); - } - public function instantSave() - { - try { - $this->submit(); - } catch (\Exception $e) { - $this->model->smtp->enabled = false; - $this->validate(); - } - } } diff --git a/app/Http/Livewire/PrivateKey/Change.php b/app/Http/Livewire/PrivateKey/Change.php index cbddfddf6..d40e70e34 100644 --- a/app/Http/Livewire/PrivateKey/Change.php +++ b/app/Http/Livewire/PrivateKey/Change.php @@ -20,12 +20,13 @@ class Change extends Component 'private_key.description' => 'description', 'private_key.private_key' => 'private key' ]; + public function delete() { try { if ($this->private_key->isEmpty()) { $this->private_key->delete(); - session('currentTeam')->privateKeys = PrivateKey::where('team_id', session('currentTeam')->id)->get(); + auth()->user()->currentTeam()->privateKeys = PrivateKey::where('team_id', auth()->user()->currentTeam()->id)->get(); return redirect()->route('private-key.all'); } $this->emit('error', 'This private key is in use and cannot be deleted. Please delete all servers, applications, and GitHub/GitLab apps that use this private key before deleting it.'); @@ -33,6 +34,7 @@ public function delete() return general_error_handler(err: $e, that: $this); } } + public function changePrivateKey() { try { @@ -41,7 +43,7 @@ public function changePrivateKey() $this->private_key->private_key .= "\n"; } $this->private_key->save(); - session('currentTeam')->privateKeys = PrivateKey::where('team_id', session('currentTeam')->id)->get(); + refreshPrivateKey($this->private_key); } catch (\Exception $e) { return general_error_handler(err: $e, that: $this); } diff --git a/app/Http/Livewire/PrivateKey/Create.php b/app/Http/Livewire/PrivateKey/Create.php index af147a9e3..82218f4a5 100644 --- a/app/Http/Livewire/PrivateKey/Create.php +++ b/app/Http/Livewire/PrivateKey/Create.php @@ -7,7 +7,7 @@ class Create extends Component { - protected string|null $from = null; + public string|null $from = null; public string $name; public string|null $description = null; public string $value; @@ -19,6 +19,7 @@ class Create extends Component 'name' => 'name', 'value' => 'private Key', ]; + public function createPrivateKey() { $this->validate(); @@ -31,7 +32,7 @@ public function createPrivateKey() 'name' => $this->name, 'description' => $this->description, 'private_key' => $this->value, - 'team_id' => session('currentTeam')->id + 'team_id' => auth()->user()->currentTeam()->id ]); if ($this->from === 'server') { return redirect()->route('server.create'); diff --git a/app/Http/Livewire/Profile/Form.php b/app/Http/Livewire/Profile/Form.php index 9075c4da2..926d638f1 100644 --- a/app/Http/Livewire/Profile/Form.php +++ b/app/Http/Livewire/Profile/Form.php @@ -17,12 +17,14 @@ class Form extends Component protected $validationAttributes = [ 'name' => 'name', ]; + public function mount() { $this->userId = auth()->user()->id; $this->name = auth()->user()->name; $this->email = auth()->user()->email; } + public function submit() { diff --git a/app/Http/Livewire/Project/AddEmpty.php b/app/Http/Livewire/Project/AddEmpty.php new file mode 100644 index 000000000..1024bc66f --- /dev/null +++ b/app/Http/Livewire/Project/AddEmpty.php @@ -0,0 +1,37 @@ + 'required|string|min:3', + 'description' => 'nullable|string', + ]; + protected $validationAttributes = [ + 'name' => 'Project Name', + 'description' => 'Project Description', + ]; + + public function submit() + { + try { + $this->validate(); + $project = Project::create([ + 'name' => $this->name, + 'description' => $this->description, + 'team_id' => auth()->user()->currentTeam()->id, + ]); + return redirect()->route('project.show', $project->uuid); + } catch (\Exception $e) { + general_error_handler($e, $this); + } finally { + $this->name = ''; + } + } +} diff --git a/app/Http/Livewire/Project/AddEnvironment.php b/app/Http/Livewire/Project/AddEnvironment.php new file mode 100644 index 000000000..ea38df073 --- /dev/null +++ b/app/Http/Livewire/Project/AddEnvironment.php @@ -0,0 +1,40 @@ + 'required|string|min:3', + ]; + protected $validationAttributes = [ + 'name' => 'Environment Name', + ]; + + public function submit() + { + try { + $this->validate(); + $environment = Environment::create([ + 'name' => $this->name, + 'project_id' => $this->project->id, + ]); + + return redirect()->route('project.resources', [ + 'project_uuid' => $this->project->uuid, + 'environment_name' => $environment->name, + ]); + } catch (\Exception $e) { + general_error_handler($e, $this); + } finally { + $this->name = ''; + } + } +} diff --git a/app/Http/Livewire/Project/Application/DeploymentLogs.php b/app/Http/Livewire/Project/Application/DeploymentLogs.php index b39cc47f7..ac3c4a6cf 100644 --- a/app/Http/Livewire/Project/Application/DeploymentLogs.php +++ b/app/Http/Livewire/Project/Application/DeploymentLogs.php @@ -10,10 +10,12 @@ class DeploymentLogs extends Component public ApplicationDeploymentQueue $application_deployment_queue; public $isKeepAliveOn = true; protected $listeners = ['refreshQueue']; + public function refreshQueue() { $this->application_deployment_queue->refresh(); } + public function polling() { $this->emit('deploymentFinished'); diff --git a/app/Http/Livewire/Project/Application/DeploymentNavbar.php b/app/Http/Livewire/Project/Application/DeploymentNavbar.php index 412f15956..1646d46e0 100644 --- a/app/Http/Livewire/Project/Application/DeploymentNavbar.php +++ b/app/Http/Livewire/Project/Application/DeploymentNavbar.php @@ -8,17 +8,16 @@ use App\Models\Server; use Illuminate\Support\Carbon; use Illuminate\Support\Facades\Process; -use Livewire\Component; use Illuminate\Support\Str; +use Livewire\Component; class DeploymentNavbar extends Component { - protected $listeners = ['deploymentFinished']; - public ApplicationDeploymentQueue $application_deployment_queue; public Application $application; public Server $server; public bool $is_debug_enabled = false; + protected $listeners = ['deploymentFinished']; public function mount() { @@ -26,10 +25,12 @@ public function mount() $this->server = $this->application->destination->server; $this->is_debug_enabled = $this->application->settings->is_debug_enabled; } + public function deploymentFinished() { $this->application_deployment_queue->refresh(); } + public function show_debug() { $this->application->settings->is_debug_enabled = !$this->application->settings->is_debug_enabled; @@ -37,6 +38,7 @@ public function show_debug() $this->is_debug_enabled = $this->application->settings->is_debug_enabled; $this->emit('refreshQueue'); } + public function cancel() { try { @@ -66,4 +68,4 @@ public function cancel() return general_error_handler(err: $e, that: $this); } } -} \ No newline at end of file +} diff --git a/app/Http/Livewire/Project/Application/Deployments.php b/app/Http/Livewire/Project/Application/Deployments.php index 16b40895b..1dd80d710 100644 --- a/app/Http/Livewire/Project/Application/Deployments.php +++ b/app/Http/Livewire/Project/Application/Deployments.php @@ -20,6 +20,7 @@ public function mount() $this->current_url = url()->current(); $this->show_more(); } + private function show_more() { if (count($this->deployments) !== 0) { @@ -30,10 +31,12 @@ private function show_more() return; } } + public function reload_deployments() { $this->load_deployments(); } + public function load_deployments(int|null $take = null) { if ($take) { diff --git a/app/Http/Livewire/Project/Application/EnvironmentVariable/All.php b/app/Http/Livewire/Project/Application/EnvironmentVariable/All.php deleted file mode 100644 index f1ac75917..000000000 --- a/app/Http/Livewire/Project/Application/EnvironmentVariable/All.php +++ /dev/null @@ -1,45 +0,0 @@ -modalId = new Cuid2(7); - } - public function refreshEnvs() - { - $this->application->refresh(); - } - public function submit($data) - { - try { - $found = $this->application->environment_variables()->where('key', $data['key'])->first(); - if ($found) { - $this->emit('error', 'Environment variable already exists.'); - return; - } - EnvironmentVariable::create([ - 'key' => $data['key'], - 'value' => $data['value'], - 'is_build_time' => $data['is_build_time'], - 'is_preview' => $data['is_preview'], - 'application_id' => $this->application->id, - ]); - $this->application->refresh(); - - $this->emit('success', 'Environment variable added successfully.'); - } catch (\Exception $e) { - return general_error_handler(err: $e, that: $this); - } - } -} diff --git a/app/Http/Livewire/Project/Application/General.php b/app/Http/Livewire/Project/Application/General.php index f632a4431..8b2ed8900 100644 --- a/app/Http/Livewire/Project/Application/General.php +++ b/app/Http/Livewire/Project/Application/General.php @@ -4,8 +4,8 @@ use App\Models\Application; use App\Models\InstanceSettings; -use Livewire\Component; use Illuminate\Support\Str; +use Livewire\Component; use Spatie\Url\Url; class General extends Component @@ -33,6 +33,7 @@ class General extends Component protected $rules = [ 'application.name' => 'required', + 'application.description' => 'nullable', 'application.fqdn' => 'nullable', 'application.git_repository' => 'required', 'application.git_branch' => 'required', @@ -46,9 +47,11 @@ class General extends Component 'application.publish_directory' => 'nullable', 'application.ports_exposes' => 'required', 'application.ports_mappings' => 'nullable', + 'application.dockerfile' => 'nullable', ]; protected $validationAttributes = [ 'application.name' => 'name', + 'application.description' => 'description', 'application.fqdn' => 'FQDN', 'application.git_repository' => 'Git repository', 'application.git_branch' => 'Git branch', @@ -62,7 +65,9 @@ class General extends Component 'application.publish_directory' => 'Publish directory', 'application.ports_exposes' => 'Ports exposes', 'application.ports_mappings' => 'Ports mappings', + 'application.dockerfile' => 'Dockerfile', ]; + public function instantSave() { // @TODO: find another way - if possible @@ -84,6 +89,7 @@ public function instantSave() $this->emit('success', 'Application settings updated!'); $this->checkWildCardDomain(); } + protected function checkWildCardDomain() { $coolify_instance_settings = InstanceSettings::get(); @@ -91,6 +97,7 @@ protected function checkWildCardDomain() $this->global_wildcard_domain = data_get($coolify_instance_settings, 'wildcard_domain'); $this->wildcard_domain = $this->server_wildcard_domain ?? $this->global_wildcard_domain ?? null; } + public function mount() { $this->is_static = $this->application->settings->is_static; @@ -102,6 +109,7 @@ public function mount() $this->is_force_https_enabled = $this->application->settings->is_force_https_enabled; $this->checkWildCardDomain(); } + public function generateGlobalRandomDomain() { // Set wildcard domain based on Global wildcard domain @@ -113,6 +121,7 @@ public function generateGlobalRandomDomain() $this->application->save(); $this->emit('success', 'Application settings updated!'); } + public function generateServerRandomDomain() { // Set wildcard domain based on Server wildcard domain @@ -124,6 +133,7 @@ public function generateServerRandomDomain() $this->application->save(); $this->emit('success', 'Application settings updated!'); } + public function submit() { try { @@ -132,6 +142,10 @@ public function submit() $domains = Str::of($this->application->fqdn)->trim()->explode(',')->map(function ($domain) { return Str::of($domain)->trim()->lower(); }); + $port = get_port_from_dockerfile($this->application->dockerfile); + if ($port) { + $this->application->ports_exposes = $port; + } if ($this->application->base_directory && $this->application->base_directory !== '/') { $this->application->base_directory = rtrim($this->application->base_directory, '/'); } diff --git a/app/Http/Livewire/Application/Heading.php b/app/Http/Livewire/Project/Application/Heading.php similarity index 81% rename from app/Http/Livewire/Application/Heading.php rename to app/Http/Livewire/Project/Application/Heading.php index 99bc7d4f8..fdbe5f71e 100644 --- a/app/Http/Livewire/Application/Heading.php +++ b/app/Http/Livewire/Project/Application/Heading.php @@ -1,9 +1,10 @@ parameters = getRouteParameters(); + $this->parameters = get_route_parameters(); } public function check_status() { - dispatch_sync(new ApplicationContainerStatusJob( - application: $this->application, + dispatch_sync(new ContainerStatusJob( + resource: $this->application, container_name: generate_container_name($this->application->uuid), )); $this->application->refresh(); } + + public function force_deploy_without_cache() + { + $this->deploy(force_rebuild: true); + } + public function deploy(bool $force_rebuild = false) { $this->setDeploymentUuid(); @@ -42,10 +49,13 @@ public function deploy(bool $force_rebuild = false) 'environment_name' => $this->parameters['environment_name'], ]); } - public function force_deploy_without_cache() + + protected function setDeploymentUuid() { - $this->deploy(force_rebuild: true); + $this->deploymentUuid = new Cuid2(7); + $this->parameters['deployment_uuid'] = $this->deploymentUuid; } + public function stop() { remote_process( @@ -54,10 +64,6 @@ public function stop() ); $this->application->status = 'stopped'; $this->application->save(); - } - protected function setDeploymentUuid() - { - $this->deploymentUuid = new Cuid2(7); - $this->parameters['deployment_uuid'] = $this->deploymentUuid; + $this->application->environment->project->team->notify(new StatusChanged($this->application)); } } diff --git a/app/Http/Livewire/Project/Application/Preview/Form.php b/app/Http/Livewire/Project/Application/Preview/Form.php index e17312005..f831b487d 100644 --- a/app/Http/Livewire/Project/Application/Preview/Form.php +++ b/app/Http/Livewire/Project/Application/Preview/Form.php @@ -17,6 +17,7 @@ class Form extends Component protected $validationAttributes = [ 'application.preview_url_template' => 'preview url template', ]; + public function resetToDefault() { $this->application->preview_url_template = '{{pr_id}}.{{domain}}'; @@ -24,6 +25,7 @@ public function resetToDefault() $this->application->save(); $this->generate_real_url(); } + public function generate_real_url() { if (data_get($this->application, 'fqdn')) { @@ -32,10 +34,12 @@ public function generate_real_url() $this->preview_url_template = Str::of($this->application->preview_url_template)->replace('{{domain}}', $host); } } + public function mount() { $this->generate_real_url(); } + public function submit() { $this->validate(); diff --git a/app/Http/Livewire/Project/Application/Previews.php b/app/Http/Livewire/Project/Application/Previews.php index 8b26b7bc2..b3582fbef 100644 --- a/app/Http/Livewire/Project/Application/Previews.php +++ b/app/Http/Livewire/Project/Application/Previews.php @@ -2,7 +2,7 @@ namespace App\Http\Livewire\Project\Application; -use App\Jobs\ApplicationContainerStatusJob; +use App\Jobs\ContainerStatusJob; use App\Models\Application; use App\Models\ApplicationPreview; use Illuminate\Support\Collection; @@ -20,21 +20,18 @@ class Previews extends Component public function mount() { $this->pull_requests = collect(); - $this->parameters = getRouteParameters(); + $this->parameters = get_route_parameters(); } + public function loadStatus($pull_request_id) { - dispatch(new ApplicationContainerStatusJob( - application: $this->application, + dispatch(new ContainerStatusJob( + resource: $this->application, container_name: generate_container_name($this->application->uuid, $pull_request_id), pull_request_id: $pull_request_id )); } - protected function setDeploymentUuid() - { - $this->deployment_uuid = new Cuid2(7); - $this->parameters['deployment_uuid'] = $this->deployment_uuid; - } + public function load_prs() { try { @@ -46,6 +43,7 @@ public function load_prs() return general_error_handler(err: $e, that: $this); } } + public function deploy(int $pull_request_id, string|null $pull_request_html_url = null) { try { @@ -74,6 +72,13 @@ public function deploy(int $pull_request_id, string|null $pull_request_html_url return general_error_handler(err: $e, that: $this); } } + + protected function setDeploymentUuid() + { + $this->deployment_uuid = new Cuid2(7); + $this->parameters['deployment_uuid'] = $this->deployment_uuid; + } + public function stop(int $pull_request_id) { try { @@ -87,6 +92,7 @@ public function stop(int $pull_request_id) return general_error_handler(err: $e, that: $this); } } + public function previewRefresh() { $this->application->previews->each(function ($preview) { diff --git a/app/Http/Livewire/Project/Application/ResourceLimits.php b/app/Http/Livewire/Project/Application/ResourceLimits.php deleted file mode 100644 index 9db5626ac..000000000 --- a/app/Http/Livewire/Project/Application/ResourceLimits.php +++ /dev/null @@ -1,60 +0,0 @@ - 'required|string', - 'application.limits_memory_swap' => 'required|string', - 'application.limits_memory_swappiness' => 'required|integer|min:0|max:100', - 'application.limits_memory_reservation' => 'required|string', - 'application.limits_cpus' => 'nullable', - 'application.limits_cpuset' => 'nullable', - 'application.limits_cpu_shares' => 'nullable', - ]; - protected $validationAttributes = [ - 'application.limits_memory' => 'memory', - 'application.limits_memory_swap' => 'swap', - 'application.limits_memory_swappiness' => 'swappiness', - 'application.limits_memory_reservation' => 'reservation', - 'application.limits_cpus' => 'cpus', - 'application.limits_cpuset' => 'cpuset', - 'application.limits_cpu_shares' => 'cpu shares', - ]; - public function submit() - { - try { - if (!$this->application->limits_memory) { - $this->application->limits_memory = "0"; - } - if (!$this->application->limits_memory_swap) { - $this->application->limits_memory_swap = "0"; - } - if (!$this->application->limits_memory_swappiness) { - $this->application->limits_memory_swappiness = "60"; - } - if (!$this->application->limits_memory_reservation) { - $this->application->limits_memory_reservation = "0"; - } - if (!$this->application->limits_cpus) { - $this->application->limits_cpus = "0"; - } - if (!$this->application->limits_cpuset) { - $this->application->limits_cpuset = "0"; - } - if (!$this->application->limits_cpu_shares) { - $this->application->limits_cpu_shares = 1024; - } - $this->validate(); - $this->application->save(); - $this->emit('success', 'Resource limits updated successfully.'); - } catch (\Exception $e) { - return general_error_handler(err: $e, that: $this); - } - } -} diff --git a/app/Http/Livewire/Project/Application/Rollback.php b/app/Http/Livewire/Project/Application/Rollback.php index 2e708ec36..d4ac53045 100644 --- a/app/Http/Livewire/Project/Application/Rollback.php +++ b/app/Http/Livewire/Project/Application/Rollback.php @@ -3,8 +3,8 @@ namespace App\Http\Livewire\Project\Application; use App\Models\Application; -use Livewire\Component; use Illuminate\Support\Str; +use Livewire\Component; use Visus\Cuid2\Cuid2; class Rollback extends Component @@ -16,8 +16,9 @@ class Rollback extends Component public function mount() { - $this->parameters = getRouteParameters(); + $this->parameters = get_route_parameters(); } + public function rollbackImage($commit) { $deployment_uuid = new Cuid2(7); @@ -36,6 +37,7 @@ public function rollbackImage($commit) 'environment_name' => $this->parameters['environment_name'], ]); } + public function loadImages() { try { diff --git a/app/Http/Livewire/Project/Application/Source.php b/app/Http/Livewire/Project/Application/Source.php index 83b457ae5..5affe9e45 100644 --- a/app/Http/Livewire/Project/Application/Source.php +++ b/app/Http/Livewire/Project/Application/Source.php @@ -21,16 +21,19 @@ class Source extends Component 'application.git_branch' => 'branch', 'application.git_commit_sha' => 'commit sha', ]; - private function get_private_keys() - { - $this->private_keys = PrivateKey::whereTeamId(session('currentTeam')->id)->get()->reject(function ($key) { - return $key->id == $this->application->private_key_id; - }); - } + public function mount() { $this->get_private_keys(); } + + private function get_private_keys() + { + $this->private_keys = PrivateKey::whereTeamId(auth()->user()->currentTeam()->id)->get()->reject(function ($key) { + return $key->id == $this->application->private_key_id; + }); + } + public function setPrivateKey(int $private_key_id) { $this->application->private_key_id = $private_key_id; @@ -38,6 +41,7 @@ public function setPrivateKey(int $private_key_id) $this->application->refresh(); $this->get_private_keys(); } + public function submit() { $this->validate(); diff --git a/app/Http/Livewire/Project/Database/BackupEdit.php b/app/Http/Livewire/Project/Database/BackupEdit.php new file mode 100644 index 000000000..39b29c83f --- /dev/null +++ b/app/Http/Livewire/Project/Database/BackupEdit.php @@ -0,0 +1,83 @@ + 'required|boolean', + 'backup.frequency' => 'required|string', + 'backup.number_of_backups_locally' => 'required|integer|min:1', + 'backup.save_s3' => 'required|boolean', + 'backup.s3_storage_id' => 'nullable|integer', + ]; + protected $validationAttributes = [ + 'backup.enabled' => 'Enabled', + 'backup.frequency' => 'Frequency', + 'backup.number_of_backups_locally' => 'Number of Backups Locally', + 'backup.save_s3' => 'Save to S3', + 'backup.s3_storage_id' => 'S3 Storage', + ]; + protected $messages = [ + 'backup.s3_storage_id' => 'Select a S3 Storage', + ]; + + public function mount() + { + $this->parameters = get_route_parameters(); + if (is_null($this->backup->s3_storage_id)) { + $this->backup->s3_storage_id = 'default'; + } + } + + + public function delete() + { + // TODO: Delete backup from server and add a confirmation modal + $this->backup->delete(); + redirect()->route('project.database.backups.all', $this->parameters); + } + + public function instantSave() + { + try { + $this->custom_validate(); + $this->backup->save(); + $this->backup->refresh(); + $this->emit('success', 'Backup updated successfully'); + } catch (\Exception $e) { + $this->emit('error', $e->getMessage()); + } + } + + private function custom_validate() + { + if (!is_numeric($this->backup->s3_storage_id)) { + $this->backup->s3_storage_id = null; + } + $isValid = validate_cron_expression($this->backup->frequency); + if (!$isValid) { + throw new \Exception('Invalid Cron / Human expression'); + } + $this->validate(); + } + + public function submit() + { + ray($this->backup->s3_storage_id); + try { + $this->custom_validate(); + $this->backup->save(); + $this->backup->refresh(); + $this->emit('success', 'Backup updated successfully'); + } catch (\Exception $e) { + $this->emit('error', $e->getMessage()); + } + } +} diff --git a/app/Http/Livewire/Project/Database/BackupExecution.php b/app/Http/Livewire/Project/Database/BackupExecution.php new file mode 100644 index 000000000..f963fa1d6 --- /dev/null +++ b/app/Http/Livewire/Project/Database/BackupExecution.php @@ -0,0 +1,23 @@ +execution->filename, $this->execution->scheduledDatabaseBackup->database->destination->server); + $this->execution->delete(); + $this->emit('success', 'Backup execution deleted successfully.'); + $this->emit('refreshBackupExecutions'); + } +} diff --git a/app/Http/Livewire/Project/Database/BackupExecutions.php b/app/Http/Livewire/Project/Database/BackupExecutions.php new file mode 100644 index 000000000..93da317f7 --- /dev/null +++ b/app/Http/Livewire/Project/Database/BackupExecutions.php @@ -0,0 +1,17 @@ +executions = $this->backup->executions; + } +} diff --git a/app/Http/Livewire/Project/Database/BackupNow.php b/app/Http/Livewire/Project/Database/BackupNow.php new file mode 100644 index 000000000..ea25c4744 --- /dev/null +++ b/app/Http/Livewire/Project/Database/BackupNow.php @@ -0,0 +1,18 @@ +backup + )); + $this->emit('success', 'Backup queued. It will be available in a few minutes'); + } +} diff --git a/app/Http/Livewire/Project/Database/CreateScheduledBackup.php b/app/Http/Livewire/Project/Database/CreateScheduledBackup.php new file mode 100644 index 000000000..e3ea97735 --- /dev/null +++ b/app/Http/Livewire/Project/Database/CreateScheduledBackup.php @@ -0,0 +1,52 @@ + 'required|string', + 'save_s3' => 'required|boolean', + ]; + protected $validationAttributes = [ + 'frequency' => 'Backup Frequency', + 'save_s3' => 'Save to S3', + ]; + + public function submit(): void + { + try { + $this->validate(); + $isValid = validate_cron_expression($this->frequency); + if (!$isValid) { + $this->emit('error', 'Invalid Cron / Human expression.'); + return; + } + ScheduledDatabaseBackup::create([ + 'enabled' => true, + 'frequency' => $this->frequency, + 'save_s3' => $this->save_s3, + 's3_storage_id' => $this->s3_storage_id, + 'database_id' => $this->database->id, + 'database_type' => $this->database->getMorphClass(), + 'team_id' => auth()->user()->currentTeam()->id, + ]); + $this->emit('refreshScheduledBackups'); + } catch (\Exception $e) { + general_error_handler($e, $this); + } finally { + $this->frequency = ''; + $this->save_s3 = true; + } + } +} diff --git a/app/Http/Livewire/Project/Database/Heading.php b/app/Http/Livewire/Project/Database/Heading.php new file mode 100644 index 000000000..59f4955a6 --- /dev/null +++ b/app/Http/Livewire/Project/Database/Heading.php @@ -0,0 +1,58 @@ +database->update([ + 'started_at' => now(), + ]); + $this->emit('refresh'); + $this->check_status(); + } + + public function check_status() + { + dispatch_sync(new ContainerStatusJob( + resource: $this->database, + container_name: generate_container_name($this->database->uuid), + )); + $this->database->refresh(); + } + + public function mount() + { + $this->parameters = get_route_parameters(); + } + + public function stop() + { + remote_process( + ["docker rm -f {$this->database->uuid}"], + $this->database->destination->server + ); + $this->database->status = 'stopped'; + $this->database->save(); + $this->database->environment->project->team->notify(new StatusChanged($this->database)); + } + + public function start() + { + if ($this->database->type() === 'standalone-postgresql') { + $activity = resolve(StartPostgresql::class)($this->database->destination->server, $this->database); + $this->emit('newMonitorActivity', $activity->id); + } + } +} diff --git a/app/Http/Livewire/Project/Database/InitScript.php b/app/Http/Livewire/Project/Database/InitScript.php new file mode 100644 index 000000000..ba70b75d3 --- /dev/null +++ b/app/Http/Livewire/Project/Database/InitScript.php @@ -0,0 +1,47 @@ + 'required|string', + 'content' => 'required|string', + ]; + protected $validationAttributes = [ + 'filename' => 'Filename', + 'content' => 'Content', + ]; + + public function mount() + { + $this->index = data_get($this->script, 'index'); + $this->filename = data_get($this->script, 'filename'); + $this->content = data_get($this->script, 'content'); + } + + public function submit() + { + $this->validate(); + try { + $this->script['index'] = $this->index; + $this->script['content'] = $this->content; + $this->script['filename'] = $this->filename; + $this->emitUp('save_init_script', $this->script); + } catch (Exception $e) { + return general_error_handler(err: $e, that: $this); + } + } + + public function delete() + { + $this->emitUp('delete_init_script', $this->script); + } +} diff --git a/app/Http/Livewire/Project/Database/Postgresql/General.php b/app/Http/Livewire/Project/Database/Postgresql/General.php new file mode 100644 index 000000000..c58bd51e3 --- /dev/null +++ b/app/Http/Livewire/Project/Database/Postgresql/General.php @@ -0,0 +1,107 @@ + 'required', + 'database.description' => 'nullable', + 'database.postgres_user' => 'required', + 'database.postgres_password' => 'required', + 'database.postgres_db' => 'required', + 'database.postgres_initdb_args' => 'nullable', + 'database.postgres_host_auth_method' => 'nullable', + 'database.init_scripts' => 'nullable', + 'database.image' => 'required', + 'database.ports_mappings' => 'nullable', + ]; + protected $validationAttributes = [ + 'database.name' => 'Name', + 'database.description' => 'Description', + 'database.postgres_user' => 'Postgres User', + 'database.postgres_password' => 'Postgres Password', + 'database.postgres_db' => 'Postgres DB', + 'database.postgres_initdb_args' => 'Postgres Initdb Args', + 'database.postgres_host_auth_method' => 'Postgres Host Auth Method', + 'database.init_scripts' => 'Init Scripts', + 'database.image' => 'Image', + 'database.ports_mappings' => 'Port Mapping', + ]; + + public function save_init_script($script) + { + $this->database->init_scripts = filter($this->database->init_scripts, fn ($s) => $s['filename'] !== $script['filename']); + $this->database->init_scripts = array_merge($this->database->init_scripts, [$script]); + $this->database->save(); + $this->emit('success', 'Init script saved successfully.'); + } + + public function delete_init_script($script) + { + $collection = collect($this->database->init_scripts); + $found = $collection->firstWhere('filename', $script['filename']); + if ($found) { + ray($collection->filter(fn ($s) => $s['filename'] !== $script['filename'])->toArray()); + $this->database->init_scripts = $collection->filter(fn ($s) => $s['filename'] !== $script['filename'])->toArray(); + $this->database->save(); + $this->refresh(); + $this->emit('success', 'Init script deleted successfully.'); + return; + } + } + + public function refresh(): void + { + $this->database->refresh(); + } + + public function save_new_init_script() + { + $this->validate([ + 'new_filename' => 'required|string', + 'new_content' => 'required|string', + ]); + $found = collect($this->database->init_scripts)->firstWhere('filename', $this->new_filename); + if ($found) { + $this->emit('error', 'Filename already exists.'); + return; + } + if (!isset($this->database->init_scripts)) { + $this->database->init_scripts = []; + } + $this->database->init_scripts = array_merge($this->database->init_scripts, [ + [ + 'index' => count($this->database->init_scripts), + 'filename' => $this->new_filename, + 'content' => $this->new_content, + ] + ]); + $this->database->save(); + $this->emit('success', 'Init script added successfully.'); + $this->new_content = ''; + $this->new_filename = ''; + } + + public function submit() + { + try { + $this->validate(); + $this->database->save(); + $this->emit('success', 'Database updated successfully.'); + } catch (Exception $e) { + return general_error_handler(err: $e, that: $this); + } + } +} diff --git a/app/Http/Livewire/Project/Database/ScheduledBackups.php b/app/Http/Livewire/Project/Database/ScheduledBackups.php new file mode 100644 index 000000000..f1abbb86d --- /dev/null +++ b/app/Http/Livewire/Project/Database/ScheduledBackups.php @@ -0,0 +1,30 @@ +parameters = get_route_parameters(); + } + + public function delete($scheduled_backup_id): void + { + $this->database->scheduledBackups->find($scheduled_backup_id)->delete(); + $this->emit('success', 'Scheduled backup deleted successfully.'); + $this->refreshScheduledBackups(); + } + + public function refreshScheduledBackups(): void + { + ray('refreshScheduledBackups'); + $this->database->refresh(); + } +} diff --git a/app/Http/Livewire/Project/DeleteEnvironment.php b/app/Http/Livewire/Project/DeleteEnvironment.php index 9e712d859..f341d7cb5 100644 --- a/app/Http/Livewire/Project/DeleteEnvironment.php +++ b/app/Http/Livewire/Project/DeleteEnvironment.php @@ -12,8 +12,9 @@ class DeleteEnvironment extends Component public function mount() { - $this->parameters = getRouteParameters(); + $this->parameters = get_route_parameters(); } + public function delete() { $this->validate([ diff --git a/app/Http/Livewire/Project/DeleteProject.php b/app/Http/Livewire/Project/DeleteProject.php index 1a22e6257..5bbfea17a 100644 --- a/app/Http/Livewire/Project/DeleteProject.php +++ b/app/Http/Livewire/Project/DeleteProject.php @@ -12,8 +12,9 @@ class DeleteProject extends Component public function mount() { - $this->parameters = getRouteParameters(); + $this->parameters = get_route_parameters(); } + public function delete() { $this->validate([ diff --git a/app/Http/Livewire/Project/New/EmptyProject.php b/app/Http/Livewire/Project/New/EmptyProject.php index 931348a22..e77ab983e 100644 --- a/app/Http/Livewire/Project/New/EmptyProject.php +++ b/app/Http/Livewire/Project/New/EmptyProject.php @@ -11,7 +11,7 @@ public function createEmptyProject() { $project = Project::create([ 'name' => generate_random_name(), - 'team_id' => session('currentTeam')->id, + 'team_id' => auth()->user()->currentTeam()->id, ]); return redirect()->route('project.show', ['project_uuid' => $project->uuid, 'environment_name' => 'production']); } diff --git a/app/Http/Livewire/Project/New/GithubPrivateRepository.php b/app/Http/Livewire/Project/New/GithubPrivateRepository.php index 89dab6b72..77b2b2dab 100644 --- a/app/Http/Livewire/Project/New/GithubPrivateRepository.php +++ b/app/Http/Livewire/Project/New/GithubPrivateRepository.php @@ -5,16 +5,14 @@ use App\Models\Application; use App\Models\GithubApp; use App\Models\Project; -use App\Models\Server; use App\Models\StandaloneDocker; use App\Models\SwarmDocker; -use Illuminate\Support\Collection; use Illuminate\Support\Facades\Http; -use Illuminate\Support\Facades\Log; use Livewire\Component; class GithubPrivateRepository extends Component { + public $current_step = 'github_apps'; public $github_apps; public GithubApp $github_app; public $parameters; @@ -29,52 +27,23 @@ class GithubPrivateRepository extends Component public string $selected_branch_name = 'main'; public string $token; - - protected int $page = 1; - public $repositories; public int $total_repositories_count = 0; - public $branches; public int $total_branches_count = 0; - public int $port = 3000; public bool $is_static = false; public string|null $publish_directory = null; + protected int $page = 1; public function mount() { - $this->parameters = getRouteParameters(); + $this->parameters = get_route_parameters(); $this->query = request()->query(); $this->repositories = $this->branches = collect(); $this->github_apps = GithubApp::private(); } - protected function loadRepositoryByPage() - { - $response = Http::withToken($this->token)->get("{$this->github_app->api_url}/installation/repositories?per_page=100&page={$this->page}"); - $json = $response->json(); - if ($response->status() !== 200) { - return $this->emit('error', $json['message']); - } - if ($json['total_count'] === 0) { - return; - } - $this->total_repositories_count = $json['total_count']; - $this->repositories = $this->repositories->concat(collect($json['repositories'])); - } - protected function loadBranchByPage() - { - Log::info('Loading page ' . $this->page); - $response = Http::withToken($this->token)->get("{$this->github_app->api_url}/repos/{$this->selected_repository_owner}/{$this->selected_repository_repo}/branches?per_page=100&page={$this->page}"); - $json = $response->json(); - if ($response->status() !== 200) { - return $this->emit('error', $json['message']); - } - - $this->total_branches_count = count($json); - $this->branches = $this->branches->concat(collect($json)); - } public function loadRepositories($github_app_id) { $this->repositories = collect(); @@ -90,7 +59,24 @@ public function loadRepositories($github_app_id) } } $this->selected_repository_id = $this->repositories[0]['id']; + $this->current_step = 'repository'; } + + protected function loadRepositoryByPage() + { + $response = Http::withToken($this->token)->get("{$this->github_app->api_url}/installation/repositories?per_page=100&page={$this->page}"); + $json = $response->json(); + if ($response->status() !== 200) { + return $this->emit('error', $json['message']); + } + + if ($json['total_count'] === 0) { + return; + } + $this->total_repositories_count = $json['total_count']; + $this->repositories = $this->repositories->concat(collect($json['repositories'])); + } + public function loadBranches() { $this->selected_repository_owner = $this->repositories->where('id', $this->selected_repository_id)->first()['owner']['login']; @@ -105,6 +91,20 @@ public function loadBranches() } } } + + protected function loadBranchByPage() + { + ray('Loading page ' . $this->page); + $response = Http::withToken($this->token)->get("{$this->github_app->api_url}/repos/{$this->selected_repository_owner}/{$this->selected_repository_repo}/branches?per_page=100&page={$this->page}"); + $json = $response->json(); + if ($response->status() !== 200) { + return $this->emit('error', $json['message']); + } + + $this->total_branches_count = count($json); + $this->branches = $this->branches->concat(collect($json)); + } + public function submit() { try { @@ -134,7 +134,7 @@ public function submit() 'destination_id' => $destination->id, 'destination_type' => $destination_class, 'source_id' => $this->github_app->id, - 'source_type' => $this->github_app->getMorphClass() + 'source_type' => $this->github_app->getMorphClass() ]); $application->settings->is_static = $this->is_static; $application->settings->save(); @@ -148,6 +148,7 @@ public function submit() return general_error_handler(err: $e, that: $this); } } + public function instantSave() { if ($this->is_static) { diff --git a/app/Http/Livewire/Project/New/GithubPrivateRepositoryDeployKey.php b/app/Http/Livewire/Project/New/GithubPrivateRepositoryDeployKey.php index 8f8611c4d..a1f275f32 100644 --- a/app/Http/Livewire/Project/New/GithubPrivateRepositoryDeployKey.php +++ b/app/Http/Livewire/Project/New/GithubPrivateRepositoryDeployKey.php @@ -14,6 +14,7 @@ class GithubPrivateRepositoryDeployKey extends Component { + public $current_step = 'private_keys'; public $parameters; public $query; public $private_keys; @@ -26,14 +27,7 @@ class GithubPrivateRepositoryDeployKey extends Component public null|string $publish_directory = null; public string $repository_url; - private object $repository_url_parsed; public string $branch; - - private GithubApp|GitlabApp $git_source; - private string $git_host; - private string $git_repository; - private string $git_branch; - protected $rules = [ 'repository_url' => 'required|url', 'branch' => 'required|string', @@ -48,15 +42,22 @@ class GithubPrivateRepositoryDeployKey extends Component 'is_static' => 'Is static', 'publish_directory' => 'Publish directory', ]; + private object $repository_url_parsed; + private GithubApp|GitlabApp $git_source; + private string $git_host; + private string $git_repository; + private string $git_branch; + public function mount() { - if (isDev()) { + if (is_dev()) { $this->repository_url = 'https://github.com/coollabsio/coolify-examples'; } - $this->parameters = getRouteParameters(); + $this->parameters = get_route_parameters(); $this->query = request()->query(); - $this->private_keys = PrivateKey::where('team_id', session('currentTeam')->id)->where('id', '!=', 0)->get(); + $this->private_keys = PrivateKey::where('team_id', auth()->user()->currentTeam()->id)->where('id', '!=', 0)->get(); } + public function instantSave() { if ($this->is_static) { @@ -67,29 +68,13 @@ public function instantSave() $this->publish_directory = null; } } + public function setPrivateKey($private_key_id) { $this->private_key_id = $private_key_id; + $this->current_step = 'repository'; } - private function get_git_source() - { - $this->repository_url_parsed = Url::fromString($this->repository_url); - $this->git_host = $this->repository_url_parsed->getHost(); - $this->git_repository = $this->repository_url_parsed->getSegment(1) . '/' . $this->repository_url_parsed->getSegment(2); - if ($this->branch) { - $this->git_branch = $this->branch; - } else { - $this->git_branch = $this->repository_url_parsed->getSegment(4) ?? 'main'; - } - if ($this->git_host == 'github.com') { - $this->git_source = GithubApp::where('name', 'Public GitHub')->first(); - } elseif ($this->git_host == 'gitlab.com') { - $this->git_source = GitlabApp::where('name', 'Public GitLab')->first(); - } elseif ($this->git_host == 'bitbucket.org') { - // Not supported yet - } - } public function submit() { $this->validate(); @@ -121,7 +106,7 @@ public function submit() 'destination_type' => $destination_class, 'private_key_id' => $this->private_key_id, 'source_id' => $this->git_source->id, - 'source_type' => $this->git_source->getMorphClass() + 'source_type' => $this->git_source->getMorphClass() ]; $application = Application::create($application_init); $application->settings->is_static = $this->is_static; @@ -136,4 +121,24 @@ public function submit() return general_error_handler(err: $e, that: $this); } } + + private function get_git_source() + { + $this->repository_url_parsed = Url::fromString($this->repository_url); + $this->git_host = $this->repository_url_parsed->getHost(); + $this->git_repository = $this->repository_url_parsed->getSegment(1) . '/' . $this->repository_url_parsed->getSegment(2); + if ($this->branch) { + $this->git_branch = $this->branch; + } else { + $this->git_branch = $this->repository_url_parsed->getSegment(4) ?? 'main'; + } + + if ($this->git_host == 'github.com') { + $this->git_source = GithubApp::where('name', 'Public GitHub')->first(); + } elseif ($this->git_host == 'gitlab.com') { + $this->git_source = GitlabApp::where('name', 'Public GitLab')->first(); + } elseif ($this->git_host == 'bitbucket.org') { + // Not supported yet + } + } } diff --git a/app/Http/Livewire/Project/New/PublicGitRepository.php b/app/Http/Livewire/Project/New/PublicGitRepository.php index 8fbaf62fb..7fd15cd34 100644 --- a/app/Http/Livewire/Project/New/PublicGitRepository.php +++ b/app/Http/Livewire/Project/New/PublicGitRepository.php @@ -15,13 +15,10 @@ class PublicGitRepository extends Component { public string $repository_url; - private object $repository_url_parsed; - public int $port = 3000; public string $type; public $parameters; public $query; - public bool $branch_found = false; public string $selected_branch = 'main'; public bool $is_static = false; @@ -29,11 +26,6 @@ class PublicGitRepository extends Component public string $git_branch = 'main'; public int $rate_limit_remaining = 0; public $rate_limit_reset = 0; - - private GithubApp|GitlabApp $git_source; - private string $git_host; - private string $git_repository; - protected $rules = [ 'repository_url' => 'required|url', 'port' => 'required|numeric', @@ -46,13 +38,18 @@ class PublicGitRepository extends Component 'is_static' => 'static', 'publish_directory' => 'publish directory', ]; + private object $repository_url_parsed; + private GithubApp|GitlabApp $git_source; + private string $git_host; + private string $git_repository; + public function mount() { - if (isDev()) { + if (is_dev()) { $this->repository_url = 'https://github.com/coollabsio/coolify-examples'; $this->port = 3000; } - $this->parameters = getRouteParameters(); + $this->parameters = get_route_parameters(); $this->query = request()->query(); } @@ -67,12 +64,7 @@ public function instantSave() } $this->emit('success', 'Application settings updated!'); } - private function get_branch() - { - ['rate_limit_remaining' => $this->rate_limit_remaining, 'rate_limit_reset' => $this->rate_limit_reset] = git_api(source: $this->git_source, endpoint: "/repos/{$this->git_repository}/branches/{$this->git_branch}"); - $this->rate_limit_reset = Carbon::parse((int)$this->rate_limit_reset)->format('Y-M-d H:i:s'); - $this->branch_found = true; - } + public function load_branch() { $this->branch_found = false; @@ -82,7 +74,9 @@ public function load_branch() $this->get_git_source(); try { $this->get_branch(); + $this->selected_branch = $this->git_branch; } catch (\Exception $e) { + return general_error_handler(err: $e, that: $this); } if (!$this->branch_found && $this->git_branch == 'main') { @@ -94,6 +88,7 @@ public function load_branch() } } } + private function get_git_source() { $this->repository_url_parsed = Url::fromString($this->repository_url); @@ -109,6 +104,14 @@ private function get_git_source() // Not supported yet } } + + private function get_branch() + { + ['rate_limit_remaining' => $this->rate_limit_remaining, 'rate_limit_reset' => $this->rate_limit_reset] = git_api(source: $this->git_source, endpoint: "/repos/{$this->git_repository}/branches/{$this->git_branch}"); + $this->rate_limit_reset = Carbon::parse((int)$this->rate_limit_reset)->format('Y-M-d H:i:s'); + $this->branch_found = true; + } + public function submit() { try { diff --git a/app/Http/Livewire/Project/New/Select.php b/app/Http/Livewire/Project/New/Select.php new file mode 100644 index 000000000..f95599154 --- /dev/null +++ b/app/Http/Livewire/Project/New/Select.php @@ -0,0 +1,51 @@ +parameters = get_route_parameters(); + } + + public function set_type(string $type) + { + $this->type = $type; + $this->current_step = 'servers'; + } + + public function set_server(Server $server) + { + $this->server_id = $server->id; + $this->destinations = $server->destinations(); + $this->current_step = 'destinations'; + } + + public function set_destination(string $destination_uuid) + { + $this->destination_uuid = $destination_uuid; + redirect()->route('project.resources.new', [ + 'project_uuid' => $this->parameters['project_uuid'], + 'environment_name' => $this->parameters['environment_name'], + 'type' => $this->type, + 'destination' => $this->destination_uuid, + ]); + } + + public function load_servers() + { + $this->servers = Server::ownedByCurrentTeam()->get(); + } +} diff --git a/app/Http/Livewire/Project/New/SimpleDockerfile.php b/app/Http/Livewire/Project/New/SimpleDockerfile.php new file mode 100644 index 000000000..bbc82fcd5 --- /dev/null +++ b/app/Http/Livewire/Project/New/SimpleDockerfile.php @@ -0,0 +1,68 @@ +parameters = get_route_parameters(); + $this->query = request()->query(); + if (is_dev()) { + $this->dockerfile = 'FROM nginx +EXPOSE 80 +CMD ["nginx", "-g", "daemon off;"] +'; + } + } + public function submit() + { + $this->validate([ + 'dockerfile' => 'required' + ]); + $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(); + + $port = get_port_from_dockerfile($this->dockerfile); + $application = Application::create([ + 'name' => 'dockerfile-' . new Cuid2(7), + 'repository_project_id' => 0, + 'git_repository' => "coollabsio/coolify", + 'git_branch' => 'main', + 'build_pack' => 'dockerfile', + 'dockerfile' => $this->dockerfile, + 'ports_exposes' => $port, + 'environment_id' => $environment->id, + 'destination_id' => $destination->id, + 'destination_type' => $destination_class, + 'source_id' => 0, + 'source_type' => GithubApp::class + ]); + redirect()->route('project.application.configuration', [ + 'application_uuid' => $application->uuid, + 'environment_name' => $environment->name, + 'project_uuid' => $project->uuid, + ]); + } +} diff --git a/app/Http/Livewire/Project/Application/Danger.php b/app/Http/Livewire/Project/Shared/Danger.php similarity index 52% rename from app/Http/Livewire/Project/Application/Danger.php rename to app/Http/Livewire/Project/Shared/Danger.php index 4af90927e..911192a2a 100644 --- a/app/Http/Livewire/Project/Application/Danger.php +++ b/app/Http/Livewire/Project/Shared/Danger.php @@ -1,28 +1,28 @@ modalId = new Cuid2(7); - $this->parameters = getRouteParameters(); + $this->parameters = get_route_parameters(); } + public function delete() { - $destination = $this->application->destination->getMorphClass()::where('id', $this->application->destination->id)->first(); + $destination = $this->resource->destination->getMorphClass()::where('id', $this->resource->destination->id)->first(); - instant_remote_process(["docker rm -f {$this->application->uuid}"], $destination->server); - $this->application->delete(); + instant_remote_process(["docker rm -f {$this->resource->uuid}"], $destination->server); + $this->resource->delete(); return redirect()->route('project.resources', [ 'project_uuid' => $this->parameters['project_uuid'], 'environment_name' => $this->parameters['environment_name'] diff --git a/app/Http/Livewire/Project/Application/Destination.php b/app/Http/Livewire/Project/Shared/Destination.php similarity index 66% rename from app/Http/Livewire/Project/Application/Destination.php rename to app/Http/Livewire/Project/Shared/Destination.php index 571c81d9b..3bdb48af6 100644 --- a/app/Http/Livewire/Project/Application/Destination.php +++ b/app/Http/Livewire/Project/Shared/Destination.php @@ -1,6 +1,6 @@ 'value', 'is_build_time' => 'build', ]; + public function mount() { - $this->parameters = getRouteParameters(); + $this->parameters = get_route_parameters(); } + public function submit() { ray('submitting'); @@ -39,6 +41,7 @@ public function submit() ]); $this->clear(); } + public function clear() { $this->key = ''; diff --git a/app/Http/Livewire/Project/Shared/EnvironmentVariable/All.php b/app/Http/Livewire/Project/Shared/EnvironmentVariable/All.php new file mode 100644 index 000000000..70bfd42a1 --- /dev/null +++ b/app/Http/Livewire/Project/Shared/EnvironmentVariable/All.php @@ -0,0 +1,52 @@ +modalId = new Cuid2(7); + } + + public function refreshEnvs() + { + $this->resource->refresh(); + } + + public function submit($data) + { + try { + $found = $this->resource->environment_variables()->where('key', $data['key'])->first(); + if ($found) { + $this->emit('error', 'Environment variable already exists.'); + return; + } + $environment = new EnvironmentVariable(); + $environment->key = $data['key']; + $environment->value = $data['value']; + $environment->is_build_time = $data['is_build_time']; + $environment->is_preview = $data['is_preview']; + + if ($this->resource->type() === 'application') { + $environment->application_id = $this->resource->id; + } + if ($this->resource->type() === 'standalone-postgresql') { + $environment->standalone_postgresql_id = $this->resource->id; + } + $environment->save(); + $this->resource->refresh(); + $this->emit('success', 'Environment variable added successfully.'); + } catch (\Exception $e) { + return general_error_handler(err: $e, that: $this); + } + } +} diff --git a/app/Http/Livewire/Project/Application/EnvironmentVariable/Show.php b/app/Http/Livewire/Project/Shared/EnvironmentVariable/Show.php similarity index 82% rename from app/Http/Livewire/Project/Application/EnvironmentVariable/Show.php rename to app/Http/Livewire/Project/Shared/EnvironmentVariable/Show.php index c5260e77c..cfc94af22 100644 --- a/app/Http/Livewire/Project/Application/EnvironmentVariable/Show.php +++ b/app/Http/Livewire/Project/Shared/EnvironmentVariable/Show.php @@ -1,6 +1,6 @@ 'value', 'is_build_time' => 'build', ]; + public function mount() { $this->modalId = new Cuid2(7); - $this->parameters = getRouteParameters(); + $this->parameters = get_route_parameters(); + } + + public function instantSave() + { + $this->submit(); } public function submit() { @@ -32,6 +38,7 @@ public function submit() $this->env->save(); $this->emit('success', 'Environment variable updated successfully.'); } + public function delete() { $this->env->delete(); diff --git a/app/Http/Livewire/Project/Shared/ResourceLimits.php b/app/Http/Livewire/Project/Shared/ResourceLimits.php new file mode 100644 index 000000000..6b4ae7e3a --- /dev/null +++ b/app/Http/Livewire/Project/Shared/ResourceLimits.php @@ -0,0 +1,60 @@ + 'required|string', + 'resource.limits_memory_swap' => 'required|string', + 'resource.limits_memory_swappiness' => 'required|integer|min:0|max:100', + 'resource.limits_memory_reservation' => 'required|string', + 'resource.limits_cpus' => 'nullable', + 'resource.limits_cpuset' => 'nullable', + 'resource.limits_cpu_shares' => 'nullable', + ]; + protected $validationAttributes = [ + 'resource.limits_memory' => 'memory', + 'resource.limits_memory_swap' => 'swap', + 'resource.limits_memory_swappiness' => 'swappiness', + 'resource.limits_memory_reservation' => 'reservation', + 'resource.limits_cpus' => 'cpus', + 'resource.limits_cpuset' => 'cpuset', + 'resource.limits_cpu_shares' => 'cpu shares', + ]; + + public function submit() + { + try { + if (!$this->resource->limits_memory) { + $this->resource->limits_memory = "0"; + } + if (!$this->resource->limits_memory_swap) { + $this->resource->limits_memory_swap = "0"; + } + if (!$this->resource->limits_memory_swappiness) { + $this->resource->limits_memory_swappiness = "60"; + } + if (!$this->resource->limits_memory_reservation) { + $this->resource->limits_memory_reservation = "0"; + } + if (!$this->resource->limits_cpus) { + $this->resource->limits_cpus = "0"; + } + if (!$this->resource->limits_cpuset) { + $this->resource->limits_cpuset = "0"; + } + if (!$this->resource->limits_cpu_shares) { + $this->resource->limits_cpu_shares = 1024; + } + $this->validate(); + $this->resource->save(); + $this->emit('success', 'Resource limits updated successfully.'); + } catch (\Exception $e) { + return general_error_handler(err: $e, that: $this); + } + } +} diff --git a/app/Http/Livewire/Project/Application/Storages/Add.php b/app/Http/Livewire/Project/Shared/Storages/Add.php similarity index 89% rename from app/Http/Livewire/Project/Application/Storages/Add.php rename to app/Http/Livewire/Project/Shared/Storages/Add.php index 818657a02..e7831f1cb 100644 --- a/app/Http/Livewire/Project/Application/Storages/Add.php +++ b/app/Http/Livewire/Project/Shared/Storages/Add.php @@ -1,6 +1,6 @@ 'mount', 'host_path' => 'host', ]; + public function mount() { - $this->parameters = getRouteParameters(); + $this->parameters = get_route_parameters(); } + public function submit() { $this->validate(); @@ -35,6 +37,7 @@ public function submit() 'host_path' => $this->host_path, ]); } + public function clear() { $this->name = ''; diff --git a/app/Http/Livewire/Project/Application/Storages/All.php b/app/Http/Livewire/Project/Shared/Storages/All.php similarity index 68% rename from app/Http/Livewire/Project/Application/Storages/All.php rename to app/Http/Livewire/Project/Shared/Storages/All.php index 59cf0e52e..ee016143b 100644 --- a/app/Http/Livewire/Project/Application/Storages/All.php +++ b/app/Http/Livewire/Project/Shared/Storages/All.php @@ -1,19 +1,20 @@ application->refresh(); + $this->resource->refresh(); } + public function submit($data) { try { @@ -21,10 +22,10 @@ public function submit($data) 'name' => $data['name'], 'mount_path' => $data['mount_path'], 'host_path' => $data['host_path'], - 'resource_id' => $this->application->id, - 'resource_type' => Application::class, + 'resource_id' => $this->resource->id, + 'resource_type' => $this->resource->getMorphClass(), ]); - $this->application->refresh(); + $this->resource->refresh(); $this->emit('success', 'Storage added successfully'); $this->emit('clearAddStorage'); } catch (\Exception $e) { diff --git a/app/Http/Livewire/Project/Application/Storages/Show.php b/app/Http/Livewire/Project/Shared/Storages/Show.php similarity index 93% rename from app/Http/Livewire/Project/Application/Storages/Show.php rename to app/Http/Livewire/Project/Shared/Storages/Show.php index ef6042baf..7cbf322f7 100644 --- a/app/Http/Livewire/Project/Application/Storages/Show.php +++ b/app/Http/Livewire/Project/Shared/Storages/Show.php @@ -1,6 +1,6 @@ 'mount', 'host_path' => 'host', ]; + public function mount() { $this->modalId = new Cuid2(7); } + public function submit() { $this->validate(); $this->storage->save(); $this->emit('success', 'Storage updated successfully'); } + public function delete() { $this->storage->delete(); diff --git a/app/Http/Livewire/RunCommand.php b/app/Http/Livewire/RunCommand.php index 50462c52c..747ce9568 100755 --- a/app/Http/Livewire/RunCommand.php +++ b/app/Http/Livewire/RunCommand.php @@ -2,7 +2,6 @@ namespace App\Http\Livewire; -use App\Enums\ActivityTypes; use App\Models\Server; use Livewire\Component; @@ -20,6 +19,7 @@ class RunCommand extends Component 'server' => 'server', 'command' => 'command', ]; + public function mount($servers) { $this->servers = $servers; diff --git a/app/Http/Livewire/Server/Form.php b/app/Http/Livewire/Server/Form.php index eeb0df413..4dcebc101 100644 --- a/app/Http/Livewire/Server/Form.php +++ b/app/Http/Livewire/Server/Form.php @@ -5,7 +5,6 @@ use App\Actions\Server\InstallDocker; use App\Models\Server; use Livewire\Component; -use Visus\Cuid2\Cuid2; class Form extends Component { @@ -14,7 +13,6 @@ class Form extends Component public $dockerVersion; public string|null $wildcard_domain = null; public int $cleanup_after_percentage; - public string|null $modalId = null; protected $rules = [ 'server.name' => 'required|min:6', @@ -35,43 +33,35 @@ class Form extends Component 'server.settings.is_reachable' => 'is reachable', 'server.settings.is_part_of_swarm' => 'is part of swarm' ]; + public function mount() { - $this->modalId = new Cuid2(7); $this->wildcard_domain = $this->server->settings->wildcard_domain; $this->cleanup_after_percentage = $this->server->settings->cleanup_after_percentage; } + public function installDocker() { - $activity = resolve(InstallDocker::class)($this->server, session('currentTeam')); + $activity = resolve(InstallDocker::class)($this->server, auth()->user()->currentTeam()); $this->emit('newMonitorActivity', $activity->id); } + public function validateServer() { try { - $this->uptime = instant_remote_process(['uptime'], $this->server); - if ($this->uptime) { - $this->server->settings->is_reachable = true; - $this->server->settings->save(); - } else { - $this->uptime = 'Server not reachable.'; - throw new \Exception('Server not reachable.'); + ['uptime' => $uptime, 'dockerVersion' => $dockerVersion] = validateServer($this->server); + if ($uptime) { + $this->uptime = $uptime; } - $this->dockerVersion = instant_remote_process(['docker version|head -2|grep -i version'], $this->server, false); - if (!$this->dockerVersion) { - $this->dockerVersion = 'Not installed.'; - } else { - $this->server->settings->is_usable = true; - $this->server->settings->save(); + if ($dockerVersion) { + $this->dockerVersion = $dockerVersion; $this->emit('proxyStatusUpdated'); } } catch (\Exception $e) { - $this->server->settings->is_reachable = false; - $this->server->settings->is_usable = false; - $this->server->settings->save(); return general_error_handler(customErrorMessage: "Server is not reachable. Reason: {$e->getMessage()}", that: $this); } } + public function delete() { if (!$this->server->isEmpty()) { @@ -81,6 +71,7 @@ public function delete() $this->server->delete(); redirect()->route('server.all'); } + public function submit() { $this->validate(); diff --git a/app/Http/Livewire/Server/New/ByIp.php b/app/Http/Livewire/Server/New/ByIp.php index 424f31e1b..c4f340413 100644 --- a/app/Http/Livewire/Server/New/ByIp.php +++ b/app/Http/Livewire/Server/New/ByIp.php @@ -2,13 +2,13 @@ namespace App\Http\Livewire\Server\New; -use App\Models\PrivateKey; use App\Models\Server; use Livewire\Component; class ByIp extends Component { public $private_keys; + public $limit_reached; public int|null $private_key_id = null; public $new_private_key_name; public $new_private_key_description; @@ -35,19 +35,23 @@ class ByIp extends Component 'user' => 'user', 'port' => 'port', ]; + public function mount() { $this->name = generate_random_name(); $this->private_key_id = $this->private_keys->first()->id; } + public function setPrivateKey(string $private_key_id) { $this->private_key_id = $private_key_id; } + public function instantSave() { $this->emit('success', 'Application settings updated!'); } + public function submit() { $this->validate(); @@ -61,7 +65,7 @@ public function submit() 'ip' => $this->ip, 'user' => $this->user, 'port' => $this->port, - 'team_id' => session('currentTeam')->id, + 'team_id' => auth()->user()->currentTeam()->id, 'private_key_id' => $this->private_key_id, ]); $server->settings->is_part_of_swarm = $this->is_part_of_swarm; diff --git a/app/Http/Livewire/Server/PrivateKey.php b/app/Http/Livewire/Server/PrivateKey.php index 36cc12bb8..366aec85f 100644 --- a/app/Http/Livewire/Server/PrivateKey.php +++ b/app/Http/Livewire/Server/PrivateKey.php @@ -3,7 +3,6 @@ namespace App\Http\Livewire\Server; use App\Models\Server; -use Illuminate\Support\Facades\Storage; use Livewire\Component; use Masmerise\Toaster\Toaster; @@ -13,35 +12,33 @@ class PrivateKey extends Component public $privateKeys; public $parameters; - public function checkConnection() - { - try { - $uptime = instant_remote_process(['uptime'], $this->server); - if ($uptime) { - Toaster::success('Server is reachable with this private key.'); - $this->server->settings->is_reachable = true; - $this->server->settings->is_usable = true; - } - } catch (\Exception $e) { - $this->server->settings->is_reachable = false; - $this->server->settings->is_usable = false; - $this->server->settings->save(); - return general_error_handler(customErrorMessage: "Server is not reachable. Reason: {$e->getMessage()}", that: $this); - } - } public function setPrivateKey($private_key_id) { $this->server->update([ 'private_key_id' => $private_key_id ]); - - // Delete the old ssh mux file to force a new one to be created - Storage::disk('ssh-mux')->delete($this->server->muxFilename()); + refreshPrivateKey($this->server->privateKey); $this->server->refresh(); $this->checkConnection(); } + + public function checkConnection() + { + try { + ['uptime' => $uptime, 'dockerVersion' => $dockerVersion] = validateServer($this->server); + if ($uptime) { + Toaster::success('Server is reachable with this private key.'); + } + if ($dockerVersion) { + Toaster::success('Server is usable for Coolify.'); + } + } catch (\Exception $e) { + return general_error_handler(customErrorMessage: "Server is not reachable. Reason: {$e->getMessage()}", that: $this); + } + } + public function mount() { - $this->parameters = getRouteParameters(); + $this->parameters = get_route_parameters(); } } diff --git a/app/Http/Livewire/Server/Proxy.php b/app/Http/Livewire/Server/Proxy.php index 5d3c1ebba..1f51d1b1c 100644 --- a/app/Http/Livewire/Server/Proxy.php +++ b/app/Http/Livewire/Server/Proxy.php @@ -2,9 +2,9 @@ namespace App\Http\Livewire\Server; -use App\Actions\Proxy\CheckProxySettingsInSync; +use App\Actions\Proxy\CheckConfigurationSync; +use App\Actions\Proxy\SaveConfigurationSync; use App\Enums\ProxyTypes; -use Illuminate\Support\Str; use App\Models\Server; use Livewire\Component; @@ -16,69 +16,61 @@ class Proxy extends Component public $proxy_settings = null; public string|null $redirect_url = null; - protected $listeners = ['proxyStatusUpdated', 'saveConfiguration']; + protected $listeners = ['proxyStatusUpdated', 'saveConfiguration' => 'submit']; + public function mount() { $this->redirect_url = $this->server->proxy->redirect_url; } + public function proxyStatusUpdated() { $this->server->refresh(); } - public function switchProxy() + + public function change_proxy() { $this->server->proxy = null; $this->server->save(); $this->emit('proxyStatusUpdated'); } - public function setProxy(string $proxy_type) + + public function select_proxy(string $proxy_type) { $this->server->proxy->type = $proxy_type; $this->server->proxy->status = 'exited'; $this->server->save(); $this->emit('proxyStatusUpdated'); } - public function stopProxy() - { - instant_remote_process([ - "docker rm -f coolify-proxy", - ], $this->server); - $this->server->proxy->status = 'exited'; - $this->server->save(); - $this->emit('proxyStatusUpdated'); - } - public function saveConfiguration() + + public function submit() { try { - $proxy_path = config('coolify.proxy_config_path'); - $this->proxy_settings = Str::of($this->proxy_settings)->trim()->value; - $docker_compose_yml_base64 = base64_encode($this->proxy_settings); - $this->server->proxy->last_saved_settings = Str::of($docker_compose_yml_base64)->pipe('md5')->value; + resolve(SaveConfigurationSync::class)($this->server, $this->proxy_settings); + $this->server->proxy->redirect_url = $this->redirect_url; $this->server->save(); - instant_remote_process([ - "echo '$docker_compose_yml_base64' | base64 -d > $proxy_path/docker-compose.yml", - ], $this->server); - $this->server->refresh(); setup_default_redirect_404(redirect_url: $this->server->proxy->redirect_url, server: $this->server); $this->emit('success', 'Proxy configuration saved.'); } catch (\Exception $e) { return general_error_handler(err: $e); } } - public function resetProxy() + + public function reset_proxy_configuration() { try { - $this->proxy_settings = resolve(CheckProxySettingsInSync::class)($this->server, true); + $this->proxy_settings = resolve(CheckConfigurationSync::class)($this->server, true); } catch (\Exception $e) { return general_error_handler(err: $e); } } - public function checkProxySettingsInSync() + + public function load_proxy_configuration() { try { - $this->proxy_settings = resolve(CheckProxySettingsInSync::class)($this->server); + $this->proxy_settings = resolve(CheckConfigurationSync::class)($this->server); } catch (\Exception $e) { return general_error_handler(err: $e); } diff --git a/app/Http/Livewire/Server/Proxy/Deploy.php b/app/Http/Livewire/Server/Proxy/Deploy.php index c1ca4ab57..9ec3898f1 100644 --- a/app/Http/Livewire/Server/Proxy/Deploy.php +++ b/app/Http/Livewire/Server/Proxy/Deploy.php @@ -5,28 +5,24 @@ use App\Actions\Proxy\StartProxy; use App\Models\Server; use Livewire\Component; -use Str; class Deploy extends Component { public Server $server; public $proxy_settings = null; - protected $listeners = ['proxyStatusUpdated']; - public function proxyStatusUpdated() - { - $this->server->refresh(); - } - public function deploy() + + public function start_proxy() { if ( $this->server->proxy->last_applied_settings && $this->server->proxy->last_saved_settings !== $this->server->proxy->last_applied_settings ) { - $this->saveConfiguration($this->server); + $this->emit('saveConfiguration', $server); } $activity = resolve(StartProxy::class)($this->server); $this->emit('newMonitorActivity', $activity->id); } + public function stop() { instant_remote_process([ @@ -36,8 +32,4 @@ public function stop() $this->server->save(); $this->emit('proxyStatusUpdated'); } - private function saveConfiguration(Server $server) - { - $this->emit('saveConfiguration', $server); - } } diff --git a/app/Http/Livewire/Server/Proxy/Status.php b/app/Http/Livewire/Server/Proxy/Status.php index e4d18e4a2..a4a29b7c2 100644 --- a/app/Http/Livewire/Server/Proxy/Status.php +++ b/app/Http/Livewire/Server/Proxy/Status.php @@ -9,20 +9,13 @@ class Status extends Component { public Server $server; - protected $listeners = ['proxyStatusUpdated']; - public function proxyStatusUpdated() + + public function get_status() { + dispatch_sync(new ProxyContainerStatusJob( + server: $this->server + )); $this->server->refresh(); - } - public function proxyStatus() - { - try { - dispatch_sync(new ProxyContainerStatusJob( - server: $this->server - )); - $this->emit('proxyStatusUpdated'); - } catch (\Exception $e) { - ray($e->getMessage()); - } + $this->emit('proxyStatusUpdated'); } } diff --git a/app/Http/Livewire/Settings/Backup.php b/app/Http/Livewire/Settings/Backup.php new file mode 100644 index 000000000..fcb105fd4 --- /dev/null +++ b/app/Http/Livewire/Settings/Backup.php @@ -0,0 +1,87 @@ + 'required', + 'database.name' => 'required', + 'database.description' => 'nullable', + 'database.postgres_user' => 'required', + 'database.postgres_password' => 'required', + + ]; + protected $validationAttributes = [ + 'database.uuid' => 'uuid', + 'database.name' => 'name', + 'database.description' => 'description', + 'database.postgres_user' => 'postgres user', + 'database.postgres_password' => 'postgres password', + ]; + + public function mount() + { + $this->backup = $this->database?->scheduledBackups->first() ?? []; + $this->executions = $this->backup?->executions ?? []; + } + public function add_coolify_database() + { + $server = Server::find(0); + $out = instant_remote_process(['docker inspect coolify-db'], $server); + $envs = format_docker_envs_to_json($out); + $postgres_password = $envs['POSTGRES_PASSWORD']; + $postgres_user = $envs['POSTGRES_USER']; + $postgres_db = $envs['POSTGRES_DB']; + $this->database = StandalonePostgresql::create([ + 'id' => 0, + 'name' => 'coolify-db', + 'description' => 'Coolify database', + 'postgres_user' => $postgres_user, + 'postgres_password' => $postgres_password, + 'postgres_db' => $postgres_db, + 'status' => 'running', + 'destination_type' => 'App\Models\StandaloneDocker', + 'destination_id' => 0, + ]); + $this->backup = ScheduledDatabaseBackup::create([ + 'id' => 0, + 'enabled' => true, + 'save_s3' => false, + 'frequency' => '0 0 * * *', + 'database_id' => $this->database->id, + 'database_type' => 'App\Models\StandalonePostgresql', + 'team_id' => auth()->user()->currentTeam()->id, + ]); + $this->database->refresh(); + $this->backup->refresh(); + ray($this->backup); + $this->s3s = S3Storage::whereTeamId(0)->get(); + } + + public function backup_now() + { + dispatch(new DatabaseBackupJob( + backup: $this->backup + )); + $this->emit('success', 'Backup queued. It will be available in a few minutes'); + } + public function submit() + { + $this->emit('success', 'Backup updated successfully'); + } +} diff --git a/app/Http/Livewire/Settings/Configuration.php b/app/Http/Livewire/Settings/Configuration.php index 98371d136..965f0335b 100644 --- a/app/Http/Livewire/Settings/Configuration.php +++ b/app/Http/Livewire/Settings/Configuration.php @@ -31,6 +31,7 @@ class Configuration extends Component 'settings.public_port_min' => 'Public port min', 'settings.public_port_max' => 'Public port max', ]; + public function mount() { $this->do_not_track = $this->settings->do_not_track; @@ -38,6 +39,7 @@ public function mount() $this->is_registration_enabled = $this->settings->is_registration_enabled; $this->next_channel = $this->settings->next_channel; } + public function instantSave() { $this->settings->do_not_track = $this->do_not_track; @@ -47,6 +49,21 @@ public function instantSave() $this->settings->save(); $this->emit('success', 'Settings updated!'); } + + public function submit() + { + $this->resetErrorBag(); + if ($this->settings->public_port_min > $this->settings->public_port_max) { + $this->addError('settings.public_port_min', 'The minimum port must be lower than the maximum port.'); + return; + } + $this->validate(); + $this->settings->save(); + $this->server = Server::findOrFail(0); + $this->setup_instance_fqdn(); + $this->emit('success', 'Instance settings updated successfully!'); + } + private function setup_instance_fqdn() { $file = "$this->dynamic_config_path/coolify.yaml"; @@ -110,6 +127,7 @@ private function setup_instance_fqdn() dispatch(new ProxyStartJob($this->server)); } } + private function save_configuration_to_disk(array $traefik_dynamic_conf, string $file) { $yaml = Yaml::dump($traefik_dynamic_conf, 12, 2); @@ -128,17 +146,4 @@ private function save_configuration_to_disk(array $traefik_dynamic_conf, string ray($yaml); } } - public function submit() - { - $this->resetErrorBag(); - if ($this->settings->public_port_min > $this->settings->public_port_max) { - $this->addError('settings.public_port_min', 'The minimum port must be lower than the maximum port.'); - return; - } - $this->validate(); - $this->settings->save(); - $this->server = Server::findOrFail(0); - $this->setup_instance_fqdn(); - $this->emit('success', 'Instance settings updated successfully!'); - } } diff --git a/app/Http/Livewire/Settings/Email.php b/app/Http/Livewire/Settings/Email.php index 6342ab4f5..a0ac3c904 100644 --- a/app/Http/Livewire/Settings/Email.php +++ b/app/Http/Livewire/Settings/Email.php @@ -3,78 +3,80 @@ namespace App\Http\Livewire\Settings; use App\Models\InstanceSettings; -use App\Notifications\TransactionalEmails\TestEmail; -use Illuminate\Support\Facades\Notification; +use App\Notifications\TransactionalEmails\Test; use Livewire\Component; class Email extends Component { public InstanceSettings $settings; - + public string $emails; protected $rules = [ - 'settings.smtp.enabled' => 'nullable|boolean', - 'settings.smtp.host' => 'required', - 'settings.smtp.port' => 'required|numeric', - 'settings.smtp.encryption' => 'nullable', - 'settings.smtp.username' => 'nullable', - 'settings.smtp.password' => 'nullable', - 'settings.smtp.timeout' => 'nullable', - 'settings.smtp.test_recipients' => 'nullable', - 'settings.smtp.from_address' => 'required|email', - 'settings.smtp.from_name' => 'required', + 'settings.smtp_enabled' => 'nullable|boolean', + 'settings.smtp_host' => 'required', + 'settings.smtp_port' => 'required|numeric', + 'settings.smtp_encryption' => 'nullable', + 'settings.smtp_username' => 'nullable', + 'settings.smtp_password' => 'nullable', + 'settings.smtp_timeout' => 'nullable', + 'settings.smtp_from_address' => 'required|email', + 'settings.smtp_from_name' => 'required', ]; protected $validationAttributes = [ - 'settings.smtp.from_address' => 'From Address', - 'settings.smtp.from_name' => 'From Name', - 'settings.smtp.recipients' => 'Recipients', - 'settings.smtp.host' => 'Host', - 'settings.smtp.port' => 'Port', - 'settings.smtp.encryption' => 'Encryption', - 'settings.smtp.username' => 'Username', - 'settings.smtp.password' => 'Password', - 'settings.smtp.test_recipients' => 'Test Recipients', + 'settings.smtp_from_address' => 'From Address', + 'settings.smtp_from_name' => 'From Name', + 'settings.smtp_recipients' => 'Recipients', + 'settings.smtp_host' => 'Host', + 'settings.smtp_port' => 'Port', + 'settings.smtp_encryption' => 'Encryption', + 'settings.smtp_username' => 'Username', + 'settings.smtp_password' => 'Password', ]; + public function mount() { $this->decrypt(); + $this->emails = auth()->user()->email; } + + private function decrypt() + { + if (data_get($this->settings, 'smtp_password')) { + try { + $this->settings->smtp_password = decrypt($this->settings->smtp_password); + } catch (\Exception $e) { + } + } + } + public function instantSave() { try { $this->submit(); $this->emit('success', 'Settings saved successfully.'); } catch (\Exception $e) { - $this->settings->smtp->enabled = false; + $this->settings->smtp_enabled = false; $this->validate(); } } - public function testNotification() - { - $this->settings->notify(new TestEmail); - $this->emit('success', 'Test email sent.'); - } - private function decrypt() - { - if (data_get($this->settings, 'smtp.password')) { - try { - $this->settings->smtp->password = decrypt($this->settings->smtp->password); - } catch (\Exception $e) { - } - } - } + public function submit() { $this->resetErrorBag(); $this->validate(); - if ($this->settings->smtp->password) { - $this->settings->smtp->password = encrypt($this->settings->smtp->password); + if ($this->settings->smtp_password) { + $this->settings->smtp_password = encrypt($this->settings->smtp_password); } else { - $this->settings->smtp->password = null; + $this->settings->smtp_password = null; } - $this->settings->smtp->test_recipients = str_replace(' ', '', $this->settings->smtp->test_recipients); $this->settings->save(); $this->emit('success', 'Transaction email settings updated successfully.'); $this->decrypt(); } + + public function sendTestNotification() + { + $this->settings->notify(new Test($this->emails)); + $this->emit('success', 'Test email sent.'); + } } diff --git a/app/Http/Livewire/Source/Github/Change.php b/app/Http/Livewire/Source/Github/Change.php index 7908b179c..e2be4e0ab 100644 --- a/app/Http/Livewire/Source/Github/Change.php +++ b/app/Http/Livewire/Source/Github/Change.php @@ -34,12 +34,14 @@ class Change extends Component 'github_app.webhook_secret' => 'nullable', 'github_app.is_system_wide' => 'required|bool', ]; + public function mount() { $this->webhook_endpoint = $this->ipv4; - $this->parameters = getRouteParameters(); + $this->parameters = get_route_parameters(); $this->is_system_wide = $this->github_app->is_system_wide; } + public function submit() { try { @@ -49,6 +51,7 @@ public function submit() return general_error_handler(err: $e, that: $this); } } + public function instantSave() { } diff --git a/app/Http/Livewire/Source/Github/Create.php b/app/Http/Livewire/Source/Github/Create.php index b95bde919..862964c45 100644 --- a/app/Http/Livewire/Source/Github/Create.php +++ b/app/Http/Livewire/Source/Github/Create.php @@ -19,6 +19,7 @@ public function mount() { $this->name = generate_random_name(); } + public function createGitHubApp() { try { @@ -39,7 +40,7 @@ public function createGitHubApp() 'custom_user' => $this->custom_user, 'custom_port' => $this->custom_port, 'is_system_wide' => $this->is_system_wide, - 'team_id' => session('currentTeam')->id, + 'team_id' => auth()->user()->currentTeam()->id, ]); redirect()->route('source.github.show', ['github_app_uuid' => $github_app->uuid]); } catch (\Exception $e) { diff --git a/app/Http/Livewire/SwitchTeam.php b/app/Http/Livewire/SwitchTeam.php index 109167dfe..087a7ad84 100644 --- a/app/Http/Livewire/SwitchTeam.php +++ b/app/Http/Livewire/SwitchTeam.php @@ -8,10 +8,12 @@ class SwitchTeam extends Component { public string $selectedTeamId = 'default'; + public function updatedSelectedTeamId() { $this->switch_to($this->selectedTeamId); } + public function switch_to($team_id) { if (!auth()->user()->teams->contains($team_id)) { diff --git a/app/Http/Livewire/Team/Create.php b/app/Http/Livewire/Team/Create.php index 5aa07bc94..6c1c71580 100644 --- a/app/Http/Livewire/Team/Create.php +++ b/app/Http/Livewire/Team/Create.php @@ -18,6 +18,7 @@ class Create extends Component 'name' => 'name', 'description' => 'description', ]; + public function submit() { $this->validate(); diff --git a/app/Http/Livewire/Team/Delete.php b/app/Http/Livewire/Team/Delete.php index f25a8b8d8..d14e82ccc 100644 --- a/app/Http/Livewire/Team/Delete.php +++ b/app/Http/Livewire/Team/Delete.php @@ -9,7 +9,7 @@ class Delete extends Component { public function delete() { - $currentTeam = session('currentTeam'); + $currentTeam = auth()->user()->currentTeam(); $currentTeam->delete(); $team = auth()->user()->teams()->first(); diff --git a/app/Http/Livewire/Team/Form.php b/app/Http/Livewire/Team/Form.php index f60f72523..eedd60c83 100644 --- a/app/Http/Livewire/Team/Form.php +++ b/app/Http/Livewire/Team/Form.php @@ -4,7 +4,6 @@ use App\Models\Team; use Livewire\Component; -use Masmerise\Toaster\Toaster; class Form extends Component { @@ -17,10 +16,12 @@ class Form extends Component 'team.name' => 'name', 'team.description' => 'description', ]; + public function mount() { - $this->team = session('currentTeam'); + $this->team = auth()->user()->currentTeam(); } + public function submit() { $this->validate(); diff --git a/app/Http/Livewire/Team/Invitations.php b/app/Http/Livewire/Team/Invitations.php index e61aca0e7..ba0b654aa 100644 --- a/app/Http/Livewire/Team/Invitations.php +++ b/app/Http/Livewire/Team/Invitations.php @@ -9,13 +9,15 @@ class Invitations extends Component { public $invitations; protected $listeners = ['refreshInvitations']; - public function refreshInvitations() - { - $this->invitations = TeamInvitation::whereTeamId(auth()->user()->currentTeam()->id)->get(); - } + public function deleteInvitation(int $invitation_id) { TeamInvitation::find($invitation_id)->delete(); $this->refreshInvitations(); } + + public function refreshInvitations() + { + $this->invitations = TeamInvitation::whereTeamId(auth()->user()->currentTeam()->id)->get(); + } } diff --git a/app/Http/Livewire/Team/InviteLink.php b/app/Http/Livewire/Team/InviteLink.php index e80e5338b..ddbe75182 100644 --- a/app/Http/Livewire/Team/InviteLink.php +++ b/app/Http/Livewire/Team/InviteLink.php @@ -4,7 +4,7 @@ use App\Models\TeamInvitation; use App\Models\User; -use App\Notifications\TransactionalEmails\InvitationLinkEmail; +use App\Notifications\TransactionalEmails\InvitationLink; use Livewire\Component; use Visus\Cuid2\Cuid2; @@ -12,14 +12,17 @@ class InviteLink extends Component { public string $email; public string $role = 'member'; + public function mount() { - $this->email = isDev() ? 'test3@example.com' : ''; + $this->email = is_dev() ? 'test3@example.com' : ''; } + public function viaEmail() { $this->generate_invite_link(isEmail: true); } + private function generate_invite_link(bool $isEmail = false) { try { @@ -32,9 +35,9 @@ private function generate_invite_link(bool $isEmail = false) return general_error_handler(that: $this, customErrorMessage: "$this->email must be registered first (or activate transactional emails to invite via email)."); } - $member_emails = session('currentTeam')->members()->get()->pluck('email'); + $member_emails = auth()->user()->currentTeam()->members()->get()->pluck('email'); if ($member_emails->contains($this->email)) { - return general_error_handler(that: $this, customErrorMessage: "$this->email is already a member of " . session('currentTeam')->name . "."); + return general_error_handler(that: $this, customErrorMessage: "$this->email is already a member of " . auth()->user()->currentTeam()->name . "."); } $invitation = TeamInvitation::whereEmail($this->email); @@ -50,7 +53,7 @@ private function generate_invite_link(bool $isEmail = false) } TeamInvitation::firstOrCreate([ - 'team_id' => session('currentTeam')->id, + 'team_id' => auth()->user()->currentTeam()->id, 'uuid' => $uuid, 'email' => $this->email, 'role' => $this->role, @@ -58,7 +61,7 @@ private function generate_invite_link(bool $isEmail = false) 'via' => $isEmail ? 'email' : 'link', ]); if ($isEmail) { - $user->first()->notify(new InvitationLinkEmail()); + $user->first()->notify(new InvitationLink); $this->emit('success', 'Invitation sent via email successfully.'); } else { $this->emit('success', 'Invitation link generated.'); @@ -72,6 +75,7 @@ private function generate_invite_link(bool $isEmail = false) return general_error_handler(err: $e, that: $this, customErrorMessage: $error_message); } } + public function viaLink() { $this->generate_invite_link(); diff --git a/app/Http/Livewire/Team/Member.php b/app/Http/Livewire/Team/Member.php index 5f037e444..237d38818 100644 --- a/app/Http/Livewire/Team/Member.php +++ b/app/Http/Livewire/Team/Member.php @@ -8,19 +8,22 @@ class Member extends Component { public User $member; + public function makeAdmin() { - $this->member->teams()->updateExistingPivot(session('currentTeam')->id, ['role' => 'admin']); + $this->member->teams()->updateExistingPivot(auth()->user()->currentTeam()->id, ['role' => 'admin']); $this->emit('reloadWindow'); } + public function makeReadonly() { - $this->member->teams()->updateExistingPivot(session('currentTeam')->id, ['role' => 'member']); + $this->member->teams()->updateExistingPivot(auth()->user()->currentTeam()->id, ['role' => 'member']); $this->emit('reloadWindow'); } + public function remove() { - $this->member->teams()->detach(session('currentTeam')); + $this->member->teams()->detach(auth()->user()->currentTeam()); $this->emit('reloadWindow'); } } diff --git a/app/Http/Livewire/Team/Storage/Create.php b/app/Http/Livewire/Team/Storage/Create.php new file mode 100644 index 000000000..ed7d277fb --- /dev/null +++ b/app/Http/Livewire/Team/Storage/Create.php @@ -0,0 +1,84 @@ + 'nullable|min:3|max:255', + 'description' => 'nullable|min:3|max:255', + 'region' => 'required|max:255', + 'key' => 'required|max:255', + 'secret' => 'required|max:255', + 'bucket' => 'required|max:255', + 'endpoint' => 'nullable|url|max:255', + ]; + protected $validationAttributes = [ + 'name' => 'Name', + 'description' => 'Description', + 'region' => 'Region', + 'key' => 'Key', + 'secret' => "Secret", + 'bucket' => 'Bucket', + 'endpoint' => 'Endpoint', + ]; + + public function mount() + { + if (is_dev()) { + $this->name = 'Local MinIO'; + $this->description = 'Local MinIO'; + $this->key = 'minioadmin'; + $this->secret = 'minioadmin'; + $this->bucket = 'local'; + $this->endpoint = 'http://coolify-minio:9000'; + } + } + + public function submit() + { + try { + $this->validate(); + $this->storage = new S3Storage(); + $this->storage->name = $this->name; + $this->storage->description = $this->description ?? null; + $this->storage->region = $this->region; + $this->storage->key = $this->key; + $this->storage->secret = $this->secret; + $this->storage->bucket = $this->bucket; + if (empty($this->endpoint)) { + $this->storage->endpoint = "https://s3.{$this->region}.amazonaws.com"; + } else { + $this->storage->endpoint = $this->endpoint; + } + $this->storage->team_id = auth()->user()->currentTeam()->id; + $this->storage->testConnection(); + $this->emit('success', 'Connection is working. Tested with "ListObjectsV2" action.'); + $this->storage->save(); + return redirect()->route('team.storages.show', $this->storage->uuid); + } catch (\Throwable $th) { + return general_error_handler($th, $this); + } + } + + private function test_s3_connection() + { + try { + $this->storage->testConnection(); + return $this->emit('success', 'Connection is working. Tested with "ListObjectsV2" action.'); + } catch (\Throwable $th) { + return general_error_handler($th, $this); + } + } +} diff --git a/app/Http/Livewire/Team/Storage/Form.php b/app/Http/Livewire/Team/Storage/Form.php new file mode 100644 index 000000000..3bdbbc508 --- /dev/null +++ b/app/Http/Livewire/Team/Storage/Form.php @@ -0,0 +1,62 @@ + 'nullable|min:3|max:255', + 'storage.description' => 'nullable|min:3|max:255', + 'storage.region' => 'required|max:255', + 'storage.key' => 'required|max:255', + 'storage.secret' => 'required|max:255', + 'storage.bucket' => 'required|max:255', + 'storage.endpoint' => 'required|url|max:255', + ]; + protected $validationAttributes = [ + 'storage.name' => 'Name', + 'storage.description' => 'Description', + 'storage.region' => 'Region', + 'storage.key' => 'Key', + 'storage.secret' => "Secret", + 'storage.bucket' => 'Bucket', + 'storage.endpoint' => 'Endpoint', + ]; + + public function test_s3_connection() + { + try { + $this->storage->testConnection(); + return $this->emit('success', 'Connection is working. Tested with "ListObjectsV2" action.'); + } catch (\Throwable $th) { + return general_error_handler($th, $this); + } + } + + public function delete() + { + try { + $this->storage->delete(); + return redirect()->route('team.storages.all'); + } catch (\Throwable $th) { + return general_error_handler($th, $this); + } + } + + public function submit() + { + $this->validate(); + try { + $this->storage->testConnection(); + $this->emit('success', 'Connection is working. Tested with "ListObjectsV2" action.'); + $this->storage->save(); + $this->emit('success', 'Storage settings saved.'); + } catch (\Throwable $th) { + return general_error_handler($th, $this); + } + } +} diff --git a/app/Http/Livewire/Upgrade.php b/app/Http/Livewire/Upgrade.php index 8bd647f1d..f46e37ff7 100644 --- a/app/Http/Livewire/Upgrade.php +++ b/app/Http/Livewire/Upgrade.php @@ -4,8 +4,8 @@ use App\Actions\Server\UpdateCoolify; use App\Models\InstanceSettings; -use Masmerise\Toaster\Toaster; use Livewire\Component; +use Masmerise\Toaster\Toaster; class Upgrade extends Component { @@ -18,7 +18,7 @@ public function checkUpdate() $this->latestVersion = get_latest_version_of_coolify(); $currentVersion = config('version'); version_compare($currentVersion, $this->latestVersion, '<') ? $this->isUpgradeAvailable = true : $this->isUpgradeAvailable = false; - if (isDev()) { + if (is_dev()) { $this->isUpgradeAvailable = true; } $settings = InstanceSettings::get(); @@ -27,6 +27,7 @@ public function checkUpdate() $this->latestVersion = 'next'; } } + public function upgrade() { try { @@ -40,4 +41,4 @@ public function upgrade() return general_error_handler(err: $e, that: $this); } } -} \ No newline at end of file +} diff --git a/app/Http/Livewire/Waitlist.php b/app/Http/Livewire/Waitlist.php new file mode 100644 index 000000000..a0d4284b6 --- /dev/null +++ b/app/Http/Livewire/Waitlist.php @@ -0,0 +1,54 @@ + 'required|email', + ]; + public function mount() + { + if (is_dev()) { + $this->email = 'test@example.com'; + } + } + public function submit() + { + $this->validate(); + try { + $already_registered = User::whereEmail($this->email)->first(); + if ($already_registered) { + $this->emit('success', 'You are already registered (Thank you 💜).'); + return; + } + $found = ModelsWaitlist::where('email', $this->email)->first(); + if ($found) { + if (!$found->verified) { + $this->emit('error', 'You are already on the waitlist.
Please check your email to verify your email address.'); + return; + } + $this->emit('error', 'You are already on the waitlist.'); + return; + } + $waitlist = ModelsWaitlist::create([ + 'email' => $this->email, + 'type' => 'registration', + ]); + + $this->emit('success', 'You have been added to the waitlist.'); + dispatch(new SendConfirmationForWaitlistJob($this->email, $waitlist->uuid)); + } catch (\Exception $e) { + return general_error_handler(err: $e, that: $this); + } + + } +} diff --git a/app/Http/Middleware/CheckForcePasswordReset.php b/app/Http/Middleware/CheckForcePasswordReset.php new file mode 100644 index 000000000..8d1670de5 --- /dev/null +++ b/app/Http/Middleware/CheckForcePasswordReset.php @@ -0,0 +1,29 @@ +user()) { + $force_password_reset = auth()->user()->force_password_reset; + if ($force_password_reset) { + if ($request->routeIs('auth.force-password-reset') || $request->path() === 'livewire/message/force-password-reset') { + return $next($request); + } + return redirect()->route('auth.force-password-reset'); + } + } + return $next($request); + } +} diff --git a/app/Http/Middleware/RedirectIfAuthenticated.php b/app/Http/Middleware/RedirectIfAuthenticated.php index afc78c4e5..3f17e6def 100644 --- a/app/Http/Middleware/RedirectIfAuthenticated.php +++ b/app/Http/Middleware/RedirectIfAuthenticated.php @@ -13,7 +13,7 @@ class RedirectIfAuthenticated /** * Handle an incoming request. * - * @param \Closure(\Illuminate\Http\Request): (\Symfony\Component\HttpFoundation\Response) $next + * @param \Closure(\Illuminate\Http\Request): (\Symfony\Component\HttpFoundation\Response) $next */ public function handle(Request $request, Closure $next, string ...$guards): Response { @@ -24,7 +24,6 @@ public function handle(Request $request, Closure $next, string ...$guards): Resp return redirect(RouteServiceProvider::HOME); } } - return $next($request); } } diff --git a/app/Http/Middleware/SubscriptionValid.php b/app/Http/Middleware/SubscriptionValid.php index e7fcdff3c..f84e6eede 100644 --- a/app/Http/Middleware/SubscriptionValid.php +++ b/app/Http/Middleware/SubscriptionValid.php @@ -8,32 +8,44 @@ class SubscriptionValid { - public function handle(Request $request, Closure $next): Response { - if (auth()->user()) { - if (isCloud() && !isSubscribed()) { - ray('SubscriptionValid Middleware'); - - $allowed_paths = [ - 'subscription', - 'login', - 'register', - 'logout', - 'livewire/message/check-license', - 'livewire/message/switch-team', - ]; - if (!in_array($request->path(), $allowed_paths)) { - return redirect('subscription'); - } else { - return $next($request); - } + if (!auth()->user() || !is_cloud()) { + if ($request->path() === 'subscription') { + return redirect('/'); } else { - if ($request->path() === 'subscription' && !auth()->user()->isInstanceAdmin()) { - return redirect('/'); - } else { - return $next($request); - } + return $next($request); + } + } + $is_instance_admin = is_instance_admin(); + if ($is_instance_admin) { + return $next($request); + } + + if (is_subscription_active() && $request->path() === 'subscription') { + return redirect('/'); + } + if (is_subscription_in_grace_period()) { + return $next($request); + } + if (!is_subscription_active() && !is_subscription_in_grace_period()) { + ray('SubscriptionValid Middleware'); + + $allowed_paths = [ + 'subscription', + 'login', + 'register', + 'waitlist', + 'force-password-reset', + 'logout', + 'livewire/message/force-password-reset', + 'livewire/message/check-license', + 'livewire/message/switch-team', + ]; + if (!in_array($request->path(), $allowed_paths)) { + return redirect('subscription'); + } else { + return $next($request); } } return $next($request); diff --git a/app/Jobs/ApplicationDeploymentJob.php b/app/Jobs/ApplicationDeploymentJob.php index a800d7c2e..fc0a3a1b7 100644 --- a/app/Jobs/ApplicationDeploymentJob.php +++ b/app/Jobs/ApplicationDeploymentJob.php @@ -12,16 +12,18 @@ use App\Models\Server; use App\Models\StandaloneDocker; use App\Models\SwarmDocker; +use App\Notifications\Application\DeploymentFailed; +use App\Notifications\Application\DeploymentSuccess; use App\Traits\ExecuteRemoteCommand; +use Exception; use Illuminate\Bus\Queueable; use Illuminate\Contracts\Queue\ShouldQueue; use Illuminate\Foundation\Bus\Dispatchable; use Illuminate\Queue\InteractsWithQueue; use Illuminate\Queue\SerializesModels; use Illuminate\Support\Collection; -use Illuminate\Support\Facades\Storage; -use Spatie\Url\Url; use Illuminate\Support\Str; +use Spatie\Url\Url; use Symfony\Component\Yaml\Yaml; use Throwable; use Visus\Cuid2\Cuid2; @@ -49,6 +51,7 @@ class ApplicationDeploymentJob implements ShouldQueue private string $container_name; private string $workdir; + private string $configuration_dir; private string $build_workdir; private string $build_image_name; private string $production_image_name; @@ -56,9 +59,11 @@ class ApplicationDeploymentJob implements ShouldQueue private $build_args; private $env_args; private $docker_compose; + private $docker_compose_base64; private $log_model; private Collection $saved_outputs; + public function __construct(int $application_deployment_queue_id) { ray()->clearScreen(); @@ -75,9 +80,9 @@ public function __construct(int $application_deployment_queue_id) $this->source = $this->application->source->getMorphClass()::where('id', $this->application->source->id)->first(); $this->destination = $this->application->destination->getMorphClass()::where('id', $this->application->destination->id)->first(); $this->server = $this->destination->server; - $this->private_key_location = save_private_key_for_server($this->server); $this->workdir = "/artifacts/{$this->deployment_uuid}"; + $this->configuration_dir = application_configuration_dir() . "/{$this->application->uuid}"; $this->build_workdir = "{$this->workdir}" . rtrim($this->application->base_directory, '/'); $this->is_debug_enabled = $this->application->settings->is_debug_enabled; @@ -112,19 +117,34 @@ public function handle(): void 'status' => ApplicationDeploymentStatus::IN_PROGRESS->value, ]); try { - if ($this->pull_request_id !== 0) { - $this->deploy_pull_request(); + if ($this->application->dockerfile) { + $this->deploy_simple_dockerfile(); } else { - $this->deploy(); + if ($this->pull_request_id !== 0) { + $this->deploy_pull_request(); + } else { + $this->deploy(); + } } if ($this->application->fqdn) dispatch(new ProxyStartJob($this->server)); $this->next(ApplicationDeploymentStatus::FINISHED->value); - } catch (\Exception $e) { + } catch (Exception $e) { ray($e); $this->fail($e); } finally { - if (isset($this->docker_compose)) { - Storage::disk('deployments')->put(Str::kebab($this->application->name) . '/docker-compose.yml', $this->docker_compose); + if (isset($this->docker_compose_base64)) { + $readme = generate_readme_file($this->application->name, $this->application_deployment_queue->updated_at); + $this->execute_remote_command( + [ + "mkdir -p $this->configuration_dir" + ], + [ + "echo '{$this->docker_compose_base64}' | base64 -d > $this->configuration_dir/docker-compose.yml", + ], + [ + "echo '{$readme}' > $this->configuration_dir/README.md", + ] + ); } $this->execute_remote_command( [ @@ -132,25 +152,34 @@ public function handle(): void "hidden" => true, ] ); - // ray()->measure(); } } - public function failed(Throwable $exception): void + private function deploy_simple_dockerfile() { + $dockerfile_base64 = base64_encode($this->application->dockerfile); $this->execute_remote_command( - ["echo 'Oops something is not okay, are you okay? 😢'"], - ["echo '{$exception->getMessage()}'"] + [ + "echo 'Starting deployment of {$this->application->name}.'" + ], ); - $this->next(ApplicationDeploymentStatus::FAILED->value); - } - private function execute_in_builder(string $command) - { - return "docker exec {$this->deployment_uuid} bash -c '{$command}'"; - // return "docker exec {$this->deployment_uuid} bash -c '{$command} |& tee -a /proc/1/fd/1; [ \$PIPESTATUS -eq 0 ] || exit \$PIPESTATUS'"; + $this->prepare_builder_image(); + $this->execute_remote_command( + [ + $this->execute_in_builder("echo '$dockerfile_base64' | base64 -d > $this->workdir/Dockerfile") + ], + ); + $this->build_image_name = "{$this->application->git_repository}:build"; + $this->production_image_name = "{$this->application->uuid}:latest"; + ray('Build Image Name: ' . $this->build_image_name . ' & Production Image Name: ' . $this->production_image_name)->green(); + $this->generate_compose_file(); + $this->generate_build_env_variables(); + $this->add_build_env_variables_to_dockerfile(); + $this->build_image(); + $this->stop_running_container(); + $this->start_by_compose_file(); } private function deploy() { - $this->execute_remote_command( [ "echo 'Starting deployment of {$this->application->git_repository}:{$this->application->git_branch}.'" @@ -162,7 +191,7 @@ private function deploy() $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 = "{$this->application->git_repository}:{$tag}-build"; $this->production_image_name = "{$this->application->uuid}:{$tag}"; @@ -183,7 +212,9 @@ private function deploy() } } $this->cleanup_git(); - $this->generate_buildpack(); + if ($this->application->build_pack === 'nixpacks') { + $this->generate_nixpacks_confs(); + } $this->generate_compose_file(); $this->generate_build_env_variables(); $this->add_build_env_variables_to_dockerfile(); @@ -202,7 +233,9 @@ private function deploy_pull_request() $this->prepare_builder_image(); $this->clone_repository(); $this->cleanup_git(); - $this->generate_buildpack(); + if ($this->application->build_pack === 'nixpacks') { + $this->generate_nixpacks_confs(); + } $this->generate_compose_file(); // Needs separate preview variables // $this->generate_build_env_variables(); @@ -212,111 +245,154 @@ private function deploy_pull_request() $this->start_by_compose_file(); } - private function next(string $status) - { - // If the deployment is cancelled by the user, don't update the status - if ($this->application_deployment_queue->status !== ApplicationDeploymentStatus::CANCELLED_BY_USER->value) { - $this->application_deployment_queue->update([ - 'status' => $status, - ]); - } - queue_next_deployment($this->application); - } - private function start_by_compose_file() + private function prepare_builder_image() { $this->execute_remote_command( - ["echo -n 'Starting new application... '"], - [$this->execute_in_builder("docker compose --project-directory {$this->workdir} up -d >/dev/null"), "hidden" => true], - ["echo 'Done. 🎉'"], + [ + "echo -n 'Pulling latest version of the builder image (ghcr.io/coollabsio/coolify-helper).'", + ], + [ + "docker run --pull=always -d --name {$this->deployment_uuid} --rm -v /var/run/docker.sock:/var/run/docker.sock ghcr.io/coollabsio/coolify-helper", + "hidden" => true, + ], + [ + "command" => $this->execute_in_builder("mkdir -p {$this->workdir}") + ], ); } - private function stop_running_container() + + private function execute_in_builder(string $command) + { + return "docker exec {$this->deployment_uuid} bash -c '{$command}'"; + // return "docker exec {$this->deployment_uuid} bash -c '{$command} |& tee -a /proc/1/fd/1; [ \$PIPESTATUS -eq 0 ] || exit \$PIPESTATUS'"; + } + + private function clone_repository() { $this->execute_remote_command( - ["echo -n 'Removing old running application.'"], - [$this->execute_in_builder("docker rm -f $this->container_name >/dev/null 2>&1"), "hidden" => true], + [ + "echo -n 'Importing {$this->application->git_repository}:{$this->application->git_branch} to {$this->workdir}. '" + ], + [ + $this->importing_git_repository() + ], + [ + $this->execute_in_builder("cd {$this->workdir} && git rev-parse HEAD"), + "hidden" => true, + "save" => "git_commit_sha" + ], + ); + $this->commit = $this->saved_outputs->get('git_commit_sha'); + } + + private function importing_git_repository() + { + $commands = collect([]); + $git_clone_command = "git clone -q -b {$this->application->git_branch}"; + if ($this->pull_request_id !== 0) { + $pr_branch_name = "pr-{$this->pull_request_id}-coolify"; + } + + if ($this->application->deploymentType() === 'source') { + $source_html_url = data_get($this->application, 'source.html_url'); + $url = parse_url(filter_var($source_html_url, FILTER_SANITIZE_URL)); + $source_html_url_host = $url['host']; + $source_html_url_scheme = $url['scheme']; + + if ($this->source->getMorphClass() == 'App\Models\GithubApp') { + if ($this->source->is_public) { + $git_clone_command = "{$git_clone_command} {$this->source->html_url}/{$this->application->git_repository} {$this->workdir}"; + $git_clone_command = $this->set_git_import_settings($git_clone_command); + + $commands->push($this->execute_in_builder($git_clone_command)); + } else { + $github_access_token = generate_github_installation_token($this->source); + $commands->push($this->execute_in_builder("git clone -q -b {$this->application->git_branch} $source_html_url_scheme://x-access-token:$github_access_token@$source_html_url_host/{$this->application->git_repository}.git {$this->workdir}")); + } + if ($this->pull_request_id !== 0) { + $commands->push($this->execute_in_builder("cd {$this->workdir} && git fetch origin pull/{$this->pull_request_id}/head:$pr_branch_name && git checkout $pr_branch_name")); + } + return $commands->implode(' && '); + } + } + if ($this->application->deploymentType() === 'deploy_key') { + $private_key = base64_encode($this->application->private_key->private_key); + $git_clone_command = "GIT_SSH_COMMAND=\"ssh -o LogLevel=ERROR -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -i /root/.ssh/id_rsa\" {$git_clone_command} {$this->application->git_full_url} {$this->workdir}"; + $git_clone_command = $this->set_git_import_settings($git_clone_command); + $commands = collect([ + $this->execute_in_builder("mkdir -p /root/.ssh"), + $this->execute_in_builder("echo '{$private_key}' | base64 -d > /root/.ssh/id_rsa"), + $this->execute_in_builder("chmod 600 /root/.ssh/id_rsa"), + $this->execute_in_builder($git_clone_command) + ]); + return $commands->implode(' && '); + } + } + + private function set_git_import_settings($git_clone_command) + { + if ($this->application->git_commit_sha !== 'HEAD') { + $git_clone_command = "{$git_clone_command} && cd {$this->workdir} && git -c advice.detachedHead=false checkout {$this->application->git_commit_sha} >/dev/null 2>&1"; + } + if ($this->application->settings->is_git_submodules_enabled) { + $git_clone_command = "{$git_clone_command} && cd {$this->workdir} && git submodule update --init --recursive"; + } + if ($this->application->settings->is_git_lfs_enabled) { + $git_clone_command = "{$git_clone_command} && cd {$this->workdir} && git lfs pull"; + } + return $git_clone_command; + } + + private function cleanup_git() + { + $this->execute_remote_command( + [$this->execute_in_builder("rm -fr {$this->workdir}/.git")], ); } - private function build_image() + + private function generate_nixpacks_confs() { - $this->execute_remote_command([ - "echo -n 'Building docker image.'", - ]); - - if ($this->application->settings->is_static) { - $this->execute_remote_command([ - $this->execute_in_builder("docker build -f {$this->workdir}/Dockerfile {$this->build_args} --progress plain -t $this->build_image_name {$this->workdir}"), "hidden" => true - ]); - - $dockerfile = base64_encode("FROM {$this->application->static_image} -WORKDIR /usr/share/nginx/html/ -LABEL coolify.deploymentId={$this->deployment_uuid} -COPY --from=$this->build_image_name /app/{$this->application->publish_directory} . -COPY ./nginx.conf /etc/nginx/conf.d/default.conf"); - - $nginx_config = base64_encode("server { - listen 80; - listen [::]:80; - server_name localhost; - - location / { - root /usr/share/nginx/html; - index index.html; - try_files \$uri \$uri.html \$uri/index.html \$uri/ /index.html =404; - } - - error_page 500 502 503 504 /50x.html; - location = /50x.html { - root /usr/share/nginx/html; - } - }"); - $this->execute_remote_command( - [ - $this->execute_in_builder("echo '{$dockerfile}' | base64 -d > {$this->workdir}/Dockerfile-prod") - ], - [ - $this->execute_in_builder("echo '{$nginx_config}' | base64 -d > {$this->workdir}/nginx.conf") - ], - [ - $this->execute_in_builder("docker build -f {$this->workdir}/Dockerfile-prod {$this->build_args} --progress plain -t $this->production_image_name {$this->workdir}"), "hidden" => true - ] - ); - } else { - $this->execute_remote_command([ - $this->execute_in_builder("docker build -f {$this->workdir}/Dockerfile {$this->build_args} --progress plain -t $this->production_image_name {$this->workdir}"), "hidden" => true - ]); - } + $this->execute_remote_command( + [ + "echo -n 'Generating nixpacks configuration.'", + ], + [$this->nixpacks_build_cmd()], + [$this->execute_in_builder("cp {$this->workdir}/.nixpacks/Dockerfile {$this->workdir}/Dockerfile")], + [$this->execute_in_builder("rm -f {$this->workdir}/.nixpacks/Dockerfile")] + ); } - private function add_build_env_variables_to_dockerfile() - { - $this->execute_remote_command([ - $this->execute_in_builder("cat {$this->workdir}/Dockerfile"), "hidden" => true, "save" => 'dockerfile' - ]); - $dockerfile = collect(Str::of($this->saved_outputs->get('dockerfile'))->trim()->explode("\n")); - foreach ($this->application->build_environment_variables as $env) { - $dockerfile->splice(1, 0, "ARG {$env->key}={$env->value}"); - } - $dockerfile_base64 = base64_encode($dockerfile->implode("\n")); - $this->execute_remote_command([ - $this->execute_in_builder("echo '{$dockerfile_base64}' | base64 -d > {$this->workdir}/Dockerfile"), - "hidden" => true - ]); - } - private function generate_build_env_variables() + private function nixpacks_build_cmd() { - $this->build_args = collect(["--build-arg SOURCE_COMMIT={$this->commit}"]); + $this->generate_env_variables(); + $nixpacks_command = "nixpacks build -o {$this->workdir} {$this->env_args} --no-error-without-start"; + if ($this->application->build_command) { + $nixpacks_command .= " --build-cmd \"{$this->application->build_command}\""; + } + if ($this->application->start_command) { + $nixpacks_command .= " --start-cmd \"{$this->application->start_command}\""; + } + if ($this->application->install_command) { + $nixpacks_command .= " --install-cmd \"{$this->application->install_command}\""; + } + $nixpacks_command .= " {$this->workdir}"; + return $this->execute_in_builder($nixpacks_command); + } + + private function generate_env_variables() + { + $this->env_args = collect([]); if ($this->pull_request_id === 0) { - foreach ($this->application->build_environment_variables as $env) { - $this->build_args->push("--build-arg {$env->key}={$env->value}"); + foreach ($this->application->nixpacks_environment_variables as $env) { + $this->env_args->push("--env {$env->key}={$env->value}"); } } else { - foreach ($this->application->build_environment_variables_preview as $env) { - $this->build_args->push("--build-arg {$env->key}={$env->value}"); + foreach ($this->application->nixpacks_environment_variables_preview as $env) { + $this->env_args->push("--env {$env->key}={$env->value}"); } } - $this->build_args = $this->build_args->implode(' '); + $this->env_args = $this->env_args->implode(' '); } private function generate_compose_file() @@ -377,9 +453,10 @@ private function generate_compose_file() $docker_compose['volumes'] = $volume_names; } $this->docker_compose = Yaml::dump($docker_compose, 10); - $docker_compose_base64 = base64_encode($this->docker_compose); - $this->execute_remote_command([$this->execute_in_builder("echo '{$docker_compose_base64}' | base64 -d > {$this->workdir}/docker-compose.yml"), "hidden" => true]); + $this->docker_compose_base64 = base64_encode($this->docker_compose); + $this->execute_remote_command([$this->execute_in_builder("echo '{$this->docker_compose_base64}' | base64 -d > {$this->workdir}/docker-compose.yml"), "hidden" => true]); } + private function generate_local_persistent_volumes() { $local_persistent_volumes = []; @@ -392,6 +469,7 @@ private function generate_local_persistent_volumes() } return $local_persistent_volumes; } + private function generate_local_persistent_volumes_only_volume_names() { $local_persistent_volumes_names = []; @@ -412,6 +490,7 @@ private function generate_local_persistent_volumes_only_volume_names() } return $local_persistent_volumes_names; } + private function generate_environment_variables($ports) { $environment_variables = collect(); @@ -433,22 +512,7 @@ private function generate_environment_variables($ports) } return $environment_variables->all(); } - private function generate_healthcheck_commands() - { - if (!$this->application->health_check_port) { - $this->application->health_check_port = $this->application->ports_exposes_array[0]; - } - if ($this->application->health_check_path) { - $generated_healthchecks_commands = [ - "curl -s -X {$this->application->health_check_method} -f {$this->application->health_check_scheme}://{$this->application->health_check_host}:{$this->application->health_check_port}{$this->application->health_check_path} > /dev/null" - ]; - } else { - $generated_healthchecks_commands = [ - "curl -s -X {$this->application->health_check_method} -f {$this->application->health_check_scheme}://{$this->application->health_check_host}:{$this->application->health_check_port}/" - ]; - } - return implode(' ', $generated_healthchecks_commands); - } + private function set_labels_for_applications() { $labels = []; @@ -512,140 +576,150 @@ private function set_labels_for_applications() } return $labels; } - private function generate_buildpack() + + private function generate_healthcheck_commands() + { + if (!$this->application->health_check_port) { + $this->application->health_check_port = $this->application->ports_exposes_array[0]; + } + if ($this->application->health_check_path) { + $generated_healthchecks_commands = [ + "curl -s -X {$this->application->health_check_method} -f {$this->application->health_check_scheme}://{$this->application->health_check_host}:{$this->application->health_check_port}{$this->application->health_check_path} > /dev/null" + ]; + } else { + $generated_healthchecks_commands = [ + "curl -s -X {$this->application->health_check_method} -f {$this->application->health_check_scheme}://{$this->application->health_check_host}:{$this->application->health_check_port}/" + ]; + } + return implode(' ', $generated_healthchecks_commands); + } + + private function build_image() + { + $this->execute_remote_command([ + "echo -n 'Building docker image.'", + ]); + + if ($this->application->settings->is_static) { + $this->execute_remote_command([ + $this->execute_in_builder("docker build -f {$this->workdir}/Dockerfile {$this->build_args} --progress plain -t $this->build_image_name {$this->workdir}"), "hidden" => true + ]); + + $dockerfile = base64_encode("FROM {$this->application->static_image} +WORKDIR /usr/share/nginx/html/ +LABEL coolify.deploymentId={$this->deployment_uuid} +COPY --from=$this->build_image_name /app/{$this->application->publish_directory} . +COPY ./nginx.conf /etc/nginx/conf.d/default.conf"); + + $nginx_config = base64_encode("server { + listen 80; + listen [::]:80; + server_name localhost; + + location / { + root /usr/share/nginx/html; + index index.html; + try_files \$uri \$uri.html \$uri/index.html \$uri/ /index.html =404; + } + + error_page 500 502 503 504 /50x.html; + location = /50x.html { + root /usr/share/nginx/html; + } + }"); + $this->execute_remote_command( + [ + $this->execute_in_builder("echo '{$dockerfile}' | base64 -d > {$this->workdir}/Dockerfile-prod") + ], + [ + $this->execute_in_builder("echo '{$nginx_config}' | base64 -d > {$this->workdir}/nginx.conf") + ], + [ + $this->execute_in_builder("docker build -f {$this->workdir}/Dockerfile-prod {$this->build_args} --progress plain -t $this->production_image_name {$this->workdir}"), "hidden" => true + ] + ); + } else { + $this->execute_remote_command([ + $this->execute_in_builder("docker build -f {$this->workdir}/Dockerfile {$this->build_args} --progress plain -t $this->production_image_name {$this->workdir}"), "hidden" => true + ]); + } + } + + private function stop_running_container() { $this->execute_remote_command( - [ - "echo -n 'Generating nixpacks configuration.'", - ], - [$this->nixpacks_build_cmd()], - [$this->execute_in_builder("cp {$this->workdir}/.nixpacks/Dockerfile {$this->workdir}/Dockerfile")], - [$this->execute_in_builder("rm -f {$this->workdir}/.nixpacks/Dockerfile")] + ["echo -n 'Removing old running application.'"], + [$this->execute_in_builder("docker rm -f $this->container_name >/dev/null 2>&1"), "hidden" => true], ); } - private function nixpacks_build_cmd() + + private function start_by_compose_file() { - $this->generate_env_variables(); - $nixpacks_command = "nixpacks build -o {$this->workdir} {$this->env_args} --no-error-without-start"; - if ($this->application->build_command) { - $nixpacks_command .= " --build-cmd \"{$this->application->build_command}\""; - } - if ($this->application->start_command) { - $nixpacks_command .= " --start-cmd \"{$this->application->start_command}\""; - } - if ($this->application->install_command) { - $nixpacks_command .= " --install-cmd \"{$this->application->install_command}\""; - } - $nixpacks_command .= " {$this->workdir}"; - return $this->execute_in_builder($nixpacks_command); + $this->execute_remote_command( + ["echo -n 'Starting new application... '"], + [$this->execute_in_builder("docker compose --project-directory {$this->workdir} up -d >/dev/null"), "hidden" => true], + ["echo 'Done. 🎉'"], + ); } - private function generate_env_variables() + + + + private function generate_build_env_variables() { - $this->env_args = collect([]); + $this->build_args = collect(["--build-arg SOURCE_COMMIT={$this->commit}"]); if ($this->pull_request_id === 0) { - foreach ($this->application->nixpacks_environment_variables as $env) { - $this->env_args->push("--env {$env->key}={$env->value}"); + foreach ($this->application->build_environment_variables as $env) { + $this->build_args->push("--build-arg {$env->key}={$env->value}"); } } else { - foreach ($this->application->nixpacks_environment_variables_preview as $env) { - $this->env_args->push("--env {$env->key}={$env->value}"); + foreach ($this->application->build_environment_variables_preview as $env) { + $this->build_args->push("--build-arg {$env->key}={$env->value}"); } } - $this->env_args = $this->env_args->implode(' '); + $this->build_args = $this->build_args->implode(' '); } - private function cleanup_git() - { - $this->execute_remote_command( - [$this->execute_in_builder("rm -fr {$this->workdir}/.git")], - ); - } - private function prepare_builder_image() - { - $this->execute_remote_command( - [ - "echo -n 'Pulling latest version of the builder image (ghcr.io/coollabsio/coolify-builder).'", - ], - [ - "docker run --pull=always -d --name {$this->deployment_uuid} --rm -v /var/run/docker.sock:/var/run/docker.sock ghcr.io/coollabsio/coolify-builder", - "hidden" => true, - ], - [ - "command" => $this->execute_in_builder("mkdir -p {$this->workdir}") - ], - ); - } - private function set_git_import_settings($git_clone_command) - { - if ($this->application->git_commit_sha !== 'HEAD') { - $git_clone_command = "{$git_clone_command} && cd {$this->workdir} && git -c advice.detachedHead=false checkout {$this->application->git_commit_sha} >/dev/null 2>&1"; - } - if ($this->application->settings->is_git_submodules_enabled) { - $git_clone_command = "{$git_clone_command} && cd {$this->workdir} && git submodule update --init --recursive"; - } - if ($this->application->settings->is_git_lfs_enabled) { - $git_clone_command = "{$git_clone_command} && cd {$this->workdir} && git lfs pull"; - } - return $git_clone_command; - } - private function importing_git_repository() - { - $commands = collect([]); - $git_clone_command = "git clone -q -b {$this->application->git_branch}"; - if ($this->pull_request_id !== 0) { - $pr_branch_name = "pr-{$this->pull_request_id}-coolify"; - } - if ($this->application->deploymentType() === 'source') { - $source_html_url = data_get($this->application, 'source.html_url'); - $url = parse_url(filter_var($source_html_url, FILTER_SANITIZE_URL)); - $source_html_url_host = $url['host']; - $source_html_url_scheme = $url['scheme']; + private function add_build_env_variables_to_dockerfile() + { + $this->execute_remote_command([ + $this->execute_in_builder("cat {$this->workdir}/Dockerfile"), "hidden" => true, "save" => 'dockerfile' + ]); + $dockerfile = collect(Str::of($this->saved_outputs->get('dockerfile'))->trim()->explode("\n")); - if ($this->source->getMorphClass() == 'App\Models\GithubApp') { - if ($this->source->is_public) { - $git_clone_command = "{$git_clone_command} {$this->source->html_url}/{$this->application->git_repository} {$this->workdir}"; - $git_clone_command = $this->set_git_import_settings($git_clone_command); - - $commands->push($this->execute_in_builder($git_clone_command)); - } else { - $github_access_token = generate_github_installation_token($this->source); - $commands->push($this->execute_in_builder("git clone -q -b {$this->application->git_branch} $source_html_url_scheme://x-access-token:$github_access_token@$source_html_url_host/{$this->application->git_repository}.git {$this->workdir}")); - } - if ($this->pull_request_id !== 0) { - $commands->push($this->execute_in_builder("cd {$this->workdir} && git fetch origin pull/{$this->pull_request_id}/head:$pr_branch_name && git checkout $pr_branch_name")); - } - return $commands->implode(' && '); - } + foreach ($this->application->build_environment_variables as $env) { + $dockerfile->splice(1, 0, "ARG {$env->key}={$env->value}"); } - if ($this->application->deploymentType() === 'deploy_key') { - $private_key = base64_encode($this->application->private_key->private_key); - $git_clone_command = "GIT_SSH_COMMAND=\"ssh -o LogLevel=ERROR -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -i /root/.ssh/id_rsa\" {$git_clone_command} {$this->application->git_full_url} {$this->workdir}"; - $git_clone_command = $this->set_git_import_settings($git_clone_command); - $commands = collect([ - $this->execute_in_builder("mkdir -p /root/.ssh"), - $this->execute_in_builder("echo '{$private_key}' | base64 -d > /root/.ssh/id_rsa"), - $this->execute_in_builder("chmod 600 /root/.ssh/id_rsa"), - $this->execute_in_builder($git_clone_command) + $dockerfile_base64 = base64_encode($dockerfile->implode("\n")); + $this->execute_remote_command([ + $this->execute_in_builder("echo '{$dockerfile_base64}' | base64 -d > {$this->workdir}/Dockerfile"), + "hidden" => true + ]); + } + + private function next(string $status) + { + // If the deployment is cancelled by the user, don't update the status + if ($this->application_deployment_queue->status !== ApplicationDeploymentStatus::CANCELLED_BY_USER->value) { + $this->application_deployment_queue->update([ + 'status' => $status, ]); - return $commands->implode(' && '); + } + queue_next_deployment($this->application); + if ($status === ApplicationDeploymentStatus::FINISHED->value) { + $this->application->environment->project->team->notify(new DeploymentSuccess($this->application, $this->deployment_uuid, $this->preview)); + } + if ($status === ApplicationDeploymentStatus::FAILED->value) { + $this->application->environment->project->team->notify(new DeploymentFailed($this->application, $this->deployment_uuid, $this->preview)); } } - private function clone_repository() + + public function failed(Throwable $exception): void { $this->execute_remote_command( - [ - "echo -n 'Importing {$this->application->git_repository}:{$this->application->git_branch} to {$this->workdir}. '" - ], - [ - $this->importing_git_repository() - ], - [ - $this->execute_in_builder("cd {$this->workdir} && git rev-parse HEAD"), - "hidden" => true, - "save" => "git_commit_sha" - ], + ["echo 'Oops something is not okay, are you okay? 😢'"], + ["echo '{$exception->getMessage()}'"] ); - $this->commit = $this->saved_outputs->get('git_commit_sha'); + $this->next(ApplicationDeploymentStatus::FAILED->value); } } diff --git a/app/Jobs/ApplicationPullRequestUpdateJob.php b/app/Jobs/ApplicationPullRequestUpdateJob.php index e0f51e0ee..f54063183 100755 --- a/app/Jobs/ApplicationPullRequestUpdateJob.php +++ b/app/Jobs/ApplicationPullRequestUpdateJob.php @@ -22,11 +22,12 @@ class ApplicationPullRequestUpdateJob implements ShouldQueue public function __construct( public string $application_id, - public int $pull_request_id, + public int $pull_request_id, public string $deployment_uuid, public string $status ) { } + public function handle() { try { @@ -61,6 +62,7 @@ public function handle() throw $e; } } + private function update_comment() { ['data' => $data] = git_api(source: $this->application->source, endpoint: "/repos/{$this->application->git_repository}/issues/comments/{$this->preview->pull_request_issue_comment_id}", method: 'patch', data: [ @@ -71,6 +73,7 @@ private function update_comment() $this->create_comment(); } } + private function create_comment() { ['data' => $data] = git_api(source: $this->application->source, endpoint: "/repos/{$this->application->git_repository}/issues/{$this->pull_request_id}/comments", method: 'post', data: [ diff --git a/app/Jobs/CheckResaleLicenseJob.php b/app/Jobs/CheckResaleLicenseJob.php index 0e925c569..f14214733 100644 --- a/app/Jobs/CheckResaleLicenseJob.php +++ b/app/Jobs/CheckResaleLicenseJob.php @@ -3,15 +3,11 @@ namespace App\Jobs; use App\Actions\License\CheckResaleLicense; -use App\Models\InstanceSettings; use Illuminate\Bus\Queueable; -use Illuminate\Contracts\Queue\ShouldBeUnique; use Illuminate\Contracts\Queue\ShouldQueue; use Illuminate\Foundation\Bus\Dispatchable; use Illuminate\Queue\InteractsWithQueue; use Illuminate\Queue\SerializesModels; -use Illuminate\Support\Facades\Http; -use Visus\Cuid2\Cuid2; class CheckResaleLicenseJob implements ShouldQueue { diff --git a/app/Jobs/CleanupInstanceStuffsJob.php b/app/Jobs/CleanupInstanceStuffsJob.php new file mode 100644 index 000000000..7b3be1a44 --- /dev/null +++ b/app/Jobs/CleanupInstanceStuffsJob.php @@ -0,0 +1,43 @@ +container_name; + // } + + public function handle(): void + { + try { + $this->cleanup_waitlist(); + } catch (\Exception $e) { + ray($e->getMessage()); + } + } + + private function cleanup_waitlist() + { + $waitlist = Waitlist::whereVerified(false)->where('created_at', '<', now()->subMinutes(config('constants.waitlist.confirmation_valid_for_minutes')))->get(); + foreach ($waitlist as $item) { + $item->delete(); + } + } +} diff --git a/app/Jobs/ApplicationContainerStatusJob.php b/app/Jobs/ContainerStatusJob.php similarity index 54% rename from app/Jobs/ApplicationContainerStatusJob.php rename to app/Jobs/ContainerStatusJob.php index 004f1f5a0..083023929 100644 --- a/app/Jobs/ApplicationContainerStatusJob.php +++ b/app/Jobs/ContainerStatusJob.php @@ -2,48 +2,53 @@ namespace App\Jobs; -use App\Models\Application; use App\Models\ApplicationPreview; +use App\Notifications\Application\StatusChanged; use Illuminate\Bus\Queueable; use Illuminate\Contracts\Queue\ShouldBeUnique; use Illuminate\Contracts\Queue\ShouldQueue; use Illuminate\Foundation\Bus\Dispatchable; use Illuminate\Queue\InteractsWithQueue; use Illuminate\Queue\SerializesModels; -use Illuminate\Support\Facades\Log; -class ApplicationContainerStatusJob implements ShouldQueue, ShouldBeUnique +class ContainerStatusJob implements ShouldQueue, ShouldBeUnique { use Dispatchable, InteractsWithQueue, Queueable, SerializesModels; public string $container_name; public string|null $pull_request_id; - public Application $application; + public $resource; - public function __construct($application, string $container_name, string|null $pull_request_id = null) + public function __construct($resource, string $container_name, string|null $pull_request_id = null) { - $this->application = $application; + $this->resource = $resource; $this->container_name = $container_name; $this->pull_request_id = $pull_request_id; } + public function uniqueId(): string { return $this->container_name; } + public function handle(): void { try { - $status = get_container_status(server: $this->application->destination->server, container_id: $this->container_name, throwError: false); + $status = get_container_status(server: $this->resource->destination->server, container_id: $this->container_name, throwError: false); + if ($this->resource->status === 'running' && $status === 'stopped') { + $this->resource->environment->project->team->notify(new StatusChanged($this->resource)); + } + if ($this->pull_request_id) { - $preview = ApplicationPreview::findPreviewByApplicationAndPullId($this->application->id, $this->pull_request_id); + $preview = ApplicationPreview::findPreviewByApplicationAndPullId($this->resource->id, $this->pull_request_id); $preview->status = $status; $preview->save(); } else { - $this->application->status = $status; - $this->application->save(); + $this->resource->status = $status; + $this->resource->save(); } } catch (\Exception $e) { - Log::error($e->getMessage()); + ray($e->getMessage()); } } } diff --git a/app/Jobs/CoolifyTask.php b/app/Jobs/CoolifyTask.php index d87468efc..e3dd9640f 100755 --- a/app/Jobs/CoolifyTask.php +++ b/app/Jobs/CoolifyTask.php @@ -19,7 +19,7 @@ class CoolifyTask implements ShouldQueue */ public function __construct( public Activity $activity, - public bool $ignore_errors = false, + public bool $ignore_errors = false, ) { } diff --git a/app/Jobs/DatabaseBackupJob.php b/app/Jobs/DatabaseBackupJob.php new file mode 100644 index 000000000..3fdbea216 --- /dev/null +++ b/app/Jobs/DatabaseBackupJob.php @@ -0,0 +1,187 @@ +backup = $backup; + $this->team = Team::find($backup->team_id); + $this->database = $this->backup->database; + $this->database_type = $this->database->type(); + $this->server = $this->database->destination->server; + $this->database_status = $this->database->status; + $this->s3 = $this->backup->s3; + } + + public function middleware(): array + { + return [new WithoutOverlapping($this->backup->id)]; + } + + public function uniqueId(): int + { + return $this->backup->id; + } + + public function handle(): void + { + if ($this->database_status !== 'running') { + ray('database not running'); + return; + } + $this->container_name = $this->database->uuid; + if ($this->database->name === 'coolify-db') { + $this->container_name = "coolify-db"; + } + + $this->backup_dir = backup_dir() . "/" . $this->container_name; + $this->backup_file = "/dumpall-" . Carbon::now()->timestamp . ".sql"; + $this->backup_location = $this->backup_dir . $this->backup_file; + + $this->backup_log = ScheduledDatabaseBackupExecution::create([ + 'filename' => $this->backup_location, + 'scheduled_database_backup_id' => $this->backup->id, + ]); + if ($this->database_type === 'standalone-postgresql') { + $this->backup_standalone_postgresql(); + } + $this->calculate_size(); + $this->remove_old_backups(); + if ($this->backup->save_s3) { + $this->upload_to_s3(); + } + $this->save_backup_logs(); + // TODO: Notify user + } + + private function backup_standalone_postgresql(): void + { + try { + $commands[] = "mkdir -p " . $this->backup_dir; + $commands[] = "docker exec $this->container_name pg_dumpall -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 $th) { + $this->backup_status = 'failed'; + $this->add_to_backup_output($th->getMessage()); + ray('Backup failed for ' . $this->container_name . ' at ' . $this->server->name . ':' . $this->backup_location . '\n\nError:' . $th->getMessage()); + $this->team->notify(new BackupFailed($this->backup, $this->database, $this->backup_output)); + } finally { + $this->backup_log->update([ + 'status' => $this->backup_status, + ]); + } + } + + private function add_to_backup_output($output): void + { + if ($this->backup_output) { + $this->backup_output = $this->backup_output . "\n" . $output; + } else { + $this->backup_output = $output; + } + } + + private function calculate_size(): void + { + $this->size = instant_remote_process(["du -b $this->backup_location | cut -f1"], $this->server); + } + + private function remove_old_backups(): void + { + if ($this->backup->number_of_backups_locally === 0) { + $deletable = $this->backup->executions()->where('status', 'success'); + } else { + $deletable = $this->backup->executions()->where('status', 'success')->orderByDesc('created_at')->skip($this->backup->number_of_backups_locally); + } + foreach ($deletable->get() as $execution) { + delete_backup_locally($execution->filename, $this->server); + $execution->delete(); + } + } + + private function upload_to_s3(): void + { + try { + if (is_null($this->s3)) { + return; + } + $key = $this->s3->key; + $secret = $this->s3->secret; + // $region = $this->s3->region; + $bucket = $this->s3->bucket; + $endpoint = $this->s3->endpoint; + + $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); + $this->add_to_backup_output('Uploaded to S3.'); + ray('Uploaded to S3. ' . $this->backup_location . ' to s3://' . $bucket . $this->backup_dir); + } catch (\Throwable $th) { + $this->add_to_backup_output($th->getMessage()); + ray($th->getMessage()); + } finally { + $command = "docker rm -f backup-of-{$this->backup->uuid}"; + instant_remote_process([$command], $this->server); + } + } + + private function save_backup_logs(): void + { + $this->backup_log->update([ + 'status' => $this->backup_status, + 'message' => $this->backup_output, + 'size' => $this->size, + ]); + } +} diff --git a/app/Jobs/DockerCleanupJob.php b/app/Jobs/DockerCleanupJob.php index 96b2d668f..52279c241 100644 --- a/app/Jobs/DockerCleanupJob.php +++ b/app/Jobs/DockerCleanupJob.php @@ -8,13 +8,14 @@ use Illuminate\Foundation\Bus\Dispatchable; use Illuminate\Queue\InteractsWithQueue; use Illuminate\Queue\SerializesModels; -use Illuminate\Support\Facades\Log; use Illuminate\Support\Str; class DockerCleanupJob implements ShouldQueue { use Dispatchable, InteractsWithQueue, Queueable, SerializesModels; + public $timeout = 500; + /** * Create a new job instance. */ @@ -31,7 +32,7 @@ public function handle(): void try { $servers = Server::all(); foreach ($servers as $server) { - if (isDev()) { + if (is_dev()) { $docker_root_filesystem = "/"; } else { $docker_root_filesystem = instant_remote_process(['stat --printf=%m $(docker info --format "{{json .DockerRootDir}}" |sed \'s/"//g\')'], $server); @@ -48,13 +49,14 @@ public function handle(): void } } } catch (\Exception $e) { - Log::error($e->getMessage()); + ray($e->getMessage()); } } - - private function get_disk_usage(Server $server, string $docker_root_filesystem) { + + private function get_disk_usage(Server $server, string $docker_root_filesystem) + { $disk_usage = json_decode(instant_remote_process(['df -hP | awk \'BEGIN {printf"{\"disks\":["}{if($1=="Filesystem")next;if(a)printf",";printf"{\"mount\":\""$6"\",\"size\":\""$2"\",\"used\":\""$3"\",\"avail\":\""$4"\",\"use%\":\""$5"\"}";a++;}END{print"]}";}\''], $server), true); $mount_point = collect(data_get($disk_usage, 'disks'))->where('mount', $docker_root_filesystem)->first(); return Str::of(data_get($mount_point, 'use%'))->trim()->replace('%', '')->value(); } -} \ No newline at end of file +} diff --git a/app/Jobs/InstanceApplicationsStatusJob.php b/app/Jobs/InstanceApplicationsStatusJob.php new file mode 100644 index 000000000..fd173215b --- /dev/null +++ b/app/Jobs/InstanceApplicationsStatusJob.php @@ -0,0 +1,37 @@ +applications = Application::all(); + } + + public function handle(): void + { + try { + foreach ($this->applications as $application) { + dispatch(new ContainerStatusJob( + resource: $application, + container_name: generate_container_name($application->uuid), + )); + } + } catch (\Exception $e) { + ray($e->getMessage()); + } + } +} diff --git a/app/Jobs/InstanceAutoUpdateJob.php b/app/Jobs/InstanceAutoUpdateJob.php index b0b19aef1..957b8fe23 100644 --- a/app/Jobs/InstanceAutoUpdateJob.php +++ b/app/Jobs/InstanceAutoUpdateJob.php @@ -3,9 +3,6 @@ namespace App\Jobs; use App\Actions\Server\UpdateCoolify; -use App\Models\InstanceSettings; -use App\Models\Server; -use Exception; use Illuminate\Bus\Queueable; use Illuminate\Contracts\Queue\ShouldBeUnique; use Illuminate\Contracts\Queue\ShouldQueue; @@ -22,6 +19,7 @@ class InstanceAutoUpdateJob implements ShouldQueue, ShouldBeUnique public function __construct(private bool $force = false) { } + public function handle(): void { resolve(UpdateCoolify::class)($this->force); diff --git a/app/Jobs/ProxyCheckJob.php b/app/Jobs/ProxyCheckJob.php index ab04d3d07..7c498107d 100755 --- a/app/Jobs/ProxyCheckJob.php +++ b/app/Jobs/ProxyCheckJob.php @@ -3,7 +3,6 @@ namespace App\Jobs; use App\Actions\Proxy\StartProxy; -use App\Enums\ProxyTypes; use App\Models\Server; use Illuminate\Bus\Queueable; use Illuminate\Contracts\Queue\ShouldQueue; @@ -18,6 +17,7 @@ class ProxyCheckJob implements ShouldQueue public function __construct() { } + public function handle() { try { @@ -28,6 +28,7 @@ public function handle() if ($status === 'running') { continue; } + // $server->team->notify(new ProxyStoppedNotification($server)); resolve(StartProxy::class)($server); } } catch (\Throwable $th) { diff --git a/app/Jobs/ProxyContainerStatusJob.php b/app/Jobs/ProxyContainerStatusJob.php index 02cd37568..68372895a 100644 --- a/app/Jobs/ProxyContainerStatusJob.php +++ b/app/Jobs/ProxyContainerStatusJob.php @@ -9,8 +9,8 @@ use Illuminate\Contracts\Queue\ShouldQueue; use Illuminate\Foundation\Bus\Dispatchable; use Illuminate\Queue\InteractsWithQueue; -use Illuminate\Queue\SerializesModels; use Illuminate\Queue\Middleware\WithoutOverlapping; +use Illuminate\Queue\SerializesModels; use Illuminate\Support\Str; class ProxyContainerStatusJob implements ShouldQueue, ShouldBeUnique @@ -20,18 +20,22 @@ class ProxyContainerStatusJob implements ShouldQueue, ShouldBeUnique public Server $server; public $tries = 1; public $timeout = 120; - public function middleware(): array - { - return [new WithoutOverlapping($this->server->id)]; - } + public function __construct(Server $server) { $this->server = $server; } + + public function middleware(): array + { + return [new WithoutOverlapping($this->server->id)]; + } + public function uniqueId(): int { return $this->server->id; } + public function handle(): void { try { diff --git a/app/Jobs/ProxyStartJob.php b/app/Jobs/ProxyStartJob.php index 074d21664..5119210fc 100755 --- a/app/Jobs/ProxyStartJob.php +++ b/app/Jobs/ProxyStartJob.php @@ -17,6 +17,7 @@ class ProxyStartJob implements ShouldQueue public function __construct(protected Server $server) { } + public function handle() { try { diff --git a/app/Jobs/SendConfirmationForWaitlistJob.php b/app/Jobs/SendConfirmationForWaitlistJob.php new file mode 100755 index 000000000..31098e180 --- /dev/null +++ b/app/Jobs/SendConfirmationForWaitlistJob.php @@ -0,0 +1,59 @@ +email . '&confirmation_code=' . $this->uuid; + $cancel_url = base_url() . '/webhooks/waitlist/cancel?email=' . $this->email . '&confirmation_code=' . $this->uuid; + + $mail->view('emails.waitlist-confirmation', + [ + 'confirmation_url' => $confirmation_url, + 'cancel_url' => $cancel_url, + ]); + $mail->subject('You are on the waitlist!'); + Mail::send( + [], + [], + fn(Message $message) => $message + ->from( + data_get($settings, 'smtp_from_address'), + data_get($settings, 'smtp_from_name') + ) + ->to($this->email) + ->subject($mail->subject) + ->html((string) $mail->render()) + ); + } catch (\Throwable $th) { + ray($th->getMessage()); + throw $th; + } + } +} diff --git a/app/Jobs/SendMessageToDiscordJob.php b/app/Jobs/SendMessageToDiscordJob.php index 11122e7e3..66e3c1c1a 100644 --- a/app/Jobs/SendMessageToDiscordJob.php +++ b/app/Jobs/SendMessageToDiscordJob.php @@ -3,7 +3,6 @@ namespace App\Jobs; use Illuminate\Bus\Queueable; -use Illuminate\Contracts\Queue\ShouldBeUnique; use Illuminate\Contracts\Queue\ShouldQueue; use Illuminate\Foundation\Bus\Dispatchable; use Illuminate\Queue\InteractsWithQueue; diff --git a/app/Models/Application.php b/app/Models/Application.php index f56a55f79..6b730edee 100644 --- a/app/Models/Application.php +++ b/app/Models/Application.php @@ -2,16 +2,14 @@ namespace App\Models; -use Illuminate\Contracts\Database\Eloquent\Builder; use Illuminate\Database\Eloquent\Casts\Attribute; -use Spatie\Activitylog\Models\Activity; use Illuminate\Database\Eloquent\Relations\HasMany; -use Masmerise\Toaster\Toastable; -use Masmerise\Toaster\Toaster; -use Spatie\SchemalessAttributes\Casts\SchemalessAttributes; +use Spatie\Activitylog\Models\Activity; class Application extends BaseModel { + protected $guarded = []; + protected static function booted() { static::created(function ($application) { @@ -24,25 +22,21 @@ protected static function booted() $application->persistentStorages()->delete(); }); } - protected $fillable = [ - 'name', - 'repository_project_id', - 'project_id', - 'description', - 'git_repository', - 'git_branch', - 'git_full_url', - 'build_pack', - 'environment_id', - 'destination_id', - 'destination_type', - 'source_id', - 'source_type', - 'ports_mappings', - 'ports_exposes', - 'publish_directory', - 'private_key_id' - ]; + + public function settings() + { + return $this->hasOne(ApplicationSetting::class); + } + + public function persistentStorages() + { + return $this->morphMany(LocalPersistentVolume::class, 'resource'); + } + + public function type() + { + return 'application'; + } public function publishDirectory(): Attribute { @@ -50,6 +44,7 @@ public function publishDirectory(): Attribute set: fn ($value) => $value ? '/' . ltrim($value, '/') : null, ); } + public function gitBranchLocation(): Attribute { return Attribute::make( @@ -61,6 +56,7 @@ public function gitBranchLocation(): Attribute ); } + public function gitCommits(): Attribute { return Attribute::make( @@ -71,99 +67,108 @@ public function gitCommits(): Attribute } ); } + public function baseDirectory(): Attribute { return Attribute::make( set: fn ($value) => '/' . ltrim($value, '/'), ); } + public function portsMappings(): Attribute { return Attribute::make( set: fn ($value) => $value === "" ? null : $value, ); } + + // Normal Deployments + public function portsMappingsArray(): Attribute { return Attribute::make( - get: fn () => - is_null($this->ports_mappings) + get: fn () => is_null($this->ports_mappings) ? [] : explode(',', $this->ports_mappings), ); } + public function portsExposesArray(): Attribute { return Attribute::make( - get: fn () => - is_null($this->ports_exposes) + get: fn () => is_null($this->ports_exposes) ? [] : explode(',', $this->ports_exposes) ); } - // Normal Deployments + public function environment_variables(): HasMany { return $this->hasMany(EnvironmentVariable::class)->where('is_preview', false); } + public function runtime_environment_variables(): HasMany { return $this->hasMany(EnvironmentVariable::class)->where('is_preview', false)->where('key', 'not like', 'NIXPACKS_%'); } + + // Preview Deployments + public function build_environment_variables(): HasMany { return $this->hasMany(EnvironmentVariable::class)->where('is_preview', false)->where('is_build_time', true)->where('key', 'not like', 'NIXPACKS_%'); } + public function nixpacks_environment_variables(): HasMany { return $this->hasMany(EnvironmentVariable::class)->where('is_preview', false)->where('key', 'like', 'NIXPACKS_%'); } - // Preview Deployments + public function environment_variables_preview(): HasMany { return $this->hasMany(EnvironmentVariable::class)->where('is_preview', true); } + public function runtime_environment_variables_preview(): HasMany { return $this->hasMany(EnvironmentVariable::class)->where('is_preview', true)->where('key', 'not like', 'NIXPACKS_%'); } + public function build_environment_variables_preview(): HasMany { return $this->hasMany(EnvironmentVariable::class)->where('is_preview', true)->where('is_build_time', true)->where('key', 'not like', 'NIXPACKS_%'); } + public function nixpacks_environment_variables_preview(): HasMany { return $this->hasMany(EnvironmentVariable::class)->where('is_preview', true)->where('key', 'like', 'NIXPACKS_%'); } + public function private_key() { return $this->belongsTo(PrivateKey::class); } + public function environment() { return $this->belongsTo(Environment::class); } + public function previews() { return $this->hasMany(ApplicationPreview::class); } - public function settings() - { - return $this->hasOne(ApplicationSetting::class); - } + public function destination() { return $this->morphTo(); } + public function source() { return $this->morphTo(); } - public function persistentStorages() - { - return $this->morphMany(LocalPersistentVolume::class, 'resource'); - } public function deployments(int $skip = 0, int $take = 10) { @@ -175,10 +180,12 @@ public function deployments(int $skip = 0, int $take = 10) 'deployments' => $deployments ]; } + public function get_deployment(string $deployment_uuid) { return Activity::where('subject_id', $this->id)->where('properties->type_uuid', '=', $deployment_uuid)->first(); } + public function isDeployable(): bool { if ($this->settings->is_auto_deploy_enabled) { @@ -186,6 +193,7 @@ public function isDeployable(): bool } return false; } + public function isPRDeployable(): bool { if ($this->settings->is_preview_deployments_enabled) { @@ -193,6 +201,7 @@ public function isPRDeployable(): bool } return false; } + public function deploymentType() { if (data_get($this, 'private_key_id')) { @@ -206,4 +215,18 @@ public function deploymentType() } throw new \Exception('No deployment type found'); } -} \ No newline at end of file + public function could_set_build_commands(): bool + { + if ($this->build_pack === 'nixpacks') { + return true; + } + return false; + } + public function git_based(): bool + { + if ($this->dockerfile || $this->build_pack === 'dockerfile') { + return false; + } + return true; + } +} diff --git a/app/Models/ApplicationPreview.php b/app/Models/ApplicationPreview.php index 910e9b73b..38ac5127d 100644 --- a/app/Models/ApplicationPreview.php +++ b/app/Models/ApplicationPreview.php @@ -13,12 +13,14 @@ class ApplicationPreview extends BaseModel 'status', 'application_id', ]; - public function application() - { - return $this->belongsTo(Application::class); - } + static function findPreviewByApplicationAndPullId(int $application_id, int $pull_request_id) { return self::where('application_id', $application_id)->where('pull_request_id', $pull_request_id)->firstOrFail(); } + + public function application() + { + return $this->belongsTo(Application::class); + } } diff --git a/app/Models/ApplicationSetting.php b/app/Models/ApplicationSetting.php index 3bdd6a7cd..1340c8fa0 100644 --- a/app/Models/ApplicationSetting.php +++ b/app/Models/ApplicationSetting.php @@ -26,6 +26,7 @@ class ApplicationSetting extends Model 'is_git_submodules_enabled', 'is_git_lfs_enabled', ]; + public function isStatic(): Attribute { return Attribute::make( @@ -42,6 +43,7 @@ public function isStatic(): Attribute } ); } + public function application() { return $this->belongsTo(Application::class); diff --git a/app/Models/BaseModel.php b/app/Models/BaseModel.php index 8f2ee86ae..be487a497 100644 --- a/app/Models/BaseModel.php +++ b/app/Models/BaseModel.php @@ -14,7 +14,7 @@ protected static function boot() static::creating(function (Model $model) { // Generate a UUID if one isn't set if (!$model->uuid) { - $model->uuid = (string) new Cuid2(7); + $model->uuid = (string)new Cuid2(7); } }); } diff --git a/app/Models/Database.php b/app/Models/Database.php deleted file mode 100644 index ab0476d27..000000000 --- a/app/Models/Database.php +++ /dev/null @@ -1,15 +0,0 @@ -belongsTo(Environment::class); - } - public function destination() - { - return $this->morphTo(); - } -} diff --git a/app/Models/Environment.php b/app/Models/Environment.php index c79472e88..470ad5e6c 100644 --- a/app/Models/Environment.php +++ b/app/Models/Environment.php @@ -11,26 +11,41 @@ class Environment extends Model 'name', 'project_id', ]; + + public function can_delete_environment() + { + return $this->applications()->count() == 0 && $this->postgresqls()->count() == 0; + } + + public function applications() + { + return $this->hasMany(Application::class); + } + + public function postgresqls() + { + return $this->hasMany(StandalonePostgresql::class); + } + + public function databases() + { + return $this->postgresqls(); + } + + public function project() + { + return $this->belongsTo(Project::class); + } + + public function services() + { + return $this->hasMany(Service::class); + } + protected function name(): Attribute { return Attribute::make( set: fn (string $value) => strtolower($value), ); } - public function project() - { - return $this->belongsTo(Project::class); - } - public function applications() - { - return $this->hasMany(Application::class); - } - public function databases() - { - return $this->hasMany(Database::class); - } - public function services() - { - return $this->hasMany(Service::class); - } } diff --git a/app/Models/EnvironmentVariable.php b/app/Models/EnvironmentVariable.php index 3847e1700..bb4a5ca47 100644 --- a/app/Models/EnvironmentVariable.php +++ b/app/Models/EnvironmentVariable.php @@ -9,10 +9,17 @@ class EnvironmentVariable extends Model { + protected $guarded = []; + protected $casts = [ + "key" => 'string', + 'value' => 'encrypted', + 'is_build_time' => 'boolean', + ]; + protected static function booted() { static::created(function ($environment_variable) { - if (!$environment_variable->is_preview) { + if ($environment_variable->application_id && !$environment_variable->is_preview) { ModelsEnvironmentVariable::create([ 'key' => $environment_variable->key, 'value' => $environment_variable->value, @@ -23,15 +30,18 @@ protected static function booted() } }); } - protected $fillable = ['key', 'value', 'is_build_time', 'application_id', 'is_preview']; - protected $casts = [ - "key" => 'string', - 'value' => 'encrypted', - 'is_build_time' => 'boolean', - ]; + + protected function value(): Attribute + { + return Attribute::make( + get: fn (string $value) => $this->get_environment_variables($value), + set: fn (string $value) => $this->set_environment_variables($value), + ); + } + private function get_environment_variables(string $environment_variable): string|null { - // $team_id = session('currentTeam')->id; + // $team_id = auth()->user()->currentTeam()->id; if (str_contains(trim($environment_variable), '{{') && str_contains(trim($environment_variable), '}}')) { $environment_variable = preg_replace('/\s+/', '', $environment_variable); $environment_variable = str_replace('{{', '', $environment_variable); @@ -44,6 +54,7 @@ private function get_environment_variables(string $environment_variable): string } return decrypt($environment_variable); } + private function set_environment_variables(string $environment_variable): string|null { $environment_variable = trim($environment_variable); @@ -52,13 +63,7 @@ private function set_environment_variables(string $environment_variable): string } return $environment_variable; } - protected function value(): Attribute - { - return Attribute::make( - get: fn (string $value) => $this->get_environment_variables($value), - set: fn (string $value) => $this->set_environment_variables($value), - ); - } + protected function key(): Attribute { return Attribute::make( diff --git a/app/Models/GithubApp.php b/app/Models/GithubApp.php index 54e68aa63..74462e8d7 100644 --- a/app/Models/GithubApp.php +++ b/app/Models/GithubApp.php @@ -6,7 +6,7 @@ class GithubApp extends BaseModel { - protected $fillable = ['name', 'uuid', 'organization', 'api_url', 'html_url', 'custom_user', 'custom_port', 'team_id', 'client_secret', 'webhook_secret']; + protected $guarded = []; protected $appends = ['type']; protected $casts = [ 'is_public' => 'boolean', @@ -16,6 +16,17 @@ class GithubApp extends BaseModel 'client_secret', 'webhook_secret', ]; + + static public function public() + { + return GithubApp::whereTeamId(auth()->user()->currentTeam()->id)->whereisPublic(true)->whereNotNull('app_id')->get(); + } + + static public function private() + { + return GithubApp::whereTeamId(auth()->user()->currentTeam()->id)->whereisPublic(false)->whereNotNull('app_id')->get(); + } + protected static function booted(): void { static::deleting(function (GithubApp $github_app) { @@ -25,14 +36,17 @@ protected static function booted(): void } }); } + public function applications() { return $this->morphMany(Application::class, 'source'); } + public function privateKey() { return $this->belongsTo(PrivateKey::class); } + public function type(): Attribute { return Attribute::make( @@ -43,12 +57,4 @@ public function type(): Attribute }, ); } - static public function public() - { - return GithubApp::whereTeamId(session('currentTeam')->id)->whereisPublic(true)->whereNotNull('app_id')->get(); - } - static public function private() - { - return GithubApp::whereTeamId(session('currentTeam')->id)->whereisPublic(false)->whereNotNull('app_id')->get(); - } } diff --git a/app/Models/GitlabApp.php b/app/Models/GitlabApp.php index dbac3414b..a789a7e65 100644 --- a/app/Models/GitlabApp.php +++ b/app/Models/GitlabApp.php @@ -8,10 +8,12 @@ class GitlabApp extends BaseModel 'webhook_token', 'app_secret', ]; + public function applications() { return $this->morphMany(Application::class, 'source'); } + public function privateKey() { return $this->belongsTo(PrivateKey::class); diff --git a/app/Models/InstanceSettings.php b/app/Models/InstanceSettings.php index b5c01f1fb..0fc892d92 100644 --- a/app/Models/InstanceSettings.php +++ b/app/Models/InstanceSettings.php @@ -3,37 +3,29 @@ namespace App\Models; use App\Notifications\Channels\SendsEmail; -use Illuminate\Contracts\Database\Eloquent\Builder; use Illuminate\Database\Eloquent\Model; use Illuminate\Notifications\Notifiable; -use Spatie\SchemalessAttributes\Casts\SchemalessAttributes; -use Spatie\SchemalessAttributes\SchemalessAttributesTrait; class InstanceSettings extends Model implements SendsEmail { - use Notifiable, SchemalessAttributesTrait; + use Notifiable; + protected $guarded = []; - protected $schemalessAttributes = [ - 'smtp', - ]; protected $casts = [ - 'smtp' => SchemalessAttributes::class, 'resale_license' => 'encrypted', ]; - public function scopeWithSmtp(): Builder + + public static function get() { - return $this->smtp->modelScope(); + return InstanceSettings::findOrFail(0); } - public function routeNotificationForEmail(string $attribute = 'test_recipients') + + public function getRecepients($notification) { - $recipients = $this->smtp->get($attribute, ''); + $recipients = data_get($notification, 'emails', null); if (is_null($recipients) || $recipients === '') { return []; } return explode(',', $recipients); } - public static function get() - { - return InstanceSettings::findOrFail(0); - } } diff --git a/app/Models/LocalPersistentVolume.php b/app/Models/LocalPersistentVolume.php index 96b550969..a35f31cdc 100644 --- a/app/Models/LocalPersistentVolume.php +++ b/app/Models/LocalPersistentVolume.php @@ -8,30 +8,32 @@ class LocalPersistentVolume extends Model { - protected $fillable = [ - 'name', - 'mount_path', - 'host_path', - 'container_id', - 'resource_id', - 'resource_type', - ]; + protected $guarded = []; + public function application() { return $this->morphTo(); } + + public function standalone_postgresql() + { + return $this->morphTo(); + } + protected function name(): Attribute { return Attribute::make( set: fn (string $value) => Str::of($value)->trim()->value, ); } + protected function mountPath(): Attribute { return Attribute::make( set: fn (string $value) => Str::of($value)->trim()->start('/')->value ); } + protected function hostPath(): Attribute { return Attribute::make( diff --git a/app/Models/PrivateKey.php b/app/Models/PrivateKey.php index 82858ff22..4d59e9ca8 100644 --- a/app/Models/PrivateKey.php +++ b/app/Models/PrivateKey.php @@ -12,27 +12,13 @@ class PrivateKey extends BaseModel 'is_git_related', 'team_id', ]; + static public function ownedByCurrentTeam(array $select = ['*']) { $selectArray = collect($select)->concat(['id']); - return PrivateKey::whereTeamId(session('currentTeam')->id)->select($selectArray->all()); - } - public function applications() - { - return $this->hasMany(Application::class); - } - public function githubApps() - { - return $this->hasMany(GithubApp::class); - } - public function gitlabApps() - { - return $this->hasMany(GitlabApp::class); - } - public function servers() - { - return $this->hasMany(Server::class); + return PrivateKey::whereTeamId(auth()->user()->currentTeam()->id)->select($selectArray->all()); } + public function isEmpty() { if ($this->servers()->count() === 0 && $this->applications()->count() === 0 && $this->githubApps()->count() === 0 && $this->gitlabApps()->count() === 0) { @@ -40,4 +26,24 @@ public function isEmpty() } return false; } + + public function servers() + { + return $this->hasMany(Server::class); + } + + public function applications() + { + return $this->hasMany(Application::class); + } + + public function githubApps() + { + return $this->hasMany(GithubApp::class); + } + + public function gitlabApps() + { + return $this->hasMany(GitlabApp::class); + } } diff --git a/app/Models/Project.php b/app/Models/Project.php index 073c654b6..a50dde167 100644 --- a/app/Models/Project.php +++ b/app/Models/Project.php @@ -4,6 +4,18 @@ class Project extends BaseModel { + protected $fillable = [ + 'name', + 'description', + 'team_id', + 'project_id' + ]; + + static public function ownedByCurrentTeam() + { + return Project::whereTeamId(auth()->user()->currentTeam()->id)->orderBy('name'); + } + protected static function booted() { static::created(function ($project) { @@ -15,31 +27,34 @@ protected static function booted() 'project_id' => $project->id, ]); }); + static::deleted(function ($project) { + $project->environments()->delete(); + $project->settings()->delete(); + }); } - protected $fillable = [ - 'name', - 'description', - 'team_id', - 'project_id' - ]; - static public function ownedByCurrentTeam() - { - return Project::whereTeamId(session('currentTeam')->id)->orderBy('name'); - } - public function team() - { - return $this->belongsTo(Team::class); - } + public function environments() { return $this->hasMany(Environment::class); } + public function settings() { return $this->hasOne(ProjectSetting::class); } + + public function team() + { + return $this->belongsTo(Team::class); + } + public function applications() { return $this->hasManyThrough(Application::class, Environment::class); } + + public function postgresqls() + { + return $this->hasManyThrough(StandalonePostgresql::class, Environment::class); + } } diff --git a/app/Models/S3Storage.php b/app/Models/S3Storage.php new file mode 100644 index 000000000..5cd2f1318 --- /dev/null +++ b/app/Models/S3Storage.php @@ -0,0 +1,33 @@ + 'encrypted', + 'secret' => 'encrypted', + ]; + + static public function ownedByCurrentTeam(array $select = ['*']) + { + $selectArray = collect($select)->concat(['id']); + return S3Storage::whereTeamId(auth()->user()->currentTeam()->id)->select($selectArray->all())->orderBy('name'); + } + + public function awsUrl() + { + return "{$this->endpoint}/{$this->bucket}"; + } + + public function testConnection() + { + set_s3_target($this); + return \Storage::disk('custom-s3')->files(); + } +} diff --git a/app/Models/ScheduledDatabaseBackup.php b/app/Models/ScheduledDatabaseBackup.php new file mode 100644 index 000000000..5c41fc4dc --- /dev/null +++ b/app/Models/ScheduledDatabaseBackup.php @@ -0,0 +1,33 @@ +morphTo(); + } + + public function latest_log(): HasOne + { + return $this->hasOne(ScheduledDatabaseBackupExecution::class)->latest(); + } + + public function executions(): HasMany + { + return $this->hasMany(ScheduledDatabaseBackupExecution::class); + } + + public function s3() + { + return $this->belongsTo(S3Storage::class, 's3_storage_id'); + } +} diff --git a/app/Models/ScheduledDatabaseBackupExecution.php b/app/Models/ScheduledDatabaseBackupExecution.php new file mode 100644 index 000000000..b06dd5b45 --- /dev/null +++ b/app/Models/ScheduledDatabaseBackupExecution.php @@ -0,0 +1,15 @@ +belongsTo(ScheduledDatabaseBackup::class); + } +} diff --git a/app/Models/Server.php b/app/Models/Server.php index 31e2d1e5c..91adb6b9b 100644 --- a/app/Models/Server.php +++ b/app/Models/Server.php @@ -9,12 +9,47 @@ class Server extends BaseModel { use SchemalessAttributesTrait; - protected $schemalessAttributes = [ - 'proxy', - ]; + public $casts = [ 'proxy' => SchemalessAttributes::class, ]; + protected $schemalessAttributes = [ + 'proxy', + ]; + protected $fillable = [ + 'name', + 'ip', + 'user', + 'port', + 'team_id', + 'private_key_id', + 'proxy', + ]; + + static public function isReachable() + { + return Server::ownedByCurrentTeam()->whereRelation('settings', 'is_reachable', true); + } + + static public function ownedByCurrentTeam(array $select = ['*']) + { + $selectArray = collect($select)->concat(['id']); + return Server::whereTeamId(auth()->user()->currentTeam()->id)->with('settings')->select($selectArray->all())->orderBy('name'); + } + + static public function isUsable() + { + return Server::ownedByCurrentTeam()->whereRelation('settings', 'is_reachable', true)->whereRelation('settings', 'is_usable', true); + } + + static public function destinationsByServer(string $server_id) + { + $server = Server::ownedByCurrentTeam()->get()->where('id', $server_id)->firstOrFail(); + $standaloneDocker = collect($server->standaloneDockers->all()); + $swarmDocker = collect($server->swarmDockers->all()); + return $standaloneDocker->concat($swarmDocker); + } + protected static function booted() { static::created(function ($server) { @@ -26,22 +61,17 @@ protected static function booted() $server->settings()->delete(); }); } - protected $fillable = [ - 'name', - 'ip', - 'user', - 'port', - 'team_id', - 'private_key_id', - 'proxy', - ]; - + public function settings() + { + return $this->hasOne(ServerSetting::class); + } public function scopeWithProxy(): Builder { return $this->proxy->modelScope(); } + public function isEmpty() { if ($this->applications()->count() === 0) { @@ -49,18 +79,21 @@ public function isEmpty() } return false; } + public function applications() { return $this->destinations()->map(function ($standaloneDocker) { return $standaloneDocker->applications; })->flatten(); } + public function destinations() { $standalone_docker = $this->hasMany(StandaloneDocker::class)->get(); $swarm_docker = $this->hasMany(SwarmDocker::class)->get(); return $standalone_docker->concat($swarm_docker); } + public function standaloneDockers() { return $this->hasMany(StandaloneDocker::class); @@ -76,38 +109,13 @@ public function privateKey() return $this->belongsTo(PrivateKey::class); } - public function settings() - { - return $this->hasOne(ServerSetting::class); - } public function muxFilename() { return "{$this->ip}_{$this->port}_{$this->user}"; } + public function team() { return $this->belongsTo(Team::class); } - static public function ownedByCurrentTeam(array $select = ['*']) - { - $selectArray = collect($select)->concat(['id']); - return Server::whereTeamId(session('currentTeam')->id)->with('settings')->select($selectArray->all())->orderBy('name'); - } - - static public function isReachable() - { - return Server::ownedByCurrentTeam()->whereRelation('settings', 'is_reachable', true); - } - static public function isUsable() - { - return Server::ownedByCurrentTeam()->whereRelation('settings', 'is_reachable', true)->whereRelation('settings', 'is_usable', true); - } - - static public function destinationsByServer(string $server_id) - { - $server = Server::ownedByCurrentTeam()->get()->where('id', $server_id)->firstOrFail(); - $standaloneDocker = collect($server->standaloneDockers->all()); - $swarmDocker = collect($server->swarmDockers->all()); - return $standaloneDocker->concat($swarmDocker); - } } diff --git a/app/Models/ServerSetting.php b/app/Models/ServerSetting.php index 6fdf931e7..198600735 100644 --- a/app/Models/ServerSetting.php +++ b/app/Models/ServerSetting.php @@ -10,6 +10,7 @@ class ServerSetting extends Model 'server_id', 'is_usable', ]; + public function server() { return $this->belongsTo(Server::class); diff --git a/app/Models/Service.php b/app/Models/Service.php deleted file mode 100644 index e3df5dfa7..000000000 --- a/app/Models/Service.php +++ /dev/null @@ -1,16 +0,0 @@ -belongsTo(Environment::class); - } - public function destination() - { - return $this->morphTo(); - } -} diff --git a/app/Models/StandaloneDocker.php b/app/Models/StandaloneDocker.php index ea257dd60..6ecc46a97 100644 --- a/app/Models/StandaloneDocker.php +++ b/app/Models/StandaloneDocker.php @@ -4,23 +4,23 @@ class StandaloneDocker extends BaseModel { - protected $fillable = [ - 'name', - 'network', - 'server_id', - ]; + protected $guarded = []; + public function applications() { return $this->morphMany(Application::class, 'destination'); } - public function databases() + + public function postgresqls() { - return $this->morphMany(Database::class, 'destination'); + return $this->morphMany(StandalonePostgresql::class, 'destination'); } + public function server() { return $this->belongsTo(Server::class); } + public function attachedTo() { return $this->applications->count() > 0 || $this->databases->count() > 0; diff --git a/app/Models/StandalonePostgresql.php b/app/Models/StandalonePostgresql.php new file mode 100644 index 000000000..6c00e302c --- /dev/null +++ b/app/Models/StandalonePostgresql.php @@ -0,0 +1,86 @@ + 'array', + 'postgres_password' => 'encrypted', + ]; + + protected static function booted() + { + static::created(function ($database) { + LocalPersistentVolume::create([ + 'name' => 'postgres-data-' . $database->uuid, + 'mount_path' => '/var/lib/postgresql/data', + 'host_path' => null, + 'resource_id' => $database->id, + 'resource_type' => $database->getMorphClass(), + 'is_readonly' => true + ]); + }); + } + + public function portsMappings(): Attribute + { + return Attribute::make( + set: fn ($value) => $value === "" ? null : $value, + ); + } + + // Normal Deployments + + public function portsMappingsArray(): Attribute + { + return Attribute::make( + get: fn () => is_null($this->ports_mappings) + ? [] + : explode(',', $this->ports_mappings), + + ); + } + + public function type(): string + { + return 'standalone-postgresql'; + } + + public function environment() + { + return $this->belongsTo(Environment::class); + } + + public function destination() + { + return $this->morphTo(); + } + + public function environment_variables(): HasMany + { + return $this->hasMany(EnvironmentVariable::class); + } + + public function runtime_environment_variables(): HasMany + { + return $this->hasMany(EnvironmentVariable::class); + } + + public function persistentStorages() + { + return $this->morphMany(LocalPersistentVolume::class, 'resource'); + } + + public function scheduledBackups() + { + return $this->morphMany(ScheduledDatabaseBackup::class, 'database'); + } +} diff --git a/app/Models/Subscription.php b/app/Models/Subscription.php index 380660b77..4b78c37a9 100644 --- a/app/Models/Subscription.php +++ b/app/Models/Subscription.php @@ -2,14 +2,32 @@ namespace App\Models; -use Illuminate\Database\Eloquent\Factories\HasFactory; use Illuminate\Database\Eloquent\Model; class Subscription extends Model { protected $guarded = []; + public function team() { return $this->belongsTo(Team::class); } + public function type() + { + $basic = explode(',', config('coolify.lemon_squeezy_basic_plan_ids')); + $pro = explode(',', config('coolify.lemon_squeezy_pro_plan_ids')); + $ultimate = explode(',', config('coolify.lemon_squeezy_ultimate_plan_ids')); + + $subscription = $this->lemon_variant_id; + if (in_array($subscription, $basic)) { + return 'basic'; + } + if (in_array($subscription, $pro)) { + return 'pro'; + } + if (in_array($subscription, $ultimate)) { + return 'ultimate'; + } + return 'unknown'; + } } diff --git a/app/Models/SwarmDocker.php b/app/Models/SwarmDocker.php index b0f341b9b..ea56f85bc 100644 --- a/app/Models/SwarmDocker.php +++ b/app/Models/SwarmDocker.php @@ -8,6 +8,7 @@ public function applications() { return $this->morphMany(Application::class, 'destination'); } + public function server() { return $this->belongsTo(Server::class); diff --git a/app/Models/Team.php b/app/Models/Team.php index ad860dd72..d8d486fec 100644 --- a/app/Models/Team.php +++ b/app/Models/Team.php @@ -2,74 +2,63 @@ namespace App\Models; -use App\Notifications\Channels\SendsEmail; use App\Notifications\Channels\SendsDiscord; -use Illuminate\Database\Eloquent\Builder; +use App\Notifications\Channels\SendsEmail; use Illuminate\Database\Eloquent\Model; use Illuminate\Notifications\Notifiable; -use Spatie\SchemalessAttributes\Casts\SchemalessAttributes; -use Spatie\SchemalessAttributes\SchemalessAttributesTrait; class Team extends Model implements SendsDiscord, SendsEmail { - use Notifiable, SchemalessAttributesTrait; + use Notifiable; - protected $schemalessAttributes = [ - 'smtp', - 'discord', - 'smtp_notifications', - 'discord_notifications', - ]; + protected $guarded = []; protected $casts = [ - 'smtp' => SchemalessAttributes::class, - 'discord' => SchemalessAttributes::class, - 'smtp_notifications' => SchemalessAttributes::class, - 'discord_notifications' => SchemalessAttributes::class, 'personal_team' => 'boolean', ]; - public function scopeWithSmtp(): Builder - { - return $this->smtp->modelScope(); - } - public function scopeWithDiscord(): Builder - { - return $this->discord->modelScope(); - } - public function scopeWithSmtpNotifications(): Builder - { - return $this->smtp_notifications->modelScope(); - } - public function scopeWithDiscordNotifications(): Builder - { - return $this->discord_notifications->modelScope(); - } - protected $fillable = [ - 'id', - 'name', - 'description', - 'personal_team', - 'smtp', - 'discord' - ]; public function routeNotificationForDiscord() { - return $this->discord->get('webhook_url'); + return data_get($this, 'discord_webhook_url', null); } - public function routeNotificationForEmail(string $attribute = 'recipients') + + public function getRecepients($notification) { - $recipients = $this->smtp->get($attribute, ''); - if (is_null($recipients) || $recipients === '') { - return []; + $recipients = data_get($notification, 'emails', null); + if (is_null($recipients)) { + $recipients = $this->members()->pluck('email')->toArray(); + return $recipients; } return explode(',', $recipients); } + public function members() + { + return $this->belongsToMany(User::class, 'team_user', 'team_id', 'user_id')->withPivot('role'); + } public function subscription() { return $this->hasOne(Subscription::class); } + + public function applications() + { + return $this->hasManyThrough(Application::class, Project::class); + } + + public function invitations() + { + return $this->hasMany(TeamInvitation::class); + } + + public function isEmpty() + { + if ($this->projects()->count() === 0 && $this->servers()->count() === 0 && $this->privateKeys()->count() === 0 && $this->sources()->count() === 0) { + return true; + } + return false; + } + public function projects() { return $this->hasMany(Project::class); @@ -80,23 +69,11 @@ public function servers() return $this->hasMany(Server::class); } - public function applications() - { - return $this->hasManyThrough(Application::class, Project::class); - } - public function privateKeys() { return $this->hasMany(PrivateKey::class); } - public function members() - { - return $this->belongsToMany(User::class, 'team_user', 'team_id', 'user_id')->withPivot('role'); - } - public function invitations() - { - return $this->hasMany(TeamInvitation::class); - } + public function sources() { $sources = collect([]); @@ -106,11 +83,9 @@ public function sources() $sources = $sources->merge($github_apps)->merge($gitlab_apps); return $sources; } - public function isEmpty() + + public function s3s() { - if ($this->projects()->count() === 0 && $this->servers()->count() === 0 && $this->privateKeys()->count() === 0 && $this->sources()->count() === 0) { - return true; - } - return false; + return $this->hasMany(S3Storage::class); } } diff --git a/app/Models/TeamInvitation.php b/app/Models/TeamInvitation.php index c326d7d40..a5c382b3b 100644 --- a/app/Models/TeamInvitation.php +++ b/app/Models/TeamInvitation.php @@ -14,6 +14,7 @@ class TeamInvitation extends Model 'link', 'via', ]; + public function team() { return $this->belongsTo(Team::class); diff --git a/app/Models/User.php b/app/Models/User.php index 13753a210..f0a85d182 100644 --- a/app/Models/User.php +++ b/app/Models/User.php @@ -3,30 +3,28 @@ namespace App\Models; use App\Notifications\Channels\SendsEmail; +use App\Notifications\TransactionalEmails\ResetPassword as TransactionalEmailsResetPassword; +use App\Notifications\TrnsactionalEmails\ResetPassword; use Illuminate\Database\Eloquent\Factories\HasFactory; -use Illuminate\Database\Eloquent\Model; use Illuminate\Foundation\Auth\User as Authenticatable; use Illuminate\Notifications\Notifiable; -use Laravel\Sanctum\HasApiTokens; -use Visus\Cuid2\Cuid2; use Laravel\Fortify\TwoFactorAuthenticatable; +use Laravel\Sanctum\HasApiTokens; class User extends Authenticatable implements SendsEmail { use HasApiTokens, HasFactory, Notifiable, TwoFactorAuthenticatable; - protected $fillable = [ - 'id', - 'name', - 'email', - 'password', - ]; + + protected $guarded = []; protected $hidden = [ 'password', 'remember_token', ]; protected $casts = [ 'email_verified_at' => 'datetime', + 'force_password_reset' => 'boolean', ]; + protected static function boot() { parent::boot(); @@ -34,20 +32,6 @@ protected static function boot() $team = [ 'name' => $user->name . "'s Team", 'personal_team' => true, - 'smtp' => [ - 'enabled' => false, - ], - 'smtp_notifications' => [ - 'test' => true, - 'deployments' => false, - ], - 'discord' => [ - 'enabled' => false, - ], - 'discord_notifications' => [ - 'test' => true, - 'deployments' => false, - ], ]; if ($user->id === 0) { $team['id'] = 0; @@ -57,14 +41,27 @@ protected static function boot() $user->teams()->attach($new_team, ['role' => 'owner']); }); } - public function routeNotificationForEmail() + + public function teams() + { + return $this->belongsToMany(Team::class)->withPivot('role'); + } + + public function getRecepients($notification) { return $this->email; } + + public function sendPasswordResetNotification($token): void + { + $this->notify(new TransactionalEmailsResetPassword($token)); + } + public function isAdmin() { return $this->pivot->role === 'admin' || $this->pivot->role === 'owner'; } + public function isAdminFromSession() { if (auth()->user()->id === 0) { @@ -79,9 +76,10 @@ public function isAdminFromSession() if ($is_part_of_root_team && $is_admin_of_root_team) { return true; } - $role = $teams->where('id', session('currentTeam')->id)->first()->pivot->role; + $role = $teams->where('id', auth()->user()->id)->first()->pivot->role; return $role === 'admin' || $role === 'owner'; } + public function isInstanceAdmin() { $found_root_team = auth()->user()->teams->filter(function ($team) { @@ -92,35 +90,36 @@ public function isInstanceAdmin() }); return $found_root_team->count() > 0; } + public function personalTeam() { return $this->teams()->where('personal_team', true)->first(); } - public function teams() - { - return $this->belongsToMany(Team::class)->withPivot('role'); - } + public function currentTeam() { return $this->teams()->where('team_id', session('currentTeam')->id)->first(); } + public function otherTeams() { - $team_id = session('currentTeam')->id; + $team_id = auth()->user()->currentTeam()->id; return auth()->user()->teams->filter(function ($team) use ($team_id) { return $team->id != $team_id; }); } + public function role() { if ($this->teams()->where('team_id', 0)->first()) { return 'admin'; } - return $this->teams()->where('team_id', session('currentTeam')->id)->first()->pivot->role; + return $this->teams()->where('team_id', auth()->user()->currentTeam()->id)->first()->pivot->role; } + public function resources() { - $team_id = session('currentTeam')->id; + $team_id = auth()->user()->currentTeam()->id; $data = Application::where('team_id', $team_id)->get(); return $data; } diff --git a/app/Models/Waitlist.php b/app/Models/Waitlist.php new file mode 100644 index 000000000..552c25eb3 --- /dev/null +++ b/app/Models/Waitlist.php @@ -0,0 +1,11 @@ + 'encrypted', ]; -} \ No newline at end of file +} diff --git a/app/Notifications/Notifications/Application/DeployedWithErrorNotification.php b/app/Notifications/Application/DeploymentFailed.php similarity index 77% rename from app/Notifications/Notifications/Application/DeployedWithErrorNotification.php rename to app/Notifications/Application/DeploymentFailed.php index 1418b7e8e..bc7f5dfde 100644 --- a/app/Notifications/Notifications/Application/DeployedWithErrorNotification.php +++ b/app/Notifications/Application/DeploymentFailed.php @@ -1,21 +1,21 @@ application = $application; $this->deployment_uuid = $deployment_uuid; $this->preview = $preview; - $this->application_name = data_get($application, 'name'); $this->project_uuid = data_get($application, 'environment.project.uuid'); $this->environment_name = data_get($application, 'environment.name'); @@ -39,24 +38,26 @@ public function __construct(Application $application, string $deployment_uuid, A if (Str::of($this->fqdn)->explode(',')->count() > 1) { $this->fqdn = Str::of($this->fqdn)->explode(',')->first(); } - $this->deployment_url = base_url() . "/project/{$this->project_uuid}/{$this->environment_name}/application/{$this->application->uuid}/deployment/{$this->deployment_uuid}"; + $this->deployment_url = base_url() . "/project/{$this->project_uuid}/{$this->environment_name}/application/{$this->application->uuid}/deployment/{$this->deployment_uuid}"; } + public function via(object $notifiable): array { $channels = []; - $isEmailEnabled = data_get($notifiable, 'smtp.enabled'); - $isDiscordEnabled = data_get($notifiable, 'discord.enabled'); - $isSubscribedToEmailDeployments = data_get($notifiable, 'smtp_notifications.deployments'); - $isSubscribedToDiscordDeployments = data_get($notifiable, 'discord_notifications.deployments'); + $isEmailEnabled = data_get($notifiable, 'smtp_enabled'); + $isDiscordEnabled = data_get($notifiable, 'discord_enabled'); + $isSubscribedToEmailEvent = data_get($notifiable, 'smtp_notifications_deployments'); + $isSubscribedToDiscordEvent = data_get($notifiable, 'discord_notifications_deployments'); - if ($isEmailEnabled && $isSubscribedToEmailDeployments) { + if ($isEmailEnabled && $isSubscribedToEmailEvent) { $channels[] = EmailChannel::class; } - if ($isDiscordEnabled && $isSubscribedToDiscordDeployments) { + if ($isDiscordEnabled && $isSubscribedToDiscordEvent) { $channels[] = DiscordChannel::class; } return $channels; } + public function toMail(): MailMessage { $mail = new MailMessage(); @@ -69,7 +70,7 @@ public function toMail(): MailMessage $mail->subject('❌ Pull request #' . $this->preview->pull_request_id . ' of ' . $this->application_name . ' deployment failed.'); } - $mail->view('emails.application-deployed-with-error', [ + $mail->view('emails.application-deployment-failed', [ 'name' => $this->application_name, 'fqdn' => $fqdn, 'deployment_url' => $this->deployment_url, diff --git a/app/Notifications/Notifications/Application/DeployedSuccessfullyNotification.php b/app/Notifications/Application/DeploymentSuccess.php similarity index 65% rename from app/Notifications/Notifications/Application/DeployedSuccessfullyNotification.php rename to app/Notifications/Application/DeploymentSuccess.php index 916cc82b8..99bd2532c 100644 --- a/app/Notifications/Notifications/Application/DeployedSuccessfullyNotification.php +++ b/app/Notifications/Application/DeploymentSuccess.php @@ -1,21 +1,21 @@ fqdn)->explode(',')->count() > 1) { $this->fqdn = Str::of($this->fqdn)->explode(',')->first(); } - $this->deployment_url = base_url() . "/project/{$this->project_uuid}/{$this->environment_name}/application/{$this->application->uuid}/deployment/{$this->deployment_uuid}"; + $this->deployment_url = base_url() . "/project/{$this->project_uuid}/{$this->environment_name}/application/{$this->application->uuid}/deployment/{$this->deployment_uuid}"; } + public function via(object $notifiable): array { $channels = []; - $isEmailEnabled = data_get($notifiable, 'smtp.enabled'); - $isDiscordEnabled = data_get($notifiable, 'discord.enabled'); - $isSubscribedToEmailDeployments = data_get($notifiable, 'smtp_notifications.deployments'); - $isSubscribedToDiscordDeployments = data_get($notifiable, 'discord_notifications.deployments'); + $isEmailEnabled = data_get($notifiable, 'smtp_enabled'); + $isDiscordEnabled = data_get($notifiable, 'discord_enabled'); + $isSubscribedToEmailEvent = data_get($notifiable, 'smtp_notifications_deployments'); + $isSubscribedToDiscordEvent = data_get($notifiable, 'discord_notifications_deployments'); - if ($isEmailEnabled && $isSubscribedToEmailDeployments) { + if ($isEmailEnabled && $isSubscribedToEmailEvent) { $channels[] = EmailChannel::class; } - if ($isDiscordEnabled && $isSubscribedToDiscordDeployments) { + if ($isDiscordEnabled && $isSubscribedToDiscordEvent) { $channels[] = DiscordChannel::class; } return $channels; } + public function toMail(): MailMessage { $mail = new MailMessage(); $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("✅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->view('emails.application-deployed-successfully', [ + $mail->view('emails.application-deployment-success', [ 'name' => $this->application_name, 'fqdn' => $fqdn, 'deployment_url' => $this->deployment_url, @@ -79,15 +81,19 @@ public function toMail(): MailMessage public function toDiscord(): string { if ($this->preview) { - $message = '✅ Pull request #' . $this->preview->pull_request_id . ' of **' . $this->application_name . '** deployed successfully: '; + $message = '✅ New PR' . $this->preview->pull_request_id . ' version successfully deployed of ' . $this->application_name . ' + +'; if ($this->preview->fqdn) { - $message .= '[Application Link](' . $this->preview->fqdn . ') |'; + $message .= '[Open Application](' . $this->preview->fqdn . ') | '; } $message .= '[Deployment logs](' . $this->deployment_url . ')'; } else { - $message = '✅ A new version has been deployed of **' . $this->application_name . '**: '; + $message = '✅ New version successfully deployed of ' . $this->application_name . ' + +'; if ($this->fqdn) { - $message .= '[Application Link](' . $this->fqdn . ') |'; + $message .= '[Open Application](' . $this->fqdn . ') | '; } $message .= '[Deployment logs](' . $this->deployment_url . ')'; } diff --git a/app/Notifications/Application/StatusChanged.php b/app/Notifications/Application/StatusChanged.php new file mode 100644 index 000000000..9c6b99fc7 --- /dev/null +++ b/app/Notifications/Application/StatusChanged.php @@ -0,0 +1,76 @@ +application = $application; + $this->application_name = data_get($application, 'name'); + $this->project_uuid = data_get($application, 'environment.project.uuid'); + $this->environment_name = data_get($application, 'environment.name'); + $this->fqdn = data_get($application, 'fqdn', null); + if (Str::of($this->fqdn)->explode(',')->count() > 1) { + $this->fqdn = Str::of($this->fqdn)->explode(',')->first(); + } + $this->application_url = base_url() . "/project/{$this->project_uuid}/{$this->environment_name}/application/{$this->application->uuid}"; + } + + public function via(object $notifiable): array + { + $channels = []; + $isEmailEnabled = data_get($notifiable, 'smtp_enabled'); + $isDiscordEnabled = data_get($notifiable, 'discord_enabled'); + $isSubscribedToEmailEvent = data_get($notifiable, 'smtp_notifications_status_changes'); + $isSubscribedToDiscordEvent = data_get($notifiable, 'discord_notifications_status_changes'); + + if ($isEmailEnabled && $isSubscribedToEmailEvent) { + $channels[] = EmailChannel::class; + } + if ($isDiscordEnabled && $isSubscribedToDiscordEvent) { + $channels[] = DiscordChannel::class; + } + return $channels; + } + + public function toMail(): MailMessage + { + $mail = new MailMessage(); + $fqdn = $this->fqdn; + $mail->subject("⛔ {$this->application_name} has been stopped"); + $mail->view('emails.application-status-changes', [ + 'name' => $this->application_name, + 'fqdn' => $fqdn, + 'application_url' => $this->application_url, + ]); + return $mail; + } + + public function toDiscord(): string + { + $message = '⛔ ' . $this->application_name . ' has been stopped. + +'; + $message .= '[Application URL](' . $this->application_url . ')'; + return $message; + } +} diff --git a/app/Notifications/Channels/EmailChannel.php b/app/Notifications/Channels/EmailChannel.php index 20c641b5d..638939589 100644 --- a/app/Notifications/Channels/EmailChannel.php +++ b/app/Notifications/Channels/EmailChannel.php @@ -2,6 +2,7 @@ namespace App\Notifications\Channels; +use Exception; use Illuminate\Mail\Message; use Illuminate\Notifications\Notification; use Illuminate\Support\Facades\Mail; @@ -11,23 +12,23 @@ class EmailChannel public function send(SendsEmail $notifiable, Notification $notification): void { $this->bootConfigs($notifiable); + ray($notification); + $recepients = $notifiable->getRecepients($notification); - $bcc = $notifiable->routeNotificationForEmail('test_recipients'); - if (count($bcc) === 0) { - if ($notifiable instanceof \App\Models\Team) { - $bcc = $notifiable->members()->pluck('email')->toArray(); - } + if (count($recepients) === 0) { + throw new Exception('No email recipients found'); } + $mailMessage = $notification->toMail($notifiable); Mail::send( [], [], fn (Message $message) => $message ->from( - data_get($notifiable, 'smtp.from_address'), - data_get($notifiable, 'smtp.from_name'), + data_get($notifiable, 'smtp_from_address'), + data_get($notifiable, 'smtp_from_name'), ) - ->bcc($bcc) + ->bcc($recepients) ->subject($mailMessage->subject) ->html((string)$mailMessage->render()) ); @@ -35,18 +36,18 @@ public function send(SendsEmail $notifiable, Notification $notification): void private function bootConfigs($notifiable): void { - $password = data_get($notifiable, 'smtp.password'); + $password = data_get($notifiable, 'smtp_password'); if ($password) $password = decrypt($password); config()->set('mail.default', 'smtp'); config()->set('mail.mailers.smtp', [ "transport" => "smtp", - "host" => data_get($notifiable, 'smtp.host'), - "port" => data_get($notifiable, 'smtp.port'), - "encryption" => data_get($notifiable, 'smtp.encryption'), - "username" => data_get($notifiable, 'smtp.username'), + "host" => data_get($notifiable, 'smtp_host'), + "port" => data_get($notifiable, 'smtp_port'), + "encryption" => data_get($notifiable, 'smtp_encryption'), + "username" => data_get($notifiable, 'smtp_username'), "password" => $password, - "timeout" => data_get($notifiable, 'smtp.timeout'), + "timeout" => data_get($notifiable, 'smtp_timeout'), "local_domain" => null, ]); } diff --git a/app/Notifications/Channels/SendsEmail.php b/app/Notifications/Channels/SendsEmail.php index ca72eef41..fc7528834 100644 --- a/app/Notifications/Channels/SendsEmail.php +++ b/app/Notifications/Channels/SendsEmail.php @@ -4,5 +4,5 @@ interface SendsEmail { - public function routeNotificationForEmail(); + public function getRecepients($notification); } diff --git a/app/Notifications/Channels/TransactionalEmailChannel.php b/app/Notifications/Channels/TransactionalEmailChannel.php index 431469e10..bf968eb3f 100644 --- a/app/Notifications/Channels/TransactionalEmailChannel.php +++ b/app/Notifications/Channels/TransactionalEmailChannel.php @@ -13,7 +13,7 @@ class TransactionalEmailChannel public function send(User $notifiable, Notification $notification): void { $settings = InstanceSettings::get(); - if (data_get($settings, 'smtp.enabled') !== true) { + if (data_get($settings, 'smtp_enabled') !== true) { return; } $email = $notifiable->email; @@ -27,8 +27,8 @@ public function send(User $notifiable, Notification $notification): void [], fn (Message $message) => $message ->from( - data_get($settings, 'smtp.from_address'), - data_get($settings, 'smtp.from_name') + data_get($settings, 'smtp_from_address'), + data_get($settings, 'smtp_from_name') ) ->to($email) ->subject($mailMessage->subject) diff --git a/app/Notifications/Database/BackupFailed.php b/app/Notifications/Database/BackupFailed.php new file mode 100644 index 000000000..b47f03291 --- /dev/null +++ b/app/Notifications/Database/BackupFailed.php @@ -0,0 +1,55 @@ +message = "❌ Database backup for {$database->name} with frequency of $backup->frequency was FAILED.\n\nReason: $output"; + } + + public function via(object $notifiable): array + { + $channels = []; + $isEmailEnabled = data_get($notifiable, 'smtp_enabled'); + $isDiscordEnabled = data_get($notifiable, 'discord_enabled'); + $isSubscribedToEmailEvent = data_get($notifiable, 'smtp_notifications_database_backups'); + $isSubscribedToDiscordEvent = data_get($notifiable, 'discord_notifications_database_backups'); + + if ($isEmailEnabled && $isSubscribedToEmailEvent) { + $channels[] = EmailChannel::class; + } + if ($isDiscordEnabled && $isSubscribedToDiscordEvent) { + $channels[] = DiscordChannel::class; + } + ray($channels); + return $channels; + } + + public function toMail(): MailMessage + { + $mail = new MailMessage(); + $mail->subject("❌ Backup FAILED for {$this->database->name}"); + $mail->line($this->message); + return $mail; + } + + public function toDiscord(): string + { + return $this->message; + } +} diff --git a/app/Notifications/Database/BackupSuccess.php b/app/Notifications/Database/BackupSuccess.php new file mode 100644 index 000000000..82b521019 --- /dev/null +++ b/app/Notifications/Database/BackupSuccess.php @@ -0,0 +1,54 @@ +message = "✅ Database backup for {$database->name} with frequency of $backup->frequency was successful."; + } + + public function via(object $notifiable): array + { + $channels = []; + $isEmailEnabled = data_get($notifiable, 'smtp_enabled'); + $isDiscordEnabled = data_get($notifiable, 'discord_enabled'); + $isSubscribedToEmailEvent = data_get($notifiable, 'smtp_notifications_database_backups'); + $isSubscribedToDiscordEvent = data_get($notifiable, 'discord_notifications_database_backups'); + + if ($isEmailEnabled && $isSubscribedToEmailEvent) { + $channels[] = EmailChannel::class; + } + if ($isDiscordEnabled && $isSubscribedToDiscordEvent) { + $channels[] = DiscordChannel::class; + } + return $channels; + } + + public function toMail(): MailMessage + { + $mail = new MailMessage(); + $mail->subject("✅ Backup success for {$this->database->name}"); + $mail->line($this->message); + return $mail; + } + + public function toDiscord(): string + { + return $this->message; + } +} diff --git a/app/Notifications/Notifications/TestNotification.php b/app/Notifications/Test.php similarity index 62% rename from app/Notifications/Notifications/TestNotification.php rename to app/Notifications/Test.php index 7753c155d..4cec28cfd 100644 --- a/app/Notifications/Notifications/TestNotification.php +++ b/app/Notifications/Test.php @@ -1,40 +1,38 @@ type = $type; } + public function via(object $notifiable): array { $channels = []; + $isEmailEnabled = data_get($notifiable, 'smtp_enabled'); + $isDiscordEnabled = data_get($notifiable, 'discord_enabled'); - $isSmtp = $this->type === 'smtp' || is_null($this->type); - $isDiscord = $this->type === 'discord' || is_null($this->type); - $isEmailEnabled = data_get($notifiable, 'smtp.enabled'); - $isDiscordEnabled = data_get($notifiable, 'discord.enabled'); - - if ($isEmailEnabled && $isSmtp) { - $channels[] = EmailChannel::class; - } - if ($isDiscordEnabled && $isDiscord) { + if ($isDiscordEnabled && empty($this->emails)) { $channels[] = DiscordChannel::class; } + if ($isEmailEnabled && !empty($this->emails)) { + $channels[] = EmailChannel::class; + } return $channels; } + public function toMail(): MailMessage { $mail = new MailMessage(); diff --git a/app/Notifications/TransactionalEmails/InvitationLinkEmail.php b/app/Notifications/TransactionalEmails/InvitationLink.php similarity index 90% rename from app/Notifications/TransactionalEmails/InvitationLinkEmail.php rename to app/Notifications/TransactionalEmails/InvitationLink.php index fe53c6fce..409234fd5 100644 --- a/app/Notifications/TransactionalEmails/InvitationLinkEmail.php +++ b/app/Notifications/TransactionalEmails/InvitationLink.php @@ -11,10 +11,11 @@ use Illuminate\Notifications\Messages\MailMessage; use Illuminate\Notifications\Notification; -class InvitationLinkEmail extends Notification implements ShouldQueue +class InvitationLink extends Notification implements ShouldQueue { use Queueable; - public function via() + + public function via(): array { return [TransactionalEmailChannel::class]; } diff --git a/app/Notifications/TransactionalEmails/ResetPassword.php b/app/Notifications/TransactionalEmails/ResetPassword.php new file mode 100644 index 000000000..e9baa16d1 --- /dev/null +++ b/app/Notifications/TransactionalEmails/ResetPassword.php @@ -0,0 +1,86 @@ +settings = InstanceSettings::get(); + $this->token = $token; + } + + public static function createUrlUsing($callback) + { + static::$createUrlCallback = $callback; + } + + public static function toMailUsing($callback) + { + static::$toMailCallback = $callback; + } + + public function via($notifiable) + { + if ($this->settings->smtp_enabled) { + $password = data_get($this->settings, 'smtp_password'); + if ($password) $password = decrypt($password); + + config()->set('mail.default', 'smtp'); + config()->set('mail.mailers.smtp', [ + "transport" => "smtp", + "host" => data_get($this->settings, 'smtp_host'), + "port" => data_get($this->settings, 'smtp_port'), + "encryption" => data_get($this->settings, 'smtp_encryption'), + "username" => data_get($this->settings, 'smtp_username'), + "password" => $password, + "timeout" => data_get($this->settings, 'smtp_timeout'), + "local_domain" => null, + ]); + return ['mail']; + } + throw new \Exception('SMTP is not enabled'); + } + + public function toMail($notifiable) + { + if (static::$toMailCallback) { + return call_user_func(static::$toMailCallback, $notifiable, $this->token); + } + + return $this->buildMailMessage($this->resetUrl($notifiable)); + } + + protected function buildMailMessage($url) + { + $mail = new MailMessage(); + $mail->from( + data_get($this->settings, 'smtp_from_address'), + data_get($this->settings, 'smtp_from_name'), + ); + $mail->subject('Reset Password'); + $mail->view('emails.reset-password', ['url' => $url, 'count' => config('auth.passwords.' . config('auth.defaults.passwords') . '.expire')]); + return $mail; + } + + protected function resetUrl($notifiable) + { + if (static::$createUrlCallback) { + return call_user_func(static::$createUrlCallback, $notifiable, $this->token); + } + + return url(route('password.reset', [ + 'token' => $this->token, + 'email' => $notifiable->getEmailForPasswordReset(), + ], false)); + } +} diff --git a/app/Notifications/TransactionalEmails/TestEmail.php b/app/Notifications/TransactionalEmails/Test.php similarity index 81% rename from app/Notifications/TransactionalEmails/TestEmail.php rename to app/Notifications/TransactionalEmails/Test.php index 4dbd7f1c3..f5962fc2a 100644 --- a/app/Notifications/TransactionalEmails/TestEmail.php +++ b/app/Notifications/TransactionalEmails/Test.php @@ -8,13 +8,19 @@ use Illuminate\Notifications\Messages\MailMessage; use Illuminate\Notifications\Notification; -class TestEmail extends Notification implements ShouldQueue +class Test extends Notification implements ShouldQueue { use Queueable; + + public function __construct(public string $emails) + { + } + public function via(): array { return [EmailChannel::class]; } + public function toMail(): MailMessage { $mail = new MailMessage(); diff --git a/app/Providers/AppServiceProvider.php b/app/Providers/AppServiceProvider.php index b8c70b770..07d02cd03 100644 --- a/app/Providers/AppServiceProvider.php +++ b/app/Providers/AppServiceProvider.php @@ -2,7 +2,6 @@ namespace App\Providers; -use Illuminate\Support\Facades\Cache; use Illuminate\Support\Facades\Http; use Illuminate\Support\ServiceProvider; diff --git a/app/Providers/EventServiceProvider.php b/app/Providers/EventServiceProvider.php index 2d65aac0e..87c58d172 100644 --- a/app/Providers/EventServiceProvider.php +++ b/app/Providers/EventServiceProvider.php @@ -5,7 +5,6 @@ use Illuminate\Auth\Events\Registered; use Illuminate\Auth\Listeners\SendEmailVerificationNotification; use Illuminate\Foundation\Support\Providers\EventServiceProvider as ServiceProvider; -use Illuminate\Support\Facades\Event; class EventServiceProvider extends ServiceProvider { diff --git a/app/Providers/FortifyServiceProvider.php b/app/Providers/FortifyServiceProvider.php index 0a3d02ff7..da9282e13 100644 --- a/app/Providers/FortifyServiceProvider.php +++ b/app/Providers/FortifyServiceProvider.php @@ -14,6 +14,7 @@ use Illuminate\Support\Facades\RateLimiter; use Illuminate\Support\ServiceProvider; use Laravel\Fortify\Contracts\RegisterResponse; +use Laravel\Fortify\Features; use Laravel\Fortify\Fortify; class FortifyServiceProvider extends ServiceProvider @@ -41,13 +42,19 @@ public function toResponse($request) */ public function boot(): void { + Fortify::createUsersUsing(CreateNewUser::class); Fortify::registerView(function () { + ray('asd'); $settings = InstanceSettings::get(); if (!$settings->is_registration_enabled) { return redirect()->route('login'); } - return view('auth.register'); + if (config('coolify.waitlist')) { + return view('auth.waitlist'); + } else { + return view('auth.register'); + } }); Fortify::loginView(function () { @@ -91,8 +98,16 @@ public function boot(): void return view('auth.two-factor-challenge'); }); + RateLimiter::for('force-password-reset', function (Request $request) { + return Limit::perMinute(15)->by($request->user()->id); + }); + + RateLimiter::for('forgot-password', function (Request $request) { + return Limit::perMinute(5)->by($request->ip()); + }); + RateLimiter::for('login', function (Request $request) { - $email = (string) $request->email; + $email = (string)$request->email; return Limit::perMinute(5)->by($email . $request->ip()); }); diff --git a/app/Traits/ExecuteRemoteCommand.php b/app/Traits/ExecuteRemoteCommand.php index 6e8c5b761..24e17a73d 100644 --- a/app/Traits/ExecuteRemoteCommand.php +++ b/app/Traits/ExecuteRemoteCommand.php @@ -3,7 +3,6 @@ namespace App\Traits; use App\Enums\ApplicationDeploymentStatus; -use App\Models\ApplicationDeploymentQueue; use App\Models\Server; use Carbon\Carbon; use Illuminate\Support\Collection; @@ -13,6 +12,7 @@ trait ExecuteRemoteCommand { public string|null $save = null; + public function execute_remote_command(...$commands) { static::$batch_counter++; diff --git a/app/View/Components/Forms/Button.php b/app/View/Components/Forms/Button.php index 0f33192f7..2b67c6915 100644 --- a/app/View/Components/Forms/Button.php +++ b/app/View/Components/Forms/Button.php @@ -12,12 +12,15 @@ class Button extends Component * Create a new component instance. */ public function __construct( - public bool $disabled = false, - public bool $isModal = false, + public bool $disabled = false, + public bool $isModal = false, + public bool $noStyle = false, public string|null $modalId = null, - public string $defaultClass = "btn btn-primary btn-xs text-white normal-case no-animation rounded border-none" + public string $defaultClass = "btn btn-primary btn-sm font-normal text-white normal-case no-animation rounded border-none" ) { - // + if ($this->noStyle) { + $this->defaultClass = ""; + } } /** @@ -27,4 +30,4 @@ public function render(): View|Closure|string { return view('components.forms.button'); } -} \ No newline at end of file +} diff --git a/app/View/Components/Forms/Checkbox.php b/app/View/Components/Forms/Checkbox.php index b79066775..1f1d634f3 100644 --- a/app/View/Components/Forms/Checkbox.php +++ b/app/View/Components/Forms/Checkbox.php @@ -17,9 +17,9 @@ public function __construct( public string|null $value = null, public string|null $label = null, public string|null $helper = null, - public bool $instantSave = false, - public bool $disabled = false, - public string $defaultClass = "toggle toggle-xs toggle-warning rounded disabled:bg-coolgray-200 disabled:opacity-50 placeholder:text-neutral-700" + public bool $instantSave = false, + public bool $disabled = false, + public string $defaultClass = "toggle toggle-xs toggle-warning rounded disabled:bg-coolgray-200 disabled:opacity-50 placeholder:text-neutral-700" ) { // } diff --git a/app/View/Components/Forms/Input.php b/app/View/Components/Forms/Input.php index b22bc11dc..6d3346dcf 100644 --- a/app/View/Components/Forms/Input.php +++ b/app/View/Components/Forms/Input.php @@ -6,7 +6,6 @@ use Illuminate\Contracts\View\View; use Illuminate\View\Component; use Visus\Cuid2\Cuid2; -use Illuminate\Support\Str; class Input extends Component { @@ -16,12 +15,12 @@ public function __construct( public string|null $type = 'text', public string|null $value = null, public string|null $label = null, - public bool $required = false, - public bool $disabled = false, - public bool $readonly = false, + public bool $required = false, + public bool $disabled = false, + public bool $readonly = false, public string|null $helper = null, - public bool $allowToPeak = true, - public string $defaultClass = "input input-sm bg-coolgray-200 rounded text-white w-full disabled:bg-coolgray-200/50 disabled:border-none placeholder:text-coolgray-500" + public bool $allowToPeak = true, + public string $defaultClass = "input input-sm bg-coolgray-200 rounded text-white w-full disabled:bg-coolgray-200/50 disabled:border-none placeholder:text-coolgray-500 read-only:text-neutral-500 read-only:bg-coolgray-200/50" ) { } @@ -30,7 +29,7 @@ public function render(): View|Closure|string if (is_null($this->id)) $this->id = new Cuid2(7); if (is_null($this->name)) $this->name = $this->id; - $this->label = Str::title($this->label); + // $this->label = Str::title($this->label); return view('components.forms.input'); } } diff --git a/app/View/Components/Forms/Select.php b/app/View/Components/Forms/Select.php index 8dfd43582..58f59a683 100644 --- a/app/View/Components/Forms/Select.php +++ b/app/View/Components/Forms/Select.php @@ -4,9 +4,9 @@ use Closure; use Illuminate\Contracts\View\View; +use Illuminate\Support\Str; use Illuminate\View\Component; use Visus\Cuid2\Cuid2; -use Illuminate\Support\Str; class Select extends Component { @@ -18,8 +18,8 @@ public function __construct( public string|null $name = null, public string|null $label = null, public string|null $helper = null, - public bool $required = false, - public string $defaultClass = "select select-sm w-full rounded text-white text-sm bg-coolgray-200 font-normal disabled:bg-coolgray-200/50 disabled:border-none" + public bool $required = false, + public string $defaultClass = "select select-sm w-full rounded text-white text-sm bg-coolgray-200 font-normal disabled:bg-coolgray-200/50 disabled:border-none" ) { // } diff --git a/app/View/Components/Forms/Textarea.php b/app/View/Components/Forms/Textarea.php index f4d1f0bee..c85c8b6f7 100644 --- a/app/View/Components/Forms/Textarea.php +++ b/app/View/Components/Forms/Textarea.php @@ -4,9 +4,9 @@ use Closure; use Illuminate\Contracts\View\View; +use Illuminate\Support\Str; use Illuminate\View\Component; use Visus\Cuid2\Cuid2; -use Illuminate\Support\Str; class Textarea extends Component { @@ -20,11 +20,11 @@ public function __construct( public string|null $value = null, public string|null $label = null, public string|null $placeholder = null, - public bool $required = false, - public bool $disabled = false, - public bool $readonly = false, + public bool $required = false, + public bool $disabled = false, + public bool $readonly = false, public string|null $helper = null, - public string $defaultClass = "textarea bg-coolgray-200 rounded text-white scrollbar disabled:bg-coolgray-200/50 disabled:border-none" + public string $defaultClass = "textarea bg-coolgray-200 rounded text-white scrollbar disabled:bg-coolgray-200/50 disabled:border-none placeholder:text-coolgray-500 read-only:text-neutral-500 read-only:bg-coolgray-200/50" ) { // } diff --git a/app/View/Components/Modal.php b/app/View/Components/Modal.php index b317d3c44..3ef89fb1d 100644 --- a/app/View/Components/Modal.php +++ b/app/View/Components/Modal.php @@ -5,7 +5,6 @@ use Closure; use Illuminate\Contracts\View\View; use Illuminate\View\Component; -use Visus\Cuid2\Cuid2; class Modal extends Component { @@ -13,12 +12,12 @@ class Modal extends Component * Create a new component instance. */ public function __construct( - public string $modalId, - public string $modalTitle, + public string $modalId, + public string|null $modalTitle = null, public string|null $modalBody = null, public string|null $modalSubmit = null, - public bool $yesOrNo = false, - public string $action = 'delete' + public bool $yesOrNo = false, + public string $action = 'delete' ) { // } diff --git a/bootstrap/app.php b/bootstrap/app.php index 55afdbffb..037e17df0 100644 --- a/bootstrap/app.php +++ b/bootstrap/app.php @@ -11,8 +11,6 @@ | */ -use Illuminate\Support\Facades\Artisan; - $app = new Illuminate\Foundation\Application( $_ENV['APP_BASE_PATH'] ?? dirname(__DIR__) ); diff --git a/bootstrap/helpers/applications.php b/bootstrap/helpers/applications.php index fa57cebde..427ef5a57 100644 --- a/bootstrap/helpers/applications.php +++ b/bootstrap/helpers/applications.php @@ -4,7 +4,7 @@ use App\Models\Application; use App\Models\ApplicationDeploymentQueue; -function queue_application_deployment(int $application_id, string $deployment_uuid, int|null $pull_request_id = 0, string $commit = 'HEAD', bool $force_rebuild = false, bool $is_webhook = false) +function queue_application_deployment(int $application_id, string $deployment_uuid, int | null $pull_request_id = 0, string $commit = 'HEAD', bool $force_rebuild = false, bool $is_webhook = false) { $deployment = ApplicationDeploymentQueue::create([ 'application_id' => $application_id, @@ -16,7 +16,7 @@ function queue_application_deployment(int $application_id, string $deployment_uu ]); $queued_deployments = ApplicationDeploymentQueue::where('application_id', $application_id)->where('status', 'queued')->get()->sortByDesc('created_at'); $running_deployments = ApplicationDeploymentQueue::where('application_id', $application_id)->where('status', 'in_progress')->get()->sortByDesc('created_at'); - ray('Q:' . $queued_deployments->count() . 'R:' . $running_deployments->count() . '| Queuing deployment: ' . $deployment_uuid . ' of applicationID: ' . $application_id . ' pull request: ' . $pull_request_id . ' with commit: ' . $commit . ' and is it forced: ' . $force_rebuild); + ray('Q:' . $queued_deployments->count() . 'R:' . $running_deployments->count() . '| Queuing deployment: ' . $deployment_uuid . ' of applicationID: ' . $application_id . ' pull request: ' . $pull_request_id . ' with commit: ' . $commit . ' and is it forced: ' . $force_rebuild); if ($queued_deployments->count() > 1) { $queued_deployments = $queued_deployments->skip(1); $queued_deployments->each(function ($queued_deployment, $key) { diff --git a/bootstrap/helpers/constants.php b/bootstrap/helpers/constants.php new file mode 100644 index 000000000..0e42b311e --- /dev/null +++ b/bootstrap/helpers/constants.php @@ -0,0 +1,11 @@ + '* * * * *', + 'hourly' => '0 * * * *', + 'daily' => '0 0 * * *', + 'weekly' => '0 0 * * 0', + 'monthly' => '0 0 1 * *', + 'yearly' => '0 0 1 1 *', +]; diff --git a/bootstrap/helpers/databases.php b/bootstrap/helpers/databases.php new file mode 100644 index 000000000..c005ad15d --- /dev/null +++ b/bootstrap/helpers/databases.php @@ -0,0 +1,42 @@ +first(); + if (!$destination) { + throw new Exception('Destination not found'); + } + return StandalonePostgresql::create([ + 'name' => generate_database_name('postgresql'), + 'postgres_password' => \Illuminate\Support\Str::password(), + 'environment_id' => $environment_id, + 'destination_id' => $destination->id, + 'destination_type' => $destination->getMorphClass(), + ]); +} + +/** + * Delete file locally on the filesystem. + * @param string $filename + * @param Server $server + * @return void + */ +function delete_backup_locally(string | null $filename, Server $server): void +{ + if (empty($filename)) { + return; + } + instant_remote_process(["rm -f \"{$filename}\""], $server, throwError: false); +} diff --git a/bootstrap/helpers/docker.php b/bootstrap/helpers/docker.php index a8bd2a2f7..2787f153e 100644 --- a/bootstrap/helpers/docker.php +++ b/bootstrap/helpers/docker.php @@ -11,6 +11,7 @@ function format_docker_command_output_to_json($rawOutput): Collection ->reject(fn ($line) => empty($line)) ->map(fn ($outputLine) => json_decode($outputLine, true, flags: JSON_THROW_ON_ERROR)); } + function format_docker_labels_to_json($rawOutput): Collection { $outputLines = explode(PHP_EOL, $rawOutput); @@ -29,6 +30,19 @@ function format_docker_labels_to_json($rawOutput): Collection })[0]; } +function format_docker_envs_to_json($rawOutput) +{ + try { + $outputLines = json_decode($rawOutput, true, flags: JSON_THROW_ON_ERROR); + return collect(data_get($outputLines[0], 'Config.Env', []))->mapWithKeys(function ($env) { + $env = explode('=', $env); + return [$env[0] => $env[1]]; + }); + } catch (\Throwable $th) { + return collect([]); + } +} + function get_container_status(Server $server, string $container_id, bool $all_data = false, bool $throwError = false) { $container = instant_remote_process(["docker inspect --format '{{json .}}' {$container_id}"], $server, $throwError); @@ -50,3 +64,13 @@ function generate_container_name(string $uuid, int $pull_request_id = 0) return $uuid; } } +function get_port_from_dockerfile($dockerfile): int +{ + $port = preg_grep('/EXPOSE\s+(\d+)/', explode("\n", $dockerfile)); + if (count($port) > 0 && preg_match('/EXPOSE\s+(\d+)/', $port[1], $matches)) { + $port = $matches[1]; + } else { + $port = 80; + } + return $port; +} diff --git a/bootstrap/helpers/github.php b/bootstrap/helpers/github.php index 8d18e6290..5c36f091d 100644 --- a/bootstrap/helpers/github.php +++ b/bootstrap/helpers/github.php @@ -3,8 +3,8 @@ use App\Models\GithubApp; use App\Models\GitlabApp; use Carbon\Carbon; -use Illuminate\Support\Str; use Illuminate\Support\Facades\Http; +use Illuminate\Support\Str; use Lcobucci\JWT\Encoding\ChainedFormatter; use Lcobucci\JWT\Encoding\JoseEncoder; use Lcobucci\JWT\Signer\Key\InMemory; @@ -33,6 +33,7 @@ function generate_github_installation_token(GithubApp $source) } return $token->json()['token']; } + function generate_github_jwt_token(GithubApp $source) { $signingKey = InMemory::plainText($source->privateKey->private_key); @@ -65,7 +66,7 @@ function git_api(GithubApp|GitlabApp $source, string $endpoint, string $method = } $json = $response->json(); if ($response->failed() && $throwError) { - throw new \Exception("Failed to get data from {$source->name} with error:

" . $json['message'] . "

Rate Limit resets at: " . Carbon::parse((int)$response->header('X-RateLimit-Reset'))->format('Y-m-d H:i:s') . 'UTC'); + throw new \Exception("Failed to get data from {$source->name} with error:

" . $json['message'] . "

Rate Limit resets at: " . Carbon::parse((int)$response->header('X-RateLimit-Reset'))->format('Y-m-d H:i:s') . 'UTC'); } return [ 'rate_limit_remaining' => $response->header('X-RateLimit-Remaining'), @@ -73,6 +74,7 @@ function git_api(GithubApp|GitlabApp $source, string $endpoint, string $method = 'data' => collect($json) ]; } + function get_installation_path(GithubApp $source) { $github = GithubApp::where('uuid', $source->uuid)->first(); diff --git a/bootstrap/helpers/proxy.php b/bootstrap/helpers/proxy.php index 10ecc10d8..86c9ca528 100644 --- a/bootstrap/helpers/proxy.php +++ b/bootstrap/helpers/proxy.php @@ -1,15 +1,18 @@ standaloneDockers)->map(function ($docker) { return $docker['network']; })->unique(); @@ -53,7 +56,7 @@ function getProxyConfiguration(Server $server) "--ping=true", "--ping.entrypoint=http", "--api.dashboard=true", - "--api.insecure=true", + "--api.insecure=false", "--entrypoints.http.address=:80", "--entrypoints.https.address=:443", "--entrypoints.http.http.encodequerysemicolons=true", @@ -79,15 +82,18 @@ function getProxyConfiguration(Server $server) ], ], ]; - if (isDev()) { + if (is_dev()) { $config['services']['traefik']['command'][] = "--log.level=debug"; } return Yaml::dump($config, 4, 2); } + function setup_default_redirect_404(string|null $redirect_url, Server $server) { - $traefik_dynamic_conf_path = '/data/coolify/proxy/dynamic'; + ray('called'); + $traefik_dynamic_conf_path = get_proxy_path() . "/dynamic"; $traefik_default_redirect_file = "$traefik_dynamic_conf_path/default_redirect_404.yaml"; + ray($redirect_url); if (empty($redirect_url)) { remote_process([ "rm -f $traefik_default_redirect_file", diff --git a/bootstrap/helpers/remoteProcess.php b/bootstrap/helpers/remoteProcess.php index cb718d8aa..3f8f567d8 100644 --- a/bootstrap/helpers/remoteProcess.php +++ b/bootstrap/helpers/remoteProcess.php @@ -5,6 +5,7 @@ use App\Enums\ActivityTypes; use App\Models\Application; use App\Models\ApplicationDeploymentQueue; +use App\Models\PrivateKey; use App\Models\Server; use Carbon\Carbon; use Illuminate\Database\Eloquent\Model; @@ -22,7 +23,7 @@ function remote_process( array $command, Server $server, - string $type = ActivityTypes::INLINE->value, + string $type = ActivityTypes::INLINE->value, ?string $type_uuid = null, ?Model $model = null, bool $ignore_errors = false, @@ -54,11 +55,13 @@ function remote_process( ), ])(); } + function get_private_key_for_server(Server $server) { $temp_file = "id.root@{$server->ip}"; return '/var/www/html/storage/app/ssh/keys/' . $temp_file; } + function save_private_key_for_server(Server $server) { if (data_get($server, 'privateKey.private_key') === null) { @@ -148,3 +151,44 @@ function decode_remote_command_output(?ApplicationDeploymentQueue $application_d return $formatted; } + +function refreshPrivateKey(PrivateKey $private_key) +{ + foreach ($private_key->servers as $server) { + // Delete the old ssh mux file to force a new one to be created + Storage::disk('ssh-mux')->delete($server->muxFilename()); + if (auth()->user()->currentTeam()->id) { + auth()->user()->currentTeam()->privateKeys = PrivateKey::where('team_id', auth()->user()->currentTeam()->id)->get(); + } + } +} + +function validateServer(Server $server) +{ + try { + refreshPrivateKey($server->privateKey); + $uptime = instant_remote_process(['uptime'], $server); + if (!$uptime) { + $uptime = 'Server not reachable.'; + throw new \Exception('Server not reachable.'); + } + $server->settings->is_reachable = true; + + $dockerVersion = instant_remote_process(['docker version|head -2|grep -i version'], $server, false); + if (!$dockerVersion) { + $dockerVersion = 'Not installed.'; + throw new \Exception('Docker not installed.'); + } + $server->settings->is_usable = true; + return [ + "uptime" => $uptime, + "dockerVersion" => $dockerVersion, + ]; + } catch (\Exception $e) { + $server->settings->is_reachable = false; + $server->settings->is_usable = false; + throw $e; + } finally { + $server->settings->save(); + } +} diff --git a/bootstrap/helpers/s3.php b/bootstrap/helpers/s3.php new file mode 100644 index 000000000..4a2252016 --- /dev/null +++ b/bootstrap/helpers/s3.php @@ -0,0 +1,23 @@ +endpoint) { + $is_digital_ocean = Str::contains($s3->endpoint, 'digitaloceanspaces.com'); + } + config()->set('filesystems.disks.custom-s3', [ + 'driver' => 's3', + 'region' => $s3['region'], + 'key' => $s3['key'], + 'secret' => $s3['secret'], + 'bucket' => $s3['bucket'], + 'endpoint' => $s3['endpoint'], + 'use_path_style_endpoint' => true, + 'bucket_endpoint' => $is_digital_ocean, + 'aws_url' => $s3->awsUrl(), + ]); +} diff --git a/bootstrap/helpers/shared.php b/bootstrap/helpers/shared.php index 71a2f3560..ff0e99679 100644 --- a/bootstrap/helpers/shared.php +++ b/bootstrap/helpers/shared.php @@ -4,26 +4,55 @@ use Illuminate\Database\QueryException; use Illuminate\Support\Facades\Http; use Illuminate\Support\Facades\Route; -use Illuminate\Support\Facades\URL; -use Visus\Cuid2\Cuid2; use Illuminate\Support\Str; +use Nubs\RandomNameGenerator\All; +use Poliander\Cron\CronExpression; +use Visus\Cuid2\Cuid2; +use DanHarrin\LivewireRateLimiting\Exceptions\TooManyRequestsException; -function general_error_handler(\Throwable|null $err = null, $that = null, $isJson = false, $customErrorMessage = null) +function application_configuration_dir(): string +{ + return '/data/coolify/applications'; +} + +function database_configuration_dir(): string +{ + return '/data/coolify/databases'; +} + +function backup_dir(): string +{ + return '/data/coolify/backups'; +} + +function generate_readme_file(string $name, string $updated_at): string +{ + return "Resource name: $name\nLatest Deployment Date: $updated_at"; +} + +function is_instance_admin() +{ + return auth()->user()?->isInstanceAdmin(); +} + +function general_error_handler(Throwable|null $err = null, $that = null, $isJson = false, $customErrorMessage = null): mixed { try { - ray('ERROR OCCURED: ' . $err->getMessage()); + ray('ERROR OCCURRED: ' . $err->getMessage()); if ($err instanceof QueryException) { if ($err->errorInfo[0] === '23505') { - throw new \Exception($customErrorMessage ?? 'Duplicate entry found.', '23505'); + throw new Exception($customErrorMessage ?? 'Duplicate entry found.', '23505'); } else if (count($err->errorInfo) === 4) { - throw new \Exception($customErrorMessage ?? $err->errorInfo[3]); + throw new Exception($customErrorMessage ?? $err->errorInfo[3]); } else { - throw new \Exception($customErrorMessage ?? $err->errorInfo[2]); + throw new Exception($customErrorMessage ?? $err->errorInfo[2]); } - } else { - throw new \Exception($customErrorMessage ?? $err->getMessage()); + } elseif($err instanceof TooManyRequestsException){ + throw new Exception($customErrorMessage ?? "Too many requests. Please try again in {$err->secondsUntilAvailable} seconds."); + }else { + throw new Exception($customErrorMessage ?? $err->getMessage()); } - } catch (\Throwable $error) { + } catch (Throwable $error) { if ($that) { return $that->emit('error', $customErrorMessage ?? $error->getMessage()); } elseif ($isJson) { @@ -34,69 +63,83 @@ function general_error_handler(\Throwable|null $err = null, $that = null, $isJso } else { ray($customErrorMessage); ray($error); + return $customErrorMessage ?? $error->getMessage(); } } } -function getRouteParameters() +function get_route_parameters(): array { return Route::current()->parameters(); } -function get_latest_version_of_coolify() +function get_latest_version_of_coolify(): string { try { $response = Http::get('https://cdn.coollabs.io/coolify/versions.json'); $versions = $response->json(); return data_get($versions, 'coolify.v4.version'); - } catch (\Throwable $th) { + } catch (Throwable $th) { //throw $th; ray($th->getMessage()); return '0.0.0'; } } -function generate_random_name() +function generate_random_name(): string { - $generator = \Nubs\RandomNameGenerator\All::create(); + $generator = All::create(); $cuid = new Cuid2(7); - return Str::kebab("{$generator->getName()}-{$cuid}"); -} -function generate_application_name(string $git_repository, string $git_branch) -{ - $cuid = new Cuid2(7); - return Str::kebab("{$git_repository}:{$git_branch}-{$cuid}"); + return Str::kebab("{$generator->getName()}-$cuid"); } -function is_transactional_emails_active() +function generate_application_name(string $git_repository, string $git_branch): string { - return data_get(InstanceSettings::get(), 'smtp.enabled'); + $cuid = new Cuid2(7); + return Str::kebab("$git_repository:$git_branch-$cuid"); } -function set_transanctional_email_settings() +function is_transactional_emails_active(): bool { - $settings = InstanceSettings::get(); - $password = data_get($settings, 'smtp.password'); - if ($password) $password = decrypt($password); + return data_get(InstanceSettings::get(), 'smtp_enabled'); +} + +function set_transanctional_email_settings(InstanceSettings|null $settings = null): void +{ + if (!$settings) { + $settings = InstanceSettings::get(); + } + $password = data_get($settings, 'smtp_password'); + if (isset($password)) { + $password = decrypt($password); + } config()->set('mail.default', 'smtp'); config()->set('mail.mailers.smtp', [ "transport" => "smtp", - "host" => data_get($settings, 'smtp.host'), - "port" => data_get($settings, 'smtp.port'), - "encryption" => data_get($settings, 'smtp.encryption'), - "username" => data_get($settings, 'smtp.username'), + "host" => data_get($settings, 'smtp_host'), + "port" => data_get($settings, 'smtp_port'), + "encryption" => data_get($settings, 'smtp_encryption'), + "username" => data_get($settings, 'smtp_username'), "password" => $password, - "timeout" => data_get($settings, 'smtp.timeout'), + "timeout" => data_get($settings, 'smtp_timeout'), "local_domain" => null, ]); } -function base_ip() + +function base_ip(): string { + if (is_dev()) { + return "http://localhost"; + } $settings = InstanceSettings::get(); - return "http://{$settings->public_ipv4}"; + return "http://$settings->public_ipv4"; } -function base_url(bool $withPort = true) + +/** + * If fqdn is set, return it, otherwise return public ip. + */ +function base_url(bool $withPort = true): string { $settings = InstanceSettings::get(); if ($settings->fqdn) { @@ -105,30 +148,43 @@ function base_url(bool $withPort = true) $port = config('app.port'); if ($settings->public_ipv4) { if ($withPort) { - if (isDev()) { - return "http://localhost:{$port}"; + if (is_dev()) { + return "http://localhost:$port"; } - return "http://{$settings->public_ipv4}:{$port}"; + return "http://$settings->public_ipv4:$port"; } - if (isDev()) { + if (is_dev()) { return "http://localhost"; } - return "http://{$settings->public_ipv4}"; + return "http://$settings->public_ipv4"; } if ($settings->public_ipv6) { if ($withPort) { - return "http://{$settings->public_ipv6}:{$port}"; + return "http://$settings->public_ipv6:$port"; } - return "http://{$settings->public_ipv6}"; + return "http://$settings->public_ipv6"; } return url('/'); } -function isDev() +function is_dev(): bool { return config('app.env') === 'local'; } -function isCloud() + +function is_cloud(): bool { return !config('coolify.self_hosted'); } + +function validate_cron_expression($expression_to_validate): bool +{ + $isValid = false; + $expression = new CronExpression($expression_to_validate); + $isValid = $expression->isValid(); + + if (isset(VALID_CRON_STRINGS[$expression_to_validate])) { + $isValid = true; + } + return $isValid; +} diff --git a/bootstrap/helpers/subscriptions.php b/bootstrap/helpers/subscriptions.php index bf7847cda..90d0947ad 100644 --- a/bootstrap/helpers/subscriptions.php +++ b/bootstrap/helpers/subscriptions.php @@ -2,13 +2,16 @@ use Illuminate\Support\Carbon; -function getSubscriptionLink() +function getSubscriptionLink($type) { + $checkout_id = config("coolify.lemon_squeezy_checkout_id_$type"); + if (!$checkout_id) { + return null; + } $user_id = auth()->user()->id; $team_id = auth()->user()->currentTeam()->id ?? null; $email = auth()->user()->email ?? null; $name = auth()->user()->name ?? null; - $checkout_id = config('coolify.lemon_squeezy_checkout_id'); $url = "https://store.coollabs.io/checkout/buy/$checkout_id?"; if ($user_id) { $url .= "&checkout[custom][user_id]={$user_id}"; @@ -24,23 +27,56 @@ function getSubscriptionLink() } return $url; } + function getPaymentLink() { return auth()->user()->currentTeam()->subscription->lemon_update_payment_menthod_url; } + function getRenewDate() { return Carbon::parse(auth()->user()->currentTeam()->subscription->lemon_renews_at)->format('Y-M-d H:i:s'); } + function getEndDate() { return Carbon::parse(auth()->user()->currentTeam()->subscription->lemon_renews_at)->format('Y-M-d H:i:s'); } -function isSubscribed() + +function is_subscription_active() { - return - auth()->user()?->currentTeam()?->subscription?->lemon_status === 'active' || - (auth()->user()?->currentTeam()?->subscription?->lemon_ends_at && - Carbon::parse(auth()->user()->currentTeam()->subscription->lemon_ends_at) > Carbon::now() - ) || auth()->user()->isInstanceAdmin(); + $team = auth()->user()?->currentTeam(); + + if (!$team) { + return false; + } + if (is_instance_admin()) { + return true; + } + $subscription = $team?->subscription; + + if (!$subscription) { + return false; + } + $is_active = $subscription->lemon_status === 'active'; + + return $is_active; +} +function is_subscription_in_grace_period() +{ + $team = auth()->user()?->currentTeam(); + if (!$team) { + return false; + } + if (is_instance_admin()) { + return true; + } + $subscription = $team?->subscription; + if (!$subscription) { + return false; + } + $is_still_grace_period = $subscription->lemon_ends_at && + Carbon::parse($subscription->lemon_ends_at) > Carbon::now(); + + return $is_still_grace_period; } diff --git a/composer.json b/composer.json index 7d101d745..7fcdd9e26 100644 --- a/composer.json +++ b/composer.json @@ -9,6 +9,7 @@ "license": "MIT", "require": { "php": "^8.2", + "danharrin/livewire-rate-limiting": "^1.1", "doctrine/dbal": "^3.6", "guzzlehttp/guzzle": "^7.5.0", "laravel/fortify": "^v1.16.0", @@ -18,9 +19,11 @@ "laravel/tinker": "^v2.8.1", "laravel/ui": "^4.2", "lcobucci/jwt": "^5.0.0", + "league/flysystem-aws-s3-v3": "^3.0", "livewire/livewire": "^v2.12.3", "masmerise/livewire-toaster": "^1.2", "nubs/random-name-generator": "^2.2", + "poliander/cron": "^3.0", "sentry/sentry-laravel": "^3.4", "spatie/laravel-activitylog": "^4.7.3", "spatie/laravel-data": "^3.4.3", @@ -41,8 +44,7 @@ "phpunit/phpunit": "^10.0.19", "serversideup/spin": "^v1.1.0", "spatie/laravel-ignition": "^2.1.0", - "symfony/http-client": "^6.2", - "toshy/bunnynet-php": "^3.0" + "symfony/http-client": "^6.2" }, "autoload": { "files": [ @@ -92,4 +94,4 @@ }, "minimum-stability": "stable", "prefer-stable": true -} \ No newline at end of file +} diff --git a/composer.lock b/composer.lock index ebff3b9e2..45671727a 100644 --- a/composer.lock +++ b/composer.lock @@ -4,8 +4,157 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "d9173515bca399807784102128591e1e", + "content-hash": "0c023bed552776ee5e4eeda1ff0a5e19", "packages": [ + { + "name": "aws/aws-crt-php", + "version": "v1.2.2", + "source": { + "type": "git", + "url": "https://github.com/awslabs/aws-crt-php.git", + "reference": "2f1dc7b7eda080498be96a4a6d683a41583030e9" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/awslabs/aws-crt-php/zipball/2f1dc7b7eda080498be96a4a6d683a41583030e9", + "reference": "2f1dc7b7eda080498be96a4a6d683a41583030e9", + "shasum": "" + }, + "require": { + "php": ">=5.5" + }, + "require-dev": { + "phpunit/phpunit": "^4.8.35||^5.6.3||^9.5", + "yoast/phpunit-polyfills": "^1.0" + }, + "suggest": { + "ext-awscrt": "Make sure you install awscrt native extension to use any of the functionality." + }, + "type": "library", + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "Apache-2.0" + ], + "authors": [ + { + "name": "AWS SDK Common Runtime Team", + "email": "aws-sdk-common-runtime@amazon.com" + } + ], + "description": "AWS Common Runtime for PHP", + "homepage": "https://github.com/awslabs/aws-crt-php", + "keywords": [ + "amazon", + "aws", + "crt", + "sdk" + ], + "support": { + "issues": "https://github.com/awslabs/aws-crt-php/issues", + "source": "https://github.com/awslabs/aws-crt-php/tree/v1.2.2" + }, + "time": "2023-07-20T16:49:55+00:00" + }, + { + "name": "aws/aws-sdk-php", + "version": "3.269.0", + "source": { + "type": "git", + "url": "https://github.com/aws/aws-sdk-php.git", + "reference": "6d759ef9f24f0c7f271baf8014f41fc0cfdfbf78" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/aws/aws-sdk-php/zipball/6d759ef9f24f0c7f271baf8014f41fc0cfdfbf78", + "reference": "6d759ef9f24f0c7f271baf8014f41fc0cfdfbf78", + "shasum": "" + }, + "require": { + "aws/aws-crt-php": "^1.0.4", + "ext-json": "*", + "ext-pcre": "*", + "ext-simplexml": "*", + "guzzlehttp/guzzle": "^6.5.8 || ^7.4.5", + "guzzlehttp/promises": "^1.4.0", + "guzzlehttp/psr7": "^1.9.1 || ^2.4.5", + "mtdowling/jmespath.php": "^2.6", + "php": ">=5.5" + }, + "require-dev": { + "andrewsville/php-token-reflection": "^1.4", + "aws/aws-php-sns-message-validator": "~1.0", + "behat/behat": "~3.0", + "composer/composer": "^1.10.22", + "dms/phpunit-arraysubset-asserts": "^0.4.0", + "doctrine/cache": "~1.4", + "ext-dom": "*", + "ext-openssl": "*", + "ext-pcntl": "*", + "ext-sockets": "*", + "nette/neon": "^2.3", + "paragonie/random_compat": ">= 2", + "phpunit/phpunit": "^4.8.35 || ^5.6.3 || ^9.5", + "psr/cache": "^1.0", + "psr/http-message": "^1.0", + "psr/simple-cache": "^1.0", + "sebastian/comparator": "^1.2.3 || ^4.0", + "yoast/phpunit-polyfills": "^1.0" + }, + "suggest": { + "aws/aws-php-sns-message-validator": "To validate incoming SNS notifications", + "doctrine/cache": "To use the DoctrineCacheAdapter", + "ext-curl": "To send requests using cURL", + "ext-openssl": "Allows working with CloudFront private distributions and verifying received SNS messages", + "ext-sockets": "To use client-side monitoring" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.0-dev" + } + }, + "autoload": { + "files": [ + "src/functions.php" + ], + "psr-4": { + "Aws\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "Apache-2.0" + ], + "authors": [ + { + "name": "Amazon Web Services", + "homepage": "http://aws.amazon.com" + } + ], + "description": "AWS SDK for PHP - Use Amazon Web Services in your PHP project", + "homepage": "http://aws.amazon.com/sdkforphp", + "keywords": [ + "amazon", + "aws", + "cloud", + "dynamodb", + "ec2", + "glacier", + "s3", + "sdk" + ], + "support": { + "forum": "https://forums.aws.amazon.com/forum.jspa?forumID=80", + "issues": "https://github.com/aws/aws-sdk-php/issues", + "source": "https://github.com/aws/aws-sdk-php/tree/3.269.0" + }, + "time": "2023-04-26T18:21:04+00:00" + }, { "name": "bacon/bacon-qr-code", "version": "2.0.8", @@ -181,6 +330,59 @@ ], "time": "2022-02-21T13:15:14+00:00" }, + { + "name": "danharrin/livewire-rate-limiting", + "version": "v1.1.0", + "source": { + "type": "git", + "url": "https://github.com/danharrin/livewire-rate-limiting.git", + "reference": "a55996683cabf2e93893280d602191243b3b80b8" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/danharrin/livewire-rate-limiting/zipball/a55996683cabf2e93893280d602191243b3b80b8", + "reference": "a55996683cabf2e93893280d602191243b3b80b8", + "shasum": "" + }, + "require": { + "illuminate/support": "^9.0|^10.0", + "php": "^8.0" + }, + "require-dev": { + "livewire/livewire": "^2.3", + "orchestra/testbench": "^7.0|^8.0", + "phpunit/phpunit": "^9.0|^10.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "DanHarrin\\LivewireRateLimiting\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Dan Harrin", + "email": "dan@danharrin.com" + } + ], + "description": "Apply rate limiters to Laravel Livewire actions.", + "homepage": "https://github.com/danharrin/livewire-rate-limiting", + "support": { + "issues": "https://github.com/danharrin/livewire-rate-limiting/issues", + "source": "https://github.com/danharrin/livewire-rate-limiting" + }, + "funding": [ + { + "url": "https://github.com/danharrin", + "type": "github" + } + ], + "time": "2023-03-12T12:17:29+00:00" + }, { "name": "dasprid/enum", "version": "1.0.4", @@ -401,16 +603,16 @@ }, { "name": "doctrine/dbal", - "version": "3.6.4", + "version": "3.6.5", "source": { "type": "git", "url": "https://github.com/doctrine/dbal.git", - "reference": "19f0dec95edd6a3c3c5ff1d188ea94c6b7fc903f" + "reference": "96d5a70fd91efdcec81fc46316efc5bf3da17ddf" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/doctrine/dbal/zipball/19f0dec95edd6a3c3c5ff1d188ea94c6b7fc903f", - "reference": "19f0dec95edd6a3c3c5ff1d188ea94c6b7fc903f", + "url": "https://api.github.com/repos/doctrine/dbal/zipball/96d5a70fd91efdcec81fc46316efc5bf3da17ddf", + "reference": "96d5a70fd91efdcec81fc46316efc5bf3da17ddf", "shasum": "" }, "require": { @@ -425,10 +627,10 @@ "require-dev": { "doctrine/coding-standard": "12.0.0", "fig/log-test": "^1", - "jetbrains/phpstorm-stubs": "2022.3", - "phpstan/phpstan": "1.10.14", + "jetbrains/phpstorm-stubs": "2023.1", + "phpstan/phpstan": "1.10.21", "phpstan/phpstan-strict-rules": "^1.5", - "phpunit/phpunit": "9.6.7", + "phpunit/phpunit": "9.6.9", "psalm/plugin-phpunit": "0.18.4", "squizlabs/php_codesniffer": "3.7.2", "symfony/cache": "^5.4|^6.0", @@ -493,7 +695,7 @@ ], "support": { "issues": "https://github.com/doctrine/dbal/issues", - "source": "https://github.com/doctrine/dbal/tree/3.6.4" + "source": "https://github.com/doctrine/dbal/tree/3.6.5" }, "funding": [ { @@ -509,7 +711,7 @@ "type": "tidelift" } ], - "time": "2023-06-15T07:40:12+00:00" + "time": "2023-07-17T09:15:50+00:00" }, { "name": "doctrine/deprecations", @@ -1206,33 +1408,29 @@ }, { "name": "guzzlehttp/promises", - "version": "2.0.0", + "version": "1.5.3", "source": { "type": "git", "url": "https://github.com/guzzle/promises.git", - "reference": "3a494dc7dc1d7d12e511890177ae2d0e6c107da6" + "reference": "67ab6e18aaa14d753cc148911d273f6e6cb6721e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/guzzle/promises/zipball/3a494dc7dc1d7d12e511890177ae2d0e6c107da6", - "reference": "3a494dc7dc1d7d12e511890177ae2d0e6c107da6", + "url": "https://api.github.com/repos/guzzle/promises/zipball/67ab6e18aaa14d753cc148911d273f6e6cb6721e", + "reference": "67ab6e18aaa14d753cc148911d273f6e6cb6721e", "shasum": "" }, "require": { - "php": "^7.2.5 || ^8.0" + "php": ">=5.5" }, "require-dev": { - "bamarni/composer-bin-plugin": "^1.8.1", - "phpunit/phpunit": "^8.5.29 || ^9.5.23" + "symfony/phpunit-bridge": "^4.4 || ^5.1" }, "type": "library", - "extra": { - "bamarni-bin": { - "bin-links": true, - "forward-command": false - } - }, "autoload": { + "files": [ + "src/functions_include.php" + ], "psr-4": { "GuzzleHttp\\Promise\\": "src/" } @@ -1269,7 +1467,7 @@ ], "support": { "issues": "https://github.com/guzzle/promises/issues", - "source": "https://github.com/guzzle/promises/tree/2.0.0" + "source": "https://github.com/guzzle/promises/tree/1.5.3" }, "funding": [ { @@ -1285,20 +1483,20 @@ "type": "tidelift" } ], - "time": "2023-05-21T13:50:22+00:00" + "time": "2023-05-21T12:31:43+00:00" }, { "name": "guzzlehttp/psr7", - "version": "2.5.0", + "version": "2.6.0", "source": { "type": "git", "url": "https://github.com/guzzle/psr7.git", - "reference": "b635f279edd83fc275f822a1188157ffea568ff6" + "reference": "8bd7c33a0734ae1c5d074360512beb716bef3f77" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/guzzle/psr7/zipball/b635f279edd83fc275f822a1188157ffea568ff6", - "reference": "b635f279edd83fc275f822a1188157ffea568ff6", + "url": "https://api.github.com/repos/guzzle/psr7/zipball/8bd7c33a0734ae1c5d074360512beb716bef3f77", + "reference": "8bd7c33a0734ae1c5d074360512beb716bef3f77", "shasum": "" }, "require": { @@ -1385,7 +1583,7 @@ ], "support": { "issues": "https://github.com/guzzle/psr7/issues", - "source": "https://github.com/guzzle/psr7/tree/2.5.0" + "source": "https://github.com/guzzle/psr7/tree/2.6.0" }, "funding": [ { @@ -1401,7 +1599,7 @@ "type": "tidelift" } ], - "time": "2023-04-17T16:11:26+00:00" + "time": "2023-08-03T15:06:02+00:00" }, { "name": "guzzlehttp/uri-template", @@ -1670,16 +1868,16 @@ }, { "name": "laravel/framework", - "version": "v10.15.0", + "version": "v10.18.0", "source": { "type": "git", "url": "https://github.com/laravel/framework.git", - "reference": "c7599dc92e04532824bafbd226c2936ce6a905b8" + "reference": "9d41928900f7ecf409627a7d06c0a4dfecff2ea7" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/laravel/framework/zipball/c7599dc92e04532824bafbd226c2936ce6a905b8", - "reference": "c7599dc92e04532824bafbd226c2936ce6a905b8", + "url": "https://api.github.com/repos/laravel/framework/zipball/9d41928900f7ecf409627a7d06c0a4dfecff2ea7", + "reference": "9d41928900f7ecf409627a7d06c0a4dfecff2ea7", "shasum": "" }, "require": { @@ -1697,11 +1895,12 @@ "ext-tokenizer": "*", "fruitcake/php-cors": "^1.2", "guzzlehttp/uri-template": "^1.0", + "laravel/prompts": "^0.1", "laravel/serializable-closure": "^1.3", "league/commonmark": "^2.2.1", "league/flysystem": "^3.8.0", "monolog/monolog": "^3.0", - "nesbot/carbon": "^2.62.1", + "nesbot/carbon": "^2.67", "nunomaduro/termwind": "^1.13", "php": "^8.1", "psr/container": "^1.1.1|^2.0.1", @@ -1780,7 +1979,6 @@ "mockery/mockery": "^1.5.1", "orchestra/testbench-core": "^8.4", "pda/pheanstalk": "^4.0", - "phpstan/phpdoc-parser": "^1.15", "phpstan/phpstan": "^1.4.7", "phpunit/phpunit": "^10.0.7", "predis/predis": "^2.0.2", @@ -1866,20 +2064,20 @@ "issues": "https://github.com/laravel/framework/issues", "source": "https://github.com/laravel/framework" }, - "time": "2023-07-11T13:43:52+00:00" + "time": "2023-08-08T14:30:38+00:00" }, { "name": "laravel/horizon", - "version": "v5.18.0", + "version": "v5.19.1", "source": { "type": "git", "url": "https://github.com/laravel/horizon.git", - "reference": "b14498a09af826035e46ae8d6b013d0ec849bdb7" + "reference": "1987f98084bc3119f78ec3da6283821a2c1acf63" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/laravel/horizon/zipball/b14498a09af826035e46ae8d6b013d0ec849bdb7", - "reference": "b14498a09af826035e46ae8d6b013d0ec849bdb7", + "url": "https://api.github.com/repos/laravel/horizon/zipball/1987f98084bc3119f78ec3da6283821a2c1acf63", + "reference": "1987f98084bc3119f78ec3da6283821a2c1acf63", "shasum": "" }, "require": { @@ -1942,9 +2140,57 @@ ], "support": { "issues": "https://github.com/laravel/horizon/issues", - "source": "https://github.com/laravel/horizon/tree/v5.18.0" + "source": "https://github.com/laravel/horizon/tree/v5.19.1" }, - "time": "2023-06-30T15:11:51+00:00" + "time": "2023-08-09T13:18:44+00:00" + }, + { + "name": "laravel/prompts", + "version": "v0.1.4", + "source": { + "type": "git", + "url": "https://github.com/laravel/prompts.git", + "reference": "1b3ab520a75eddefcda99f49fb551d231769b1fa" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/laravel/prompts/zipball/1b3ab520a75eddefcda99f49fb551d231769b1fa", + "reference": "1b3ab520a75eddefcda99f49fb551d231769b1fa", + "shasum": "" + }, + "require": { + "ext-mbstring": "*", + "illuminate/collections": "^10.0|^11.0", + "php": "^8.1", + "symfony/console": "^6.2" + }, + "require-dev": { + "mockery/mockery": "^1.5", + "pestphp/pest": "^2.3", + "phpstan/phpstan": "^1.10", + "phpstan/phpstan-mockery": "^1.1" + }, + "suggest": { + "ext-pcntl": "Required for the spinner to be animated." + }, + "type": "library", + "autoload": { + "files": [ + "src/helpers.php" + ], + "psr-4": { + "Laravel\\Prompts\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "support": { + "issues": "https://github.com/laravel/prompts/issues", + "source": "https://github.com/laravel/prompts/tree/v0.1.4" + }, + "time": "2023-08-07T13:14:59+00:00" }, { "name": "laravel/sanctum", @@ -2014,16 +2260,16 @@ }, { "name": "laravel/serializable-closure", - "version": "v1.3.0", + "version": "v1.3.1", "source": { "type": "git", "url": "https://github.com/laravel/serializable-closure.git", - "reference": "f23fe9d4e95255dacee1bf3525e0810d1a1b0f37" + "reference": "e5a3057a5591e1cfe8183034b0203921abe2c902" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/laravel/serializable-closure/zipball/f23fe9d4e95255dacee1bf3525e0810d1a1b0f37", - "reference": "f23fe9d4e95255dacee1bf3525e0810d1a1b0f37", + "url": "https://api.github.com/repos/laravel/serializable-closure/zipball/e5a3057a5591e1cfe8183034b0203921abe2c902", + "reference": "e5a3057a5591e1cfe8183034b0203921abe2c902", "shasum": "" }, "require": { @@ -2070,7 +2316,7 @@ "issues": "https://github.com/laravel/serializable-closure/issues", "source": "https://github.com/laravel/serializable-closure" }, - "time": "2023-01-30T18:31:20+00:00" + "time": "2023-07-14T13:56:28+00:00" }, { "name": "laravel/tinker", @@ -2553,6 +2799,72 @@ ], "time": "2023-05-04T09:04:26+00:00" }, + { + "name": "league/flysystem-aws-s3-v3", + "version": "3.15.0", + "source": { + "type": "git", + "url": "https://github.com/thephpleague/flysystem-aws-s3-v3.git", + "reference": "d8de61ee10b6a607e7996cff388c5a3a663e8c8a" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/thephpleague/flysystem-aws-s3-v3/zipball/d8de61ee10b6a607e7996cff388c5a3a663e8c8a", + "reference": "d8de61ee10b6a607e7996cff388c5a3a663e8c8a", + "shasum": "" + }, + "require": { + "aws/aws-sdk-php": "^3.220.0", + "league/flysystem": "^3.10.0", + "league/mime-type-detection": "^1.0.0", + "php": "^8.0.2" + }, + "conflict": { + "guzzlehttp/guzzle": "<7.0", + "guzzlehttp/ringphp": "<1.1.1" + }, + "type": "library", + "autoload": { + "psr-4": { + "League\\Flysystem\\AwsS3V3\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Frank de Jonge", + "email": "info@frankdejonge.nl" + } + ], + "description": "AWS S3 filesystem adapter for Flysystem.", + "keywords": [ + "Flysystem", + "aws", + "file", + "files", + "filesystem", + "s3", + "storage" + ], + "support": { + "issues": "https://github.com/thephpleague/flysystem-aws-s3-v3/issues", + "source": "https://github.com/thephpleague/flysystem-aws-s3-v3/tree/3.15.0" + }, + "funding": [ + { + "url": "https://ecologi.com/frankdejonge", + "type": "custom" + }, + { + "url": "https://github.com/frankdejonge", + "type": "github" + } + ], + "time": "2023-05-02T20:02:14+00:00" + }, { "name": "league/flysystem-local", "version": "3.15.0", @@ -2615,26 +2927,26 @@ }, { "name": "league/mime-type-detection", - "version": "1.11.0", + "version": "1.13.0", "source": { "type": "git", "url": "https://github.com/thephpleague/mime-type-detection.git", - "reference": "ff6248ea87a9f116e78edd6002e39e5128a0d4dd" + "reference": "a6dfb1194a2946fcdc1f38219445234f65b35c96" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/thephpleague/mime-type-detection/zipball/ff6248ea87a9f116e78edd6002e39e5128a0d4dd", - "reference": "ff6248ea87a9f116e78edd6002e39e5128a0d4dd", + "url": "https://api.github.com/repos/thephpleague/mime-type-detection/zipball/a6dfb1194a2946fcdc1f38219445234f65b35c96", + "reference": "a6dfb1194a2946fcdc1f38219445234f65b35c96", "shasum": "" }, "require": { "ext-fileinfo": "*", - "php": "^7.2 || ^8.0" + "php": "^7.4 || ^8.0" }, "require-dev": { "friendsofphp/php-cs-fixer": "^3.2", "phpstan/phpstan": "^0.12.68", - "phpunit/phpunit": "^8.5.8 || ^9.3" + "phpunit/phpunit": "^8.5.8 || ^9.3 || ^10.0" }, "type": "library", "autoload": { @@ -2655,7 +2967,7 @@ "description": "Mime-type detection for Flysystem", "support": { "issues": "https://github.com/thephpleague/mime-type-detection/issues", - "source": "https://github.com/thephpleague/mime-type-detection/tree/1.11.0" + "source": "https://github.com/thephpleague/mime-type-detection/tree/1.13.0" }, "funding": [ { @@ -2667,20 +2979,20 @@ "type": "tidelift" } ], - "time": "2022-04-17T13:12:02+00:00" + "time": "2023-08-05T12:09:49+00:00" }, { "name": "livewire/livewire", - "version": "v2.12.3", + "version": "v2.12.5", "source": { "type": "git", "url": "https://github.com/livewire/livewire.git", - "reference": "019b1e69d8cd8c7e749eba7a38e4fa69ecbc8f74" + "reference": "96a249f5ab51d8377817d802f91d1e440869c1d6" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/livewire/livewire/zipball/019b1e69d8cd8c7e749eba7a38e4fa69ecbc8f74", - "reference": "019b1e69d8cd8c7e749eba7a38e4fa69ecbc8f74", + "url": "https://api.github.com/repos/livewire/livewire/zipball/96a249f5ab51d8377817d802f91d1e440869c1d6", + "reference": "96a249f5ab51d8377817d802f91d1e440869c1d6", "shasum": "" }, "require": { @@ -2732,7 +3044,7 @@ "description": "A front-end framework for Laravel.", "support": { "issues": "https://github.com/livewire/livewire/issues", - "source": "https://github.com/livewire/livewire/tree/v2.12.3" + "source": "https://github.com/livewire/livewire/tree/v2.12.5" }, "funding": [ { @@ -2740,20 +3052,20 @@ "type": "github" } ], - "time": "2023-03-03T20:12:38+00:00" + "time": "2023-08-02T06:31:31+00:00" }, { "name": "masmerise/livewire-toaster", - "version": "1.2.1", + "version": "1.3.0", "source": { "type": "git", "url": "https://github.com/masmerise/livewire-toaster.git", - "reference": "a47875b10a7dfd41ad832d79cdfdfce0291dac25" + "reference": "da21267abd9684c590a131a40173fb3c42eab722" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/masmerise/livewire-toaster/zipball/a47875b10a7dfd41ad832d79cdfdfce0291dac25", - "reference": "a47875b10a7dfd41ad832d79cdfdfce0291dac25", + "url": "https://api.github.com/repos/masmerise/livewire-toaster/zipball/da21267abd9684c590a131a40173fb3c42eab722", + "reference": "da21267abd9684c590a131a40173fb3c42eab722", "shasum": "" }, "require": { @@ -2806,9 +3118,9 @@ ], "support": { "issues": "https://github.com/masmerise/livewire-toaster/issues", - "source": "https://github.com/masmerise/livewire-toaster/tree/1.2.1" + "source": "https://github.com/masmerise/livewire-toaster/tree/1.3.0" }, - "time": "2023-07-03T19:45:56+00:00" + "time": "2023-08-05T18:22:07+00:00" }, { "name": "monolog/monolog", @@ -2911,6 +3223,67 @@ ], "time": "2023-06-21T08:46:11+00:00" }, + { + "name": "mtdowling/jmespath.php", + "version": "2.6.1", + "source": { + "type": "git", + "url": "https://github.com/jmespath/jmespath.php.git", + "reference": "9b87907a81b87bc76d19a7fb2d61e61486ee9edb" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/jmespath/jmespath.php/zipball/9b87907a81b87bc76d19a7fb2d61e61486ee9edb", + "reference": "9b87907a81b87bc76d19a7fb2d61e61486ee9edb", + "shasum": "" + }, + "require": { + "php": "^5.4 || ^7.0 || ^8.0", + "symfony/polyfill-mbstring": "^1.17" + }, + "require-dev": { + "composer/xdebug-handler": "^1.4 || ^2.0", + "phpunit/phpunit": "^4.8.36 || ^7.5.15" + }, + "bin": [ + "bin/jp.php" + ], + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.6-dev" + } + }, + "autoload": { + "files": [ + "src/JmesPath.php" + ], + "psr-4": { + "JmesPath\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Michael Dowling", + "email": "mtdowling@gmail.com", + "homepage": "https://github.com/mtdowling" + } + ], + "description": "Declaratively specify how to extract elements from a JSON document", + "keywords": [ + "json", + "jsonpath" + ], + "support": { + "issues": "https://github.com/jmespath/jmespath.php/issues", + "source": "https://github.com/jmespath/jmespath.php/tree/2.6.1" + }, + "time": "2021-06-14T00:11:39+00:00" + }, { "name": "nesbot/carbon", "version": "2.68.1", @@ -3015,21 +3388,21 @@ }, { "name": "nette/schema", - "version": "v1.2.3", + "version": "v1.2.4", "source": { "type": "git", "url": "https://github.com/nette/schema.git", - "reference": "abbdbb70e0245d5f3bf77874cea1dfb0c930d06f" + "reference": "c9ff517a53903b3d4e29ec547fb20feecb05b8ab" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/nette/schema/zipball/abbdbb70e0245d5f3bf77874cea1dfb0c930d06f", - "reference": "abbdbb70e0245d5f3bf77874cea1dfb0c930d06f", + "url": "https://api.github.com/repos/nette/schema/zipball/c9ff517a53903b3d4e29ec547fb20feecb05b8ab", + "reference": "c9ff517a53903b3d4e29ec547fb20feecb05b8ab", "shasum": "" }, "require": { "nette/utils": "^2.5.7 || ^3.1.5 || ^4.0", - "php": ">=7.1 <8.3" + "php": "7.1 - 8.3" }, "require-dev": { "nette/tester": "^2.3 || ^2.4", @@ -3071,26 +3444,26 @@ ], "support": { "issues": "https://github.com/nette/schema/issues", - "source": "https://github.com/nette/schema/tree/v1.2.3" + "source": "https://github.com/nette/schema/tree/v1.2.4" }, - "time": "2022-10-13T01:24:26+00:00" + "time": "2023-08-05T18:56:25+00:00" }, { "name": "nette/utils", - "version": "v4.0.0", + "version": "v4.0.1", "source": { "type": "git", "url": "https://github.com/nette/utils.git", - "reference": "cacdbf5a91a657ede665c541eda28941d4b09c1e" + "reference": "9124157137da01b1f5a5a22d6486cb975f26db7e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/nette/utils/zipball/cacdbf5a91a657ede665c541eda28941d4b09c1e", - "reference": "cacdbf5a91a657ede665c541eda28941d4b09c1e", + "url": "https://api.github.com/repos/nette/utils/zipball/9124157137da01b1f5a5a22d6486cb975f26db7e", + "reference": "9124157137da01b1f5a5a22d6486cb975f26db7e", "shasum": "" }, "require": { - "php": ">=8.0 <8.3" + "php": ">=8.0 <8.4" }, "conflict": { "nette/finder": "<3", @@ -3098,7 +3471,7 @@ }, "require-dev": { "jetbrains/phpstorm-attributes": "dev-master", - "nette/tester": "^2.4", + "nette/tester": "^2.5", "phpstan/phpstan": "^1.0", "tracy/tracy": "^2.9" }, @@ -3158,9 +3531,9 @@ ], "support": { "issues": "https://github.com/nette/utils/issues", - "source": "https://github.com/nette/utils/tree/v4.0.0" + "source": "https://github.com/nette/utils/tree/v4.0.1" }, - "time": "2023-02-02T10:41:53+00:00" + "time": "2023-07-30T15:42:21+00:00" }, { "name": "nikic/php-parser", @@ -4075,16 +4448,16 @@ }, { "name": "phpstan/phpdoc-parser", - "version": "1.22.1", + "version": "1.23.1", "source": { "type": "git", "url": "https://github.com/phpstan/phpdoc-parser.git", - "reference": "65c39594fbd8c67abfc68bb323f86447bab79cc0" + "reference": "846ae76eef31c6d7790fac9bc399ecee45160b26" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpstan/phpdoc-parser/zipball/65c39594fbd8c67abfc68bb323f86447bab79cc0", - "reference": "65c39594fbd8c67abfc68bb323f86447bab79cc0", + "url": "https://api.github.com/repos/phpstan/phpdoc-parser/zipball/846ae76eef31c6d7790fac9bc399ecee45160b26", + "reference": "846ae76eef31c6d7790fac9bc399ecee45160b26", "shasum": "" }, "require": { @@ -4116,9 +4489,9 @@ "description": "PHPDoc parser with support for nullable, intersection and generic types", "support": { "issues": "https://github.com/phpstan/phpdoc-parser/issues", - "source": "https://github.com/phpstan/phpdoc-parser/tree/1.22.1" + "source": "https://github.com/phpstan/phpdoc-parser/tree/1.23.1" }, - "time": "2023-06-29T20:46:06+00:00" + "time": "2023-08-03T16:32:59+00:00" }, { "name": "pimple/pimple", @@ -4173,6 +4546,50 @@ }, "time": "2021-10-28T11:13:42+00:00" }, + { + "name": "poliander/cron", + "version": "3.0.6", + "source": { + "type": "git", + "url": "https://github.com/poliander/cron.git", + "reference": "f604b1f734a64221ebef5b13da34c403488afd28" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/poliander/cron/zipball/f604b1f734a64221ebef5b13da34c403488afd28", + "reference": "f604b1f734a64221ebef5b13da34c403488afd28", + "shasum": "" + }, + "require": { + "php": "7.4.* || 8.0.* || 8.1.* || 8.2.*" + }, + "require-dev": { + "phpunit/phpunit": "~9.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Poliander\\Cron\\": "src/Cron/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "GPL-3.0-or-later" + ], + "authors": [ + { + "name": "René Pollesch", + "role": "Developer" + } + ], + "description": "Standard (V7) compliant crontab expression parser/validator with support for time zones", + "homepage": "https://github.com/poliander/cron", + "support": { + "issues": "https://github.com/poliander/cron/issues", + "source": "https://github.com/poliander/cron/tree/3.0.6" + }, + "time": "2023-07-30T07:53:24+00:00" + }, { "name": "pragmarx/google2fa", "version": "v8.0.1", @@ -4688,16 +5105,16 @@ }, { "name": "psy/psysh", - "version": "v0.11.18", + "version": "v0.11.20", "source": { "type": "git", "url": "https://github.com/bobthecow/psysh.git", - "reference": "4f00ee9e236fa6a48f4560d1300b9c961a70a7ec" + "reference": "0fa27040553d1d280a67a4393194df5228afea5b" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/bobthecow/psysh/zipball/4f00ee9e236fa6a48f4560d1300b9c961a70a7ec", - "reference": "4f00ee9e236fa6a48f4560d1300b9c961a70a7ec", + "url": "https://api.github.com/repos/bobthecow/psysh/zipball/0fa27040553d1d280a67a4393194df5228afea5b", + "reference": "0fa27040553d1d280a67a4393194df5228afea5b", "shasum": "" }, "require": { @@ -4758,9 +5175,9 @@ ], "support": { "issues": "https://github.com/bobthecow/psysh/issues", - "source": "https://github.com/bobthecow/psysh/tree/v0.11.18" + "source": "https://github.com/bobthecow/psysh/tree/v0.11.20" }, - "time": "2023-05-23T02:31:11+00:00" + "time": "2023-07-31T14:32:22+00:00" }, { "name": "ralouphie/getallheaders", @@ -5046,16 +5463,16 @@ }, { "name": "sentry/sentry", - "version": "3.20.1", + "version": "3.21.0", "source": { "type": "git", "url": "https://github.com/getsentry/sentry-php.git", - "reference": "644ad9768c18139a80ac510090fad000d9ffd8a4" + "reference": "624aafc22b84b089ffa43b71fb01e0096505ec4f" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/getsentry/sentry-php/zipball/644ad9768c18139a80ac510090fad000d9ffd8a4", - "reference": "644ad9768c18139a80ac510090fad000d9ffd8a4", + "url": "https://api.github.com/repos/getsentry/sentry-php/zipball/624aafc22b84b089ffa43b71fb01e0096505ec4f", + "reference": "624aafc22b84b089ffa43b71fb01e0096505ec4f", "shasum": "" }, "require": { @@ -5099,11 +5516,6 @@ "monolog/monolog": "Allow sending log messages to Sentry by using the included Monolog handler." }, "type": "library", - "extra": { - "branch-alias": { - "dev-master": "3.13.x-dev" - } - }, "autoload": { "files": [ "src/functions.php" @@ -5135,7 +5547,7 @@ ], "support": { "issues": "https://github.com/getsentry/sentry-php/issues", - "source": "https://github.com/getsentry/sentry-php/tree/3.20.1" + "source": "https://github.com/getsentry/sentry-php/tree/3.21.0" }, "funding": [ { @@ -5147,20 +5559,20 @@ "type": "custom" } ], - "time": "2023-06-26T11:01:40+00:00" + "time": "2023-07-31T15:31:24+00:00" }, { "name": "sentry/sentry-laravel", - "version": "3.6.1", + "version": "3.7.3", "source": { "type": "git", "url": "https://github.com/getsentry/sentry-laravel.git", - "reference": "eb94a52b88794d0c108dc46ca1a680531c3a8bad" + "reference": "2aee4ad217be8ef04ffcde6e9f7dd17af5a3b0bf" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/getsentry/sentry-laravel/zipball/eb94a52b88794d0c108dc46ca1a680531c3a8bad", - "reference": "eb94a52b88794d0c108dc46ca1a680531c3a8bad", + "url": "https://api.github.com/repos/getsentry/sentry-laravel/zipball/2aee4ad217be8ef04ffcde6e9f7dd17af5a3b0bf", + "reference": "2aee4ad217be8ef04ffcde6e9f7dd17af5a3b0bf", "shasum": "" }, "require": { @@ -5168,7 +5580,7 @@ "nyholm/psr7": "^1.0", "php": "^7.2 | ^8.0", "sentry/sdk": "^3.4", - "sentry/sentry": "^3.20", + "sentry/sentry": "^3.20.1", "symfony/psr-http-message-bridge": "^1.0 | ^2.0" }, "require-dev": { @@ -5176,6 +5588,7 @@ "laravel/framework": "^6.0 | ^7.0 | ^8.0 | ^9.0 | ^10.0", "mockery/mockery": "^1.3", "orchestra/testbench": "^4.7 | ^5.1 | ^6.0 | ^7.0 | ^8.0", + "phpstan/phpstan": "^1.10", "phpunit/phpunit": "^8.4 | ^9.3" }, "type": "library", @@ -5225,7 +5638,7 @@ ], "support": { "issues": "https://github.com/getsentry/sentry-laravel/issues", - "source": "https://github.com/getsentry/sentry-laravel/tree/3.6.1" + "source": "https://github.com/getsentry/sentry-laravel/tree/3.7.3" }, "funding": [ { @@ -5237,7 +5650,7 @@ "type": "custom" } ], - "time": "2023-07-04T10:30:42+00:00" + "time": "2023-08-03T10:10:23+00:00" }, { "name": "spatie/backtrace", @@ -5394,16 +5807,16 @@ }, { "name": "spatie/laravel-data", - "version": "3.7.0", + "version": "3.8.0", "source": { "type": "git", "url": "https://github.com/spatie/laravel-data.git", - "reference": "a975123d86e0133a361ac225d17acb3d11aa351f" + "reference": "0de2ecfc8fa98bcca6e2e041d22de8c74a19c45b" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/spatie/laravel-data/zipball/a975123d86e0133a361ac225d17acb3d11aa351f", - "reference": "a975123d86e0133a361ac225d17acb3d11aa351f", + "url": "https://api.github.com/repos/spatie/laravel-data/zipball/0de2ecfc8fa98bcca6e2e041d22de8c74a19c45b", + "reference": "0de2ecfc8fa98bcca6e2e041d22de8c74a19c45b", "shasum": "" }, "require": { @@ -5465,7 +5878,7 @@ ], "support": { "issues": "https://github.com/spatie/laravel-data/issues", - "source": "https://github.com/spatie/laravel-data/tree/3.7.0" + "source": "https://github.com/spatie/laravel-data/tree/3.8.0" }, "funding": [ { @@ -5473,20 +5886,20 @@ "type": "github" } ], - "time": "2023-07-05T11:45:14+00:00" + "time": "2023-08-09T14:09:06+00:00" }, { "name": "spatie/laravel-package-tools", - "version": "1.15.0", + "version": "1.16.0", "source": { "type": "git", "url": "https://github.com/spatie/laravel-package-tools.git", - "reference": "efab1844b8826443135201c4443690f032c3d533" + "reference": "38fe533e93f86a1b2fb1000bf7df09c4748e6458" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/spatie/laravel-package-tools/zipball/efab1844b8826443135201c4443690f032c3d533", - "reference": "efab1844b8826443135201c4443690f032c3d533", + "url": "https://api.github.com/repos/spatie/laravel-package-tools/zipball/38fe533e93f86a1b2fb1000bf7df09c4748e6458", + "reference": "38fe533e93f86a1b2fb1000bf7df09c4748e6458", "shasum": "" }, "require": { @@ -5525,7 +5938,7 @@ ], "support": { "issues": "https://github.com/spatie/laravel-package-tools/issues", - "source": "https://github.com/spatie/laravel-package-tools/tree/1.15.0" + "source": "https://github.com/spatie/laravel-package-tools/tree/1.16.0" }, "funding": [ { @@ -5533,20 +5946,20 @@ "type": "github" } ], - "time": "2023-04-27T08:09:01+00:00" + "time": "2023-08-09T14:08:04+00:00" }, { "name": "spatie/laravel-ray", - "version": "1.32.5", + "version": "1.32.6", "source": { "type": "git", "url": "https://github.com/spatie/laravel-ray.git", - "reference": "288f30c94c9725dfd78d8a1b82b230521f4d697e" + "reference": "8526dd6c6c838b4bac4e0df2ea7896b688b0d43e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/spatie/laravel-ray/zipball/288f30c94c9725dfd78d8a1b82b230521f4d697e", - "reference": "288f30c94c9725dfd78d8a1b82b230521f4d697e", + "url": "https://api.github.com/repos/spatie/laravel-ray/zipball/8526dd6c6c838b4bac4e0df2ea7896b688b0d43e", + "reference": "8526dd6c6c838b4bac4e0df2ea7896b688b0d43e", "shasum": "" }, "require": { @@ -5606,7 +6019,7 @@ ], "support": { "issues": "https://github.com/spatie/laravel-ray/issues", - "source": "https://github.com/spatie/laravel-ray/tree/1.32.5" + "source": "https://github.com/spatie/laravel-ray/tree/1.32.6" }, "funding": [ { @@ -5618,7 +6031,7 @@ "type": "other" } ], - "time": "2023-06-23T07:04:32+00:00" + "time": "2023-07-19T12:30:16+00:00" }, { "name": "spatie/laravel-schemaless-attributes", @@ -5886,16 +6299,16 @@ }, { "name": "symfony/console", - "version": "v6.3.0", + "version": "v6.3.2", "source": { "type": "git", "url": "https://github.com/symfony/console.git", - "reference": "8788808b07cf0bdd6e4b7fdd23d8ddb1470c83b7" + "reference": "aa5d64ad3f63f2e48964fc81ee45cb318a723898" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/console/zipball/8788808b07cf0bdd6e4b7fdd23d8ddb1470c83b7", - "reference": "8788808b07cf0bdd6e4b7fdd23d8ddb1470c83b7", + "url": "https://api.github.com/repos/symfony/console/zipball/aa5d64ad3f63f2e48964fc81ee45cb318a723898", + "reference": "aa5d64ad3f63f2e48964fc81ee45cb318a723898", "shasum": "" }, "require": { @@ -5956,7 +6369,7 @@ "terminal" ], "support": { - "source": "https://github.com/symfony/console/tree/v6.3.0" + "source": "https://github.com/symfony/console/tree/v6.3.2" }, "funding": [ { @@ -5972,20 +6385,20 @@ "type": "tidelift" } ], - "time": "2023-05-29T12:49:39+00:00" + "time": "2023-07-19T20:17:28+00:00" }, { "name": "symfony/css-selector", - "version": "v6.3.0", + "version": "v6.3.2", "source": { "type": "git", "url": "https://github.com/symfony/css-selector.git", - "reference": "88453e64cd86c5b60e8d2fb2c6f953bbc353ffbf" + "reference": "883d961421ab1709877c10ac99451632a3d6fa57" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/css-selector/zipball/88453e64cd86c5b60e8d2fb2c6f953bbc353ffbf", - "reference": "88453e64cd86c5b60e8d2fb2c6f953bbc353ffbf", + "url": "https://api.github.com/repos/symfony/css-selector/zipball/883d961421ab1709877c10ac99451632a3d6fa57", + "reference": "883d961421ab1709877c10ac99451632a3d6fa57", "shasum": "" }, "require": { @@ -6021,7 +6434,7 @@ "description": "Converts CSS selectors to XPath expressions", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/css-selector/tree/v6.3.0" + "source": "https://github.com/symfony/css-selector/tree/v6.3.2" }, "funding": [ { @@ -6037,7 +6450,7 @@ "type": "tidelift" } ], - "time": "2023-03-20T16:43:42+00:00" + "time": "2023-07-12T16:00:22+00:00" }, { "name": "symfony/deprecation-contracts", @@ -6108,16 +6521,16 @@ }, { "name": "symfony/error-handler", - "version": "v6.3.0", + "version": "v6.3.2", "source": { "type": "git", "url": "https://github.com/symfony/error-handler.git", - "reference": "99d2d814a6351461af350ead4d963bd67451236f" + "reference": "85fd65ed295c4078367c784e8a5a6cee30348b7a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/error-handler/zipball/99d2d814a6351461af350ead4d963bd67451236f", - "reference": "99d2d814a6351461af350ead4d963bd67451236f", + "url": "https://api.github.com/repos/symfony/error-handler/zipball/85fd65ed295c4078367c784e8a5a6cee30348b7a", + "reference": "85fd65ed295c4078367c784e8a5a6cee30348b7a", "shasum": "" }, "require": { @@ -6162,7 +6575,7 @@ "description": "Provides tools to manage errors and ease debugging PHP code", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/error-handler/tree/v6.3.0" + "source": "https://github.com/symfony/error-handler/tree/v6.3.2" }, "funding": [ { @@ -6178,20 +6591,20 @@ "type": "tidelift" } ], - "time": "2023-05-10T12:03:13+00:00" + "time": "2023-07-16T17:05:46+00:00" }, { "name": "symfony/event-dispatcher", - "version": "v6.3.0", + "version": "v6.3.2", "source": { "type": "git", "url": "https://github.com/symfony/event-dispatcher.git", - "reference": "3af8ac1a3f98f6dbc55e10ae59c9e44bfc38dfaa" + "reference": "adb01fe097a4ee930db9258a3cc906b5beb5cf2e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/event-dispatcher/zipball/3af8ac1a3f98f6dbc55e10ae59c9e44bfc38dfaa", - "reference": "3af8ac1a3f98f6dbc55e10ae59c9e44bfc38dfaa", + "url": "https://api.github.com/repos/symfony/event-dispatcher/zipball/adb01fe097a4ee930db9258a3cc906b5beb5cf2e", + "reference": "adb01fe097a4ee930db9258a3cc906b5beb5cf2e", "shasum": "" }, "require": { @@ -6242,7 +6655,7 @@ "description": "Provides tools that allow your application components to communicate with each other by dispatching events and listening to them", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/event-dispatcher/tree/v6.3.0" + "source": "https://github.com/symfony/event-dispatcher/tree/v6.3.2" }, "funding": [ { @@ -6258,7 +6671,7 @@ "type": "tidelift" } ], - "time": "2023-04-21T14:41:17+00:00" + "time": "2023-07-06T06:56:43+00:00" }, { "name": "symfony/event-dispatcher-contracts", @@ -6338,16 +6751,16 @@ }, { "name": "symfony/finder", - "version": "v6.3.0", + "version": "v6.3.3", "source": { "type": "git", "url": "https://github.com/symfony/finder.git", - "reference": "d9b01ba073c44cef617c7907ce2419f8d00d75e2" + "reference": "9915db259f67d21eefee768c1abcf1cc61b1fc9e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/finder/zipball/d9b01ba073c44cef617c7907ce2419f8d00d75e2", - "reference": "d9b01ba073c44cef617c7907ce2419f8d00d75e2", + "url": "https://api.github.com/repos/symfony/finder/zipball/9915db259f67d21eefee768c1abcf1cc61b1fc9e", + "reference": "9915db259f67d21eefee768c1abcf1cc61b1fc9e", "shasum": "" }, "require": { @@ -6382,7 +6795,7 @@ "description": "Finds files and directories via an intuitive fluent interface", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/finder/tree/v6.3.0" + "source": "https://github.com/symfony/finder/tree/v6.3.3" }, "funding": [ { @@ -6398,20 +6811,20 @@ "type": "tidelift" } ], - "time": "2023-04-02T01:25:41+00:00" + "time": "2023-07-31T08:31:44+00:00" }, { "name": "symfony/http-client", - "version": "v6.3.1", + "version": "v6.3.2", "source": { "type": "git", "url": "https://github.com/symfony/http-client.git", - "reference": "1c828a06aef2f5eeba42026dfc532d4fc5406123" + "reference": "15f9f4bad62bfcbe48b5dedd866f04a08fc7ff00" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/http-client/zipball/1c828a06aef2f5eeba42026dfc532d4fc5406123", - "reference": "1c828a06aef2f5eeba42026dfc532d4fc5406123", + "url": "https://api.github.com/repos/symfony/http-client/zipball/15f9f4bad62bfcbe48b5dedd866f04a08fc7ff00", + "reference": "15f9f4bad62bfcbe48b5dedd866f04a08fc7ff00", "shasum": "" }, "require": { @@ -6474,7 +6887,7 @@ "http" ], "support": { - "source": "https://github.com/symfony/http-client/tree/v6.3.1" + "source": "https://github.com/symfony/http-client/tree/v6.3.2" }, "funding": [ { @@ -6490,7 +6903,7 @@ "type": "tidelift" } ], - "time": "2023-06-24T11:51:27+00:00" + "time": "2023-07-05T08:41:27+00:00" }, { "name": "symfony/http-client-contracts", @@ -6572,16 +6985,16 @@ }, { "name": "symfony/http-foundation", - "version": "v6.3.1", + "version": "v6.3.2", "source": { "type": "git", "url": "https://github.com/symfony/http-foundation.git", - "reference": "e0ad0d153e1c20069250986cd9e9dd1ccebb0d66" + "reference": "43ed99d30f5f466ffa00bdac3f5f7aa9cd7617c3" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/http-foundation/zipball/e0ad0d153e1c20069250986cd9e9dd1ccebb0d66", - "reference": "e0ad0d153e1c20069250986cd9e9dd1ccebb0d66", + "url": "https://api.github.com/repos/symfony/http-foundation/zipball/43ed99d30f5f466ffa00bdac3f5f7aa9cd7617c3", + "reference": "43ed99d30f5f466ffa00bdac3f5f7aa9cd7617c3", "shasum": "" }, "require": { @@ -6629,7 +7042,7 @@ "description": "Defines an object-oriented layer for the HTTP specification", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/http-foundation/tree/v6.3.1" + "source": "https://github.com/symfony/http-foundation/tree/v6.3.2" }, "funding": [ { @@ -6645,20 +7058,20 @@ "type": "tidelift" } ], - "time": "2023-06-24T11:51:27+00:00" + "time": "2023-07-23T21:58:39+00:00" }, { "name": "symfony/http-kernel", - "version": "v6.3.1", + "version": "v6.3.3", "source": { "type": "git", "url": "https://github.com/symfony/http-kernel.git", - "reference": "161e16fd2e35fb4881a43bc8b383dfd5be4ac374" + "reference": "d3b567f0addf695e10b0c6d57564a9bea2e058ee" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/http-kernel/zipball/161e16fd2e35fb4881a43bc8b383dfd5be4ac374", - "reference": "161e16fd2e35fb4881a43bc8b383dfd5be4ac374", + "url": "https://api.github.com/repos/symfony/http-kernel/zipball/d3b567f0addf695e10b0c6d57564a9bea2e058ee", + "reference": "d3b567f0addf695e10b0c6d57564a9bea2e058ee", "shasum": "" }, "require": { @@ -6742,7 +7155,7 @@ "description": "Provides a structured process for converting a Request into a Response", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/http-kernel/tree/v6.3.1" + "source": "https://github.com/symfony/http-kernel/tree/v6.3.3" }, "funding": [ { @@ -6758,7 +7171,7 @@ "type": "tidelift" } ], - "time": "2023-06-26T06:07:32+00:00" + "time": "2023-07-31T10:33:00+00:00" }, { "name": "symfony/mailer", @@ -6842,20 +7255,21 @@ }, { "name": "symfony/mime", - "version": "v6.3.0", + "version": "v6.3.3", "source": { "type": "git", "url": "https://github.com/symfony/mime.git", - "reference": "7b5d2121858cd6efbed778abce9cfdd7ab1f62ad" + "reference": "9a0cbd52baa5ba5a5b1f0cacc59466f194730f98" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/mime/zipball/7b5d2121858cd6efbed778abce9cfdd7ab1f62ad", - "reference": "7b5d2121858cd6efbed778abce9cfdd7ab1f62ad", + "url": "https://api.github.com/repos/symfony/mime/zipball/9a0cbd52baa5ba5a5b1f0cacc59466f194730f98", + "reference": "9a0cbd52baa5ba5a5b1f0cacc59466f194730f98", "shasum": "" }, "require": { "php": ">=8.1", + "symfony/deprecation-contracts": "^2.5|^3", "symfony/polyfill-intl-idn": "^1.10", "symfony/polyfill-mbstring": "^1.0" }, @@ -6864,7 +7278,7 @@ "phpdocumentor/reflection-docblock": "<3.2.2", "phpdocumentor/type-resolver": "<1.4.0", "symfony/mailer": "<5.4", - "symfony/serializer": "<6.2" + "symfony/serializer": "<6.2.13|>=6.3,<6.3.2" }, "require-dev": { "egulias/email-validator": "^2.1.10|^3.1|^4", @@ -6873,7 +7287,7 @@ "symfony/dependency-injection": "^5.4|^6.0", "symfony/property-access": "^5.4|^6.0", "symfony/property-info": "^5.4|^6.0", - "symfony/serializer": "^6.2" + "symfony/serializer": "~6.2.13|^6.3.2" }, "type": "library", "autoload": { @@ -6905,7 +7319,7 @@ "mime-type" ], "support": { - "source": "https://github.com/symfony/mime/tree/v6.3.0" + "source": "https://github.com/symfony/mime/tree/v6.3.3" }, "funding": [ { @@ -6921,7 +7335,7 @@ "type": "tidelift" } ], - "time": "2023-04-28T15:57:00+00:00" + "time": "2023-07-31T07:08:24+00:00" }, { "name": "symfony/options-resolver", @@ -7810,16 +8224,16 @@ }, { "name": "symfony/process", - "version": "v6.3.0", + "version": "v6.3.2", "source": { "type": "git", "url": "https://github.com/symfony/process.git", - "reference": "8741e3ed7fe2e91ec099e02446fb86667a0f1628" + "reference": "c5ce962db0d9b6e80247ca5eb9af6472bd4d7b5d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/process/zipball/8741e3ed7fe2e91ec099e02446fb86667a0f1628", - "reference": "8741e3ed7fe2e91ec099e02446fb86667a0f1628", + "url": "https://api.github.com/repos/symfony/process/zipball/c5ce962db0d9b6e80247ca5eb9af6472bd4d7b5d", + "reference": "c5ce962db0d9b6e80247ca5eb9af6472bd4d7b5d", "shasum": "" }, "require": { @@ -7851,7 +8265,7 @@ "description": "Executes commands in sub-processes", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/process/tree/v6.3.0" + "source": "https://github.com/symfony/process/tree/v6.3.2" }, "funding": [ { @@ -7867,25 +8281,26 @@ "type": "tidelift" } ], - "time": "2023-05-19T08:06:44+00:00" + "time": "2023-07-12T16:00:22+00:00" }, { "name": "symfony/psr-http-message-bridge", - "version": "v2.2.0", + "version": "v2.3.1", "source": { "type": "git", "url": "https://github.com/symfony/psr-http-message-bridge.git", - "reference": "28a732c05bbad801304ad5a5c674cf2970508993" + "reference": "581ca6067eb62640de5ff08ee1ba6850a0ee472e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/psr-http-message-bridge/zipball/28a732c05bbad801304ad5a5c674cf2970508993", - "reference": "28a732c05bbad801304ad5a5c674cf2970508993", + "url": "https://api.github.com/repos/symfony/psr-http-message-bridge/zipball/581ca6067eb62640de5ff08ee1ba6850a0ee472e", + "reference": "581ca6067eb62640de5ff08ee1ba6850a0ee472e", "shasum": "" }, "require": { "php": ">=7.2.5", "psr/http-message": "^1.0 || ^2.0", + "symfony/deprecation-contracts": "^2.5 || ^3.0", "symfony/http-foundation": "^5.4 || ^6.0" }, "require-dev": { @@ -7904,7 +8319,7 @@ "type": "symfony-bridge", "extra": { "branch-alias": { - "dev-main": "2.2-dev" + "dev-main": "2.3-dev" } }, "autoload": { @@ -7939,7 +8354,7 @@ ], "support": { "issues": "https://github.com/symfony/psr-http-message-bridge/issues", - "source": "https://github.com/symfony/psr-http-message-bridge/tree/v2.2.0" + "source": "https://github.com/symfony/psr-http-message-bridge/tree/v2.3.1" }, "funding": [ { @@ -7955,24 +8370,25 @@ "type": "tidelift" } ], - "time": "2023-04-21T08:40:19+00:00" + "time": "2023-07-26T11:53:26+00:00" }, { "name": "symfony/routing", - "version": "v6.3.1", + "version": "v6.3.3", "source": { "type": "git", "url": "https://github.com/symfony/routing.git", - "reference": "d37ad1779c38b8eb71996d17dc13030dcb7f9cf5" + "reference": "e7243039ab663822ff134fbc46099b5fdfa16f6a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/routing/zipball/d37ad1779c38b8eb71996d17dc13030dcb7f9cf5", - "reference": "d37ad1779c38b8eb71996d17dc13030dcb7f9cf5", + "url": "https://api.github.com/repos/symfony/routing/zipball/e7243039ab663822ff134fbc46099b5fdfa16f6a", + "reference": "e7243039ab663822ff134fbc46099b5fdfa16f6a", "shasum": "" }, "require": { - "php": ">=8.1" + "php": ">=8.1", + "symfony/deprecation-contracts": "^2.5|^3" }, "conflict": { "doctrine/annotations": "<1.12", @@ -8021,7 +8437,7 @@ "url" ], "support": { - "source": "https://github.com/symfony/routing/tree/v6.3.1" + "source": "https://github.com/symfony/routing/tree/v6.3.3" }, "funding": [ { @@ -8037,7 +8453,7 @@ "type": "tidelift" } ], - "time": "2023-06-05T15:30:22+00:00" + "time": "2023-07-31T07:08:24+00:00" }, { "name": "symfony/service-contracts", @@ -8185,16 +8601,16 @@ }, { "name": "symfony/string", - "version": "v6.3.0", + "version": "v6.3.2", "source": { "type": "git", "url": "https://github.com/symfony/string.git", - "reference": "f2e190ee75ff0f5eced645ec0be5c66fac81f51f" + "reference": "53d1a83225002635bca3482fcbf963001313fb68" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/string/zipball/f2e190ee75ff0f5eced645ec0be5c66fac81f51f", - "reference": "f2e190ee75ff0f5eced645ec0be5c66fac81f51f", + "url": "https://api.github.com/repos/symfony/string/zipball/53d1a83225002635bca3482fcbf963001313fb68", + "reference": "53d1a83225002635bca3482fcbf963001313fb68", "shasum": "" }, "require": { @@ -8251,7 +8667,7 @@ "utf8" ], "support": { - "source": "https://github.com/symfony/string/tree/v6.3.0" + "source": "https://github.com/symfony/string/tree/v6.3.2" }, "funding": [ { @@ -8267,24 +8683,25 @@ "type": "tidelift" } ], - "time": "2023-03-21T21:06:29+00:00" + "time": "2023-07-05T08:41:27+00:00" }, { "name": "symfony/translation", - "version": "v6.3.0", + "version": "v6.3.3", "source": { "type": "git", "url": "https://github.com/symfony/translation.git", - "reference": "f72b2cba8f79dd9d536f534f76874b58ad37876f" + "reference": "3ed078c54bc98bbe4414e1e9b2d5e85ed5a5c8bd" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/translation/zipball/f72b2cba8f79dd9d536f534f76874b58ad37876f", - "reference": "f72b2cba8f79dd9d536f534f76874b58ad37876f", + "url": "https://api.github.com/repos/symfony/translation/zipball/3ed078c54bc98bbe4414e1e9b2d5e85ed5a5c8bd", + "reference": "3ed078c54bc98bbe4414e1e9b2d5e85ed5a5c8bd", "shasum": "" }, "require": { "php": ">=8.1", + "symfony/deprecation-contracts": "^2.5|^3", "symfony/polyfill-mbstring": "~1.0", "symfony/translation-contracts": "^2.5|^3.0" }, @@ -8345,7 +8762,7 @@ "description": "Provides tools to internationalize your application", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/translation/tree/v6.3.0" + "source": "https://github.com/symfony/translation/tree/v6.3.3" }, "funding": [ { @@ -8361,7 +8778,7 @@ "type": "tidelift" } ], - "time": "2023-05-19T12:46:45+00:00" + "time": "2023-07-31T07:08:24+00:00" }, { "name": "symfony/translation-contracts", @@ -8517,20 +8934,21 @@ }, { "name": "symfony/var-dumper", - "version": "v6.3.1", + "version": "v6.3.3", "source": { "type": "git", "url": "https://github.com/symfony/var-dumper.git", - "reference": "c81268d6960ddb47af17391a27d222bd58cf0515" + "reference": "77fb4f2927f6991a9843633925d111147449ee7a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/var-dumper/zipball/c81268d6960ddb47af17391a27d222bd58cf0515", - "reference": "c81268d6960ddb47af17391a27d222bd58cf0515", + "url": "https://api.github.com/repos/symfony/var-dumper/zipball/77fb4f2927f6991a9843633925d111147449ee7a", + "reference": "77fb4f2927f6991a9843633925d111147449ee7a", "shasum": "" }, "require": { "php": ">=8.1", + "symfony/deprecation-contracts": "^2.5|^3", "symfony/polyfill-mbstring": "~1.0" }, "conflict": { @@ -8539,6 +8957,7 @@ "require-dev": { "ext-iconv": "*", "symfony/console": "^5.4|^6.0", + "symfony/http-kernel": "^5.4|^6.0", "symfony/process": "^5.4|^6.0", "symfony/uid": "^5.4|^6.0", "twig/twig": "^2.13|^3.0.4" @@ -8579,7 +8998,7 @@ "dump" ], "support": { - "source": "https://github.com/symfony/var-dumper/tree/v6.3.1" + "source": "https://github.com/symfony/var-dumper/tree/v6.3.3" }, "funding": [ { @@ -8595,24 +9014,25 @@ "type": "tidelift" } ], - "time": "2023-06-21T12:08:28+00:00" + "time": "2023-07-31T07:08:24+00:00" }, { "name": "symfony/yaml", - "version": "v6.3.0", + "version": "v6.3.3", "source": { "type": "git", "url": "https://github.com/symfony/yaml.git", - "reference": "a9a8337aa641ef2aa39c3e028f9107ec391e5927" + "reference": "e23292e8c07c85b971b44c1c4b87af52133e2add" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/yaml/zipball/a9a8337aa641ef2aa39c3e028f9107ec391e5927", - "reference": "a9a8337aa641ef2aa39c3e028f9107ec391e5927", + "url": "https://api.github.com/repos/symfony/yaml/zipball/e23292e8c07c85b971b44c1c4b87af52133e2add", + "reference": "e23292e8c07c85b971b44c1c4b87af52133e2add", "shasum": "" }, "require": { "php": ">=8.1", + "symfony/deprecation-contracts": "^2.5|^3", "symfony/polyfill-ctype": "^1.8" }, "conflict": { @@ -8650,7 +9070,7 @@ "description": "Loads and dumps YAML files", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/yaml/tree/v6.3.0" + "source": "https://github.com/symfony/yaml/tree/v6.3.3" }, "funding": [ { @@ -8666,7 +9086,7 @@ "type": "tidelift" } ], - "time": "2023-04-28T13:28:14+00:00" + "time": "2023-07-31T07:08:24+00:00" }, { "name": "tijsverkoyen/css-to-inline-styles", @@ -9207,16 +9627,16 @@ "packages-dev": [ { "name": "brianium/paratest", - "version": "v7.2.2", + "version": "v7.2.5", "source": { "type": "git", "url": "https://github.com/paratestphp/paratest.git", - "reference": "eb9d6b0924bf39781ab7a7ed1d7db89514f9ea76" + "reference": "4d7ad5b6564f63baa1b948ecad05439f22880942" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/paratestphp/paratest/zipball/eb9d6b0924bf39781ab7a7ed1d7db89514f9ea76", - "reference": "eb9d6b0924bf39781ab7a7ed1d7db89514f9ea76", + "url": "https://api.github.com/repos/paratestphp/paratest/zipball/4d7ad5b6564f63baa1b948ecad05439f22880942", + "reference": "4d7ad5b6564f63baa1b948ecad05439f22880942", "shasum": "" }, "require": { @@ -9227,25 +9647,25 @@ "fidry/cpu-core-counter": "^0.4.1 || ^0.5.1", "jean85/pretty-package-versions": "^2.0.5", "php": "~8.1.0 || ~8.2.0 || ~8.3.0", - "phpunit/php-code-coverage": "^10.1.1", + "phpunit/php-code-coverage": "^10.1.3", "phpunit/php-file-iterator": "^4.0.2", "phpunit/php-timer": "^6.0", - "phpunit/phpunit": "^10.2.2", + "phpunit/phpunit": "^10.3.1", "sebastian/environment": "^6.0.1", - "symfony/console": "^6.3.0", - "symfony/process": "^6.3.0" + "symfony/console": "^6.3.2", + "symfony/process": "^6.3.2" }, "require-dev": { "doctrine/coding-standard": "^12.0.0", "ext-pcov": "*", "ext-posix": "*", "infection/infection": "^0.27.0", - "phpstan/phpstan": "^1.10.18", + "phpstan/phpstan": "^1.10.26", "phpstan/phpstan-deprecation-rules": "^1.1.3", "phpstan/phpstan-phpunit": "^1.3.13", "phpstan/phpstan-strict-rules": "^1.5.1", "squizlabs/php_codesniffer": "^3.7.2", - "symfony/filesystem": "^6.3.0" + "symfony/filesystem": "^6.3.1" }, "bin": [ "bin/paratest", @@ -9286,7 +9706,7 @@ ], "support": { "issues": "https://github.com/paratestphp/paratest/issues", - "source": "https://github.com/paratestphp/paratest/tree/v7.2.2" + "source": "https://github.com/paratestphp/paratest/tree/v7.2.5" }, "funding": [ { @@ -9298,7 +9718,7 @@ "type": "paypal" } ], - "time": "2023-06-22T14:22:36+00:00" + "time": "2023-08-08T13:23:59+00:00" }, { "name": "fakerphp/faker", @@ -9553,21 +9973,22 @@ }, { "name": "laravel/dusk", - "version": "v7.8.0", + "version": "v7.9.3", "source": { "type": "git", "url": "https://github.com/laravel/dusk.git", - "reference": "82cf388bac1e0ff80f1c2c4b5107ee7c0496b3a5" + "reference": "8d7ce583fb362472558cc1852adccfcd86cf87e4" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/laravel/dusk/zipball/82cf388bac1e0ff80f1c2c4b5107ee7c0496b3a5", - "reference": "82cf388bac1e0ff80f1c2c4b5107ee7c0496b3a5", + "url": "https://api.github.com/repos/laravel/dusk/zipball/8d7ce583fb362472558cc1852adccfcd86cf87e4", + "reference": "8d7ce583fb362472558cc1852adccfcd86cf87e4", "shasum": "" }, "require": { "ext-json": "*", "ext-zip": "*", + "guzzlehttp/guzzle": "^7.2", "illuminate/console": "^9.0|^10.0", "illuminate/support": "^9.0|^10.0", "nesbot/carbon": "^2.0", @@ -9622,22 +10043,22 @@ ], "support": { "issues": "https://github.com/laravel/dusk/issues", - "source": "https://github.com/laravel/dusk/tree/v7.8.0" + "source": "https://github.com/laravel/dusk/tree/v7.9.3" }, - "time": "2023-07-08T21:23:29+00:00" + "time": "2023-08-03T16:00:26+00:00" }, { "name": "laravel/pint", - "version": "v1.10.4", + "version": "v1.10.6", "source": { "type": "git", "url": "https://github.com/laravel/pint.git", - "reference": "f56798088068af8bd75a8f2c4ecae022990fdf75" + "reference": "d1915b6ecc6406c00472c6b9ae75b46aa153bbb2" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/laravel/pint/zipball/f56798088068af8bd75a8f2c4ecae022990fdf75", - "reference": "f56798088068af8bd75a8f2c4ecae022990fdf75", + "url": "https://api.github.com/repos/laravel/pint/zipball/d1915b6ecc6406c00472c6b9ae75b46aa153bbb2", + "reference": "d1915b6ecc6406c00472c6b9ae75b46aa153bbb2", "shasum": "" }, "require": { @@ -9650,7 +10071,7 @@ "require-dev": { "friendsofphp/php-cs-fixer": "^3.21.1", "illuminate/view": "^10.5.1", - "laravel-zero/framework": "^10.0.2", + "laravel-zero/framework": "^10.1.1", "mockery/mockery": "^1.5.1", "nunomaduro/larastan": "^2.5.1", "nunomaduro/termwind": "^1.15.1", @@ -9690,41 +10111,37 @@ "issues": "https://github.com/laravel/pint/issues", "source": "https://github.com/laravel/pint" }, - "time": "2023-07-11T15:18:27+00:00" + "time": "2023-08-08T15:17:16+00:00" }, { "name": "mockery/mockery", - "version": "1.6.2", + "version": "1.6.6", "source": { "type": "git", "url": "https://github.com/mockery/mockery.git", - "reference": "13a7fa2642c76c58fa2806ef7f565344c817a191" + "reference": "b8e0bb7d8c604046539c1115994632c74dcb361e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/mockery/mockery/zipball/13a7fa2642c76c58fa2806ef7f565344c817a191", - "reference": "13a7fa2642c76c58fa2806ef7f565344c817a191", + "url": "https://api.github.com/repos/mockery/mockery/zipball/b8e0bb7d8c604046539c1115994632c74dcb361e", + "reference": "b8e0bb7d8c604046539c1115994632c74dcb361e", "shasum": "" }, "require": { "hamcrest/hamcrest-php": "^2.0.1", "lib-pcre": ">=7.0", - "php": "^7.4 || ^8.0" + "php": ">=7.3" }, "conflict": { "phpunit/phpunit": "<8.0" }, "require-dev": { - "phpunit/phpunit": "^8.5 || ^9.3", - "psalm/plugin-phpunit": "^0.18", - "vimeo/psalm": "^5.9" + "phpunit/phpunit": "^8.5 || ^9.6.10", + "psalm/plugin-phpunit": "^0.18.4", + "symplify/easy-coding-standard": "^11.5.0", + "vimeo/psalm": "^4.30" }, "type": "library", - "extra": { - "branch-alias": { - "dev-main": "1.6.x-dev" - } - }, "autoload": { "files": [ "library/helpers.php", @@ -9742,12 +10159,20 @@ { "name": "Pádraic Brady", "email": "padraic.brady@gmail.com", - "homepage": "http://blog.astrumfutura.com" + "homepage": "https://github.com/padraic", + "role": "Author" }, { "name": "Dave Marshall", "email": "dave.marshall@atstsolutions.co.uk", - "homepage": "http://davedevelopment.co.uk" + "homepage": "https://davedevelopment.co.uk", + "role": "Developer" + }, + { + "name": "Nathanael Esayeas", + "email": "nathanael.esayeas@protonmail.com", + "homepage": "https://github.com/ghostwriter", + "role": "Lead Developer" } ], "description": "Mockery is a simple yet flexible PHP mock object framework", @@ -9765,10 +10190,13 @@ "testing" ], "support": { + "docs": "https://docs.mockery.io/", "issues": "https://github.com/mockery/mockery/issues", - "source": "https://github.com/mockery/mockery/tree/1.6.2" + "rss": "https://github.com/mockery/mockery/releases.atom", + "security": "https://github.com/mockery/mockery/security/advisories", + "source": "https://github.com/mockery/mockery" }, - "time": "2023-06-07T09:07:52+00:00" + "time": "2023-08-09T00:03:52+00:00" }, { "name": "myclabs/deep-copy", @@ -9831,38 +10259,35 @@ }, { "name": "nunomaduro/collision", - "version": "v7.7.0", + "version": "v7.8.1", "source": { "type": "git", "url": "https://github.com/nunomaduro/collision.git", - "reference": "69a07197d055456d29911116fca3bc2c985f524b" + "reference": "61553ad3260845d7e3e49121b7074619233d361b" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/nunomaduro/collision/zipball/69a07197d055456d29911116fca3bc2c985f524b", - "reference": "69a07197d055456d29911116fca3bc2c985f524b", + "url": "https://api.github.com/repos/nunomaduro/collision/zipball/61553ad3260845d7e3e49121b7074619233d361b", + "reference": "61553ad3260845d7e3e49121b7074619233d361b", "shasum": "" }, "require": { - "filp/whoops": "^2.15.2", + "filp/whoops": "^2.15.3", "nunomaduro/termwind": "^1.15.1", "php": "^8.1.0", - "symfony/console": "^6.3.0" - }, - "conflict": { - "phpunit/phpunit": "<10.1.2" + "symfony/console": "^6.3.2" }, "require-dev": { - "brianium/paratest": "^7.2.2", - "laravel/framework": "^10.14.1", - "laravel/pint": "^1.10.3", - "laravel/sail": "^1.23.0", + "brianium/paratest": "^7.2.4", + "laravel/framework": "^10.17.1", + "laravel/pint": "^1.10.5", + "laravel/sail": "^1.23.1", "laravel/sanctum": "^3.2.5", "laravel/tinker": "^2.8.1", - "nunomaduro/larastan": "^2.6.3", - "orchestra/testbench-core": "^8.5.8", - "pestphp/pest": "^2.8.1", - "phpunit/phpunit": "^10.2.2", + "nunomaduro/larastan": "^2.6.4", + "orchestra/testbench-core": "^8.5.9", + "pestphp/pest": "^2.12.1", + "phpunit/phpunit": "^10.3.1", "sebastian/environment": "^6.0.1", "spatie/laravel-ignition": "^2.2.0" }, @@ -9923,38 +10348,39 @@ "type": "patreon" } ], - "time": "2023-06-29T09:10:16+00:00" + "time": "2023-08-07T08:03:21+00:00" }, { "name": "pestphp/pest", - "version": "v2.8.3", + "version": "v2.13.0", "source": { "type": "git", "url": "https://github.com/pestphp/pest.git", - "reference": "805b81edc05e6b5fafe84caee8350e81c9f54842" + "reference": "47f2ae32c14cd3f15520f5a12a3c36fdec25a2ee" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/pestphp/pest/zipball/805b81edc05e6b5fafe84caee8350e81c9f54842", - "reference": "805b81edc05e6b5fafe84caee8350e81c9f54842", + "url": "https://api.github.com/repos/pestphp/pest/zipball/47f2ae32c14cd3f15520f5a12a3c36fdec25a2ee", + "reference": "47f2ae32c14cd3f15520f5a12a3c36fdec25a2ee", "shasum": "" }, "require": { - "brianium/paratest": "^7.2.2", - "nunomaduro/collision": "^7.7.0", + "brianium/paratest": "^7.2.5", + "nunomaduro/collision": "^7.8.1", "nunomaduro/termwind": "^1.15.1", "pestphp/pest-plugin": "^2.0.1", - "pestphp/pest-plugin-arch": "^2.2.2", + "pestphp/pest-plugin-arch": "^2.2.3", "php": "^8.1.0", - "phpunit/phpunit": "^10.2.3" + "phpunit/phpunit": "^10.3.1" }, "conflict": { - "phpunit/phpunit": ">10.2.3", + "phpunit/phpunit": ">10.3.1", "webmozart/assert": "<1.11.0" }, "require-dev": { - "pestphp/pest-dev-tools": "^2.12.0", - "symfony/process": "^6.3.0" + "pestphp/pest-dev-tools": "^2.14.0", + "pestphp/pest-plugin-type-coverage": "^2.0.0", + "symfony/process": "^6.3.2" }, "bin": [ "bin/pest" @@ -9975,6 +10401,8 @@ "Pest\\Plugins\\ProcessIsolation", "Pest\\Plugins\\Profile", "Pest\\Plugins\\Retry", + "Pest\\Plugins\\Snapshot", + "Pest\\Plugins\\Verbose", "Pest\\Plugins\\Version", "Pest\\Plugins\\Parallel" ] @@ -9999,7 +10427,7 @@ "email": "enunomaduro@gmail.com" } ], - "description": "An elegant PHP Testing Framework.", + "description": "The elegant PHP Testing Framework.", "keywords": [ "framework", "pest", @@ -10010,7 +10438,7 @@ ], "support": { "issues": "https://github.com/pestphp/pest/issues", - "source": "https://github.com/pestphp/pest/tree/v2.8.3" + "source": "https://github.com/pestphp/pest/tree/v2.13.0" }, "funding": [ { @@ -10022,7 +10450,7 @@ "type": "github" } ], - "time": "2023-07-12T20:26:47+00:00" + "time": "2023-08-09T11:14:39+00:00" }, { "name": "pestphp/pest-plugin", @@ -10095,16 +10523,16 @@ }, { "name": "pestphp/pest-plugin-arch", - "version": "v2.2.2", + "version": "v2.2.3", "source": { "type": "git", "url": "https://github.com/pestphp/pest-plugin-arch.git", - "reference": "733d5c396484c7518c53f02ffb88cf2e2f31b97d" + "reference": "f44834b728b44028fb7d99c4e3efc88b699728a8" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/pestphp/pest-plugin-arch/zipball/733d5c396484c7518c53f02ffb88cf2e2f31b97d", - "reference": "733d5c396484c7518c53f02ffb88cf2e2f31b97d", + "url": "https://api.github.com/repos/pestphp/pest-plugin-arch/zipball/f44834b728b44028fb7d99c4e3efc88b699728a8", + "reference": "f44834b728b44028fb7d99c4e3efc88b699728a8", "shasum": "" }, "require": { @@ -10114,7 +10542,7 @@ "ta-tikoma/phpunit-architecture-test": "^0.7.3" }, "require-dev": { - "pestphp/pest": "dev-develop as 2.6.2", + "pestphp/pest": "^2.9.4", "pestphp/pest-dev-tools": "^2.12.0" }, "type": "library", @@ -10143,7 +10571,7 @@ "unit" ], "support": { - "source": "https://github.com/pestphp/pest-plugin-arch/tree/v2.2.2" + "source": "https://github.com/pestphp/pest-plugin-arch/tree/v2.2.3" }, "funding": [ { @@ -10155,7 +10583,7 @@ "type": "github" } ], - "time": "2023-07-08T12:39:42+00:00" + "time": "2023-07-24T18:04:14+00:00" }, { "name": "phar-io/manifest", @@ -10393,16 +10821,16 @@ }, { "name": "phpstan/phpstan", - "version": "1.10.25", + "version": "1.10.28", "source": { "type": "git", "url": "https://github.com/phpstan/phpstan.git", - "reference": "578f4e70d117f9a90699324c555922800ac38d8c" + "reference": "e4545b55904ebef470423d3ddddb74fa7325497a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpstan/phpstan/zipball/578f4e70d117f9a90699324c555922800ac38d8c", - "reference": "578f4e70d117f9a90699324c555922800ac38d8c", + "url": "https://api.github.com/repos/phpstan/phpstan/zipball/e4545b55904ebef470423d3ddddb74fa7325497a", + "reference": "e4545b55904ebef470423d3ddddb74fa7325497a", "shasum": "" }, "require": { @@ -10451,20 +10879,20 @@ "type": "tidelift" } ], - "time": "2023-07-06T12:11:37+00:00" + "time": "2023-08-08T12:33:42+00:00" }, { "name": "phpunit/php-code-coverage", - "version": "10.1.2", + "version": "10.1.3", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/php-code-coverage.git", - "reference": "db1497ec8dd382e82c962f7abbe0320e4882ee4e" + "reference": "be1fe461fdc917de2a29a452ccf2657d325b443d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/db1497ec8dd382e82c962f7abbe0320e4882ee4e", - "reference": "db1497ec8dd382e82c962f7abbe0320e4882ee4e", + "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/be1fe461fdc917de2a29a452ccf2657d325b443d", + "reference": "be1fe461fdc917de2a29a452ccf2657d325b443d", "shasum": "" }, "require": { @@ -10521,7 +10949,7 @@ "support": { "issues": "https://github.com/sebastianbergmann/php-code-coverage/issues", "security": "https://github.com/sebastianbergmann/php-code-coverage/security/policy", - "source": "https://github.com/sebastianbergmann/php-code-coverage/tree/10.1.2" + "source": "https://github.com/sebastianbergmann/php-code-coverage/tree/10.1.3" }, "funding": [ { @@ -10529,7 +10957,7 @@ "type": "github" } ], - "time": "2023-05-22T09:04:27+00:00" + "time": "2023-07-26T13:45:28+00:00" }, { "name": "phpunit/php-file-iterator", @@ -10775,16 +11203,16 @@ }, { "name": "phpunit/phpunit", - "version": "10.2.3", + "version": "10.3.1", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/phpunit.git", - "reference": "35c8cac1734ede2ae354a6644f7088356ff5b08e" + "reference": "d442ce7c4104d5683c12e67e4dcb5058159e9804" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/35c8cac1734ede2ae354a6644f7088356ff5b08e", - "reference": "35c8cac1734ede2ae354a6644f7088356ff5b08e", + "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/d442ce7c4104d5683c12e67e4dcb5058159e9804", + "reference": "d442ce7c4104d5683c12e67e4dcb5058159e9804", "shasum": "" }, "require": { @@ -10809,7 +11237,7 @@ "sebastian/diff": "^5.0", "sebastian/environment": "^6.0", "sebastian/exporter": "^5.0", - "sebastian/global-state": "^6.0", + "sebastian/global-state": "^6.0.1", "sebastian/object-enumerator": "^5.0", "sebastian/recursion-context": "^5.0", "sebastian/type": "^4.0", @@ -10824,7 +11252,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-main": "10.2-dev" + "dev-main": "10.3-dev" } }, "autoload": { @@ -10856,7 +11284,7 @@ "support": { "issues": "https://github.com/sebastianbergmann/phpunit/issues", "security": "https://github.com/sebastianbergmann/phpunit/security/policy", - "source": "https://github.com/sebastianbergmann/phpunit/tree/10.2.3" + "source": "https://github.com/sebastianbergmann/phpunit/tree/10.3.1" }, "funding": [ { @@ -10872,7 +11300,7 @@ "type": "tidelift" } ], - "time": "2023-06-30T06:17:38+00:00" + "time": "2023-08-04T06:48:08+00:00" }, { "name": "sebastian/cli-parser", @@ -11384,16 +11812,16 @@ }, { "name": "sebastian/global-state", - "version": "6.0.0", + "version": "6.0.1", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/global-state.git", - "reference": "aab257c712de87b90194febd52e4d184551c2d44" + "reference": "7ea9ead78f6d380d2a667864c132c2f7b83055e4" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/global-state/zipball/aab257c712de87b90194febd52e4d184551c2d44", - "reference": "aab257c712de87b90194febd52e4d184551c2d44", + "url": "https://api.github.com/repos/sebastianbergmann/global-state/zipball/7ea9ead78f6d380d2a667864c132c2f7b83055e4", + "reference": "7ea9ead78f6d380d2a667864c132c2f7b83055e4", "shasum": "" }, "require": { @@ -11433,7 +11861,8 @@ ], "support": { "issues": "https://github.com/sebastianbergmann/global-state/issues", - "source": "https://github.com/sebastianbergmann/global-state/tree/6.0.0" + "security": "https://github.com/sebastianbergmann/global-state/security/policy", + "source": "https://github.com/sebastianbergmann/global-state/tree/6.0.1" }, "funding": [ { @@ -11441,7 +11870,7 @@ "type": "github" } ], - "time": "2023-02-03T07:07:38+00:00" + "time": "2023-07-19T07:19:23+00:00" }, { "name": "sebastian/lines-of-code", @@ -11831,16 +12260,16 @@ }, { "name": "spatie/flare-client-php", - "version": "1.4.1", + "version": "1.4.2", "source": { "type": "git", "url": "https://github.com/spatie/flare-client-php.git", - "reference": "943894c6a6b00501365ac0b91ae0dce56f2226fa" + "reference": "5f2c6a7a0d2c1d90c12559dc7828fd942911a544" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/spatie/flare-client-php/zipball/943894c6a6b00501365ac0b91ae0dce56f2226fa", - "reference": "943894c6a6b00501365ac0b91ae0dce56f2226fa", + "url": "https://api.github.com/repos/spatie/flare-client-php/zipball/5f2c6a7a0d2c1d90c12559dc7828fd942911a544", + "reference": "5f2c6a7a0d2c1d90c12559dc7828fd942911a544", "shasum": "" }, "require": { @@ -11889,7 +12318,7 @@ ], "support": { "issues": "https://github.com/spatie/flare-client-php/issues", - "source": "https://github.com/spatie/flare-client-php/tree/1.4.1" + "source": "https://github.com/spatie/flare-client-php/tree/1.4.2" }, "funding": [ { @@ -11897,7 +12326,7 @@ "type": "github" } ], - "time": "2023-07-06T09:29:49+00:00" + "time": "2023-07-28T08:07:24+00:00" }, { "name": "spatie/ignition", @@ -12076,16 +12505,16 @@ }, { "name": "ta-tikoma/phpunit-architecture-test", - "version": "0.7.3", + "version": "0.7.4", "source": { "type": "git", "url": "https://github.com/ta-tikoma/phpunit-architecture-test.git", - "reference": "90b2e1d53b2c09b6371f84476699b69b36e378fd" + "reference": "abe1f8a5f4635e7cbe0a8a37d6b8d20c687af0f2" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/ta-tikoma/phpunit-architecture-test/zipball/90b2e1d53b2c09b6371f84476699b69b36e378fd", - "reference": "90b2e1d53b2c09b6371f84476699b69b36e378fd", + "url": "https://api.github.com/repos/ta-tikoma/phpunit-architecture-test/zipball/abe1f8a5f4635e7cbe0a8a37d6b8d20c687af0f2", + "reference": "abe1f8a5f4635e7cbe0a8a37d6b8d20c687af0f2", "shasum": "" }, "require": { @@ -12129,9 +12558,9 @@ ], "support": { "issues": "https://github.com/ta-tikoma/phpunit-architecture-test/issues", - "source": "https://github.com/ta-tikoma/phpunit-architecture-test/tree/0.7.3" + "source": "https://github.com/ta-tikoma/phpunit-architecture-test/tree/0.7.4" }, - "time": "2023-04-19T08:46:06+00:00" + "time": "2023-08-03T06:50:14+00:00" }, { "name": "theseer/tokenizer", @@ -12182,68 +12611,6 @@ } ], "time": "2021-07-28T10:34:58+00:00" - }, - { - "name": "toshy/bunnynet-php", - "version": "3.3.0", - "source": { - "type": "git", - "url": "https://github.com/ToshY/BunnyNet-PHP.git", - "reference": "29ccf083f71d97045bc5aedf2760db9bec17c06f" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/ToshY/BunnyNet-PHP/zipball/29ccf083f71d97045bc5aedf2760db9bec17c06f", - "reference": "29ccf083f71d97045bc5aedf2760db9bec17c06f", - "shasum": "" - }, - "require": { - "ext-json": "*", - "nyholm/psr7": "^1.6.1", - "php": "^8.1", - "psr/http-client": "^1.0" - }, - "require-dev": { - "friendsofphp/php-cs-fixer": "^3.13", - "phpmd/phpmd": "^2.13", - "phpro/grumphp": "^1.15 || ^2.0", - "phpstan/phpstan": "^1.9", - "phpunit/phpunit": "^10.0", - "rector/rector": "^0.15.1 || ^0.16.0 || ^0.17.0" - }, - "type": "library", - "autoload": { - "psr-4": { - "ToshY\\BunnyNet\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "ToshY", - "homepage": "https://github.com/ToshY" - } - ], - "description": "BunnyNet API client for PHP", - "keywords": [ - "Bunny CDN PHP", - "Bunny PHP", - "Bunny.net", - "BunnyNet PHP client", - "BunnyNet-PHP", - "BunnyWay", - "BunnyWay-PHP", - "ToshY", - "bunnyCDN" - ], - "support": { - "issues": "https://github.com/ToshY/BunnyNet-PHP/issues", - "source": "https://github.com/ToshY/BunnyNet-PHP/tree/3.3.0" - }, - "time": "2023-06-30T22:02:59+00:00" } ], "aliases": [], diff --git a/config/app.php b/config/app.php index 78df1ec8a..690029fd6 100644 --- a/config/app.php +++ b/config/app.php @@ -43,7 +43,7 @@ | */ - 'debug' => (bool) env('APP_DEBUG', false), + 'debug' => (bool)env('APP_DEBUG', false), /* |-------------------------------------------------------------------------- diff --git a/config/broadcasting.php b/config/broadcasting.php index 9e4d4aa44..4dbd22c7e 100644 --- a/config/broadcasting.php +++ b/config/broadcasting.php @@ -36,7 +36,7 @@ 'secret' => env('PUSHER_APP_SECRET'), 'app_id' => env('PUSHER_APP_ID'), 'options' => [ - 'host' => env('PUSHER_HOST') ?: 'api-'.env('PUSHER_APP_CLUSTER', 'mt1').'.pusher.com', + 'host' => env('PUSHER_HOST') ?: 'api-' . env('PUSHER_APP_CLUSTER', 'mt1') . '.pusher.com', 'port' => env('PUSHER_PORT', 443), 'scheme' => env('PUSHER_SCHEME', 'https'), 'encrypted' => true, diff --git a/config/constants.php b/config/constants.php index a3c88c8e8..6cb455265 100644 --- a/config/constants.php +++ b/config/constants.php @@ -1,9 +1,19 @@ [ + 'confirmation_valid_for_minutes' => 10, + ], 'invitation' => [ 'link' => [ 'base_url' => '/invitations/', 'expiration' => 10, ], ], + 'limits' => [ + 'server' => [ + 'basic' => 1, + 'pro' => 3, + 'ultimate' => 9999999999999999999, + ], + ], ]; diff --git a/config/coolify.php b/config/coolify.php index 0a4220887..b9e092175 100644 --- a/config/coolify.php +++ b/config/coolify.php @@ -2,11 +2,19 @@ return [ 'self_hosted' => env('SELF_HOSTED', true), - 'lemon_squeezy_webhook_secret' => env('LEMON_SQUEEZY_WEBHOOK_SECRET'), - 'lemon_squeezy_product_id' => env('LEMON_SQUEEZY_PRODUCT_ID'), - 'lemon_squeezy_checkout_id' => env('LEMON_SQUEEZY_CHECKOUT_ID'), + 'waitlist' => env('WAITLIST', false), + 'license_url' => 'https://license.coolify.io', + 'lemon_squeezy_webhook_secret' => env('LEMON_SQUEEZY_WEBHOOK_SECRET', null), + 'lemon_squeezy_checkout_id_monthly_basic' => env('LEMON_SQUEEZY_CHECKOUT_ID_MONTHLY_BASIC', null), + 'lemon_squeezy_checkout_id_monthly_pro' => env('LEMON_SQUEEZY_CHECKOUT_ID_MONTHLY_PRO', null), + 'lemon_squeezy_checkout_id_monthly_ultimate' => env('LEMON_SQUEEZY_CHECKOUT_ID_MONTHLY_ULTIMATE', null), + 'lemon_squeezy_checkout_id_yearly_basic' => env('LEMON_SQUEEZY_CHECKOUT_ID_YEARLY_BASIC', null), + 'lemon_squeezy_checkout_id_yearly_pro' => env('LEMON_SQUEEZY_CHECKOUT_ID_YEARLY_PRO', null), + 'lemon_squeezy_checkout_id_yearly_ultimate' => env('LEMON_SQUEEZY_CHECKOUT_ID_YEARLY_ULTIMATE', null), + 'lemon_squeezy_basic_plan_ids' => env('LEMON_SQUEEZY_BASIC_PLAN_IDS', ""), + 'lemon_squeezy_pro_plan_ids' => env('LEMON_SQUEEZY_PRO_PLAN_IDS', ""), + 'lemon_squeezy_ultimate_plan_ids' => env('LEMON_SQUEEZY_ULTIMATE_PLAN_IDS', ""), 'mux_enabled' => env('MUX_ENABLED', true), 'dev_webhook' => env('SERVEO_URL'), 'base_config_path' => env('BASE_CONFIG_PATH', '/data/coolify'), - 'proxy_config_path' => env('BASE_CONFIG_PATH', '/data/coolify') . "/proxy", ]; diff --git a/config/filesystems.php b/config/filesystems.php index a7a0ba465..5c9ffc39c 100644 --- a/config/filesystems.php +++ b/config/filesystems.php @@ -62,6 +62,12 @@ 'visibility' => 'private', 'throw' => false, ], + 'backups' => [ + 'driver' => 'local', + 'root' => storage_path('app/backups'), + 'visibility' => 'private', + 'throw' => false, + ], 's3' => [ 'driver' => 's3', diff --git a/config/fortify.php b/config/fortify.php index 510a53912..6b78c1c65 100644 --- a/config/fortify.php +++ b/config/fortify.php @@ -105,6 +105,7 @@ 'limiters' => [ 'login' => 'login', 'two-factor' => 'two-factor', + 'forgot-password' => 'forgot-password', ], /* diff --git a/config/livewire.php b/config/livewire.php new file mode 100644 index 000000000..59f8d5c4d --- /dev/null +++ b/config/livewire.php @@ -0,0 +1,158 @@ + 'App\\Http\\Livewire', + + /* + |-------------------------------------------------------------------------- + | View Path + |-------------------------------------------------------------------------- + | + | This value sets the path for Livewire component views. This affects + | file manipulation helper commands like `artisan make:livewire`. + | + */ + + 'view_path' => resource_path('views/livewire'), + + /* + |-------------------------------------------------------------------------- + | Layout + |-------------------------------------------------------------------------- + | The default layout view that will be used when rendering a component via + | Route::get('/some-endpoint', SomeComponent::class);. In this case the + | the view returned by SomeComponent will be wrapped in "layouts.app" + | + */ + + 'layout' => 'layouts.app', + + /* + |-------------------------------------------------------------------------- + | Livewire Assets URL + |-------------------------------------------------------------------------- + | + | This value sets the path to Livewire JavaScript assets, for cases where + | your app's domain root is not the correct path. By default, Livewire + | will load its JavaScript assets from the app's "relative root". + | + | Examples: "/assets", "myurl.com/app". + | + */ + + 'asset_url' => null, + + /* + |-------------------------------------------------------------------------- + | Livewire App URL + |-------------------------------------------------------------------------- + | + | This value should be used if livewire assets are served from CDN. + | Livewire will communicate with an app through this url. + | + | Examples: "https://my-app.com", "myurl.com/app". + | + */ + + 'app_url' => null, + + /* + |-------------------------------------------------------------------------- + | Livewire Endpoint Middleware Group + |-------------------------------------------------------------------------- + | + | This value sets the middleware group that will be applied to the main + | Livewire "message" endpoint (the endpoint that gets hit everytime + | a Livewire component updates). It is set to "web" by default. + | + */ + + 'middleware_group' => 'web', + + /* + |-------------------------------------------------------------------------- + | Livewire Temporary File Uploads Endpoint Configuration + |-------------------------------------------------------------------------- + | + | Livewire handles file uploads by storing uploads in a temporary directory + | before the file is validated and stored permanently. All file uploads + | are directed to a global endpoint for temporary storage. The config + | items below are used for customizing the way the endpoint works. + | + */ + + 'temporary_file_upload' => [ + 'disk' => null, // Example: 'local', 's3' Default: 'default' + 'rules' => null, // Example: ['file', 'mimes:png,jpg'] Default: ['required', 'file', 'max:12288'] (12MB) + 'directory' => null, // Example: 'tmp' Default 'livewire-tmp' + 'middleware' => null, // Example: 'throttle:5,1' Default: 'throttle:60,1' + 'preview_mimes' => [ // Supported file types for temporary pre-signed file URLs. + 'png', 'gif', 'bmp', 'svg', 'wav', 'mp4', + 'mov', 'avi', 'wmv', 'mp3', 'm4a', + 'jpg', 'jpeg', 'mpga', 'webp', 'wma', + ], + 'max_upload_time' => 5, // Max duration (in minutes) before an upload gets invalidated. + ], + + /* + |-------------------------------------------------------------------------- + | Manifest File Path + |-------------------------------------------------------------------------- + | + | This value sets the path to the Livewire manifest file. + | The default should work for most cases (which is + | "/bootstrap/cache/livewire-components.php"), but for specific + | cases like when hosting on Laravel Vapor, it could be set to a different value. + | + | Example: for Laravel Vapor, it would be "/tmp/storage/bootstrap/cache/livewire-components.php". + | + */ + + 'manifest_path' => null, + + /* + |-------------------------------------------------------------------------- + | Back Button Cache + |-------------------------------------------------------------------------- + | + | This value determines whether the back button cache will be used on pages + | that contain Livewire. By disabling back button cache, it ensures that + | the back button shows the correct state of components, instead of + | potentially stale, cached data. + | + | Setting it to "false" (default) will disable back button cache. + | + */ + + 'back_button_cache' => false, + + /* + |-------------------------------------------------------------------------- + | Render On Redirect + |-------------------------------------------------------------------------- + | + | This value determines whether Livewire will render before it's redirected + | or not. Setting it to "false" (default) will mean the render method is + | skipped when redirecting. And "true" will mean the render method is + | run before redirecting. Browsers bfcache can store a potentially + | stale view if render is skipped on redirect. + | + */ + + 'render_on_redirect' => false, + +]; diff --git a/config/logging.php b/config/logging.php index 4c3df4ce1..a97262cb3 100644 --- a/config/logging.php +++ b/config/logging.php @@ -85,7 +85,7 @@ 'handler_with' => [ 'host' => env('PAPERTRAIL_URL'), 'port' => env('PAPERTRAIL_PORT'), - 'connectionString' => 'tls://'.env('PAPERTRAIL_URL').':'.env('PAPERTRAIL_PORT'), + 'connectionString' => 'tls://' . env('PAPERTRAIL_URL') . ':' . env('PAPERTRAIL_PORT'), ], ], diff --git a/config/proxy.php b/config/proxy.php deleted file mode 100644 index 3b5832666..000000000 --- a/config/proxy.php +++ /dev/null @@ -1,5 +0,0 @@ - env('PROJECT_PATH_ON_HOST', '/var/www/html') -]; diff --git a/config/version.php b/config/version.php index 743126446..8ecc24277 100644 --- a/config/version.php +++ b/config/version.php @@ -1,3 +1,3 @@ text('two_factor_secret') - ->after('password') - ->nullable(); + ->after('password') + ->nullable(); $table->text('two_factor_recovery_codes') - ->after('two_factor_secret') - ->nullable(); + ->after('two_factor_secret') + ->nullable(); if (Fortify::confirmsTwoFactorAuthentication()) { $table->timestamp('two_factor_confirmed_at') - ->after('two_factor_recovery_codes') - ->nullable(); + ->after('two_factor_recovery_codes') + ->nullable(); } }); } diff --git a/database/migrations/2023_03_20_112410_create_activity_log_table.php b/database/migrations/2023_03_20_112410_create_activity_log_table.php index a6fa05393..c94274b31 100755 --- a/database/migrations/2023_03_20_112410_create_activity_log_table.php +++ b/database/migrations/2023_03_20_112410_create_activity_log_table.php @@ -1,8 +1,8 @@ string('wildcard_domain')->nullable(); }); } diff --git a/database/migrations/2023_06_23_114133_use_application_deployment_queues_as_activity.php b/database/migrations/2023_06_23_114133_use_application_deployment_queues_as_activity.php index 642184abc..36623ee7b 100644 --- a/database/migrations/2023_06_23_114133_use_application_deployment_queues_as_activity.php +++ b/database/migrations/2023_06_23_114133_use_application_deployment_queues_as_activity.php @@ -27,4 +27,4 @@ public function down(): void $table->dropColumn('current_process_id'); }); } -}; \ No newline at end of file +}; diff --git a/database/migrations/2023_06_23_114134_add_disk_usage_percentage_to_servers.php b/database/migrations/2023_06_23_114134_add_disk_usage_percentage_to_servers.php index d2b0f892c..79846bb0a 100644 --- a/database/migrations/2023_06_23_114134_add_disk_usage_percentage_to_servers.php +++ b/database/migrations/2023_06_23_114134_add_disk_usage_percentage_to_servers.php @@ -25,4 +25,4 @@ public function down(): void $table->dropColumn('cleanup_after_percentage'); }); } -}; \ No newline at end of file +}; diff --git a/database/migrations/2023_07_13_115117_create_subscriptions_table.php b/database/migrations/2023_07_13_115117_create_subscriptions_table.php index 08aeaaa17..e24e56555 100644 --- a/database/migrations/2023_07_13_115117_create_subscriptions_table.php +++ b/database/migrations/2023_07_13_115117_create_subscriptions_table.php @@ -36,4 +36,4 @@ public function down(): void { Schema::dropIfExists('subscriptions'); } -}; \ No newline at end of file +}; diff --git a/database/migrations/2023_07_27_182013_smtp_discord_schemaless_to_normal.php b/database/migrations/2023_07_27_182013_smtp_discord_schemaless_to_normal.php new file mode 100644 index 000000000..8bb8d7824 --- /dev/null +++ b/database/migrations/2023_07_27_182013_smtp_discord_schemaless_to_normal.php @@ -0,0 +1,193 @@ +boolean('smtp_enabled')->default(false); + $table->string('smtp_from_address')->nullable(); + $table->string('smtp_from_name')->nullable(); + $table->string('smtp_recipients')->nullable(); + $table->string('smtp_host')->nullable(); + $table->integer('smtp_port')->nullable(); + $table->string('smtp_encryption')->nullable(); + $table->text('smtp_username')->nullable(); + $table->text('smtp_password')->nullable(); + $table->integer('smtp_timeout')->nullable(); + $table->boolean('smtp_notifications_test')->default(true); + $table->boolean('smtp_notifications_deployments')->default(false); + $table->boolean('smtp_notifications_status_changes')->default(false); + + $table->boolean('discord_enabled')->default(false); + $table->string('discord_webhook_url')->nullable(); + $table->boolean('discord_notifications_test')->default(true); + $table->boolean('discord_notifications_deployments')->default(true); + $table->boolean('discord_notifications_status_changes')->default(true); + }); + $teams = Team::all(); + foreach ($teams as $team) { + $team->smtp_enabled = data_get($team, 'smtp.enabled', false); + $team->smtp_from_address = data_get($team, 'smtp.from_address'); + $team->smtp_from_name = data_get($team, 'smtp.from_name'); + $team->smtp_recipients = data_get($team, 'smtp.recipients'); + $team->smtp_host = data_get($team, 'smtp.host'); + $team->smtp_port = data_get($team, 'smtp.port'); + $team->smtp_encryption = data_get($team, 'smtp.encryption'); + $team->smtp_username = data_get($team, 'smtp.username'); + $team->smtp_password = data_get($team, 'smtp.password'); + $team->smtp_timeout = data_get($team, 'smtp.timeout'); + $team->smtp_notifications_test = data_get($team, 'smtp_notifications.test', true); + $team->smtp_notifications_deployments = data_get($team, 'smtp_notifications.deployments', false); + $team->smtp_notifications_status_changes = data_get($team, 'smtp_notifications.status_changes', false); + + $team->discord_enabled = data_get($team, 'discord.enabled', false); + $team->discord_webhook_url = data_get($team, 'discord.webhook_url'); + $team->discord_notifications_test = data_get($team, 'discord_notifications.test', true); + $team->discord_notifications_deployments = data_get($team, 'discord_notifications.deployments', true); + $team->discord_notifications_status_changes = data_get($team, 'discord_notifications.status_changes', true); + + $team->save(); + } + Schema::table('teams', function (Blueprint $table) { + $table->dropColumn('smtp'); + $table->dropColumn('smtp_notifications'); + $table->dropColumn('discord'); + $table->dropColumn('discord_notifications'); + }); + + Schema::table('instance_settings', function (Blueprint $table) { + $table->boolean('smtp_enabled')->default(false); + $table->string('smtp_from_address')->nullable(); + $table->string('smtp_from_name')->nullable(); + $table->text('smtp_recipients')->nullable(); + $table->string('smtp_host')->nullable(); + $table->integer('smtp_port')->nullable(); + $table->string('smtp_encryption')->nullable(); + $table->text('smtp_username')->nullable(); + $table->text('smtp_password')->nullable(); + $table->integer('smtp_timeout')->nullable(); + }); + $instance_settings = InstanceSettings::all(); + foreach ($instance_settings as $instance_setting) { + $instance_setting->smtp_enabled = data_get($instance_setting, 'smtp.enabled', false); + $instance_setting->smtp_from_address = data_get($instance_setting, 'smtp.from_address'); + $instance_setting->smtp_from_name = data_get($instance_setting, 'smtp.from_name'); + $instance_setting->smtp_recipients = data_get($instance_setting, 'smtp.recipients'); + $instance_setting->smtp_host = data_get($instance_setting, 'smtp.host'); + $instance_setting->smtp_port = data_get($instance_setting, 'smtp.port'); + $instance_setting->smtp_encryption = data_get($instance_setting, 'smtp.encryption'); + $instance_setting->smtp_username = data_get($instance_setting, 'smtp.username'); + $instance_setting->smtp_password = data_get($instance_setting, 'smtp.password'); + $instance_setting->smtp_timeout = data_get($instance_setting, 'smtp.timeout'); + $instance_setting->save(); + } + Schema::table('instance_settings', function (Blueprint $table) { + $table->dropColumn('smtp'); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::table('teams', function (Blueprint $table) { + $table->schemalessAttributes('smtp'); + $table->schemalessAttributes('smtp_notifications'); + $table->schemalessAttributes('discord'); + $table->schemalessAttributes('discord_notifications'); + }); + $teams = Team::all(); + foreach ($teams as $team) { + $team->smtp = [ + 'enabled' => $team->smtp_enabled, + 'from_address' => $team->smtp_from_address, + 'from_name' => $team->smtp_from_name, + 'recipients' => $team->smtp_recipients, + 'host' => $team->smtp_host, + 'port' => $team->smtp_port, + 'encryption' => $team->smtp_encryption, + 'username' => $team->smtp_username, + 'password' => $team->smtp_password, + 'timeout' => $team->smtp_timeout, + ]; + $team->smtp_notifications = [ + 'test' => $team->smtp_notifications_test, + 'deployments' => $team->smtp_notifications_deployments, + 'status_changes' => $team->smtp_notifications_status_changes, + ]; + $team->discord = [ + 'enabled' => $team->discord_enabled, + 'webhook_url' => $team->discord_webhook_url, + ]; + $team->discord_notifications = [ + 'test' => $team->discord_notifications_test, + 'deployments' => $team->discord_notifications_deployments, + 'status_changes' => $team->discord_notifications_status_changes, + ]; + $team->save(); + } + Schema::table('teams', function (Blueprint $table) { + $table->dropColumn('smtp_enabled'); + $table->dropColumn('smtp_from_address'); + $table->dropColumn('smtp_from_name'); + $table->dropColumn('smtp_recipients'); + $table->dropColumn('smtp_host'); + $table->dropColumn('smtp_port'); + $table->dropColumn('smtp_encryption'); + $table->dropColumn('smtp_username'); + $table->dropColumn('smtp_password'); + $table->dropColumn('smtp_timeout'); + $table->dropColumn('smtp_notifications_test'); + $table->dropColumn('smtp_notifications_deployments'); + $table->dropColumn('smtp_notifications_status_changes'); + + $table->dropColumn('discord_enabled'); + $table->dropColumn('discord_webhook_url'); + $table->dropColumn('discord_notifications_test'); + $table->dropColumn('discord_notifications_deployments'); + $table->dropColumn('discord_notifications_status_changes'); + }); + + Schema::table('instance_settings', function (Blueprint $table) { + $table->schemalessAttributes('smtp'); + }); + + $instance_setting = InstanceSettings::get(); + $instance_setting->smtp = [ + 'enabled' => $instance_setting->smtp_enabled, + 'from_address' => $instance_setting->smtp_from_address, + 'from_name' => $instance_setting->smtp_from_name, + 'recipients' => $instance_setting->smtp_recipients, + 'host' => $instance_setting->smtp_host, + 'port' => $instance_setting->smtp_port, + 'encryption' => $instance_setting->smtp_encryption, + 'username' => $instance_setting->smtp_username, + 'password' => $instance_setting->smtp_password, + 'timeout' => $instance_setting->smtp_timeout, + ]; + $instance_setting->save(); + Schema::table('instance_settings', function (Blueprint $table) { + $table->dropColumn('smtp_enabled'); + $table->dropColumn('smtp_from_address'); + $table->dropColumn('smtp_from_name'); + $table->dropColumn('smtp_recipients'); + $table->dropColumn('smtp_host'); + $table->dropColumn('smtp_port'); + $table->dropColumn('smtp_encryption'); + $table->dropColumn('smtp_username'); + $table->dropColumn('smtp_password'); + $table->dropColumn('smtp_timeout'); + }); + } +}; diff --git a/database/migrations/2023_08_06_142951_add_description_field_to_applications_table.php b/database/migrations/2023_08_06_142951_add_description_field_to_applications_table.php new file mode 100644 index 000000000..5be2f2959 --- /dev/null +++ b/database/migrations/2023_08_06_142951_add_description_field_to_applications_table.php @@ -0,0 +1,28 @@ +string('description')->nullable(); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::table('applications', function (Blueprint $table) { + $table->dropColumn('description'); + }); + } +}; diff --git a/database/migrations/2023_08_06_142952_remove_foreignId_environment_variables.php b/database/migrations/2023_08_06_142952_remove_foreignId_environment_variables.php new file mode 100644 index 000000000..d24dbb446 --- /dev/null +++ b/database/migrations/2023_08_06_142952_remove_foreignId_environment_variables.php @@ -0,0 +1,32 @@ +dropColumn('service_id'); + $table->dropColumn('database_id'); + $table->foreignId('standalone_postgresql_id')->nullable(); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::table('environment_variables', function (Blueprint $table) { + $table->foreignId('service_id')->nullable(); + $table->foreignId('database_id')->nullable(); + $table->dropColumn('standalone_postgresql_id'); + }); + } +}; diff --git a/database/migrations/2023_08_06_142954_add_readonly_localpersistentvolumes.php b/database/migrations/2023_08_06_142954_add_readonly_localpersistentvolumes.php new file mode 100644 index 000000000..f0bce760d --- /dev/null +++ b/database/migrations/2023_08_06_142954_add_readonly_localpersistentvolumes.php @@ -0,0 +1,28 @@ +boolean('is_readonly')->default(false); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::table('local_persistent_volumes', function (Blueprint $table) { + $table->dropColumn('is_readonly'); + }); + } +}; diff --git a/database/migrations/2023_08_07_073651_create_s3_storages_table.php b/database/migrations/2023_08_07_073651_create_s3_storages_table.php new file mode 100644 index 000000000..0eded3024 --- /dev/null +++ b/database/migrations/2023_08_07_073651_create_s3_storages_table.php @@ -0,0 +1,37 @@ +id(); + $table->string('uuid')->unique(); + $table->string('name'); + $table->longText('description')->nullable(); + $table->string('region')->default('us-east-1'); + $table->longText('key'); + $table->longText('secret'); + $table->longText('bucket'); + $table->longText('endpoint')->nullable(); + + $table->foreignId('team_id'); + $table->timestamps(); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::dropIfExists('s3_storages'); + } +}; diff --git a/database/migrations/2023_08_07_142950_create_standalone_postgresqls_table.php b/database/migrations/2023_08_07_142950_create_standalone_postgresqls_table.php new file mode 100644 index 000000000..ddaf19a7d --- /dev/null +++ b/database/migrations/2023_08_07_142950_create_standalone_postgresqls_table.php @@ -0,0 +1,58 @@ +id(); + $table->string('uuid')->unique(); + $table->string('name'); + $table->string('description')->nullable(); + + $table->string('postgres_user')->default('postgres'); + $table->text('postgres_password'); + $table->string('postgres_db')->default('postgres'); + $table->string('postgres_initdb_args')->nullable(); + $table->string('postgres_host_auth_method')->nullable(); + $table->json('init_scripts')->nullable(); + + $table->string('status')->default('exited'); + + $table->string('image')->default('postgres:15-alpine'); + $table->boolean('is_public')->default(false); + $table->integer('public_port')->nullable(); + $table->text('ports_mappings')->nullable(); + + $table->string('limits_memory')->default("0"); + $table->string('limits_memory_swap')->default("0"); + $table->integer('limits_memory_swappiness')->default(60); + $table->string('limits_memory_reservation')->default("0"); + + $table->string('limits_cpus')->default("0"); + $table->string('limits_cpuset')->nullable()->default("0"); + $table->integer('limits_cpu_shares')->default(1024); + + $table->timestamp('started_at')->nullable(); + $table->morphs('destination'); + + $table->foreignId('environment_id')->nullable(); + $table->timestamps(); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::dropIfExists('standalone_postgresqls'); + } +}; diff --git a/database/migrations/2023_08_08_150103_create_scheduled_database_backups_table.php b/database/migrations/2023_08_08_150103_create_scheduled_database_backups_table.php new file mode 100644 index 000000000..cc6726f36 --- /dev/null +++ b/database/migrations/2023_08_08_150103_create_scheduled_database_backups_table.php @@ -0,0 +1,30 @@ +id(); + $table->text('description')->nullable(); + $table->string('uuid')->unique(); + $table->boolean('enabled')->default(true); + $table->boolean('save_s3')->default(true); + $table->string('frequency'); + $table->integer('number_of_backups_locally')->default(7); + $table->morphs('database'); + $table->foreignId('s3_storage_id')->nullable(); + $table->foreignId('team_id'); + $table->timestamps(); + }); + } + + public function down(): void + { + Schema::dropIfExists('scheduled_database_backups'); + } +}; diff --git a/database/migrations/2023_08_10_113306_create_scheduled_database_backup_executions_table.php b/database/migrations/2023_08_10_113306_create_scheduled_database_backup_executions_table.php new file mode 100644 index 000000000..a7ecc3796 --- /dev/null +++ b/database/migrations/2023_08_10_113306_create_scheduled_database_backup_executions_table.php @@ -0,0 +1,27 @@ +id(); + $table->string('uuid')->unique(); + $table->enum('status', ['success', 'failed', 'running'])->default('running'); + $table->longText('message')->nullable(); + $table->text('size')->nullable(); + $table->text('filename')->nullable(); + $table->foreignId('scheduled_database_backup_id'); + $table->timestamps(); + }); + } + + public function down(): void + { + Schema::dropIfExists('scheduled_database_backup_executions'); + } +}; diff --git a/database/migrations/2023_08_10_201311_add_backup_notifications_to_teams.php b/database/migrations/2023_08_10_201311_add_backup_notifications_to_teams.php new file mode 100644 index 000000000..128aa5b25 --- /dev/null +++ b/database/migrations/2023_08_10_201311_add_backup_notifications_to_teams.php @@ -0,0 +1,24 @@ +boolean('smtp_notifications_database_backups')->default(true)->after('smtp_notifications_status_changes'); + $table->boolean('discord_notifications_database_backups')->default(true)->after('discord_notifications_status_changes'); + }); + } + + public function down(): void + { + Schema::table('teams', function (Blueprint $table) { + $table->dropColumn('smtp_notifications_database_backups'); + $table->dropColumn('discord_notifications_database_backups'); + }); + } +}; diff --git a/database/migrations/2023_08_11_190528_add_dockerfile_to_applications_table.php b/database/migrations/2023_08_11_190528_add_dockerfile_to_applications_table.php new file mode 100644 index 000000000..f2ee10640 --- /dev/null +++ b/database/migrations/2023_08_11_190528_add_dockerfile_to_applications_table.php @@ -0,0 +1,25 @@ +longText('dockerfile')->nullable(); + }); + } + + public function down(): void + { + Schema::table('applications', function (Blueprint $table) { + $table->dropColumn('dockerfile'); + }); + } +}; diff --git a/database/migrations/2023_03_27_083620_create_databases_table.php b/database/migrations/2023_08_15_095902_create_waitlists_table.php similarity index 60% rename from database/migrations/2023_03_27_083620_create_databases_table.php rename to database/migrations/2023_08_15_095902_create_waitlists_table.php index bfafb9c0c..641d152d1 100644 --- a/database/migrations/2023_03_27_083620_create_databases_table.php +++ b/database/migrations/2023_08_15_095902_create_waitlists_table.php @@ -11,14 +11,12 @@ */ public function up(): void { - Schema::create('databases', function (Blueprint $table) { + Schema::create('waitlists', function (Blueprint $table) { $table->id(); - $table->string('uuid')->unique(); - $table->string('name'); - - $table->morphs('destination'); - - $table->foreignId('environment_id'); + $table->string('uuid'); + $table->string('type'); + $table->string('email')->unique(); + $table->boolean('verified')->default(false); $table->timestamps(); }); } @@ -28,6 +26,6 @@ public function up(): void */ public function down(): void { - Schema::dropIfExists('databases'); + Schema::dropIfExists('waitlists'); } }; diff --git a/database/migrations/2023_08_15_111125_update_users_table.php b/database/migrations/2023_08_15_111125_update_users_table.php new file mode 100644 index 000000000..4453d39d4 --- /dev/null +++ b/database/migrations/2023_08_15_111125_update_users_table.php @@ -0,0 +1,28 @@ +boolean('force_password_reset')->default(false); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::table('users', function (Blueprint $table) { + $table->dropColumn('force_password_reset'); + }); + } +}; diff --git a/database/seeders/ApplicationPreviewSeeder.php b/database/seeders/ApplicationPreviewSeeder.php index 512f1c990..764939073 100644 --- a/database/seeders/ApplicationPreviewSeeder.php +++ b/database/seeders/ApplicationPreviewSeeder.php @@ -2,11 +2,6 @@ namespace Database\Seeders; -use App\Models\Application; -use App\Models\ApplicationPreview; -use App\Models\Environment; -use App\Models\GithubApp; -use App\Models\StandaloneDocker; use Illuminate\Database\Seeder; class ApplicationPreviewSeeder extends Seeder diff --git a/database/seeders/ApplicationSeeder.php b/database/seeders/ApplicationSeeder.php index 2859bed81..a43e0f19a 100644 --- a/database/seeders/ApplicationSeeder.php +++ b/database/seeders/ApplicationSeeder.php @@ -2,14 +2,9 @@ namespace Database\Seeders; -use App\Data\ApplicationPreview; use App\Models\Application; -use App\Models\ApplicationSetting; -use App\Models\Environment; use App\Models\GithubApp; -use App\Models\LocalPersistentVolume; use App\Models\StandaloneDocker; -use App\Models\SwarmDocker; use Illuminate\Database\Seeder; class ApplicationSeeder extends Seeder @@ -19,24 +14,36 @@ class ApplicationSeeder extends Seeder */ public function run(): void { - $environment_1 = Environment::find(1); - $standalone_docker_1 = StandaloneDocker::find(1); - - $github_public_source = GithubApp::where('name', 'Public GitHub')->first(); - Application::create([ 'name' => 'coollabsio/coolify-examples:nodejs-fastify', + 'description' => 'NodeJS Fastify Example', 'fqdn' => 'http://foo.com', 'repository_project_id' => 603035348, 'git_repository' => 'coollabsio/coolify-examples', 'git_branch' => 'nodejs-fastify', 'build_pack' => 'nixpacks', 'ports_exposes' => '3000', - 'ports_mappings' => '3000:3000', - 'environment_id' => $environment_1->id, - 'destination_id' => $standalone_docker_1->id, + 'ports_mappings' => '3005:3000', + 'environment_id' => 1, + 'destination_id' => 0, 'destination_type' => StandaloneDocker::class, - 'source_id' => $github_public_source->id, + 'source_id' => 0, + 'source_type' => GithubApp::class + ]); + Application::create([ + 'name' => 'coollabsio/coolify-examples:dockerfile', + 'description' => 'Dockerfile Example', + 'fqdn' => 'http://foos.com', + 'repository_project_id' => 603035348, + 'git_repository' => 'coollabsio/coolify-examples', + 'git_branch' => 'dockerfile', + 'build_pack' => 'dockerfile', + 'ports_exposes' => '3000', + 'ports_mappings' => '3080:80', + 'environment_id' => 1, + 'destination_id' => 0, + 'destination_type' => StandaloneDocker::class, + 'source_id' => 0, 'source_type' => GithubApp::class ]); } diff --git a/database/seeders/ApplicationSettingsSeeder.php b/database/seeders/ApplicationSettingsSeeder.php index 3ab5bd967..8e439fd16 100644 --- a/database/seeders/ApplicationSettingsSeeder.php +++ b/database/seeders/ApplicationSettingsSeeder.php @@ -3,11 +3,6 @@ namespace Database\Seeders; use App\Models\Application; -use App\Models\ApplicationSetting; -use App\Models\Environment; -use App\Models\GithubApp; -use App\Models\StandaloneDocker; -use App\Models\SwarmDocker; use Illuminate\Database\Seeder; class ApplicationSettingsSeeder extends Seeder diff --git a/database/seeders/DBSeeder.php b/database/seeders/DBSeeder.php deleted file mode 100644 index 35effa0c6..000000000 --- a/database/seeders/DBSeeder.php +++ /dev/null @@ -1,24 +0,0 @@ - 1, - // 'name'=> "My first database", - // 'environment_id' => $environment_1->id, - // 'destination_id' => $standalone_docker_1->id, - // 'destination_type' => StandaloneDocker::class, - // ]); - } -} diff --git a/database/seeders/DatabaseSeeder.php b/database/seeders/DatabaseSeeder.php index 1f19fca02..2a2767d78 100644 --- a/database/seeders/DatabaseSeeder.php +++ b/database/seeders/DatabaseSeeder.php @@ -2,7 +2,6 @@ namespace Database\Seeders; -use App\Models\Environment; use Illuminate\Database\Seeder; class DatabaseSeeder extends Seeder @@ -27,10 +26,12 @@ public function run(): void ApplicationSeeder::class, ApplicationSettingsSeeder::class, ApplicationPreviewSeeder::class, - DBSeeder::class, - ServiceSeeder::class, EnvironmentVariableSeeder::class, LocalPersistentVolumeSeeder::class, + S3StorageSeeder::class, + StandalonePostgresqlSeeder::class, + ScheduledDatabaseBackupSeeder::class, + ScheduledDatabaseBackupExecutionSeeder::class, ]); } } diff --git a/database/seeders/EnvironmentSeeder.php b/database/seeders/EnvironmentSeeder.php index 345bda5c5..0e980f22b 100644 --- a/database/seeders/EnvironmentSeeder.php +++ b/database/seeders/EnvironmentSeeder.php @@ -2,9 +2,6 @@ namespace Database\Seeders; -use App\Models\Environment; -use App\Models\Project; -use Illuminate\Database\Console\Seeds\WithoutModelEvents; use Illuminate\Database\Seeder; class EnvironmentSeeder extends Seeder diff --git a/database/seeders/EnvironmentVariableSeeder.php b/database/seeders/EnvironmentVariableSeeder.php index d49e97fc2..b09ff705e 100644 --- a/database/seeders/EnvironmentVariableSeeder.php +++ b/database/seeders/EnvironmentVariableSeeder.php @@ -2,7 +2,6 @@ namespace Database\Seeders; -use App\Models\EnvironmentVariable; use Illuminate\Database\Seeder; class EnvironmentVariableSeeder extends Seeder @@ -12,11 +11,11 @@ class EnvironmentVariableSeeder extends Seeder */ public function run(): void { - EnvironmentVariable::create([ - 'key' => 'NODE_ENV', - 'value' => 'production', - 'is_build_time' => true, - 'application_id' => 1, - ]); + // EnvironmentVariable::create([ + // 'key' => 'NODE_ENV', + // 'value' => 'production', + // 'is_build_time' => true, + // 'application_id' => 1, + // ]); } } diff --git a/database/seeders/GitSeeder.php b/database/seeders/GitSeeder.php index ce426d162..c8dc3ab6d 100644 --- a/database/seeders/GitSeeder.php +++ b/database/seeders/GitSeeder.php @@ -3,9 +3,6 @@ namespace Database\Seeders; use App\Models\Git; -use App\Models\PrivateKey; -use App\Models\Project; -use App\Models\Team; use Illuminate\Database\Seeder; class GitSeeder extends Seeder diff --git a/database/seeders/GithubAppSeeder.php b/database/seeders/GithubAppSeeder.php index e2d1bb9c6..4aa5ec753 100644 --- a/database/seeders/GithubAppSeeder.php +++ b/database/seeders/GithubAppSeeder.php @@ -3,9 +3,6 @@ namespace Database\Seeders; use App\Models\GithubApp; -use App\Models\PrivateKey; -use App\Models\Team; -use Illuminate\Database\Console\Seeds\WithoutModelEvents; use Illuminate\Database\Seeder; class GithubAppSeeder extends Seeder @@ -15,14 +12,13 @@ class GithubAppSeeder extends Seeder */ public function run(): void { - $root_team = Team::find(0); - $private_key_2 = PrivateKey::find(1); GithubApp::create([ + 'id' => 0, 'name' => 'Public GitHub', 'api_url' => 'https://api.github.com', 'html_url' => 'https://github.com', 'is_public' => true, - 'team_id' => $root_team->id, + 'team_id' => 0, ]); GithubApp::create([ 'name' => 'coolify-laravel-development-public', @@ -35,8 +31,8 @@ public function run(): void 'client_id' => 'Iv1.220e564d2b0abd8c', 'client_secret' => '116d1d80289f378410dd70ab4e4b81dd8d2c52b6', 'webhook_secret' => '326a47b49054f03288f800d81247ec9414d0abf3', - 'private_key_id' => $private_key_2->id, - 'team_id' => $root_team->id, + 'private_key_id' => 1, + 'team_id' => 0, ]); } } diff --git a/database/seeders/GitlabAppSeeder.php b/database/seeders/GitlabAppSeeder.php index bf1833ae4..340e7d44f 100644 --- a/database/seeders/GitlabAppSeeder.php +++ b/database/seeders/GitlabAppSeeder.php @@ -3,9 +3,6 @@ namespace Database\Seeders; use App\Models\GitlabApp; -use App\Models\PrivateKey; -use App\Models\Team; -use Illuminate\Database\Console\Seeds\WithoutModelEvents; use Illuminate\Database\Seeder; class GitlabAppSeeder extends Seeder @@ -15,15 +12,13 @@ class GitlabAppSeeder extends Seeder */ public function run(): void { - $root_team = Team::find(0); - $private_key_2 = PrivateKey::find(2); GitlabApp::create([ 'id' => 1, 'name' => 'Public GitLab', 'api_url' => 'https://gitlab.com/api/v4', 'html_url' => 'https://gitlab.com', 'is_public' => true, - 'team_id' => $root_team->id, + 'team_id' => 0, ]); GitlabApp::create([ 'id' => 2, @@ -36,8 +31,8 @@ public function run(): void 'deploy_key_id' => '1234', 'public_key' => 'dfjasiourj', 'webhook_token' => '4u3928u4y392', - 'private_key_id' => $private_key_2->id, - 'team_id' => $root_team->id, + 'private_key_id' => 2, + 'team_id' => 0 ]); } } diff --git a/database/seeders/InstanceSettingsSeeder.php b/database/seeders/InstanceSettingsSeeder.php index 12e6e2fcb..a00d8715c 100644 --- a/database/seeders/InstanceSettingsSeeder.php +++ b/database/seeders/InstanceSettingsSeeder.php @@ -16,14 +16,11 @@ public function run(): void InstanceSettings::create([ 'id' => 0, 'is_registration_enabled' => true, - 'smtp' => [ - 'enabled' => true, - 'test_recipients' => 'test@example.com,test2@example.com', - 'host' => 'coolify-mail', - 'port' => 1025, - 'from_address' => 'hi@localhost.com', - 'from_name' => 'Coolify', - ] + 'smtp_enabled' => true, + 'smtp_host' => 'coolify-mail', + 'smtp_port' => 1025, + 'smtp_from_address' => 'hi@localhost.com', + 'smtp_from_name' => 'Coolify', ]); try { $ipv4 = Process::run('curl -4s https://ifconfig.io')->output(); diff --git a/database/seeders/KubernetesSeeder.php b/database/seeders/KubernetesSeeder.php index e43a8df60..f6a852e05 100644 --- a/database/seeders/KubernetesSeeder.php +++ b/database/seeders/KubernetesSeeder.php @@ -2,7 +2,6 @@ namespace Database\Seeders; -use Illuminate\Database\Console\Seeds\WithoutModelEvents; use Illuminate\Database\Seeder; class KubernetesSeeder extends Seeder diff --git a/database/seeders/PrivateKeySeeder.php b/database/seeders/PrivateKeySeeder.php index 52785f164..d0c9a4570 100644 --- a/database/seeders/PrivateKeySeeder.php +++ b/database/seeders/PrivateKeySeeder.php @@ -3,9 +3,6 @@ namespace Database\Seeders; use App\Models\PrivateKey; -use App\Models\Server; -use App\Models\Team; -use Illuminate\Database\Console\Seeds\WithoutModelEvents; use Illuminate\Database\Seeder; class PrivateKeySeeder extends Seeder @@ -15,10 +12,9 @@ class PrivateKeySeeder extends Seeder */ public function run(): void { - $team_1 = Team::find(0); PrivateKey::create([ "id" => 0, - "team_id" => $team_1->id, + "team_id" => 0, "name" => "Testing-host", "description" => "This is a test docker container", "private_key" => "-----BEGIN OPENSSH PRIVATE KEY----- @@ -32,7 +28,7 @@ public function run(): void ]); PrivateKey::create([ - "team_id" => $team_1->id, + "team_id" => 0, "name" => "development-github-app", "description" => "This is the key for using the development GitHub app", "private_key" => "-----BEGIN RSA PRIVATE KEY----- @@ -65,7 +61,7 @@ public function run(): void "is_git_related" => true ]); PrivateKey::create([ - "team_id" => $team_1->id, + "team_id" => 0, "name" => "development-gitlab-app", "description" => "This is the key for using the development Gitlab app", "private_key" => "asdf" diff --git a/database/seeders/ProjectSeeder.php b/database/seeders/ProjectSeeder.php index 6aaca3833..304417ed5 100644 --- a/database/seeders/ProjectSeeder.php +++ b/database/seeders/ProjectSeeder.php @@ -3,18 +3,16 @@ namespace Database\Seeders; use App\Models\Project; -use App\Models\Team; use Illuminate\Database\Seeder; class ProjectSeeder extends Seeder { public function run(): void { - $root_team = Team::find(0); Project::create([ 'name' => "My first project", 'description' => "This is a test project in development", - 'team_id' => $root_team->id, + 'team_id' => 0, ]); } } diff --git a/database/seeders/ProjectSettingSeeder.php b/database/seeders/ProjectSettingSeeder.php index 2fe41976f..2a8cdfdb4 100644 --- a/database/seeders/ProjectSettingSeeder.php +++ b/database/seeders/ProjectSettingSeeder.php @@ -2,15 +2,14 @@ namespace Database\Seeders; -use App\Models\Project; use Illuminate\Database\Seeder; class ProjectSettingSeeder extends Seeder { public function run(): void { - $first_project = Project::find(1); + // $first_project = Project::find(1); // $first_project->settings->wildcard_domain = 'wildcard.example.com'; - $first_project->settings->save(); + // $first_project->settings->save(); } } diff --git a/database/seeders/S3StorageSeeder.php b/database/seeders/S3StorageSeeder.php new file mode 100644 index 000000000..de7cef6dc --- /dev/null +++ b/database/seeders/S3StorageSeeder.php @@ -0,0 +1,25 @@ + 'Local MinIO', + 'description' => 'Local MinIO S3 Storage', + 'key' => 'minioadmin', + 'secret' => 'minioadmin', + 'bucket' => 'local', + 'endpoint' => 'http://coolify-minio:9000', + 'team_id' => 0, + ]); + } +} diff --git a/database/seeders/ScheduledDatabaseBackupExecutionSeeder.php b/database/seeders/ScheduledDatabaseBackupExecutionSeeder.php new file mode 100644 index 000000000..e4b5780c9 --- /dev/null +++ b/database/seeders/ScheduledDatabaseBackupExecutionSeeder.php @@ -0,0 +1,28 @@ + 'success', + 'message' => 'Backup created successfully.', + 'size' => '10243467789556', + 'scheduled_database_backup_id' => 1, + ]); + ScheduledDatabaseBackupExecution::create([ + 'status' => 'failed', + 'message' => 'Backup failed.', + 'size' => '10243456', + 'scheduled_database_backup_id' => 1, + ]); + } +} diff --git a/database/seeders/ScheduledDatabaseBackupSeeder.php b/database/seeders/ScheduledDatabaseBackupSeeder.php new file mode 100644 index 000000000..96ca0c012 --- /dev/null +++ b/database/seeders/ScheduledDatabaseBackupSeeder.php @@ -0,0 +1,33 @@ + true, + 'frequency' => '* * * * *', + 'number_of_backups_locally' => 2, + 'database_id' => 1, + 'database_type' => 'App\Models\StandalonePostgresql', + 's3_storage_id' => 1, + 'team_id' => 0, + ]); + ScheduledDatabaseBackup::create([ + 'enabled' => true, + 'frequency' => '* * * * *', + 'number_of_backups_locally' => 3, + 'database_id' => 1, + 'database_type' => 'App\Models\StandalonePostgresql', + 'team_id' => 0, + ]); + } +} diff --git a/database/seeders/ServerSeeder.php b/database/seeders/ServerSeeder.php index 82ab2552b..77a4516e5 100644 --- a/database/seeders/ServerSeeder.php +++ b/database/seeders/ServerSeeder.php @@ -2,39 +2,24 @@ namespace Database\Seeders; -use App\Data\ServerMetadata; -use App\Enums\ProxyStatus; -use App\Enums\ProxyTypes; -use App\Models\PrivateKey; use App\Models\Server; -use App\Models\Team; use Illuminate\Database\Seeder; class ServerSeeder extends Seeder { public function run(): void { - $root_team = Team::find(0); - $private_key_1 = PrivateKey::find(0); - Server::create([ 'id' => 0, 'name' => "testing-local-docker-container", 'description' => "This is a test docker container", 'ip' => "coolify-testing-host", - 'team_id' => $root_team->id, - 'private_key_id' => $private_key_1->id, + 'team_id' => 0, + 'private_key_id' => 0, // 'proxy' => ServerMetadata::from([ // 'type' => ProxyTypes::TRAEFIK_V2->value, // 'status' => ProxyStatus::EXITED->value // ]), ]); - Server::create([ - 'name' => "testing-local-docker-container-2", - 'description' => "This is a test docker container", - 'ip' => "coolify-testing-host-2", - 'team_id' => $root_team->id, - 'private_key_id' => $private_key_1->id - ]); } } diff --git a/database/seeders/ServerSettingSeeder.php b/database/seeders/ServerSettingSeeder.php index fe366ba77..6d8c68188 100644 --- a/database/seeders/ServerSettingSeeder.php +++ b/database/seeders/ServerSettingSeeder.php @@ -3,7 +3,6 @@ namespace Database\Seeders; use App\Models\Server; -use App\Models\Team; use Illuminate\Database\Seeder; class ServerSettingSeeder extends Seeder @@ -19,11 +18,5 @@ public function run(): void $server_2->settings->is_usable = true; $server_2->settings->is_reachable = true; $server_2->settings->save(); - - $server_3 = Server::find(1)->load(['settings']); - $server_3->settings->is_part_of_swarm = false; - $server_2->settings->is_usable = false; - $server_3->settings->is_reachable = false; - $server_3->settings->save(); } } diff --git a/database/seeders/ServiceSeeder.php b/database/seeders/ServiceSeeder.php deleted file mode 100644 index 360c946c5..000000000 --- a/database/seeders/ServiceSeeder.php +++ /dev/null @@ -1,27 +0,0 @@ - 1, - // 'name'=> "My first service", - // 'environment_id' => $environment_1->id, - // 'destination_id' => $standalone_docker_1->id, - // 'destination_type' => StandaloneDocker::class, - // ]); - } -} diff --git a/database/seeders/StandaloneDockerSeeder.php b/database/seeders/StandaloneDockerSeeder.php index cbf32759f..9f67de710 100644 --- a/database/seeders/StandaloneDockerSeeder.php +++ b/database/seeders/StandaloneDockerSeeder.php @@ -3,9 +3,7 @@ namespace Database\Seeders; use App\Models\Destination; -use App\Models\Server; use App\Models\StandaloneDocker; -use Illuminate\Database\Console\Seeds\WithoutModelEvents; use Illuminate\Database\Seeder; class StandaloneDockerSeeder extends Seeder @@ -15,11 +13,11 @@ class StandaloneDockerSeeder extends Seeder */ public function run(): void { - $server_1 = Server::find(0); StandaloneDocker::create([ + 'id' => 0, 'name' => 'Standalone Docker 1', 'network' => 'coolify', - 'server_id' => $server_1->id, + 'server_id' => 0, ]); } } diff --git a/database/seeders/StandalonePostgresqlSeeder.php b/database/seeders/StandalonePostgresqlSeeder.php new file mode 100644 index 000000000..1fc96a610 --- /dev/null +++ b/database/seeders/StandalonePostgresqlSeeder.php @@ -0,0 +1,22 @@ + 'Local PostgreSQL', + 'description' => 'Local PostgreSQL for testing', + 'postgres_password' => 'postgres', + 'environment_id' => 1, + 'destination_id' => 0, + 'destination_type' => StandaloneDocker::class, + ]); + } +} diff --git a/database/seeders/SubscriptionSeeder.php b/database/seeders/SubscriptionSeeder.php index e76d793a3..03a5ed8f3 100644 --- a/database/seeders/SubscriptionSeeder.php +++ b/database/seeders/SubscriptionSeeder.php @@ -2,7 +2,6 @@ namespace Database\Seeders; -use Illuminate\Database\Console\Seeds\WithoutModelEvents; use Illuminate\Database\Seeder; class SubscriptionSeeder extends Seeder diff --git a/database/seeders/SwarmDockerSeeder.php b/database/seeders/SwarmDockerSeeder.php index a9662a969..906e1bccc 100644 --- a/database/seeders/SwarmDockerSeeder.php +++ b/database/seeders/SwarmDockerSeeder.php @@ -3,10 +3,7 @@ namespace Database\Seeders; use App\Models\Destination; -use App\Models\Server; -use App\Models\StandaloneDocker; use App\Models\SwarmDocker; -use Illuminate\Database\Console\Seeds\WithoutModelEvents; use Illuminate\Database\Seeder; class SwarmDockerSeeder extends Seeder @@ -16,10 +13,9 @@ class SwarmDockerSeeder extends Seeder */ public function run(): void { - $server_2 = Server::find(1); SwarmDocker::create([ 'name' => 'Swarm Docker 1', - 'server_id' => $server_2->id, + 'server_id' => 1, ]); } } diff --git a/database/seeders/WaitlistSeeder.php b/database/seeders/WaitlistSeeder.php new file mode 100644 index 000000000..ce259253e --- /dev/null +++ b/database/seeders/WaitlistSeeder.php @@ -0,0 +1,17 @@ + /dev/null RUN echo deb [arch=amd64,arm64,ppc64el signed-by=/usr/share/keyrings/postgresql.gpg] http://apt.postgresql.org/pub/repos/apt/ jammy-pgdg main | tee -a /etc/apt/sources.list.d/postgresql.list -RUN apt-get update +RUN apt-get update RUN apt-get install postgresql-client-$POSTGRES_VERSION -y # Coolify requirements -RUN apt-get install -y php-pgsql openssh-client git git-lfs jq +RUN apt-get install -y php-pgsql openssh-client git git-lfs jq lsof RUN apt-get -y autoremove && apt-get clean && rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/* /usr/share/doc/* diff --git a/docker/dev-ssu/nginx.conf b/docker/dev-ssu/nginx.conf index 60e1f966d..f26dc3049 100644 --- a/docker/dev-ssu/nginx.conf +++ b/docker/dev-ssu/nginx.conf @@ -1,4 +1,4 @@ # Custom nginx configuration # Disable access logs -access_log off; \ No newline at end of file +access_log off; diff --git a/docker/prod-ssu/Dockerfile b/docker/prod-ssu/Dockerfile index 5bcbaa1f2..0804ee076 100644 --- a/docker/prod-ssu/Dockerfile +++ b/docker/prod-ssu/Dockerfile @@ -15,18 +15,18 @@ FROM serversideup/php:8.2-fpm-nginx WORKDIR /var/www/html ARG POSTGRES_VERSION=15 -RUN apt-get update +RUN apt-get update # Postgres version requirements RUN apt install dirmngr ca-certificates software-properties-common gnupg gnupg2 apt-transport-https curl -y RUN curl -fSsL https://www.postgresql.org/media/keys/ACCC4CF8.asc | gpg --dearmor | tee /usr/share/keyrings/postgresql.gpg > /dev/null RUN echo deb [arch=amd64,arm64,ppc64el signed-by=/usr/share/keyrings/postgresql.gpg] http://apt.postgresql.org/pub/repos/apt/ jammy-pgdg main | tee -a /etc/apt/sources.list.d/postgresql.list -RUN apt-get update +RUN apt-get update RUN apt-get install postgresql-client-$POSTGRES_VERSION -y # Coolify requirements -RUN apt-get install -y php-pgsql openssh-client git git-lfs jq +RUN apt-get install -y php-pgsql openssh-client git git-lfs jq lsof RUN apt-get -y autoremove && apt-get clean && rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/* /usr/share/doc/* COPY docker/prod-ssu/nginx.conf /etc/nginx/conf.d/custom.conf diff --git a/docker/prod-ssu/nginx.conf b/docker/prod-ssu/nginx.conf index 60e1f966d..f26dc3049 100644 --- a/docker/prod-ssu/nginx.conf +++ b/docker/prod-ssu/nginx.conf @@ -1,4 +1,4 @@ # Custom nginx configuration # Disable access logs -access_log off; \ No newline at end of file +access_log off; diff --git a/docker/testing-host/Dockerfile b/docker/testing-host/Dockerfile index 1fdce8a9d..82871941d 100644 --- a/docker/testing-host/Dockerfile +++ b/docker/testing-host/Dockerfile @@ -10,11 +10,11 @@ ARG DOCKER_BUILDX_VERSION=0.10.5 # https://github.com/buildpacks/pack/releases ARG PACK_VERSION=0.29.0 # https://github.com/railwayapp/nixpacks/releases -ARG NIXPACKS_VERSION=1.9.0 +ARG NIXPACKS_VERSION=1.12.0 USER root WORKDIR /root -RUN apk add --no-cache bash curl git git-lfs openssh-client openssh-server tar tini postgresql-client +RUN apk add --no-cache bash curl git git-lfs openssh-client openssh-server tar tini postgresql-client lsof RUN mkdir -p ~/.docker/cli-plugins RUN if [[ ${TARGETPLATFORM} == 'linux/amd64' ]]; then \ curl -sSL https://github.com/docker/buildx/releases/download/v${DOCKER_BUILDX_VERSION}/buildx-v${DOCKER_BUILDX_VERSION}.linux-amd64 -o ~/.docker/cli-plugins/docker-buildx && \ diff --git a/other/logos/appwrite.svg b/other/logos/appwrite.svg index 7128b7b73..b5b8e4ab7 100644 --- a/other/logos/appwrite.svg +++ b/other/logos/appwrite.svg @@ -1,2 +1,14 @@ - - \ No newline at end of file + + + + + + diff --git a/public/index.php b/public/index.php index 1d69f3a28..f3c2ebcd3 100644 --- a/public/index.php +++ b/public/index.php @@ -16,7 +16,7 @@ | */ -if (file_exists($maintenance = __DIR__.'/../storage/framework/maintenance.php')) { +if (file_exists($maintenance = __DIR__ . '/../storage/framework/maintenance.php')) { require $maintenance; } @@ -31,7 +31,7 @@ | */ -require __DIR__.'/../vendor/autoload.php'; +require __DIR__ . '/../vendor/autoload.php'; /* |-------------------------------------------------------------------------- @@ -44,7 +44,7 @@ | */ -$app = require_once __DIR__.'/../bootstrap/app.php'; +$app = require_once __DIR__ . '/../bootstrap/app.php'; $kernel = $app->make(Kernel::class); diff --git a/public/svgs/external-link.svg b/public/svgs/external-link.svg index e0e47d694..c3c982ff0 100644 --- a/public/svgs/external-link.svg +++ b/public/svgs/external-link.svg @@ -1,6 +1,6 @@ -
@endif @if (session('status')) -
+
{{ session('status') }}
@endif diff --git a/resources/views/auth/force-password-reset.blade.php b/resources/views/auth/force-password-reset.blade.php new file mode 100644 index 000000000..df1f415f6 --- /dev/null +++ b/resources/views/auth/force-password-reset.blade.php @@ -0,0 +1,3 @@ + + + diff --git a/resources/views/auth/forgot-password.blade.php b/resources/views/auth/forgot-password.blade.php index 0a8bccff6..89645e8f5 100644 --- a/resources/views/auth/forgot-password.blade.php +++ b/resources/views/auth/forgot-password.blade.php @@ -22,7 +22,9 @@ @else
Transactional emails are not active on this instance.
See how to set it in our docs, or how to manually reset password.
+ href="https://docs.coollabs.io/coolify">docs, or how to + manually reset password. +
@endif @if ($errors->any())
diff --git a/resources/views/auth/login.blade.php b/resources/views/auth/login.blade.php index 0a66be609..838bc5a11 100644 --- a/resources/views/auth/login.blade.php +++ b/resources/views/auth/login.blade.php @@ -3,14 +3,21 @@
Coolify
-

{{ __('auth.login') }}

@if ($is_registration_enabled) - - {{ __('auth.register_now') }} - + @if (config('coolify.waitlist')) + + Join the waitlist + + @else + + {{ __('auth.register_now') }} + + @endif @endif
diff --git a/resources/views/auth/reset-password.blade.php b/resources/views/auth/reset-password.blade.php index c5e1ab850..e1afe3443 100644 --- a/resources/views/auth/reset-password.blade.php +++ b/resources/views/auth/reset-password.blade.php @@ -1,14 +1,13 @@ -
+
-
-

{{ __('auth.reset_password') }}

+
+

{{ __('auth.reset_password') }}

@@ -16,7 +15,7 @@ -
+
@endif @if (session('status')) -
+
{{ session('status') }}
@endif diff --git a/resources/views/auth/two-factor-challenge.blade.php b/resources/views/auth/two-factor-challenge.blade.php index a5e645d7e..4ec05cdae 100644 --- a/resources/views/auth/two-factor-challenge.blade.php +++ b/resources/views/auth/two-factor-challenge.blade.php @@ -36,7 +36,7 @@
@endif @if (session('status')) -
+
{{ session('status') }}
@endif diff --git a/resources/views/auth/waitlist.blade.php b/resources/views/auth/waitlist.blade.php new file mode 100644 index 000000000..497bb0868 --- /dev/null +++ b/resources/views/auth/waitlist.blade.php @@ -0,0 +1,3 @@ + + + diff --git a/resources/views/command-center.blade.php b/resources/views/command-center.blade.php index cd4ed7baa..33474eac6 100644 --- a/resources/views/command-center.blade.php +++ b/resources/views/command-center.blade.php @@ -1,12 +1,12 @@

Command Center

-
Execute commands on your servers without leaving the browser.
+
Execute commands on your servers without leaving the browser.
@if ($servers->count() > 0) @else
-
No validated servers found.
- +
No servers found. Without a server, you won't be able to do much.
+
@endif
diff --git a/resources/views/components/applications/actions.blade.php b/resources/views/components/applications/actions.blade.php deleted file mode 100644 index fe5b235fc..000000000 --- a/resources/views/components/applications/actions.blade.php +++ /dev/null @@ -1,82 +0,0 @@ -
-
- - -
-
diff --git a/resources/views/components/applications/advanced.blade.php b/resources/views/components/applications/advanced.blade.php new file mode 100644 index 000000000..622708103 --- /dev/null +++ b/resources/views/components/applications/advanced.blade.php @@ -0,0 +1,50 @@ +
+
+ + +
+
diff --git a/resources/views/components/applications/breadcrumbs.blade.php b/resources/views/components/applications/breadcrumbs.blade.php deleted file mode 100644 index b707d118e..000000000 --- a/resources/views/components/applications/breadcrumbs.blade.php +++ /dev/null @@ -1,49 +0,0 @@ - diff --git a/resources/views/components/applications/links.blade.php b/resources/views/components/applications/links.blade.php index b410cf0e1..9d0a75c70 100644 --- a/resources/views/components/applications/links.blade.php +++ b/resources/views/components/applications/links.blade.php @@ -45,7 +45,8 @@ class="text-xs text-white rounded-none hover:no-underline hover:bg-coollabs hove - PR{{ data_get($preview, 'pull_request_id') }} | + + PR{{ data_get($preview, 'pull_request_id') }} | {{ data_get($preview, 'fqdn') }} @@ -54,7 +55,7 @@ class="text-xs text-white rounded-none hover:no-underline hover:bg-coollabs hove @endif @if (data_get($application, 'ports_mappings_array')) @foreach ($application->ports_mappings_array as $port) - @if (isDev()) + @if (is_dev())
  • @@ -66,7 +67,8 @@ class="text-xs text-white rounded-none hover:no-underline hover:bg-coollabs hove - Port {{ $port }} + + Port {{ $port }}
  • @else @@ -82,7 +84,8 @@ class="text-xs text-white rounded-none hover:no-underline hover:bg-coollabs hove - Port {{ $port }} + + Port {{ $port }} @endif diff --git a/resources/views/components/applications/navbar.blade.php b/resources/views/components/applications/navbar.blade.php index 5858787d5..897c68662 100644 --- a/resources/views/components/applications/navbar.blade.php +++ b/resources/views/components/applications/navbar.blade.php @@ -1,4 +1,4 @@ -
    + diff --git a/resources/views/components/databases/navbar.blade.php b/resources/views/components/databases/navbar.blade.php new file mode 100644 index 000000000..6d6abcfb9 --- /dev/null +++ b/resources/views/components/databases/navbar.blade.php @@ -0,0 +1,35 @@ + diff --git a/resources/views/components/forms/button.blade.php b/resources/views/components/forms/button.blade.php index da81d81b1..a2350beda 100644 --- a/resources/views/components/forms/button.blade.php +++ b/resources/views/components/forms/button.blade.php @@ -1,11 +1,11 @@ + + diff --git a/resources/views/components/navbar.blade.php b/resources/views/components/navbar.blade.php index feb2e7aac..11c297ecd 100644 --- a/resources/views/components/navbar.blade.php +++ b/resources/views/components/navbar.blade.php @@ -10,6 +10,21 @@ +
  • + is('servers')) href="/servers" @endif> + + + + + + + + + +
  • is('projects')) href="/projects" @endif> - is('servers')) href="/servers" @endif> +
  • + is('command-center')) href="/command-center" @endif> - - - - - + +
  • - @if (auth()->user()->isInstanceAdmin()) -
  • - is('command-center')) href="/command-center" @endif> - - - - - - -
  • - -
  • - is('profile')) href="/profile" @endif> - - - - - - - -
  • -
  • - - - - - - - - - - - -
  • +
    + @if (is_instance_admin()) -
    + @endif +
  • + is('profile')) href="/profile" @endif> + + + + + + + +
  • +
  • + + + + + + + + + + + +
  • + @if (is_instance_admin())
  • is('settings')) href="/settings" @endif>
  • @endif -
  • @csrf - + +
  • diff --git a/resources/views/components/pricing-plans.blade.php b/resources/views/components/pricing-plans.blade.php new file mode 100644 index 000000000..5ccde5e23 --- /dev/null +++ b/resources/views/components/pricing-plans.blade.php @@ -0,0 +1,317 @@ +
    +
    +
    +
    + Payment frequency + + +
    +
    +
    +
    Save 10% annually with the yearly plan. +
    +
    +
    +
    Congratulations! 🎉 You are saving money with this choice! +
    +
    +
    +
    +
    +

    Unlimited Trial

    +

    + + Free + + + Still Free + +

    + (save $?) +
    Get + Started +

    Start self-hosting without limits with our + OSS + version.

    +
      +
    • + + Same features as the paid version +
    • +
    • + + Managed by you +
    • +
    • + + If you brave enough +
    • +
    • + + Community Support +
    • +
    • + + + + + + + + All upcoming features +
    • +
    +
    +
    +

    Basic

    +

    + + $5 + /monthly + + + $54 + /yearly + +

    + (save $6) + + Subscribe + Subscribe +

    Start self-hosting in + the cloud + with a + single + server. +

    +
      +
    • + + 1 server +
    • +
    • + + Unlimited Deployments +
    • +
    • + + Bring your own S3 +
    • +
    • + + Basic Support +
    • +
    • + + + + + + + + All upcoming features +
    • +
    +
    +
    +

    Essential

    +

    + + $29 + /monthly + + + $319 + /yearly + +

    + (save $29) + Subscribe + Subscribe +

    Scale your business or self-hosting environment. +

    +
      +
    • + + 5 servers +
    • +
    • + + Unlimited Deployments +
    • +
    • + + Provided (optional) S3 +
    • +
    • + + Basic Support +
    • +
    • + + + + + + + + All upcoming features +
    • +
    +
    +
    +

    Growth

    +

    + + $49 + /monthly + + + $539 + /yearly + +

    + (save $69) + Subscribe + Subscribe +

    Deploy complex infrastuctures and + manage them easily in one place.

    +
      +
    • + + Unlimited servers +
    • +
    • + + Unlimited deployments +
    • +
    • + + Provided (optional) S3 +
    • +
    • + + Priority Support +
    • +
    • + + + + + + + + All upcoming features +
    • +
    +
    +
    +
    +
    +
    diff --git a/resources/views/components/resources/breadcrumbs.blade.php b/resources/views/components/resources/breadcrumbs.blade.php new file mode 100644 index 000000000..cb1878581 --- /dev/null +++ b/resources/views/components/resources/breadcrumbs.blade.php @@ -0,0 +1,49 @@ + diff --git a/resources/views/components/server/navbar.blade.php b/resources/views/components/server/navbar.blade.php index dcfd76dbb..32691c44d 100644 --- a/resources/views/components/server/navbar.blade.php +++ b/resources/views/components/server/navbar.blade.php @@ -1,34 +1,35 @@
    -

    Server

    -
    {{ data_get($server, 'name') }}
    -
    diff --git a/resources/views/components/settings/navbar.blade.php b/resources/views/components/settings/navbar.blade.php index 3f671a3d9..ab7d19c09 100644 --- a/resources/views/components/settings/navbar.blade.php +++ b/resources/views/components/settings/navbar.blade.php @@ -1,15 +1,12 @@ -
    +

    Settings

    -
    Instance wide settings for Coolify.
    -
    - @if ($environment->applications->count() === 0) + @if ($environment->can_delete_environment())

    No resources found.

    @endif
    @foreach ($environment->applications->sortBy('name') as $application) - {{ $application->name }} +
    +
    {{ $application->name }}
    +
    {{ $application->description }}
    +
    +
    + @endforeach + @foreach ($environment->databases->sortBy('name') as $databases) + +
    +
    {{ $databases->name }}
    +
    {{ $databases->description }}
    +
    @endforeach
    diff --git a/resources/views/project/service.blade.php b/resources/views/project/service.blade.php deleted file mode 100644 index 04cbcd4df..000000000 --- a/resources/views/project/service.blade.php +++ /dev/null @@ -1,5 +0,0 @@ - -

    Service

    - - -
    diff --git a/resources/views/project/show.blade.php b/resources/views/project/show.blade.php index d2568c91f..dcd350419 100644 --- a/resources/views/project/show.blade.php +++ b/resources/views/project/show.blade.php @@ -1,11 +1,10 @@

    Environments

    - @if ($project->applications->count() === 0) - - @endif + + Add +
    -
    {{ $project->name }}
    +
    {{ $project->name }}
    @forelse ($project->environments as $environment) diff --git a/resources/views/projects.blade.php b/resources/views/projects.blade.php index 51a99cd56..563230edf 100644 --- a/resources/views/projects.blade.php +++ b/resources/views/projects.blade.php @@ -1,34 +1,49 @@ -

    Projects

    -
    All Projects
    +
    +

    Projects

    + @if ($servers > 0) + + Add + + @endif +
    +
    All Projects
    - @forelse ($projects as $project) -
    -
    - {{ $project->name }} -
    - {{ $project->description }}
    -
    -
    - - - - - -
    - @empty + @if ($servers === 0)
    -
    No project found.
    - +
    No servers found. Without a server, you won't be able to do much.
    +
    - @endforelse + @else + @forelse ($projects as $project) +
    +
    + {{ $project->name }} +
    + {{ $project->description }}
    +
    +
    + + + + + + + +
    + @empty +
    +
    No project found.
    + +
    + @endforelse + @endif + + diff --git a/resources/views/team/storages/create.blade.php b/resources/views/team/storages/create.blade.php new file mode 100644 index 000000000..711c521e3 --- /dev/null +++ b/resources/views/team/storages/create.blade.php @@ -0,0 +1,3 @@ + + + diff --git a/resources/views/team/storages/show.blade.php b/resources/views/team/storages/show.blade.php new file mode 100644 index 000000000..88f8780d6 --- /dev/null +++ b/resources/views/team/storages/show.blade.php @@ -0,0 +1,6 @@ + + + + diff --git a/resources/views/vendor/mail/html/button.blade.php b/resources/views/vendor/mail/html/button.blade.php new file mode 100644 index 000000000..957b3ae45 --- /dev/null +++ b/resources/views/vendor/mail/html/button.blade.php @@ -0,0 +1,21 @@ +@props(['url', 'color' => 'primary', 'align' => 'center']) + + + + + diff --git a/resources/views/vendor/mail/html/footer.blade.php b/resources/views/vendor/mail/html/footer.blade.php new file mode 100644 index 000000000..33f7dad76 --- /dev/null +++ b/resources/views/vendor/mail/html/footer.blade.php @@ -0,0 +1,11 @@ + + + + + + + + + diff --git a/resources/views/vendor/mail/html/header.blade.php b/resources/views/vendor/mail/html/header.blade.php new file mode 100644 index 000000000..6e30a3df9 --- /dev/null +++ b/resources/views/vendor/mail/html/header.blade.php @@ -0,0 +1,12 @@ +@props(['url']) + + + + @if (trim($slot) === 'Laravel') + + @else + {{ $slot }} + @endif + + + diff --git a/resources/views/vendor/mail/html/layout.blade.php b/resources/views/vendor/mail/html/layout.blade.php new file mode 100644 index 000000000..1f7a16fe8 --- /dev/null +++ b/resources/views/vendor/mail/html/layout.blade.php @@ -0,0 +1,63 @@ + + + + + {{ config('app.name') }} + + + + + + + + + + + + + + + + + diff --git a/resources/views/vendor/mail/html/message.blade.php b/resources/views/vendor/mail/html/message.blade.php new file mode 100644 index 000000000..80bce2112 --- /dev/null +++ b/resources/views/vendor/mail/html/message.blade.php @@ -0,0 +1,27 @@ + + {{-- Header --}} + + + {{ config('app.name') }} + + + + {{-- Body --}} + {{ $slot }} + + {{-- Subcopy --}} + @isset($subcopy) + + + {{ $subcopy }} + + + @endisset + + {{-- Footer --}} + + + © {{ date('Y') }} {{ config('app.name') }}. @lang('All rights reserved.') + + + diff --git a/resources/views/vendor/mail/html/panel.blade.php b/resources/views/vendor/mail/html/panel.blade.php new file mode 100644 index 000000000..783af16ce --- /dev/null +++ b/resources/views/vendor/mail/html/panel.blade.php @@ -0,0 +1,13 @@ + + + + + diff --git a/resources/views/vendor/mail/html/subcopy.blade.php b/resources/views/vendor/mail/html/subcopy.blade.php new file mode 100644 index 000000000..f559e8f1b --- /dev/null +++ b/resources/views/vendor/mail/html/subcopy.blade.php @@ -0,0 +1,7 @@ + + + + + diff --git a/resources/views/vendor/mail/html/table.blade.php b/resources/views/vendor/mail/html/table.blade.php new file mode 100644 index 000000000..b3bb3c8e4 --- /dev/null +++ b/resources/views/vendor/mail/html/table.blade.php @@ -0,0 +1,3 @@ +
    + {{ Illuminate\Mail\Markdown::parse($slot) }} +
    diff --git a/resources/views/vendor/mail/html/themes/default.css b/resources/views/vendor/mail/html/themes/default.css new file mode 100644 index 000000000..03d811653 --- /dev/null +++ b/resources/views/vendor/mail/html/themes/default.css @@ -0,0 +1,290 @@ +/* Base */ + +body, +body *:not(html):not(style):not(br):not(tr):not(code) { + box-sizing: border-box; + font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif, + 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol'; + position: relative; +} + +body { + -webkit-text-size-adjust: none; + background-color: #ffffff; + color: #718096; + height: 100%; + line-height: 1.4; + margin: 0; + padding: 0; + width: 100% !important; +} + +p, +ul, +ol, +blockquote { + line-height: 1.4; + text-align: left; +} + +a { + color: #3869d4; +} + +a img { + border: none; +} + +/* Typography */ + +h1 { + color: #3d4852; + font-size: 18px; + font-weight: bold; + margin-top: 0; + text-align: left; +} + +h2 { + font-size: 16px; + font-weight: bold; + margin-top: 0; + text-align: left; +} + +h3 { + font-size: 14px; + font-weight: bold; + margin-top: 0; + text-align: left; +} + +p { + font-size: 16px; + line-height: 1.5em; + margin-top: 0; + text-align: left; +} + +p.sub { + font-size: 12px; +} + +img { + max-width: 100%; +} + +/* Layout */ + +.wrapper { + -premailer-cellpadding: 0; + -premailer-cellspacing: 0; + -premailer-width: 100%; + background-color: #edf2f7; + margin: 0; + padding: 0; + width: 100%; +} + +.content { + -premailer-cellpadding: 0; + -premailer-cellspacing: 0; + -premailer-width: 100%; + margin: 0; + padding: 0; + width: 100%; +} + +/* Header */ + +.header { + padding: 25px 0; + text-align: center; +} + +.header a { + color: #3d4852; + font-size: 19px; + font-weight: bold; + text-decoration: none; +} + +/* Logo */ + +.logo { + height: 75px; + max-height: 75px; + width: 75px; +} + +/* Body */ + +.body { + -premailer-cellpadding: 0; + -premailer-cellspacing: 0; + -premailer-width: 100%; + background-color: #edf2f7; + border-bottom: 1px solid #edf2f7; + border-top: 1px solid #edf2f7; + margin: 0; + padding: 0; + width: 100%; +} + +.inner-body { + -premailer-cellpadding: 0; + -premailer-cellspacing: 0; + -premailer-width: 570px; + background-color: #ffffff; + border-color: #e8e5ef; + border-radius: 2px; + border-width: 1px; + box-shadow: 0 2px 0 rgba(0, 0, 150, 0.025), 2px 4px 0 rgba(0, 0, 150, 0.015); + margin: 0 auto; + padding: 0; + width: 570px; +} + +/* Subcopy */ + +.subcopy { + border-top: 1px solid #e8e5ef; + margin-top: 25px; + padding-top: 25px; +} + +.subcopy p { + font-size: 14px; +} + +/* Footer */ + +.footer { + -premailer-cellpadding: 0; + -premailer-cellspacing: 0; + -premailer-width: 570px; + margin: 0 auto; + padding: 0; + text-align: center; + width: 570px; +} + +.footer p { + color: #b0adc5; + font-size: 12px; + text-align: center; +} + +.footer a { + color: #b0adc5; + text-decoration: underline; +} + +/* Tables */ + +.table table { + -premailer-cellpadding: 0; + -premailer-cellspacing: 0; + -premailer-width: 100%; + margin: 30px auto; + width: 100%; +} + +.table th { + border-bottom: 1px solid #edeff2; + margin: 0; + padding-bottom: 8px; +} + +.table td { + color: #74787e; + font-size: 15px; + line-height: 18px; + margin: 0; + padding: 10px 0; +} + +.content-cell { + max-width: 100vw; + padding: 32px; +} + +/* Buttons */ + +.action { + -premailer-cellpadding: 0; + -premailer-cellspacing: 0; + -premailer-width: 100%; + margin: 30px auto; + padding: 0; + text-align: center; + width: 100%; +} + +.button { + -webkit-text-size-adjust: none; + border-radius: 4px; + color: #fff; + display: inline-block; + overflow: hidden; + text-decoration: none; +} + +.button-blue, +.button-primary { + background-color: #2d3748; + border-bottom: 8px solid #2d3748; + border-left: 18px solid #2d3748; + border-right: 18px solid #2d3748; + border-top: 8px solid #2d3748; +} + +.button-green, +.button-success { + background-color: #48bb78; + border-bottom: 8px solid #48bb78; + border-left: 18px solid #48bb78; + border-right: 18px solid #48bb78; + border-top: 8px solid #48bb78; +} + +.button-red, +.button-error { + background-color: #e53e3e; + border-bottom: 8px solid #e53e3e; + border-left: 18px solid #e53e3e; + border-right: 18px solid #e53e3e; + border-top: 8px solid #e53e3e; +} + +/* Panels */ + +.panel { + border-left: #2d3748 solid 4px; + margin: 21px 0; +} + +.panel-content { + background-color: #edf2f7; + color: #718096; + padding: 16px; +} + +.panel-content p { + color: #718096; +} + +.panel-item { + padding: 0; +} + +.panel-item p:last-of-type { + margin-bottom: 0; + padding-bottom: 0; +} + +/* Utilities */ + +.break-all { + word-break: break-all; +} diff --git a/resources/views/vendor/mail/text/button.blade.php b/resources/views/vendor/mail/text/button.blade.php new file mode 100644 index 000000000..97444ebdc --- /dev/null +++ b/resources/views/vendor/mail/text/button.blade.php @@ -0,0 +1 @@ +{{ $slot }}: {{ $url }} diff --git a/resources/views/vendor/mail/text/footer.blade.php b/resources/views/vendor/mail/text/footer.blade.php new file mode 100644 index 000000000..3338f620e --- /dev/null +++ b/resources/views/vendor/mail/text/footer.blade.php @@ -0,0 +1 @@ +{{ $slot }} diff --git a/resources/views/vendor/mail/text/header.blade.php b/resources/views/vendor/mail/text/header.blade.php new file mode 100644 index 000000000..aaa3e5754 --- /dev/null +++ b/resources/views/vendor/mail/text/header.blade.php @@ -0,0 +1 @@ +[{{ $slot }}]({{ $url }}) diff --git a/resources/views/vendor/mail/text/layout.blade.php b/resources/views/vendor/mail/text/layout.blade.php new file mode 100644 index 000000000..b1a79aec9 --- /dev/null +++ b/resources/views/vendor/mail/text/layout.blade.php @@ -0,0 +1,8 @@ +{!! strip_tags($header) !!} + +{!! strip_tags($slot) !!} +@isset($subcopy) + {!! strip_tags($subcopy) !!} +@endisset + +{!! strip_tags($footer) !!} diff --git a/resources/views/vendor/mail/text/message.blade.php b/resources/views/vendor/mail/text/message.blade.php new file mode 100644 index 000000000..80bce2112 --- /dev/null +++ b/resources/views/vendor/mail/text/message.blade.php @@ -0,0 +1,27 @@ + + {{-- Header --}} + + + {{ config('app.name') }} + + + + {{-- Body --}} + {{ $slot }} + + {{-- Subcopy --}} + @isset($subcopy) + + + {{ $subcopy }} + + + @endisset + + {{-- Footer --}} + + + © {{ date('Y') }} {{ config('app.name') }}. @lang('All rights reserved.') + + + diff --git a/resources/views/vendor/mail/text/panel.blade.php b/resources/views/vendor/mail/text/panel.blade.php new file mode 100644 index 000000000..3338f620e --- /dev/null +++ b/resources/views/vendor/mail/text/panel.blade.php @@ -0,0 +1 @@ +{{ $slot }} diff --git a/resources/views/vendor/mail/text/subcopy.blade.php b/resources/views/vendor/mail/text/subcopy.blade.php new file mode 100644 index 000000000..3338f620e --- /dev/null +++ b/resources/views/vendor/mail/text/subcopy.blade.php @@ -0,0 +1 @@ +{{ $slot }} diff --git a/resources/views/vendor/mail/text/table.blade.php b/resources/views/vendor/mail/text/table.blade.php new file mode 100644 index 000000000..3338f620e --- /dev/null +++ b/resources/views/vendor/mail/text/table.blade.php @@ -0,0 +1 @@ +{{ $slot }} diff --git a/routes/channels.php b/routes/channels.php index 961394624..745a40640 100644 --- a/routes/channels.php +++ b/routes/channels.php @@ -1,7 +1,5 @@ json(['message' => 'Transactional emails are not active'], 400); })->name('password.forgot'); +Route::get('/waitlist', [Controller::class, 'waitlist'])->name('auth.waitlist'); + Route::prefix('magic')->middleware(['auth'])->group(function () { Route::get('/servers', [MagicController::class, 'servers']); Route::get('/destinations', [MagicController::class, 'destinations']); @@ -51,47 +55,57 @@ Route::get('/project/{project_uuid}', [ProjectController::class, 'show'])->name('project.show'); Route::get('/project/{project_uuid}/{environment_name}/new', [ProjectController::class, 'new'])->name('project.resources.new'); Route::get('/project/{project_uuid}/{environment_name}', [ProjectController::class, 'resources'])->name('project.resources'); + + // Applications Route::get('/project/{project_uuid}/{environment_name}/application/{application_uuid}', [ApplicationController::class, 'configuration'])->name('project.application.configuration'); Route::get('/project/{project_uuid}/{environment_name}/application/{application_uuid}/deployment', [ApplicationController::class, 'deployments'])->name('project.application.deployments'); Route::get( '/project/{project_uuid}/{environment_name}/application/{application_uuid}/deployment/{deployment_uuid}', [ApplicationController::class, 'deployment'] )->name('project.application.deployment'); + + // Databases + Route::get('/project/{project_uuid}/{environment_name}/database/{database_uuid}', [DatabaseController::class, 'configuration'])->name('project.database.configuration'); + Route::get('/project/{project_uuid}/{environment_name}/database/{database_uuid}/backups', [DatabaseController::class, 'backups'])->name('project.database.backups.all'); + Route::get('/project/{project_uuid}/{environment_name}/database/{database_uuid}/backups/{backup_uuid}', [DatabaseController::class, 'executions'])->name('project.database.backups.executions'); }); Route::middleware(['auth'])->group(function () { Route::get('/servers', fn () => view('server.all', [ 'servers' => Server::ownedByCurrentTeam()->get() ]))->name('server.all'); - Route::get('/server/new', fn () => view('server.create', [ - 'private_keys' => PrivateKey::ownedByCurrentTeam()->get(), - ]))->name('server.create'); + Route::get('/server/new', [ServerController::class, 'new_server'])->name('server.create'); Route::get('/server/{server_uuid}', fn () => view('server.show', [ - 'server' => Server::ownedByCurrentTeam(['name', 'description', 'ip', 'port', 'user'])->whereUuid(request()->server_uuid)->firstOrFail(), + 'server' => Server::ownedByCurrentTeam(['name', 'description', 'ip', 'port', 'user', 'proxy'])->whereUuid(request()->server_uuid)->firstOrFail(), ]))->name('server.show'); Route::get('/server/{server_uuid}/proxy', fn () => view('server.proxy', [ 'server' => Server::ownedByCurrentTeam(['name', 'proxy'])->whereUuid(request()->server_uuid)->firstOrFail(), ]))->name('server.proxy'); Route::get('/server/{server_uuid}/private-key', fn () => view('server.private-key', [ 'server' => Server::ownedByCurrentTeam()->whereUuid(request()->server_uuid)->firstOrFail(), - 'privateKeys' => PrivateKey::ownedByCurrentTeam()->where('id', '!=', 0)->get(), + 'privateKeys' => PrivateKey::ownedByCurrentTeam()->get(), ]))->name('server.private-key'); Route::get('/server/{server_uuid}/destinations', fn () => view('server.destinations', [ - 'server' => Server::ownedByCurrentTeam(['name'])->whereUuid(request()->server_uuid)->firstOrFail() + 'server' => Server::ownedByCurrentTeam(['name', 'proxy'])->whereUuid(request()->server_uuid)->firstOrFail() ]))->name('server.destinations'); }); Route::middleware(['auth'])->group(function () { Route::get('/', [Controller::class, 'dashboard'])->name('dashboard'); + Route::middleware(['throttle:force-password-reset'])->group(function() { + Route::get('/force-password-reset', [Controller::class, 'force_passoword_reset'])->name('auth.force-password-reset'); + }); Route::get('/subscription', [Controller::class, 'subscription'])->name('subscription'); Route::get('/settings', [Controller::class, 'settings'])->name('settings.configuration'); - Route::get('/settings/emails', [Controller::class, 'emails'])->name('settings.emails'); Route::get('/settings/license', [Controller::class, 'license'])->name('settings.license'); Route::get('/profile', fn () => view('profile', ['request' => request()]))->name('profile'); Route::get('/team', [Controller::class, 'team'])->name('team.show'); Route::get('/team/new', fn () => view('team.create'))->name('team.create'); Route::get('/team/notifications', fn () => view('team.notifications'))->name('team.notifications'); + Route::get('/team/storages', [Controller::class, 'storages'])->name('team.storages.all'); + Route::get('/team/storages/new', fn () => view('team.storages.create'))->name('team.storages.new'); + Route::get('/team/storages/{storage_uuid}', [Controller::class, 'storages_show'])->name('team.storages.show'); Route::get('/team/members', [Controller::class, 'members'])->name('team.members'); Route::get('/command-center', fn () => view('command-center', ['servers' => Server::isReachable()->get()]))->name('command-center'); Route::get('/invitations/{uuid}', [Controller::class, 'acceptInvitation'])->name('team.invitation.accept'); @@ -112,7 +126,7 @@ Route::middleware(['auth'])->group(function () { Route::get('/source/new', fn () => view('source.new'))->name('source.new'); Route::get('/sources', function () { - $sources = session('currentTeam')->sources(); + $sources = auth()->user()->currentTeam()->sources(); return view('source.all', [ 'sources' => $sources, ]); diff --git a/routes/webhooks.php b/routes/webhooks.php index 21a81d70e..0739c1c38 100644 --- a/routes/webhooks.php +++ b/routes/webhooks.php @@ -2,12 +2,12 @@ use App\Models\Application; use App\Models\ApplicationPreview; -use App\Models\PrivateKey; use App\Models\GithubApp; -use App\Models\Webhook; -use App\Models\User; -use App\Models\Team; +use App\Models\PrivateKey; use App\Models\Subscription; +use App\Models\Team; +use App\Models\Waitlist; +use App\Models\Webhook; use Illuminate\Support\Facades\Http; use Illuminate\Support\Facades\Route; use Illuminate\Support\Str; @@ -40,7 +40,7 @@ $github_app->private_key_id = $private_key->id; $github_app->save(); return redirect()->route('source.github.show', ['github_app_uuid' => $github_app->uuid]); - } catch (\Exception $e) { + } catch (Exception $e) { return general_error_handler(err: $e); } }); @@ -56,7 +56,7 @@ $github_app->save(); } return redirect()->route('source.github.show', ['github_app_uuid' => $github_app->uuid]); - } catch (\Exception $e) { + } catch (Exception $e) { return general_error_handler(err: $e); } }); @@ -140,7 +140,7 @@ ApplicationPreview::create([ 'application_id' => $application->id, 'pull_request_id' => $pull_request_id, - 'pull_request_html_url' => $pull_request_html_url + 'pull_request_html_url' => $pull_request_html_url, ]); } queue_application_deployment( @@ -170,18 +170,49 @@ } } } - } catch (\Exception $e) { + } catch (Exception $e) { return general_error_handler(err: $e); } }); -if (isCloud()) { +if (is_cloud()) { + Route::get('/waitlist/confirm', function () { + $email = request()->get('email'); + $confirmation_code = request()->get('confirmation_code'); + ray($email, $confirmation_code); + try { + $found = Waitlist::where('uuid', $confirmation_code)->where('email', $email)->first(); + if ($found && !$found->verified && $found->created_at > now()->subMinutes(config('constants.waitlist.confirmation_valid_for_minutes'))) { + $found->verified = true; + $found->save(); + return 'Thank you for confirming your email address. We will notify you when you are next in line.'; + } + return redirect()->route('dashboard'); + } catch (error) { + return redirect()->route('dashboard'); + } + + })->name('webhooks.waitlist.confirm'); + Route::get('/waitlist/cancel', function () { + $email = request()->get('email'); + $confirmation_code = request()->get('confirmation_code'); + try { + $found = Waitlist::where('uuid', $confirmation_code)->where('email', $email)->first(); + if ($found && !$found->verified) { + $found->delete(); + return 'Your email address has been removed from the waitlist.'; + } + return redirect()->route('dashboard'); + } catch (error) { + return redirect()->route('dashboard'); + } + + })->name('webhooks.waitlist.cancel'); Route::post('/payments/events', function () { try { - - $secret = config('coolify.lemon_squeezy_webhook_secret'); - $payload = request()->collect(); - $hash = hash_hmac('sha256', $payload, $secret); + $secret = config('coolify.lemon_squeezy_webhook_secret'); + $payload = request()->collect(); + $hash = hash_hmac('sha256', $payload, $secret); $signature = request()->header('X-Signature'); if (!hash_equals($hash, $signature)) { @@ -190,14 +221,14 @@ $webhook = Webhook::create([ 'type' => 'lemonsqueezy', - 'payload' => $payload + 'payload' => $payload, ]); $event = data_get($payload, 'meta.event_name'); ray('Subscription event: ' . $event); $email = data_get($payload, 'data.attributes.user_email'); $team_id = data_get($payload, 'meta.custom_data.team_id'); if (is_null($team_id) || empty($team_id)) { - throw new \Exception('No team_id found in webhook payload.'); + throw new Exception('No team_id found in webhook payload.'); } $subscription_id = data_get($payload, 'data.id'); $order_id = data_get($payload, 'data.attributes.order_id'); @@ -213,7 +244,7 @@ $team = Team::find($team_id); $found = $team->members->where('email', $email)->first(); if (!$found->isAdmin()) { - throw new \Exception("User {$email} is not an admin or owner of team {$team->id}."); + throw new Exception("User {$email} is not an admin or owner of team {$team->id}."); } switch ($event) { case 'subscription_created': @@ -254,11 +285,11 @@ $webhook->update([ 'status' => 'success', ]); - } catch (\Exception $e) { + } catch (Exception $e) { ray($e->getMessage()); $webhook->update([ 'status' => 'failed', - 'failure_reason' => $e->getMessage() + 'failure_reason' => $e->getMessage(), ]); } finally { return response('OK'); diff --git a/scripts/install.sh b/scripts/install.sh index 530bc4ea6..5c3014353 100644 --- a/scripts/install.sh +++ b/scripts/install.sh @@ -12,7 +12,7 @@ CDN="https://cdn.coollabs.io/coolify" OS_TYPE=$(cat /etc/os-release | grep -w "ID" | cut -d "=" -f 2 | tr -d '"') OS_VERSION=$(cat /etc/os-release | grep -w "VERSION_ID" | cut -d "=" -f 2 | tr -d '"') LATEST_VERSION=$(curl --silent $CDN/versions.json | grep -i version | sed -n '2p' | xargs | awk '{print $2}' | tr -d ',') -DATE=$(date +"%Y%m%d-%H%M%S") +DATE=$(date +"%Y%m%d-%H%M%S") if [ $EUID != 0 ]; then echo "Please run as root" @@ -84,7 +84,6 @@ fi echo -e "-------------" -mkdir -p /data/coolify/deployments mkdir -p /data/coolify/ssh/keys mkdir -p /data/coolify/ssh/mux mkdir -p /data/coolify/source diff --git a/scripts/run b/scripts/run index 5535014f1..2723f55c6 100755 --- a/scripts/run +++ b/scripts/run @@ -36,7 +36,7 @@ function queue { } function horizon { - bash spin exec -u webuser coolify php artisan horizon -vvv + bash spin exec -u webuser coolify php artisan horizon -vvv } function schedule { @@ -92,8 +92,8 @@ function tinker { } -function build:builder { - act -W .github/workflows/coolify-builder.yml --secret-file .env.secrets +function build:helper { + act -W .github/workflows/coolify-helper.yml --secret-file .env.secrets } function default { help diff --git a/scripts/upgrade.sh b/scripts/upgrade.sh index 97874c20c..01405e873 100644 --- a/scripts/upgrade.sh +++ b/scripts/upgrade.sh @@ -15,4 +15,4 @@ curl -fsSL $CDN/.env.production -o /data/coolify/source/.env.production # Merge .env and .env.production. New values will be added to .env sort -u -t '=' -k 1,1 /data/coolify/source/.env /data/coolify/source/.env.production | sed '/^$/d' > /data/coolify/source/.env.temp && mv /data/coolify/source/.env.temp /data/coolify/source/.env -docker run --pull always -v /data/coolify/source:/data/coolify/source -v /var/run/docker.sock:/var/run/docker.sock --rm ghcr.io/coollabsio/coolify-builder bash -c "LATEST_IMAGE=${1:-} docker compose --env-file /data/coolify/source/.env -f /data/coolify/source/docker-compose.yml -f /data/coolify/source/docker-compose.prod.yml up -d --pull always --remove-orphans --force-recreate" \ No newline at end of file +docker run --pull always -v /data/coolify/source:/data/coolify/source -v /var/run/docker.sock:/var/run/docker.sock --rm ghcr.io/coollabsio/coolify-helper bash -c "LATEST_IMAGE=${1:-} docker compose --env-file /data/coolify/source/.env -f /data/coolify/source/docker-compose.yml -f /data/coolify/source/docker-compose.prod.yml up -d --pull always --remove-orphans --force-recreate" diff --git a/tailwind.config.js b/tailwind.config.js index 6219d41a2..274f50773 100644 --- a/tailwind.config.js +++ b/tailwind.config.js @@ -39,7 +39,8 @@ module.exports = { themes: [ { coollabs: { - primary: "#6B16ED", + primary: "#323232", + "primary-focus": "#242424", secondary: "#4338ca", accent: "#4338ca", neutral: "#1B1D1D", diff --git a/tests/Browser/ExampleTest.php b/tests/Browser/ExampleTest.php index d5ad6b63b..15dc8f5f1 100644 --- a/tests/Browser/ExampleTest.php +++ b/tests/Browser/ExampleTest.php @@ -2,7 +2,6 @@ namespace Tests\Browser; -use Illuminate\Foundation\Testing\DatabaseMigrations; use Laravel\Dusk\Browser; use Tests\DuskTestCase; @@ -15,7 +14,7 @@ public function testBasicExample(): void { $this->browse(function (Browser $browser) { $browser->visit('/') - ->assertSee('Laravel'); + ->assertSee('Laravel'); }); } } diff --git a/tests/CreatesApplication.php b/tests/CreatesApplication.php index cc6830112..e3ad27ecf 100644 --- a/tests/CreatesApplication.php +++ b/tests/CreatesApplication.php @@ -12,7 +12,7 @@ trait CreatesApplication */ public function createApplication(): Application { - $app = require __DIR__.'/../bootstrap/app.php'; + $app = require __DIR__ . '/../bootstrap/app.php'; $app->make(Kernel::class)->bootstrap(); diff --git a/tests/DuskTestCase.php b/tests/DuskTestCase.php index c78c37e3d..04827e5e9 100644 --- a/tests/DuskTestCase.php +++ b/tests/DuskTestCase.php @@ -2,10 +2,10 @@ namespace Tests; -use Illuminate\Support\Collection; use Facebook\WebDriver\Chrome\ChromeOptions; use Facebook\WebDriver\Remote\DesiredCapabilities; use Facebook\WebDriver\Remote\RemoteWebDriver; +use Illuminate\Support\Collection; use Laravel\Dusk\TestCase as BaseTestCase; abstract class DuskTestCase extends BaseTestCase @@ -19,7 +19,7 @@ abstract class DuskTestCase extends BaseTestCase */ public static function prepare(): void { - if (! static::runningInSail()) { + if (!static::runningInSail()) { static::startChromeDriver(); } } @@ -41,31 +41,32 @@ protected function driver(): RemoteWebDriver return RemoteWebDriver::create( $_ENV['DUSK_DRIVER_URL'] ?? 'http://localhost:9515', DesiredCapabilities::chrome()->setCapability( - ChromeOptions::CAPABILITY, $options + ChromeOptions::CAPABILITY, + $options ) ); } - protected function baseUrl() - { - return rtrim(config('app.url'), '/'); - } - - /** - * Determine whether the Dusk command has disabled headless mode. - */ - protected function hasHeadlessDisabled(): bool - { - return isset($_SERVER['DUSK_HEADLESS_DISABLED']) || - isset($_ENV['DUSK_HEADLESS_DISABLED']); - } - /** * Determine if the browser window should start maximized. */ protected function shouldStartMaximized(): bool { return isset($_SERVER['DUSK_START_MAXIMIZED']) || - isset($_ENV['DUSK_START_MAXIMIZED']); + isset($_ENV['DUSK_START_MAXIMIZED']); + } + + /** + * Determine whether the Dusk command has disabled headless mode. + */ + protected function hasHeadlessDisabled(): bool + { + return isset($_SERVER['DUSK_HEADLESS_DISABLED']) || + isset($_ENV['DUSK_HEADLESS_DISABLED']); + } + + protected function baseUrl() + { + return rtrim(config('app.url'), '/'); } } diff --git a/tests/Feature/DockerCommandsTest.php b/tests/Feature/DockerCommandsTest.php index 202c276aa..77e9d77e1 100644 --- a/tests/Feature/DockerCommandsTest.php +++ b/tests/Feature/DockerCommandsTest.php @@ -2,8 +2,8 @@ use App\Actions\CoolifyTask\RunRemoteProcess; use App\Actions\CoolifyTask\TidyOutput; -use App\Models\User; use App\Models\Server; +use App\Models\User; use Database\Seeders\DatabaseSeeder; use Illuminate\Foundation\Testing\DatabaseMigrations; use Illuminate\Foundation\Testing\RefreshDatabase; diff --git a/versions.json b/versions.json index 0e4f87627..25d09437e 100644 --- a/versions.json +++ b/versions.json @@ -1,10 +1,10 @@ { - "coolify": { - "main": { - "version": "3.12.32" - }, - "v4": { - "version": "4.0.0-beta.18" - } + "coolify": { + "main": { + "version": "3.12.36" + }, + "v4": { + "version": "4.0.0-beta.18" } -} \ No newline at end of file + } +}