Merge branch 'next' into oauth
This commit is contained in:
		@@ -32,8 +32,6 @@ class DatabaseBackupJob implements ShouldBeEncrypted, ShouldQueue
 | 
			
		||||
 | 
			
		||||
    public Server $server;
 | 
			
		||||
 | 
			
		||||
    public ScheduledDatabaseBackup $backup;
 | 
			
		||||
 | 
			
		||||
    public StandalonePostgresql|StandaloneMongodb|StandaloneMysql|StandaloneMariadb|ServiceDatabase $database;
 | 
			
		||||
 | 
			
		||||
    public ?string $container_name = null;
 | 
			
		||||
@@ -58,10 +56,9 @@ class DatabaseBackupJob implements ShouldBeEncrypted, ShouldQueue
 | 
			
		||||
 | 
			
		||||
    public ?S3Storage $s3 = null;
 | 
			
		||||
 | 
			
		||||
    public function __construct($backup)
 | 
			
		||||
    public function __construct(public ScheduledDatabaseBackup $backup)
 | 
			
		||||
    {
 | 
			
		||||
        $this->onQueue('high');
 | 
			
		||||
        $this->backup = $backup;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function handle(): void
 | 
			
		||||
 
 | 
			
		||||
@@ -21,16 +21,28 @@ class Index extends Component
 | 
			
		||||
 | 
			
		||||
    public function mount()
 | 
			
		||||
    {
 | 
			
		||||
        if (! isCloud()) {
 | 
			
		||||
        if (! isCloud() && ! isDev()) {
 | 
			
		||||
            return redirect()->route('dashboard');
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if (Auth::id() !== 0) {
 | 
			
		||||
        if (Auth::id() !== 0 && ! session('impersonating')) {
 | 
			
		||||
            return redirect()->route('dashboard');
 | 
			
		||||
        }
 | 
			
		||||
        $this->getSubscribers();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function back()
 | 
			
		||||
    {
 | 
			
		||||
        if (session('impersonating')) {
 | 
			
		||||
            session()->forget('impersonating');
 | 
			
		||||
            $user = User::find(0);
 | 
			
		||||
            $team_to_switch_to = $user->teams->first();
 | 
			
		||||
            Auth::login($user);
 | 
			
		||||
            refreshSession($team_to_switch_to);
 | 
			
		||||
 | 
			
		||||
            return redirect(request()->header('Referer'));
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function submitSearch()
 | 
			
		||||
    {
 | 
			
		||||
        if ($this->search !== '') {
 | 
			
		||||
@@ -52,9 +64,10 @@ class Index extends Component
 | 
			
		||||
        if (Auth::id() !== 0) {
 | 
			
		||||
            return redirect()->route('dashboard');
 | 
			
		||||
        }
 | 
			
		||||
        session(['impersonating' => true]);
 | 
			
		||||
        $user = User::find($user_id);
 | 
			
		||||
        $team_to_switch_to = $user->teams->first();
 | 
			
		||||
        Cache::forget("team:{$user->id}");
 | 
			
		||||
        // Cache::forget("team:{$user->id}");
 | 
			
		||||
        Auth::login($user);
 | 
			
		||||
        refreshSession($team_to_switch_to);
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -8,14 +8,21 @@ use Livewire\Component;
 | 
			
		||||
 | 
			
		||||
class Configuration extends Component
 | 
			
		||||
{
 | 
			
		||||
    public $currentRoute;
 | 
			
		||||
 | 
			
		||||
    public Application $application;
 | 
			
		||||
 | 
			
		||||
    public $project;
 | 
			
		||||
 | 
			
		||||
    public $environment;
 | 
			
		||||
 | 
			
		||||
    public $servers;
 | 
			
		||||
 | 
			
		||||
    protected $listeners = ['buildPackUpdated' => '$refresh'];
 | 
			
		||||
 | 
			
		||||
    public function mount()
 | 
			
		||||
    {
 | 
			
		||||
        $this->currentRoute = request()->route()->getName();
 | 
			
		||||
        $project = currentTeam()
 | 
			
		||||
            ->projects()
 | 
			
		||||
            ->select('id', 'uuid', 'team_id')
 | 
			
		||||
@@ -30,6 +37,8 @@ class Configuration extends Component
 | 
			
		||||
            ->where('uuid', request()->route('application_uuid'))
 | 
			
		||||
            ->firstOrFail();
 | 
			
		||||
 | 
			
		||||
        $this->project = $project;
 | 
			
		||||
        $this->environment = $environment;
 | 
			
		||||
        $this->application = $application;
 | 
			
		||||
        if ($application->destination && $application->destination->server) {
 | 
			
		||||
            $mainServer = $application->destination->server;
 | 
			
		||||
 
 | 
			
		||||
@@ -327,7 +327,7 @@ class General extends Component
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function set_redirect()
 | 
			
		||||
    public function setRedirect()
 | 
			
		||||
    {
 | 
			
		||||
        try {
 | 
			
		||||
            $has_www = collect($this->application->fqdns)->filter(fn ($fqdn) => str($fqdn)->contains('www.'))->count();
 | 
			
		||||
@@ -360,10 +360,10 @@ class General extends Component
 | 
			
		||||
            if ($warning) {
 | 
			
		||||
                $this->dispatch('warning', __('warning.sslipdomain'));
 | 
			
		||||
            }
 | 
			
		||||
            $this->resetDefaultLabels();
 | 
			
		||||
            // $this->resetDefaultLabels();
 | 
			
		||||
 | 
			
		||||
            if ($this->application->isDirty('redirect')) {
 | 
			
		||||
                $this->set_redirect();
 | 
			
		||||
                $this->setRedirect();
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            $this->checkFqdns();
 | 
			
		||||
 
 | 
			
		||||
@@ -9,11 +9,9 @@ class BackupNow extends Component
 | 
			
		||||
{
 | 
			
		||||
    public $backup;
 | 
			
		||||
 | 
			
		||||
    public function backup_now()
 | 
			
		||||
    public function backupNow()
 | 
			
		||||
    {
 | 
			
		||||
        dispatch(new DatabaseBackupJob(
 | 
			
		||||
            backup: $this->backup
 | 
			
		||||
        ));
 | 
			
		||||
        DatabaseBackupJob::dispatch($this->backup);
 | 
			
		||||
        $this->dispatch('success', 'Backup queued. It will be available in a few minutes.');
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -5,7 +5,6 @@ namespace App\Livewire\Project\Shared\EnvironmentVariable;
 | 
			
		||||
use App\Models\EnvironmentVariable as ModelsEnvironmentVariable;
 | 
			
		||||
use App\Models\SharedEnvironmentVariable;
 | 
			
		||||
use Livewire\Component;
 | 
			
		||||
use Visus\Cuid2\Cuid2;
 | 
			
		||||
 | 
			
		||||
class Show extends Component
 | 
			
		||||
{
 | 
			
		||||
@@ -13,8 +12,6 @@ class Show extends Component
 | 
			
		||||
 | 
			
		||||
    public ModelsEnvironmentVariable|SharedEnvironmentVariable $env;
 | 
			
		||||
 | 
			
		||||
    public ?string $modalId = null;
 | 
			
		||||
 | 
			
		||||
    public bool $isDisabled = false;
 | 
			
		||||
 | 
			
		||||
    public bool $isLocked = false;
 | 
			
		||||
@@ -61,7 +58,6 @@ class Show extends Component
 | 
			
		||||
        if ($this->env->getMorphClass() === \App\Models\SharedEnvironmentVariable::class) {
 | 
			
		||||
            $this->isSharedVariable = true;
 | 
			
		||||
        }
 | 
			
		||||
        $this->modalId = new Cuid2;
 | 
			
		||||
        $this->parameters = get_route_parameters();
 | 
			
		||||
        $this->checkEnvs();
 | 
			
		||||
    }
 | 
			
		||||
 
 | 
			
		||||
@@ -42,9 +42,11 @@ class ResourceOperations extends Component
 | 
			
		||||
        $uuid = (string) new Cuid2;
 | 
			
		||||
        $server = $new_destination->server;
 | 
			
		||||
        if ($this->resource->getMorphClass() === \App\Models\Application::class) {
 | 
			
		||||
            $name = 'clone-of-'.str($this->resource->name)->limit(20).'-'.$uuid;
 | 
			
		||||
 | 
			
		||||
            $new_resource = $this->resource->replicate()->fill([
 | 
			
		||||
                'uuid' => $uuid,
 | 
			
		||||
                'name' => $this->resource->name.'-clone-'.$uuid,
 | 
			
		||||
                'name' => $name,
 | 
			
		||||
                'fqdn' => generateFqdn($server, $uuid),
 | 
			
		||||
                'status' => 'exited',
 | 
			
		||||
                'destination_id' => $new_destination->id,
 | 
			
		||||
@@ -64,8 +66,12 @@ class ResourceOperations extends Component
 | 
			
		||||
            }
 | 
			
		||||
            $persistentVolumes = $this->resource->persistentStorages()->get();
 | 
			
		||||
            foreach ($persistentVolumes as $volume) {
 | 
			
		||||
                $volumeName = str($volume->name)->replace($this->resource->uuid, $new_resource->uuid)->value();
 | 
			
		||||
                if ($volumeName === $volume->name) {
 | 
			
		||||
                    $volumeName = $new_resource->uuid.'-'.str($volume->name)->afterLast('-');
 | 
			
		||||
                }
 | 
			
		||||
                $newPersistentVolume = $volume->replicate()->fill([
 | 
			
		||||
                    'name' => $new_resource->uuid.'-'.str($volume->name)->afterLast('-'),
 | 
			
		||||
                    'name' => $volumeName,
 | 
			
		||||
                    'resource_id' => $new_resource->id,
 | 
			
		||||
                ]);
 | 
			
		||||
                $newPersistentVolume->save();
 | 
			
		||||
 
 | 
			
		||||
@@ -69,6 +69,7 @@ function allowedPathsForUnsubscribedAccounts()
 | 
			
		||||
        'logout',
 | 
			
		||||
        'force-password-reset',
 | 
			
		||||
        'livewire/update',
 | 
			
		||||
        'admin',
 | 
			
		||||
    ];
 | 
			
		||||
}
 | 
			
		||||
function allowedPathsForBoardingAccounts()
 | 
			
		||||
 
 | 
			
		||||
@@ -2,7 +2,7 @@
 | 
			
		||||
 | 
			
		||||
return [
 | 
			
		||||
    'coolify' => [
 | 
			
		||||
        'version' => '4.0.0-beta.379',
 | 
			
		||||
        'version' => '4.0.0-beta.380',
 | 
			
		||||
        'self_hosted' => env('SELF_HOSTED', true),
 | 
			
		||||
        'autoupdate' => env('AUTOUPDATE'),
 | 
			
		||||
        'base_config_path' => env('BASE_CONFIG_PATH', '/data/coolify'),
 | 
			
		||||
 
 | 
			
		||||
@@ -4,6 +4,7 @@ namespace Database\Seeders;
 | 
			
		||||
 | 
			
		||||
use App\Models\OauthSetting;
 | 
			
		||||
use Illuminate\Database\Seeder;
 | 
			
		||||
use Illuminate\Support\Facades\Log;
 | 
			
		||||
 | 
			
		||||
class OauthSettingSeeder extends Seeder
 | 
			
		||||
{
 | 
			
		||||
@@ -12,63 +13,54 @@ class OauthSettingSeeder extends Seeder
 | 
			
		||||
     */
 | 
			
		||||
    public function run(): void
 | 
			
		||||
    {
 | 
			
		||||
        $providers = collect([
 | 
			
		||||
            'azure',
 | 
			
		||||
            'bitbucket',
 | 
			
		||||
            'github',
 | 
			
		||||
            'gitlab',
 | 
			
		||||
            'google',
 | 
			
		||||
            'authentik',
 | 
			
		||||
            'infomaniak',
 | 
			
		||||
        ]);
 | 
			
		||||
        try {
 | 
			
		||||
            $providers = collect([
 | 
			
		||||
                'azure',
 | 
			
		||||
                'bitbucket',
 | 
			
		||||
                'github',
 | 
			
		||||
                'gitlab',
 | 
			
		||||
                'google',
 | 
			
		||||
                'authentik',
 | 
			
		||||
                'infomaniak',
 | 
			
		||||
            ])
 | 
			
		||||
 | 
			
		||||
        $isOauthSeeded = OauthSetting::count() > 0;
 | 
			
		||||
            $isOauthSeeded = OauthSetting::count() > 0;
 | 
			
		||||
 | 
			
		||||
        // We changed how providers are defined in the database, so we authentik does not exists, we need to recreate all of the auth providers
 | 
			
		||||
        // Before authentik was a provider, providers started with 0 id
 | 
			
		||||
            // We changed how providers are defined in the database, so we authentik does not exists, we need to recreate all of the auth providers
 | 
			
		||||
            // Before authentik was a provider, providers started with 0 id
 | 
			
		||||
 | 
			
		||||
        $isOauthAuthentik = OauthSetting::where('provider', 'authentik')->exists();
 | 
			
		||||
        if ($isOauthSeeded) {
 | 
			
		||||
            if (! $isOauthAuthentik) {
 | 
			
		||||
                $allProviders = OauthSetting::all();
 | 
			
		||||
                $notFoundProviders = $providers->diff($allProviders->pluck('provider'));
 | 
			
		||||
 | 
			
		||||
                $allProviders->each(function ($provider) {
 | 
			
		||||
                    $provider->delete();
 | 
			
		||||
                });
 | 
			
		||||
                $allProviders->each(function ($provider) use ($providers) {
 | 
			
		||||
                    $providerName = $provider->provider;
 | 
			
		||||
 | 
			
		||||
                    $foundProvider = $providers->first(function ($provider) use ($providerName) {
 | 
			
		||||
                        return $provider === $providerName;
 | 
			
		||||
                    });
 | 
			
		||||
 | 
			
		||||
                    if ($foundProvider) {
 | 
			
		||||
                        $newProvder = new OauthSetting;
 | 
			
		||||
                        $newProvder = $provider;
 | 
			
		||||
                        unset($newProvder->id);
 | 
			
		||||
                        $newProvder->save();
 | 
			
		||||
                    }
 | 
			
		||||
                });
 | 
			
		||||
 | 
			
		||||
                foreach ($notFoundProviders as $provider) {
 | 
			
		||||
                    OauthSetting::create([
 | 
			
		||||
                        'provider' => $provider,
 | 
			
		||||
                    ]);
 | 
			
		||||
                }
 | 
			
		||||
            } else {
 | 
			
		||||
            $isOauthAuthentik = OauthSetting::where('provider', 'authentik')->exists();
 | 
			
		||||
            if (! $isOauthSeeded || $isOauthAuthentik) {
 | 
			
		||||
                foreach ($providers as $provider) {
 | 
			
		||||
                    OauthSetting::updateOrCreate([
 | 
			
		||||
                        'provider' => $provider,
 | 
			
		||||
                    ]);
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                return;
 | 
			
		||||
            }
 | 
			
		||||
        } else {
 | 
			
		||||
            foreach ($providers as $provider) {
 | 
			
		||||
                OauthSetting::updateOrCreate([
 | 
			
		||||
 | 
			
		||||
            $allProviders = OauthSetting::all();
 | 
			
		||||
            $notFoundProviders = $providers->diff($allProviders->pluck('provider'));
 | 
			
		||||
 | 
			
		||||
            $allProviders->each(function ($provider) {
 | 
			
		||||
                $provider->delete();
 | 
			
		||||
            });
 | 
			
		||||
            $allProviders->each(function ($provider) {
 | 
			
		||||
                $provider = new OauthSetting;
 | 
			
		||||
                $provider->provider = $provider->provider;
 | 
			
		||||
                unset($provider->id);
 | 
			
		||||
                $provider->save();
 | 
			
		||||
            });
 | 
			
		||||
 | 
			
		||||
            foreach ($notFoundProviders as $provider) {
 | 
			
		||||
                OauthSetting::create([
 | 
			
		||||
                    'provider' => $provider,
 | 
			
		||||
                ]);
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
        } catch (\Exception $e) {
 | 
			
		||||
            Log::error($e->getMessage());
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -39,7 +39,7 @@ location ~ \.php$ {
 | 
			
		||||
    fastcgi_index  index.php;
 | 
			
		||||
    fastcgi_param  SCRIPT_FILENAME  $document_root$fastcgi_script_name;
 | 
			
		||||
    include        fastcgi_params;
 | 
			
		||||
    fastcgi_buffers 8 8k;
 | 
			
		||||
    fastcgi_buffer_size 8k;
 | 
			
		||||
    fastcgi_buffers 16 16k;
 | 
			
		||||
    fastcgi_buffer_size 32k;
 | 
			
		||||
    fastcgi_read_timeout 99;
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -99,6 +99,9 @@ RUN mkdir -p /usr/local/bin && \
 | 
			
		||||
COPY docker/production/etc/php/conf.d/zzz-custom-php.ini /usr/local/etc/php/conf.d/zzz-custom-php.ini
 | 
			
		||||
ENV PHP_OPCACHE_ENABLE=1
 | 
			
		||||
 | 
			
		||||
# Configure entrypoint
 | 
			
		||||
COPY --chmod=755 docker/production/entrypoint.d/ /etc/entrypoint.d
 | 
			
		||||
 | 
			
		||||
# Copy application files from previous stages
 | 
			
		||||
COPY --from=base --chown=www-data:www-data /var/www/html/vendor ./vendor
 | 
			
		||||
COPY --from=static-assets --chown=www-data:www-data /app/public/build ./public/build
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										8
									
								
								docker/production/entrypoint.d/99-debug-mode.sh
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										8
									
								
								docker/production/entrypoint.d/99-debug-mode.sh
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,8 @@
 | 
			
		||||
# Debug mode
 | 
			
		||||
if [ "$APP_DEBUG" = "true" ]; then
 | 
			
		||||
    echo "Debug mode is enabled"
 | 
			
		||||
    echo "Installing development dependencies..."
 | 
			
		||||
    composer install --dev --no-scripts
 | 
			
		||||
    echo "Clearing optimized classes..."
 | 
			
		||||
    php artisan optimize:clear
 | 
			
		||||
fi
 | 
			
		||||
@@ -39,7 +39,7 @@ location ~ \.php$ {
 | 
			
		||||
    fastcgi_index  index.php;
 | 
			
		||||
    fastcgi_param  SCRIPT_FILENAME  $document_root$fastcgi_script_name;
 | 
			
		||||
    include        fastcgi_params;
 | 
			
		||||
    fastcgi_buffers 8 8k;
 | 
			
		||||
    fastcgi_buffer_size 8k;
 | 
			
		||||
    fastcgi_buffers 16 16k;
 | 
			
		||||
    fastcgi_buffer_size 32k;
 | 
			
		||||
    fastcgi_read_timeout 99;
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -340,17 +340,19 @@
 | 
			
		||||
                        </li>
 | 
			
		||||
                    @endif
 | 
			
		||||
 | 
			
		||||
                    @if (isCloud() && isInstanceAdmin())
 | 
			
		||||
                        <li>
 | 
			
		||||
                            <a title="Admin" class="menu-item" href="/admin">
 | 
			
		||||
                                <svg class="text-pink-600 icon" viewBox="0 0 256 256"
 | 
			
		||||
                                    xmlns="http://www.w3.org/2000/svg">
 | 
			
		||||
                                    <path fill="currentColor"
 | 
			
		||||
                                        d="M177.62 159.6a52 52 0 0 1-34 34a12.2 12.2 0 0 1-3.6.55a12 12 0 0 1-3.6-23.45a28 28 0 0 0 18.32-18.32a12 12 0 0 1 22.9 7.2ZM220 144a92 92 0 0 1-184 0c0-28.81 11.27-58.18 33.48-87.28a12 12 0 0 1 17.9-1.33l19.69 19.11L127 19.89a12 12 0 0 1 18.94-5.12C168.2 33.25 220 82.85 220 144m-24 0c0-41.71-30.61-78.39-52.52-99.29l-20.21 55.4a12 12 0 0 1-19.63 4.5L80.71 82.36C67 103.38 60 124.06 60 144a68 68 0 0 0 136 0" />
 | 
			
		||||
                                </svg>
 | 
			
		||||
                                Admin
 | 
			
		||||
                            </a>
 | 
			
		||||
                        </li>
 | 
			
		||||
                    @if (isCloud() || isDev())
 | 
			
		||||
                        @if (isInstanceAdmin() || session('impersonating'))
 | 
			
		||||
                            <li>
 | 
			
		||||
                                <a title="Admin" class="menu-item" href="/admin">
 | 
			
		||||
                                    <svg class="text-pink-600 icon" viewBox="0 0 256 256"
 | 
			
		||||
                                        xmlns="http://www.w3.org/2000/svg">
 | 
			
		||||
                                        <path fill="currentColor"
 | 
			
		||||
                                            d="M177.62 159.6a52 52 0 0 1-34 34a12.2 12.2 0 0 1-3.6.55a12 12 0 0 1-3.6-23.45a28 28 0 0 0 18.32-18.32a12 12 0 0 1 22.9 7.2ZM220 144a92 92 0 0 1-184 0c0-28.81 11.27-58.18 33.48-87.28a12 12 0 0 1 17.9-1.33l19.69 19.11L127 19.89a12 12 0 0 1 18.94-5.12C168.2 33.25 220 82.85 220 144m-24 0c0-41.71-30.61-78.39-52.52-99.29l-20.21 55.4a12 12 0 0 1-19.63 4.5L80.71 82.36C67 103.38 60 124.06 60 144a68 68 0 0 0 136 0" />
 | 
			
		||||
                                    </svg>
 | 
			
		||||
                                    Admin
 | 
			
		||||
                                </a>
 | 
			
		||||
                            </li>
 | 
			
		||||
                        @endif
 | 
			
		||||
                    @endif
 | 
			
		||||
                    <div class="flex-1"></div>
 | 
			
		||||
                    @if (isInstanceAdmin() && !isCloud())
 | 
			
		||||
 
 | 
			
		||||
@@ -1,7 +1,12 @@
 | 
			
		||||
<div>
 | 
			
		||||
    <h1>Admin Dashboard</h1>
 | 
			
		||||
    <h3 class="pt-4">Who am I now?</h3>
 | 
			
		||||
    <div class="pb-4">{{ auth()->user()->name }}</div>
 | 
			
		||||
    <div class="flex gap-2 pt-4">
 | 
			
		||||
        <h3>Who am I now?</h3>
 | 
			
		||||
        @if (session('impersonating'))
 | 
			
		||||
            <x-forms.button wire:click="back">Go back to root</x-forms.button>
 | 
			
		||||
        @endif
 | 
			
		||||
    </div>
 | 
			
		||||
    <div class="pb-4">{{ auth()->user()->name }} ({{ auth()->user()->email }})</div>
 | 
			
		||||
    <form wire:submit="submitSearch" class="flex flex-col gap-2 lg:flex-row">
 | 
			
		||||
        <x-forms.input wire:model="search" placeholder="Search for a user" />
 | 
			
		||||
        <x-forms.button type="submit">Search</x-forms.button>
 | 
			
		||||
 
 | 
			
		||||
@@ -5,34 +5,36 @@
 | 
			
		||||
    <h1>Configuration</h1>
 | 
			
		||||
    <livewire:project.shared.configuration-checker :resource="$application" />
 | 
			
		||||
    <livewire:project.application.heading :application="$application" />
 | 
			
		||||
    <div x-data="{ activeTab: window.location.hash ? window.location.hash.substring(1) : 'general' }" class="flex flex-col h-full gap-8 pt-6 sm:flex-row">
 | 
			
		||||
 | 
			
		||||
    <div class="flex flex-col h-full gap-8 pt-6 sm:flex-row">
 | 
			
		||||
        <div class="flex flex-col items-start gap-2 min-w-fit">
 | 
			
		||||
            <a class="menu-item" :class="activeTab === 'general' && 'menu-item-active'"
 | 
			
		||||
                @click.prevent="activeTab = 'general'; window.location.hash = 'general'" href="#">General</a>
 | 
			
		||||
            <a class='menu-item' wire:current.exact="menu-item-active"
 | 
			
		||||
                href="{{ route('project.application.configuration', ['project_uuid' => $project->uuid, 'environment_name' => $environment->name, 'application_uuid' => $application->uuid]) }}"
 | 
			
		||||
                wire:navigate>General</a>
 | 
			
		||||
            <a class='menu-item' wire:current.exact="menu-item-active"
 | 
			
		||||
                href="{{ route('project.application.advanced', ['project_uuid' => $project->uuid, 'environment_name' => $environment->name, 'application_uuid' => $application->uuid]) }}"
 | 
			
		||||
                wire:navigate>Advanced</a>
 | 
			
		||||
            @if ($application->destination->server->isSwarm())
 | 
			
		||||
                <a class="menu-item" :class="activeTab === 'swarm' && 'menu-item-active'"
 | 
			
		||||
                    @click.prevent="activeTab = 'swarm'; window.location.hash = 'swarm'" href="#">Swarm
 | 
			
		||||
                <a class="menu-item"
 | 
			
		||||
                    wire:current.exact="menu-item-active
 | 
			
		||||
                    href="{{ route('project.application.swarm', ['project_uuid' => $project->uuid, 'environment_name' => $environment->name, 'application_uuid' => $application->uuid]) }}"
 | 
			
		||||
                    wire:navigate>Swarm
 | 
			
		||||
                    Configuration</a>
 | 
			
		||||
            @endif
 | 
			
		||||
            <a class="menu-item" :class="activeTab === 'advanced' && 'menu-item-active'"
 | 
			
		||||
                @click.prevent="activeTab = 'advanced'; window.location.hash = 'advanced'" href="#">Advanced</a>
 | 
			
		||||
            @if ($application->build_pack !== 'static')
 | 
			
		||||
                <a class="menu-item" :class="activeTab === 'environment-variables' && 'menu-item-active'"
 | 
			
		||||
                    @click.prevent="activeTab = 'environment-variables'; window.location.hash = 'environment-variables'"
 | 
			
		||||
                    href="#">Environment
 | 
			
		||||
                    Variables</a>
 | 
			
		||||
            @endif
 | 
			
		||||
            @if ($application->build_pack !== 'static')
 | 
			
		||||
                <a class="menu-item" :class="activeTab === 'storages' && 'menu-item-active'"
 | 
			
		||||
                    @click.prevent="activeTab = 'storages'; window.location.hash = 'storages'" href="#">Storages
 | 
			
		||||
                </a>
 | 
			
		||||
            @endif
 | 
			
		||||
            <a class='menu-item' wire:current.exact="menu-item-active"
 | 
			
		||||
                href="{{ route('project.application.environment-variables', ['project_uuid' => $project->uuid, 'environment_name' => $environment->name, 'application_uuid' => $application->uuid]) }}"
 | 
			
		||||
                wire:navigate>Environment Variables</a>
 | 
			
		||||
            <a class='menu-item' wire:current.exact="menu-item-active"
 | 
			
		||||
                href="{{ route('project.application.persistent-storage', ['project_uuid' => $project->uuid, 'environment_name' => $environment->name, 'application_uuid' => $application->uuid]) }}"
 | 
			
		||||
                wire:navigate>Persistent Storage</a>
 | 
			
		||||
            @if ($application->git_based())
 | 
			
		||||
                <a class="menu-item" :class="activeTab === 'source' && 'menu-item-active'"
 | 
			
		||||
                    @click.prevent="activeTab = 'source'; window.location.hash = 'source'" href="#">Source</a>
 | 
			
		||||
                <a class='menu-item' wire:current.exact="menu-item-active"
 | 
			
		||||
                    href="{{ route('project.application.source', ['project_uuid' => $project->uuid, 'environment_name' => $environment->name, 'application_uuid' => $application->uuid]) }}"
 | 
			
		||||
                    wire:navigate>Git Source</a>
 | 
			
		||||
            @endif
 | 
			
		||||
            <a class="menu-item" :class="activeTab === 'servers' && 'menu-item-active'" class="flex items-center gap-2"
 | 
			
		||||
                @click.prevent="activeTab = 'servers'; window.location.hash = 'servers'" href="#">Servers
 | 
			
		||||
            <a class="menu-item flex items-center gap-2" wire:current.exact="menu-item-active"
 | 
			
		||||
                href="{{ route('project.application.servers', ['project_uuid' => $project->uuid, 'environment_name' => $environment->name, 'application_uuid' => $application->uuid]) }}"
 | 
			
		||||
                wire:navigate>Servers
 | 
			
		||||
                @if (str($application->status)->contains('degraded'))
 | 
			
		||||
                    <span title="Some servers are unavailable">
 | 
			
		||||
                        <svg class="w-4 h-4 text-error" viewBox="0 0 256 256" xmlns="http://www.w3.org/2000/svg">
 | 
			
		||||
@@ -50,102 +52,74 @@
 | 
			
		||||
                    </span>
 | 
			
		||||
                @endif
 | 
			
		||||
            </a>
 | 
			
		||||
            <a class="menu-item" :class="activeTab === 'scheduled-tasks' && 'menu-item-active'"
 | 
			
		||||
                @click.prevent="activeTab = 'scheduled-tasks'; window.location.hash = 'scheduled-tasks'"
 | 
			
		||||
                href="#">Scheduled Tasks
 | 
			
		||||
            </a>
 | 
			
		||||
            <a class="menu-item" wire:current.exact="menu-item-active"
 | 
			
		||||
                href="{{ route('project.application.scheduled-tasks.show', ['project_uuid' => $project->uuid, 'environment_name' => $environment->name, 'application_uuid' => $application->uuid]) }}"
 | 
			
		||||
                wire:navigate>Scheduled Tasks</a>
 | 
			
		||||
            <a class="menu-item" wire:current.exact="menu-item-active"
 | 
			
		||||
                href="{{ route('project.application.webhooks', ['project_uuid' => $project->uuid, 'environment_name' => $environment->name, 'application_uuid' => $application->uuid]) }}"
 | 
			
		||||
                wire:navigate>Webhooks</a>
 | 
			
		||||
            <a class="menu-item" wire:current.exact="menu-item-active"
 | 
			
		||||
                href="{{ route('project.application.preview-deployments', ['project_uuid' => $project->uuid, 'environment_name' => $environment->name, 'application_uuid' => $application->uuid]) }}"
 | 
			
		||||
                wire:navigate>Preview Deployments</a>
 | 
			
		||||
            <a class="menu-item" wire:current.exact="menu-item-active"
 | 
			
		||||
                href="{{ route('project.application.healthcheck', ['project_uuid' => $project->uuid, 'environment_name' => $environment->name, 'application_uuid' => $application->uuid]) }}"
 | 
			
		||||
                wire:navigate>Healthcheck</a>
 | 
			
		||||
            <a class="menu-item" wire:current.exact="menu-item-active"
 | 
			
		||||
                href="{{ route('project.application.rollback', ['project_uuid' => $project->uuid, 'environment_name' => $environment->name, 'application_uuid' => $application->uuid]) }}"
 | 
			
		||||
                wire:navigate>Rollback</a>
 | 
			
		||||
            <a class="menu-item" wire:current.exact="menu-item-active"
 | 
			
		||||
                href="{{ route('project.application.resource-limits', ['project_uuid' => $project->uuid, 'environment_name' => $environment->name, 'application_uuid' => $application->uuid]) }}"
 | 
			
		||||
                wire:navigate>Resource Limits</a>
 | 
			
		||||
 | 
			
		||||
            <a class="menu-item" :class="activeTab === 'webhooks' && 'menu-item-active'"
 | 
			
		||||
                @click.prevent="activeTab = 'webhooks'; window.location.hash = 'webhooks'" href="#">Webhooks
 | 
			
		||||
            </a>
 | 
			
		||||
            @if ($application->git_based())
 | 
			
		||||
                <a class="menu-item" :class="activeTab === 'previews' && 'menu-item-active'"
 | 
			
		||||
                    @click.prevent="activeTab = 'previews'; window.location.hash = 'previews'" href="#">Preview
 | 
			
		||||
                    Deployments
 | 
			
		||||
                </a>
 | 
			
		||||
            @endif
 | 
			
		||||
            @if ($application->build_pack !== 'static' && $application->build_pack !== 'dockercompose')
 | 
			
		||||
                <a class="menu-item" :class="activeTab === 'health' && 'menu-item-active'"
 | 
			
		||||
                    @click.prevent="activeTab = 'health'; window.location.hash = 'health'" href="#">Healthchecks
 | 
			
		||||
                </a>
 | 
			
		||||
            @endif
 | 
			
		||||
            <a class="menu-item" :class="activeTab === 'rollback' && 'menu-item-active'"
 | 
			
		||||
                @click.prevent="activeTab = 'rollback'; window.location.hash = 'rollback'" href="#">Rollback
 | 
			
		||||
            </a>
 | 
			
		||||
            @if ($application->build_pack !== 'dockercompose')
 | 
			
		||||
                <a class="menu-item" :class="activeTab === 'resource-limits' && 'menu-item-active'"
 | 
			
		||||
                    @click.prevent="activeTab = 'resource-limits'; window.location.hash = 'resource-limits'"
 | 
			
		||||
                    href="#">Resource Limits
 | 
			
		||||
                </a>
 | 
			
		||||
            @endif
 | 
			
		||||
            <a class="menu-item" :class="activeTab === 'resource-operations' && 'menu-item-active'"
 | 
			
		||||
                @click.prevent="activeTab = 'resource-operations'; window.location.hash = 'resource-operations'"
 | 
			
		||||
                href="#">Resource Operations
 | 
			
		||||
            </a>
 | 
			
		||||
            <a class="menu-item" :class="activeTab === 'metrics' && 'menu-item-active'"
 | 
			
		||||
                @click.prevent="activeTab = 'metrics'; window.location.hash = 'metrics'" href="#">Metrics
 | 
			
		||||
            </a>
 | 
			
		||||
            <a class="menu-item" :class="activeTab === 'tags' && 'menu-item-active'"
 | 
			
		||||
                @click.prevent="activeTab = 'tags'; window.location.hash = 'tags'" href="#">Tags
 | 
			
		||||
            </a>
 | 
			
		||||
            <a class="menu-item" :class="activeTab === 'danger' && 'menu-item-active'"
 | 
			
		||||
                @click.prevent="activeTab = 'danger'; window.location.hash = 'danger'" href="#">Danger Zone
 | 
			
		||||
            </a>
 | 
			
		||||
            <a class="menu-item" wire:current.exact="menu-item-active"
 | 
			
		||||
                href="{{ route('project.application.resource-operations', ['project_uuid' => $project->uuid, 'environment_name' => $environment->name, 'application_uuid' => $application->uuid]) }}"
 | 
			
		||||
                wire:navigate>Resource Operations</a>
 | 
			
		||||
            <a class="menu-item" wire:current.exact="menu-item-active"
 | 
			
		||||
                href="{{ route('project.application.metrics', ['project_uuid' => $project->uuid, 'environment_name' => $environment->name, 'application_uuid' => $application->uuid]) }}"
 | 
			
		||||
                wire:navigate>Metrics</a>
 | 
			
		||||
            <a class="menu-item" wire:current.exact="menu-item-active"
 | 
			
		||||
                href="{{ route('project.application.tags', ['project_uuid' => $project->uuid, 'environment_name' => $environment->name, 'application_uuid' => $application->uuid]) }}"
 | 
			
		||||
                wire:navigate>Tags</a>
 | 
			
		||||
            <a class="menu-item" wire:current.exact="menu-item-active"
 | 
			
		||||
                href="{{ route('project.application.danger', ['project_uuid' => $project->uuid, 'environment_name' => $environment->name, 'application_uuid' => $application->uuid]) }}"
 | 
			
		||||
                wire:navigate>Danger Zone</a>
 | 
			
		||||
        </div>
 | 
			
		||||
        <div class="w-full">
 | 
			
		||||
            <div x-cloak x-show="activeTab === 'general'" class="h-full">
 | 
			
		||||
            @if (request()->route()->getName() === 'project.application.configuration')
 | 
			
		||||
                <livewire:project.application.general :application="$application" />
 | 
			
		||||
            </div>
 | 
			
		||||
            <div x-cloak x-show="activeTab === 'swarm'" class="h-full">
 | 
			
		||||
            @elseif (request()->route()->getName() === 'project.application.swarm' && $application->destination->server->isSwarm())
 | 
			
		||||
                <livewire:project.application.swarm :application="$application" />
 | 
			
		||||
            </div>
 | 
			
		||||
            <div x-cloak x-show="activeTab === 'advanced'" class="h-full">
 | 
			
		||||
            @elseif (request()->route()->getName() === 'project.application.advanced')
 | 
			
		||||
                <livewire:project.application.advanced :application="$application" />
 | 
			
		||||
            </div>
 | 
			
		||||
            <div x-cloak x-show="activeTab === 'environment-variables'">
 | 
			
		||||
            @elseif (request()->route()->getName() === 'project.application.environment-variables')
 | 
			
		||||
                <livewire:project.shared.environment-variable.all :resource="$application" />
 | 
			
		||||
            </div>
 | 
			
		||||
            @if ($application->git_based())
 | 
			
		||||
                <div x-cloak x-show="activeTab === 'source'">
 | 
			
		||||
                    <livewire:project.application.source :application="$application" />
 | 
			
		||||
                </div>
 | 
			
		||||
            @endif
 | 
			
		||||
            <div x-cloak x-show="activeTab === 'servers'">
 | 
			
		||||
            @elseif (request()->route()->getName() === 'project.application.persistent-storage')
 | 
			
		||||
                <livewire:project.service.storage :resource="$application" />
 | 
			
		||||
            @elseif (request()->route()->getName() === 'project.application.source' && $application->git_based())
 | 
			
		||||
                <livewire:project.application.source :application="$application" />
 | 
			
		||||
            @elseif (request()->route()->getName() === 'project.application.servers')
 | 
			
		||||
                <livewire:project.shared.destination :resource="$application" :servers="$servers" />
 | 
			
		||||
            </div>
 | 
			
		||||
            <div x-cloak x-show="activeTab === 'storages'">
 | 
			
		||||
                <livewire:project.service.storage :resource="$application" lazy />
 | 
			
		||||
            </div>
 | 
			
		||||
            <div x-cloak x-show="activeTab === 'webhooks'">
 | 
			
		||||
                <livewire:project.shared.webhooks :resource="$application" lazy />
 | 
			
		||||
            </div>
 | 
			
		||||
            <div x-cloak x-show="activeTab === 'previews'">
 | 
			
		||||
                <livewire:project.application.previews :application="$application" />
 | 
			
		||||
            </div>
 | 
			
		||||
            <div x-cloak x-show="activeTab === 'health'">
 | 
			
		||||
                <livewire:project.shared.health-checks :resource="$application" />
 | 
			
		||||
            </div>
 | 
			
		||||
            <div x-cloak x-show="activeTab === 'rollback'">
 | 
			
		||||
                <livewire:project.application.rollback :application="$application" />
 | 
			
		||||
            </div>
 | 
			
		||||
            <div x-cloak x-show="activeTab === 'resource-limits'">
 | 
			
		||||
                <livewire:project.shared.resource-limits :resource="$application" />
 | 
			
		||||
            </div>
 | 
			
		||||
            <div x-cloak x-show="activeTab === 'scheduled-tasks'">
 | 
			
		||||
            @elseif (request()->route()->getName() === 'project.application.scheduled-tasks.show')
 | 
			
		||||
                <livewire:project.shared.scheduled-task.all :resource="$application" />
 | 
			
		||||
            </div>
 | 
			
		||||
            <div x-cloak x-show="activeTab === 'resource-operations'">
 | 
			
		||||
            @elseif (request()->route()->getName() === 'project.application.webhooks')
 | 
			
		||||
                <livewire:project.shared.webhooks :resource="$application" />
 | 
			
		||||
            @elseif (request()->route()->getName() === 'project.application.preview-deployments')
 | 
			
		||||
                <livewire:project.application.previews :application="$application" />
 | 
			
		||||
            @elseif (request()->route()->getName() === 'project.application.healthcheck')
 | 
			
		||||
                <livewire:project.shared.health-checks :resource="$application" />
 | 
			
		||||
            @elseif (request()->route()->getName() === 'project.application.rollback')
 | 
			
		||||
                <livewire:project.application.rollback :application="$application" />
 | 
			
		||||
            @elseif (request()->route()->getName() === 'project.application.resource-limits')
 | 
			
		||||
                <livewire:project.shared.resource-limits :resource="$application" />
 | 
			
		||||
            @elseif (request()->route()->getName() === 'project.application.resource-operations')
 | 
			
		||||
                <livewire:project.shared.resource-operations :resource="$application" />
 | 
			
		||||
            </div>
 | 
			
		||||
            <div x-cloak x-show="activeTab === 'metrics'">
 | 
			
		||||
            @elseif (request()->route()->getName() === 'project.application.metrics')
 | 
			
		||||
                <livewire:project.shared.metrics :resource="$application" />
 | 
			
		||||
            </div>
 | 
			
		||||
            <div x-cloak x-show="activeTab === 'tags'">
 | 
			
		||||
                <livewire:project.shared.tags :resource="$application" lazy />
 | 
			
		||||
            </div>
 | 
			
		||||
            <div x-cloak x-show="activeTab === 'danger'">
 | 
			
		||||
            @elseif (request()->route()->getName() === 'project.application.tags')
 | 
			
		||||
                <livewire:project.shared.tags :resource="$application" />
 | 
			
		||||
            @elseif (request()->route()->getName() === 'project.application.danger')
 | 
			
		||||
                <livewire:project.shared.danger :resource="$application" />
 | 
			
		||||
            </div>
 | 
			
		||||
            @endif
 | 
			
		||||
        </div>
 | 
			
		||||
    </div>
 | 
			
		||||
</div>
 | 
			
		||||
 
 | 
			
		||||
@@ -84,7 +84,7 @@
 | 
			
		||||
                        <option value="non-www">Redirect to non-www.</option>
 | 
			
		||||
                    </x-forms.select>
 | 
			
		||||
                    <x-modal-confirmation title="Confirm Redirection Setting?" buttonTitle="Set Direction"
 | 
			
		||||
                        submitAction="set_redirect" :actions="['All traffic will be redirected to the selected direction.']" confirmationText="{{ $application->fqdn . '/' }}"
 | 
			
		||||
                        submitAction="setRedirect" :actions="['All traffic will be redirected to the selected direction.']" confirmationText="{{ $application->fqdn . '/' }}"
 | 
			
		||||
                        confirmationLabel="Please confirm the execution of the action by entering the Application URL below"
 | 
			
		||||
                        shortConfirmationLabel="Application URL" :confirmWithPassword="false" step2ButtonText="Set Direction">
 | 
			
		||||
                        <x-slot:customButton>
 | 
			
		||||
@@ -161,8 +161,7 @@
 | 
			
		||||
                            </div>
 | 
			
		||||
                            <div class="pt-1 text-xs">Nixpacks will detect the required configuration
 | 
			
		||||
                                automatically.
 | 
			
		||||
                                <a class="underline"
 | 
			
		||||
                                    href="https://coolify.io/docs/applications">Framework
 | 
			
		||||
                                <a class="underline" href="https://coolify.io/docs/applications">Framework
 | 
			
		||||
                                    Specific Docs</a>
 | 
			
		||||
                            </div>
 | 
			
		||||
                        @endif
 | 
			
		||||
 
 | 
			
		||||
@@ -183,6 +183,23 @@ Route::middleware(['auth', 'verified'])->group(function () {
 | 
			
		||||
    });
 | 
			
		||||
    Route::prefix('project/{project_uuid}/{environment_name}/application/{application_uuid}')->group(function () {
 | 
			
		||||
        Route::get('/', ApplicationConfiguration::class)->name('project.application.configuration');
 | 
			
		||||
        Route::get('/swarm', ApplicationConfiguration::class)->name('project.application.swarm');
 | 
			
		||||
        Route::get('/advanced', ApplicationConfiguration::class)->name('project.application.advanced');
 | 
			
		||||
        Route::get('/environment-variables', ApplicationConfiguration::class)->name('project.application.environment-variables');
 | 
			
		||||
        Route::get('/persistent-storage', ApplicationConfiguration::class)->name('project.application.persistent-storage');
 | 
			
		||||
        Route::get('/source', ApplicationConfiguration::class)->name('project.application.source');
 | 
			
		||||
        Route::get('/servers', ApplicationConfiguration::class)->name('project.application.servers');
 | 
			
		||||
        Route::get('/scheduled-tasks', ApplicationConfiguration::class)->name('project.application.scheduled-tasks.show');
 | 
			
		||||
        Route::get('/webhooks', ApplicationConfiguration::class)->name('project.application.webhooks');
 | 
			
		||||
        Route::get('/preview-deployments', ApplicationConfiguration::class)->name('project.application.preview-deployments');
 | 
			
		||||
        Route::get('/healthcheck', ApplicationConfiguration::class)->name('project.application.healthcheck');
 | 
			
		||||
        Route::get('/rollback', ApplicationConfiguration::class)->name('project.application.rollback');
 | 
			
		||||
        Route::get('/resource-limits', ApplicationConfiguration::class)->name('project.application.resource-limits');
 | 
			
		||||
        Route::get('/resource-operations', ApplicationConfiguration::class)->name('project.application.resource-operations');
 | 
			
		||||
        Route::get('/metrics', ApplicationConfiguration::class)->name('project.application.metrics');
 | 
			
		||||
        Route::get('/tags', ApplicationConfiguration::class)->name('project.application.tags');
 | 
			
		||||
        Route::get('/danger', ApplicationConfiguration::class)->name('project.application.danger');
 | 
			
		||||
 | 
			
		||||
        Route::get('/deployment', DeploymentIndex::class)->name('project.application.deployment.index');
 | 
			
		||||
        Route::get('/deployment/{deployment_uuid}', DeploymentShow::class)->name('project.application.deployment.show');
 | 
			
		||||
        Route::get('/logs', Logs::class)->name('project.application.logs');
 | 
			
		||||
 
 | 
			
		||||
@@ -57,6 +57,10 @@ services:
 | 
			
		||||
      - GITHUB_APP_NAME=${GITHUB_APP_NAME}
 | 
			
		||||
      - GITHUB_APP_ID=${GITHUB_APP_ID}
 | 
			
		||||
      - GITHUB_APP_PRIVATE_KEY=${GITHUB_APP_PRIVATE_KEY}
 | 
			
		||||
      - DISCORD_CLIENT_ID=${DISCORD_CLIENT_ID}
 | 
			
		||||
      - DISCORD_CLIENT_SECRET=${DISCORD_CLIENT_SECRET}
 | 
			
		||||
      - DISCORD_SERVER_ID=${DISCORD_SERVER_ID}
 | 
			
		||||
      - DISCORD_SERVER_ROLES=${DISCORD_SERVER_ROLES}
 | 
			
		||||
      - PGSSLMODE=${PGSSLMODE:-disable}
 | 
			
		||||
      - FORCE_HTTPS=${FORCE_HTTPS:-true}
 | 
			
		||||
    healthcheck:
 | 
			
		||||
 
 | 
			
		||||
@@ -1,10 +1,10 @@
 | 
			
		||||
{
 | 
			
		||||
    "coolify": {
 | 
			
		||||
        "v4": {
 | 
			
		||||
            "version": "4.0.0-beta.379"
 | 
			
		||||
            "version": "4.0.0-beta.380"
 | 
			
		||||
        },
 | 
			
		||||
        "nightly": {
 | 
			
		||||
            "version": "4.0.0-beta.380"
 | 
			
		||||
            "version": "4.0.0-beta.381"
 | 
			
		||||
        },
 | 
			
		||||
        "helper": {
 | 
			
		||||
            "version": "1.0.4"
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user