Merge branch 'next' into feat/deployment-token
This commit is contained in:
		@@ -30,7 +30,7 @@ class GetContainersStatus
 | 
			
		||||
        $this->containerReplicates = $containerReplicates;
 | 
			
		||||
        $this->server = $server;
 | 
			
		||||
        if (! $this->server->isFunctional()) {
 | 
			
		||||
            return 'Server is not ready.';
 | 
			
		||||
            return 'Server is not functional.';
 | 
			
		||||
        }
 | 
			
		||||
        $this->applications = $this->server->applications();
 | 
			
		||||
        $skip_these_applications = collect([]);
 | 
			
		||||
 
 | 
			
		||||
@@ -40,7 +40,7 @@ class CreateNewUser implements CreatesNewUsers
 | 
			
		||||
            $user = User::create([
 | 
			
		||||
                'id' => 0,
 | 
			
		||||
                'name' => $input['name'],
 | 
			
		||||
                'email' => $input['email'],
 | 
			
		||||
                'email' => strtolower($input['email']),
 | 
			
		||||
                'password' => Hash::make($input['password']),
 | 
			
		||||
            ]);
 | 
			
		||||
            $team = $user->teams()->first();
 | 
			
		||||
@@ -52,7 +52,7 @@ class CreateNewUser implements CreatesNewUsers
 | 
			
		||||
        } else {
 | 
			
		||||
            $user = User::create([
 | 
			
		||||
                'name' => $input['name'],
 | 
			
		||||
                'email' => $input['email'],
 | 
			
		||||
                'email' => strtolower($input['email']),
 | 
			
		||||
                'password' => Hash::make($input['password']),
 | 
			
		||||
            ]);
 | 
			
		||||
            $team = $user->teams()->first();
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										41
									
								
								app/Actions/Server/ResourcesCheck.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										41
									
								
								app/Actions/Server/ResourcesCheck.php
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,41 @@
 | 
			
		||||
<?php
 | 
			
		||||
 | 
			
		||||
namespace App\Actions\Server;
 | 
			
		||||
 | 
			
		||||
use App\Models\Application;
 | 
			
		||||
use App\Models\ServiceApplication;
 | 
			
		||||
use App\Models\ServiceDatabase;
 | 
			
		||||
use App\Models\StandaloneClickhouse;
 | 
			
		||||
use App\Models\StandaloneDragonfly;
 | 
			
		||||
use App\Models\StandaloneKeydb;
 | 
			
		||||
use App\Models\StandaloneMariadb;
 | 
			
		||||
use App\Models\StandaloneMongodb;
 | 
			
		||||
use App\Models\StandaloneMysql;
 | 
			
		||||
use App\Models\StandalonePostgresql;
 | 
			
		||||
use App\Models\StandaloneRedis;
 | 
			
		||||
use Lorisleiva\Actions\Concerns\AsAction;
 | 
			
		||||
 | 
			
		||||
class ResourcesCheck
 | 
			
		||||
{
 | 
			
		||||
    use AsAction;
 | 
			
		||||
 | 
			
		||||
    public function handle()
 | 
			
		||||
    {
 | 
			
		||||
        $seconds = 60;
 | 
			
		||||
        try {
 | 
			
		||||
            Application::where('last_online_at', '<', now()->subSeconds($seconds))->update(['status' => 'exited']);
 | 
			
		||||
            ServiceApplication::where('last_online_at', '<', now()->subSeconds($seconds))->update(['status' => 'exited']);
 | 
			
		||||
            ServiceDatabase::where('last_online_at', '<', now()->subSeconds($seconds))->update(['status' => 'exited']);
 | 
			
		||||
            StandalonePostgresql::where('last_online_at', '<', now()->subSeconds($seconds))->update(['status' => 'exited']);
 | 
			
		||||
            StandaloneRedis::where('last_online_at', '<', now()->subSeconds($seconds))->update(['status' => 'exited']);
 | 
			
		||||
            StandaloneMongodb::where('last_online_at', '<', now()->subSeconds($seconds))->update(['status' => 'exited']);
 | 
			
		||||
            StandaloneMysql::where('last_online_at', '<', now()->subSeconds($seconds))->update(['status' => 'exited']);
 | 
			
		||||
            StandaloneMariadb::where('last_online_at', '<', now()->subSeconds($seconds))->update(['status' => 'exited']);
 | 
			
		||||
            StandaloneKeydb::where('last_online_at', '<', now()->subSeconds($seconds))->update(['status' => 'exited']);
 | 
			
		||||
            StandaloneDragonfly::where('last_online_at', '<', now()->subSeconds($seconds))->update(['status' => 'exited']);
 | 
			
		||||
            StandaloneClickhouse::where('last_online_at', '<', now()->subSeconds($seconds))->update(['status' => 'exited']);
 | 
			
		||||
        } catch (\Throwable $e) {
 | 
			
		||||
            return handleError($e);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										269
									
								
								app/Actions/Server/ServerCheck.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										269
									
								
								app/Actions/Server/ServerCheck.php
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,269 @@
 | 
			
		||||
<?php
 | 
			
		||||
 | 
			
		||||
namespace App\Actions\Server;
 | 
			
		||||
 | 
			
		||||
use App\Actions\Database\StartDatabaseProxy;
 | 
			
		||||
use App\Actions\Proxy\CheckProxy;
 | 
			
		||||
use App\Actions\Proxy\StartProxy;
 | 
			
		||||
use App\Jobs\CheckAndStartSentinelJob;
 | 
			
		||||
use App\Jobs\ServerStorageCheckJob;
 | 
			
		||||
use App\Models\Application;
 | 
			
		||||
use App\Models\ApplicationPreview;
 | 
			
		||||
use App\Models\Server;
 | 
			
		||||
use App\Models\Service;
 | 
			
		||||
use App\Models\ServiceApplication;
 | 
			
		||||
use App\Models\ServiceDatabase;
 | 
			
		||||
use App\Notifications\Container\ContainerRestarted;
 | 
			
		||||
use Arr;
 | 
			
		||||
use Lorisleiva\Actions\Concerns\AsAction;
 | 
			
		||||
 | 
			
		||||
class ServerCheck
 | 
			
		||||
{
 | 
			
		||||
    use AsAction;
 | 
			
		||||
 | 
			
		||||
    public Server $server;
 | 
			
		||||
 | 
			
		||||
    public bool $isSentinel = false;
 | 
			
		||||
 | 
			
		||||
    public $containers;
 | 
			
		||||
 | 
			
		||||
    public $databases;
 | 
			
		||||
 | 
			
		||||
    public function handle(Server $server, $data = null)
 | 
			
		||||
    {
 | 
			
		||||
        $this->server = $server;
 | 
			
		||||
        try {
 | 
			
		||||
            if ($this->server->isFunctional() === false) {
 | 
			
		||||
                return 'Server is not functional.';
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            if (! $this->server->isSwarmWorker() && ! $this->server->isBuildServer()) {
 | 
			
		||||
 | 
			
		||||
                if (isset($data)) {
 | 
			
		||||
                    $data = collect($data);
 | 
			
		||||
 | 
			
		||||
                    $this->server->sentinelHeartbeat();
 | 
			
		||||
 | 
			
		||||
                    $this->containers = collect(data_get($data, 'containers'));
 | 
			
		||||
 | 
			
		||||
                    $filesystemUsageRoot = data_get($data, 'filesystem_usage_root.used_percentage');
 | 
			
		||||
                    ServerStorageCheckJob::dispatch($this->server, $filesystemUsageRoot);
 | 
			
		||||
 | 
			
		||||
                    $containerReplicates = null;
 | 
			
		||||
                    $this->isSentinel = true;
 | 
			
		||||
 | 
			
		||||
                } else {
 | 
			
		||||
                    ['containers' => $this->containers, 'containerReplicates' => $containerReplicates] = $this->server->getContainers();
 | 
			
		||||
                    // ServerStorageCheckJob::dispatch($this->server);
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                if (is_null($this->containers)) {
 | 
			
		||||
                    return 'No containers found.';
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                if (isset($containerReplicates)) {
 | 
			
		||||
                    foreach ($containerReplicates as $containerReplica) {
 | 
			
		||||
                        $name = data_get($containerReplica, 'Name');
 | 
			
		||||
                        $this->containers = $this->containers->map(function ($container) use ($name, $containerReplica) {
 | 
			
		||||
                            if (data_get($container, 'Spec.Name') === $name) {
 | 
			
		||||
                                $replicas = data_get($containerReplica, 'Replicas');
 | 
			
		||||
                                $running = str($replicas)->explode('/')[0];
 | 
			
		||||
                                $total = str($replicas)->explode('/')[1];
 | 
			
		||||
                                if ($running === $total) {
 | 
			
		||||
                                    data_set($container, 'State.Status', 'running');
 | 
			
		||||
                                    data_set($container, 'State.Health.Status', 'healthy');
 | 
			
		||||
                                } else {
 | 
			
		||||
                                    data_set($container, 'State.Status', 'starting');
 | 
			
		||||
                                    data_set($container, 'State.Health.Status', 'unhealthy');
 | 
			
		||||
                                }
 | 
			
		||||
                            }
 | 
			
		||||
 | 
			
		||||
                            return $container;
 | 
			
		||||
                        });
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
                $this->checkContainers();
 | 
			
		||||
 | 
			
		||||
                if ($this->server->isSentinelEnabled() && $this->isSentinel === false) {
 | 
			
		||||
                    CheckAndStartSentinelJob::dispatch($this->server);
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                if ($this->server->isLogDrainEnabled()) {
 | 
			
		||||
                    $this->checkLogDrainContainer();
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                if ($this->server->proxySet() && ! $this->server->proxy->force_stop) {
 | 
			
		||||
                    $foundProxyContainer = $this->containers->filter(function ($value, $key) {
 | 
			
		||||
                        if ($this->server->isSwarm()) {
 | 
			
		||||
                            return data_get($value, 'Spec.Name') === 'coolify-proxy_traefik';
 | 
			
		||||
                        } else {
 | 
			
		||||
                            return data_get($value, 'Name') === '/coolify-proxy';
 | 
			
		||||
                        }
 | 
			
		||||
                    })->first();
 | 
			
		||||
                    if (! $foundProxyContainer) {
 | 
			
		||||
                        try {
 | 
			
		||||
                            $shouldStart = CheckProxy::run($this->server);
 | 
			
		||||
                            if ($shouldStart) {
 | 
			
		||||
                                StartProxy::run($this->server, false);
 | 
			
		||||
                                $this->server->team?->notify(new ContainerRestarted('coolify-proxy', $this->server));
 | 
			
		||||
                            }
 | 
			
		||||
                        } catch (\Throwable $e) {
 | 
			
		||||
                        }
 | 
			
		||||
                    } else {
 | 
			
		||||
                        $this->server->proxy->status = data_get($foundProxyContainer, 'State.Status');
 | 
			
		||||
                        $this->server->save();
 | 
			
		||||
                        $connectProxyToDockerNetworks = connectProxyToNetworks($this->server);
 | 
			
		||||
                        instant_remote_process($connectProxyToDockerNetworks, $this->server, false);
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        } catch (\Throwable $e) {
 | 
			
		||||
            return handleError($e);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private function checkLogDrainContainer()
 | 
			
		||||
    {
 | 
			
		||||
        $foundLogDrainContainer = $this->containers->filter(function ($value, $key) {
 | 
			
		||||
            return data_get($value, 'Name') === '/coolify-log-drain';
 | 
			
		||||
        })->first();
 | 
			
		||||
        if ($foundLogDrainContainer) {
 | 
			
		||||
            $status = data_get($foundLogDrainContainer, 'State.Status');
 | 
			
		||||
            if ($status !== 'running') {
 | 
			
		||||
                StartLogDrain::dispatch($this->server);
 | 
			
		||||
            }
 | 
			
		||||
        } else {
 | 
			
		||||
            StartLogDrain::dispatch($this->server);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private function checkContainers()
 | 
			
		||||
    {
 | 
			
		||||
        foreach ($this->containers as $container) {
 | 
			
		||||
            if ($this->isSentinel) {
 | 
			
		||||
                $labels = Arr::undot(data_get($container, 'labels'));
 | 
			
		||||
            } else {
 | 
			
		||||
                if ($this->server->isSwarm()) {
 | 
			
		||||
                    $labels = Arr::undot(data_get($container, 'Spec.Labels'));
 | 
			
		||||
                } else {
 | 
			
		||||
                    $labels = Arr::undot(data_get($container, 'Config.Labels'));
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
            }
 | 
			
		||||
            $managed = data_get($labels, 'coolify.managed');
 | 
			
		||||
            if (! $managed) {
 | 
			
		||||
                continue;
 | 
			
		||||
            }
 | 
			
		||||
            $uuid = data_get($labels, 'coolify.name');
 | 
			
		||||
            if (! $uuid) {
 | 
			
		||||
                $uuid = data_get($labels, 'com.docker.compose.service');
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            if ($this->isSentinel) {
 | 
			
		||||
                $containerStatus = data_get($container, 'state');
 | 
			
		||||
                $containerHealth = data_get($container, 'health_status');
 | 
			
		||||
            } else {
 | 
			
		||||
                $containerStatus = data_get($container, 'State.Status');
 | 
			
		||||
                $containerHealth = data_get($container, 'State.Health.Status', 'unhealthy');
 | 
			
		||||
            }
 | 
			
		||||
            $containerStatus = "$containerStatus ($containerHealth)";
 | 
			
		||||
 | 
			
		||||
            $applicationId = data_get($labels, 'coolify.applicationId');
 | 
			
		||||
            $serviceId = data_get($labels, 'coolify.serviceId');
 | 
			
		||||
            $databaseId = data_get($labels, 'coolify.databaseId');
 | 
			
		||||
            $pullRequestId = data_get($labels, 'coolify.pullRequestId');
 | 
			
		||||
 | 
			
		||||
            if ($applicationId) {
 | 
			
		||||
                // Application
 | 
			
		||||
                if ($pullRequestId != 0) {
 | 
			
		||||
                    if (str($applicationId)->contains('-')) {
 | 
			
		||||
                        $applicationId = str($applicationId)->before('-');
 | 
			
		||||
                    }
 | 
			
		||||
                    $preview = ApplicationPreview::where('application_id', $applicationId)->where('pull_request_id', $pullRequestId)->first();
 | 
			
		||||
                    if ($preview) {
 | 
			
		||||
                        $preview->update(['status' => $containerStatus]);
 | 
			
		||||
                    }
 | 
			
		||||
                } else {
 | 
			
		||||
                    $application = Application::where('id', $applicationId)->first();
 | 
			
		||||
                    if ($application) {
 | 
			
		||||
                        $application->update([
 | 
			
		||||
                            'status' => $containerStatus,
 | 
			
		||||
                            'last_online_at' => now(),
 | 
			
		||||
                        ]);
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
            } elseif (isset($serviceId)) {
 | 
			
		||||
                // Service
 | 
			
		||||
                $subType = data_get($labels, 'coolify.service.subType');
 | 
			
		||||
                $subId = data_get($labels, 'coolify.service.subId');
 | 
			
		||||
                $service = Service::where('id', $serviceId)->first();
 | 
			
		||||
                if (! $service) {
 | 
			
		||||
                    continue;
 | 
			
		||||
                }
 | 
			
		||||
                if ($subType === 'application') {
 | 
			
		||||
                    $service = ServiceApplication::where('id', $subId)->first();
 | 
			
		||||
                } else {
 | 
			
		||||
                    $service = ServiceDatabase::where('id', $subId)->first();
 | 
			
		||||
                }
 | 
			
		||||
                if ($service) {
 | 
			
		||||
                    $service->update([
 | 
			
		||||
                        'status' => $containerStatus,
 | 
			
		||||
                        'last_online_at' => now(),
 | 
			
		||||
                    ]);
 | 
			
		||||
                    if ($subType === 'database') {
 | 
			
		||||
                        $isPublic = data_get($service, 'is_public');
 | 
			
		||||
                        if ($isPublic) {
 | 
			
		||||
                            $foundTcpProxy = $this->containers->filter(function ($value, $key) use ($uuid) {
 | 
			
		||||
                                if ($this->isSentinel) {
 | 
			
		||||
                                    return data_get($value, 'name') === $uuid.'-proxy';
 | 
			
		||||
                                } else {
 | 
			
		||||
 | 
			
		||||
                                    if ($this->server->isSwarm()) {
 | 
			
		||||
                                        return data_get($value, 'Spec.Name') === "coolify-proxy_$uuid";
 | 
			
		||||
                                    } else {
 | 
			
		||||
                                        return data_get($value, 'Name') === "/$uuid-proxy";
 | 
			
		||||
                                    }
 | 
			
		||||
                                }
 | 
			
		||||
                            })->first();
 | 
			
		||||
                            if (! $foundTcpProxy) {
 | 
			
		||||
                                StartDatabaseProxy::run($service);
 | 
			
		||||
                            }
 | 
			
		||||
                        }
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
            } else {
 | 
			
		||||
                // Database
 | 
			
		||||
                if (is_null($this->databases)) {
 | 
			
		||||
                    $this->databases = $this->server->databases();
 | 
			
		||||
                }
 | 
			
		||||
                $database = $this->databases->where('uuid', $uuid)->first();
 | 
			
		||||
                if ($database) {
 | 
			
		||||
                    $database->update([
 | 
			
		||||
                        'status' => $containerStatus,
 | 
			
		||||
                        'last_online_at' => now(),
 | 
			
		||||
                    ]);
 | 
			
		||||
 | 
			
		||||
                    $isPublic = data_get($database, 'is_public');
 | 
			
		||||
                    if ($isPublic) {
 | 
			
		||||
                        $foundTcpProxy = $this->containers->filter(function ($value, $key) use ($uuid) {
 | 
			
		||||
                            if ($this->isSentinel) {
 | 
			
		||||
                                return data_get($value, 'name') === $uuid.'-proxy';
 | 
			
		||||
                            } else {
 | 
			
		||||
                                if ($this->server->isSwarm()) {
 | 
			
		||||
                                    return data_get($value, 'Spec.Name') === "coolify-proxy_$uuid";
 | 
			
		||||
                                } else {
 | 
			
		||||
 | 
			
		||||
                                    return data_get($value, 'Name') === "/$uuid-proxy";
 | 
			
		||||
                                }
 | 
			
		||||
                            }
 | 
			
		||||
                        })->first();
 | 
			
		||||
                        if (! $foundTcpProxy) {
 | 
			
		||||
                            StartDatabaseProxy::run($database);
 | 
			
		||||
                            $this->server->team?->notify(new ContainerRestarted("TCP Proxy for {$database->name}", $this->server));
 | 
			
		||||
                        }
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -10,6 +10,7 @@ use App\Models\Environment;
 | 
			
		||||
use App\Models\ScheduledDatabaseBackup;
 | 
			
		||||
use App\Models\Server;
 | 
			
		||||
use App\Models\StandalonePostgresql;
 | 
			
		||||
use App\Models\User;
 | 
			
		||||
use Illuminate\Console\Command;
 | 
			
		||||
use Illuminate\Support\Facades\File;
 | 
			
		||||
use Illuminate\Support\Facades\Http;
 | 
			
		||||
@@ -41,6 +42,7 @@ class Init extends Command
 | 
			
		||||
        $this->disable_metrics();
 | 
			
		||||
        $this->replace_slash_in_environment_name();
 | 
			
		||||
        $this->restore_coolify_db_backup();
 | 
			
		||||
        $this->update_user_emails();
 | 
			
		||||
        //
 | 
			
		||||
        $this->update_traefik_labels();
 | 
			
		||||
        if (! isCloud() || $this->option('force-cloud')) {
 | 
			
		||||
@@ -92,6 +94,15 @@ class Init extends Command
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private function update_user_emails()
 | 
			
		||||
    {
 | 
			
		||||
        try {
 | 
			
		||||
            User::whereRaw('email ~ \'[A-Z]\'')->get()->each(fn (User $user) => $user->update(['email' => strtolower($user->email)]));
 | 
			
		||||
        } catch (\Throwable $e) {
 | 
			
		||||
            echo "Error in updating user emails: {$e->getMessage()}\n";
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private function update_traefik_labels()
 | 
			
		||||
    {
 | 
			
		||||
        try {
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										58
									
								
								app/Console/Commands/Weird.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										58
									
								
								app/Console/Commands/Weird.php
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,58 @@
 | 
			
		||||
<?php
 | 
			
		||||
 | 
			
		||||
namespace App\Console\Commands;
 | 
			
		||||
 | 
			
		||||
use App\Actions\Server\ServerCheck;
 | 
			
		||||
use App\Enums\ProxyStatus;
 | 
			
		||||
use App\Enums\ProxyTypes;
 | 
			
		||||
use App\Models\Server;
 | 
			
		||||
use Illuminate\Console\Command;
 | 
			
		||||
use Str;
 | 
			
		||||
 | 
			
		||||
class Weird extends Command
 | 
			
		||||
{
 | 
			
		||||
    protected $signature = 'weird {--number=1} {--run}';
 | 
			
		||||
 | 
			
		||||
    protected $description = 'Weird stuff';
 | 
			
		||||
 | 
			
		||||
    public function handle()
 | 
			
		||||
    {
 | 
			
		||||
        try {
 | 
			
		||||
            if (! isDev()) {
 | 
			
		||||
                $this->error('This command can only be run in development mode');
 | 
			
		||||
 | 
			
		||||
                return;
 | 
			
		||||
            }
 | 
			
		||||
            $run = $this->option('run');
 | 
			
		||||
            if ($run) {
 | 
			
		||||
                $servers = Server::all();
 | 
			
		||||
                foreach ($servers as $server) {
 | 
			
		||||
                    ServerCheck::dispatch($server);
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                return;
 | 
			
		||||
            }
 | 
			
		||||
            $number = $this->option('number');
 | 
			
		||||
            for ($i = 0; $i < $number; $i++) {
 | 
			
		||||
                $uuid = Str::uuid();
 | 
			
		||||
                $server = Server::create([
 | 
			
		||||
                    'name' => 'localhost-'.$uuid,
 | 
			
		||||
                    'description' => 'This is a test docker container in development mode',
 | 
			
		||||
                    'ip' => 'coolify-testing-host',
 | 
			
		||||
                    'team_id' => 0,
 | 
			
		||||
                    'private_key_id' => 1,
 | 
			
		||||
                    'proxy' => [
 | 
			
		||||
                        'type' => ProxyTypes::NONE->value,
 | 
			
		||||
                        'status' => ProxyStatus::EXITED->value,
 | 
			
		||||
                    ],
 | 
			
		||||
                ]);
 | 
			
		||||
                $server->settings->update([
 | 
			
		||||
                    'is_usable' => true,
 | 
			
		||||
                    'is_reachable' => true,
 | 
			
		||||
                ]);
 | 
			
		||||
            }
 | 
			
		||||
        } catch (\Exception $e) {
 | 
			
		||||
            $this->error($e->getMessage());
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -13,6 +13,7 @@ use App\Jobs\PullTemplatesFromCDN;
 | 
			
		||||
use App\Jobs\ScheduledTaskJob;
 | 
			
		||||
use App\Jobs\ServerCheckJob;
 | 
			
		||||
use App\Jobs\ServerCleanupMux;
 | 
			
		||||
use App\Jobs\ServerStorageCheckJob;
 | 
			
		||||
use App\Jobs\UpdateCoolifyJob;
 | 
			
		||||
use App\Models\InstanceSettings;
 | 
			
		||||
use App\Models\ScheduledDatabaseBackup;
 | 
			
		||||
@@ -31,7 +32,7 @@ class Kernel extends ConsoleKernel
 | 
			
		||||
 | 
			
		||||
    protected function schedule(Schedule $schedule): void
 | 
			
		||||
    {
 | 
			
		||||
        $this->allServers = Server::where('ip', '!=', '1.2.3.4')->get();
 | 
			
		||||
        $this->allServers = Server::where('ip', '!=', '1.2.3.4');
 | 
			
		||||
 | 
			
		||||
        $this->settings = instanceSettings();
 | 
			
		||||
 | 
			
		||||
@@ -41,13 +42,16 @@ class Kernel extends ConsoleKernel
 | 
			
		||||
            // Instance Jobs
 | 
			
		||||
            $schedule->command('horizon:snapshot')->everyMinute();
 | 
			
		||||
            $schedule->job(new CleanupInstanceStuffsJob)->everyMinute()->onOneServer();
 | 
			
		||||
            $schedule->job(new CheckHelperImageJob)->everyFiveMinutes()->onOneServer();
 | 
			
		||||
 | 
			
		||||
            // Server Jobs
 | 
			
		||||
            $this->checkScheduledBackups($schedule);
 | 
			
		||||
            $this->checkResources($schedule);
 | 
			
		||||
 | 
			
		||||
            $this->checkScheduledBackups($schedule);
 | 
			
		||||
            $this->checkScheduledTasks($schedule);
 | 
			
		||||
 | 
			
		||||
            $schedule->command('uploads:clear')->everyTwoMinutes();
 | 
			
		||||
 | 
			
		||||
            $schedule->job(new CheckHelperImageJob)->everyFiveMinutes()->onOneServer();
 | 
			
		||||
        } else {
 | 
			
		||||
            // Instance Jobs
 | 
			
		||||
            $schedule->command('horizon:snapshot')->everyFiveMinutes();
 | 
			
		||||
@@ -57,9 +61,11 @@ class Kernel extends ConsoleKernel
 | 
			
		||||
            $this->scheduleUpdates($schedule);
 | 
			
		||||
 | 
			
		||||
            // Server Jobs
 | 
			
		||||
            $this->checkScheduledBackups($schedule);
 | 
			
		||||
            $this->checkResources($schedule);
 | 
			
		||||
 | 
			
		||||
            $this->pullImages($schedule);
 | 
			
		||||
 | 
			
		||||
            $this->checkScheduledBackups($schedule);
 | 
			
		||||
            $this->checkScheduledTasks($schedule);
 | 
			
		||||
 | 
			
		||||
            $schedule->command('cleanup:database --yes')->daily();
 | 
			
		||||
@@ -69,7 +75,7 @@ class Kernel extends ConsoleKernel
 | 
			
		||||
 | 
			
		||||
    private function pullImages($schedule): void
 | 
			
		||||
    {
 | 
			
		||||
        $servers = $this->allServers->whereRelation('settings', 'is_usable', true)->whereRelation('settings', 'is_reachable', true);
 | 
			
		||||
        $servers = $this->allServers->whereRelation('settings', 'is_usable', true)->whereRelation('settings', 'is_reachable', true)->get();
 | 
			
		||||
        foreach ($servers as $server) {
 | 
			
		||||
            if ($server->isSentinelEnabled()) {
 | 
			
		||||
                $schedule->job(function () use ($server) {
 | 
			
		||||
@@ -103,23 +109,33 @@ class Kernel extends ConsoleKernel
 | 
			
		||||
    private function checkResources($schedule): void
 | 
			
		||||
    {
 | 
			
		||||
        if (isCloud()) {
 | 
			
		||||
            $servers = $this->allServers->whereNotNull('team.subscription')->where('team.subscription.stripe_trial_already_ended', false);
 | 
			
		||||
            $servers = $this->allServers->whereHas('team.subscription')->get();
 | 
			
		||||
            $own = Team::find(0)->servers;
 | 
			
		||||
            $servers = $servers->merge($own);
 | 
			
		||||
        } else {
 | 
			
		||||
            $servers = $this->allServers;
 | 
			
		||||
            $servers = $this->allServers->get();
 | 
			
		||||
        }
 | 
			
		||||
        // $schedule->job(new \App\Jobs\ResourcesCheck)->everyMinute()->onOneServer();
 | 
			
		||||
 | 
			
		||||
        foreach ($servers as $server) {
 | 
			
		||||
            $lastSentinelUpdate = $server->sentinel_updated_at;
 | 
			
		||||
            $serverTimezone = $server->settings->server_timezone;
 | 
			
		||||
 | 
			
		||||
            // Sentinel check
 | 
			
		||||
            $lastSentinelUpdate = $server->sentinel_updated_at;
 | 
			
		||||
            if (Carbon::parse($lastSentinelUpdate)->isBefore(now()->subSeconds($server->waitBeforeDoingSshCheck()))) {
 | 
			
		||||
                // Check container status every minute if Sentinel does not activated
 | 
			
		||||
                $schedule->job(new ServerCheckJob($server))->everyMinute()->onOneServer();
 | 
			
		||||
                // $schedule->job(new \App\Jobs\ServerCheckNewJob($server))->everyMinute()->onOneServer();
 | 
			
		||||
 | 
			
		||||
                // Check storage usage every 10 minutes if Sentinel does not activated
 | 
			
		||||
                $schedule->job(new ServerStorageCheckJob($server))->everyTenMinutes()->onOneServer();
 | 
			
		||||
            }
 | 
			
		||||
            if ($server->settings->force_docker_cleanup) {
 | 
			
		||||
                $schedule->job(new DockerCleanupJob($server))->cron($server->settings->docker_cleanup_frequency)->timezone($serverTimezone)->onOneServer();
 | 
			
		||||
            } else {
 | 
			
		||||
                $schedule->job(new DockerCleanupJob($server))->everyTenMinutes()->timezone($serverTimezone)->onOneServer();
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            // Cleanup multiplexed connections every hour
 | 
			
		||||
            $schedule->job(new ServerCleanupMux($server))->hourly()->onOneServer();
 | 
			
		||||
 | 
			
		||||
@@ -134,14 +150,11 @@ class Kernel extends ConsoleKernel
 | 
			
		||||
 | 
			
		||||
    private function checkScheduledBackups($schedule): void
 | 
			
		||||
    {
 | 
			
		||||
        $scheduled_backups = ScheduledDatabaseBackup::all();
 | 
			
		||||
        $scheduled_backups = ScheduledDatabaseBackup::where('enabled', true)->get();
 | 
			
		||||
        if ($scheduled_backups->isEmpty()) {
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
        foreach ($scheduled_backups as $scheduled_backup) {
 | 
			
		||||
            if (! $scheduled_backup->enabled) {
 | 
			
		||||
                continue;
 | 
			
		||||
            }
 | 
			
		||||
            if (is_null(data_get($scheduled_backup, 'database'))) {
 | 
			
		||||
                $scheduled_backup->delete();
 | 
			
		||||
 | 
			
		||||
@@ -150,7 +163,7 @@ class Kernel extends ConsoleKernel
 | 
			
		||||
 | 
			
		||||
            $server = $scheduled_backup->server();
 | 
			
		||||
 | 
			
		||||
            if (! $server) {
 | 
			
		||||
            if (is_null($server)) {
 | 
			
		||||
                continue;
 | 
			
		||||
            }
 | 
			
		||||
            $serverTimezone = $server->settings->server_timezone;
 | 
			
		||||
 
 | 
			
		||||
@@ -230,7 +230,7 @@ class ApplicationDeploymentJob implements ShouldBeEncrypted, ShouldQueue
 | 
			
		||||
        $this->application_deployment_queue->update([
 | 
			
		||||
            'status' => ApplicationDeploymentStatus::IN_PROGRESS->value,
 | 
			
		||||
        ]);
 | 
			
		||||
        if (! $this->server->isFunctional()) {
 | 
			
		||||
        if ($this->server->isFunctional() === false) {
 | 
			
		||||
            $this->application_deployment_queue->addLogEntry('Server is not functional.');
 | 
			
		||||
            $this->fail('Server is not functional.');
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -39,7 +39,6 @@ class ServerCheckJob implements ShouldBeEncrypted, ShouldQueue
 | 
			
		||||
                if (is_null($this->containers)) {
 | 
			
		||||
                    return 'No containers found.';
 | 
			
		||||
                }
 | 
			
		||||
                ServerStorageCheckJob::dispatch($this->server);
 | 
			
		||||
                GetContainersStatus::run($this->server, $this->containers, $containerReplicates);
 | 
			
		||||
 | 
			
		||||
                if ($this->server->isSentinelEnabled()) {
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										32
									
								
								app/Jobs/ServerCheckNewJob.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										32
									
								
								app/Jobs/ServerCheckNewJob.php
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,32 @@
 | 
			
		||||
<?php
 | 
			
		||||
 | 
			
		||||
namespace App\Jobs;
 | 
			
		||||
 | 
			
		||||
use App\Actions\Server\ServerCheck;
 | 
			
		||||
use App\Models\Server;
 | 
			
		||||
use Illuminate\Bus\Queueable;
 | 
			
		||||
use Illuminate\Contracts\Queue\ShouldBeEncrypted;
 | 
			
		||||
use Illuminate\Contracts\Queue\ShouldQueue;
 | 
			
		||||
use Illuminate\Foundation\Bus\Dispatchable;
 | 
			
		||||
use Illuminate\Queue\InteractsWithQueue;
 | 
			
		||||
use Illuminate\Queue\SerializesModels;
 | 
			
		||||
 | 
			
		||||
class ServerCheckNewJob implements ShouldBeEncrypted, ShouldQueue
 | 
			
		||||
{
 | 
			
		||||
    use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
 | 
			
		||||
 | 
			
		||||
    public $tries = 1;
 | 
			
		||||
 | 
			
		||||
    public $timeout = 60;
 | 
			
		||||
 | 
			
		||||
    public function __construct(public Server $server) {}
 | 
			
		||||
 | 
			
		||||
    public function handle()
 | 
			
		||||
    {
 | 
			
		||||
        try {
 | 
			
		||||
            ServerCheck::run($this->server);
 | 
			
		||||
        } catch (\Throwable $e) {
 | 
			
		||||
            return handleError($e);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -30,8 +30,8 @@ class ServerStorageCheckJob implements ShouldBeEncrypted, ShouldQueue
 | 
			
		||||
    public function handle()
 | 
			
		||||
    {
 | 
			
		||||
        try {
 | 
			
		||||
            if (! $this->server->isFunctional()) {
 | 
			
		||||
                return 'Server is not ready.';
 | 
			
		||||
            if ($this->server->isFunctional() === false) {
 | 
			
		||||
                return 'Server is not functional.';
 | 
			
		||||
            }
 | 
			
		||||
            $team = data_get($this->server, 'team');
 | 
			
		||||
            $serverDiskUsageNotificationThreshold = data_get($this->server, 'settings.server_disk_usage_notification_threshold');
 | 
			
		||||
 
 | 
			
		||||
@@ -3,16 +3,19 @@
 | 
			
		||||
namespace App\Livewire\Admin;
 | 
			
		||||
 | 
			
		||||
use App\Models\User;
 | 
			
		||||
use Illuminate\Support\Collection;
 | 
			
		||||
use Illuminate\Support\Facades\Cache;
 | 
			
		||||
use Livewire\Component;
 | 
			
		||||
 | 
			
		||||
class Index extends Component
 | 
			
		||||
{
 | 
			
		||||
    public $active_subscribers = [];
 | 
			
		||||
    public int $activeSubscribers;
 | 
			
		||||
 | 
			
		||||
    public $inactive_subscribers = [];
 | 
			
		||||
    public int $inactiveSubscribers;
 | 
			
		||||
 | 
			
		||||
    public $search = '';
 | 
			
		||||
    public Collection $foundUsers;
 | 
			
		||||
 | 
			
		||||
    public string $search = '';
 | 
			
		||||
 | 
			
		||||
    public function mount()
 | 
			
		||||
    {
 | 
			
		||||
@@ -29,39 +32,21 @@ class Index extends Component
 | 
			
		||||
    public function submitSearch()
 | 
			
		||||
    {
 | 
			
		||||
        if ($this->search !== '') {
 | 
			
		||||
            $this->inactive_subscribers = User::whereDoesntHave('teams', function ($query) {
 | 
			
		||||
                $query->whereRelation('subscription', 'stripe_subscription_id', '!=', null);
 | 
			
		||||
            })->where(function ($query) {
 | 
			
		||||
            $this->foundUsers = User::where(function ($query) {
 | 
			
		||||
                $query->where('name', 'like', "%{$this->search}%")
 | 
			
		||||
                    ->orWhere('email', 'like', "%{$this->search}%");
 | 
			
		||||
            })->get()->filter(function ($user) {
 | 
			
		||||
                return $user->id !== 0;
 | 
			
		||||
            });
 | 
			
		||||
            $this->active_subscribers = User::whereHas('teams', function ($query) {
 | 
			
		||||
                $query->whereRelation('subscription', 'stripe_subscription_id', '!=', null);
 | 
			
		||||
            })->where(function ($query) {
 | 
			
		||||
                $query->where('name', 'like', "%{$this->search}%")
 | 
			
		||||
                    ->orWhere('email', 'like', "%{$this->search}%");
 | 
			
		||||
            })->get()->filter(function ($user) {
 | 
			
		||||
                return $user->id !== 0;
 | 
			
		||||
            });
 | 
			
		||||
        } else {
 | 
			
		||||
            $this->getSubscribers();
 | 
			
		||||
            })->get();
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function getSubscribers()
 | 
			
		||||
    {
 | 
			
		||||
        $this->inactive_subscribers = User::whereDoesntHave('teams', function ($query) {
 | 
			
		||||
        $this->inactiveSubscribers = User::whereDoesntHave('teams', function ($query) {
 | 
			
		||||
            $query->whereRelation('subscription', 'stripe_subscription_id', '!=', null);
 | 
			
		||||
        })->get()->filter(function ($user) {
 | 
			
		||||
            return $user->id !== 0;
 | 
			
		||||
        });
 | 
			
		||||
        $this->active_subscribers = User::whereHas('teams', function ($query) {
 | 
			
		||||
        })->count();
 | 
			
		||||
        $this->activeSubscribers = User::whereHas('teams', function ($query) {
 | 
			
		||||
            $query->whereRelation('subscription', 'stripe_subscription_id', '!=', null);
 | 
			
		||||
        })->get()->filter(function ($user) {
 | 
			
		||||
            return $user->id !== 0;
 | 
			
		||||
        });
 | 
			
		||||
        })->count();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function switchUser(int $user_id)
 | 
			
		||||
 
 | 
			
		||||
@@ -16,28 +16,28 @@ class Dashboard extends Component
 | 
			
		||||
 | 
			
		||||
    public Collection $servers;
 | 
			
		||||
 | 
			
		||||
    public Collection $private_keys;
 | 
			
		||||
    public Collection $privateKeys;
 | 
			
		||||
 | 
			
		||||
    public $deployments_per_server;
 | 
			
		||||
    public array $deploymentsPerServer = [];
 | 
			
		||||
 | 
			
		||||
    public function mount()
 | 
			
		||||
    {
 | 
			
		||||
        $this->private_keys = PrivateKey::ownedByCurrentTeam()->get();
 | 
			
		||||
        $this->privateKeys = PrivateKey::ownedByCurrentTeam()->get();
 | 
			
		||||
        $this->servers = Server::ownedByCurrentTeam()->get();
 | 
			
		||||
        $this->projects = Project::ownedByCurrentTeam()->get();
 | 
			
		||||
        $this->get_deployments();
 | 
			
		||||
        $this->loadDeployments();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function cleanup_queue()
 | 
			
		||||
    public function cleanupQueue()
 | 
			
		||||
    {
 | 
			
		||||
        Artisan::queue('cleanup:deployment-queue', [
 | 
			
		||||
            '--team-id' => currentTeam()->id,
 | 
			
		||||
        ]);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function get_deployments()
 | 
			
		||||
    public function loadDeployments()
 | 
			
		||||
    {
 | 
			
		||||
        $this->deployments_per_server = ApplicationDeploymentQueue::whereIn('status', ['in_progress', 'queued'])->whereIn('server_id', $this->servers->pluck('id'))->get([
 | 
			
		||||
        $this->deploymentsPerServer = ApplicationDeploymentQueue::whereIn('status', ['in_progress', 'queued'])->whereIn('server_id', $this->servers->pluck('id'))->get([
 | 
			
		||||
            'id',
 | 
			
		||||
            'application_id',
 | 
			
		||||
            'application_name',
 | 
			
		||||
 
 | 
			
		||||
@@ -1,46 +0,0 @@
 | 
			
		||||
<?php
 | 
			
		||||
 | 
			
		||||
namespace App\Livewire\Destination;
 | 
			
		||||
 | 
			
		||||
use Livewire\Component;
 | 
			
		||||
 | 
			
		||||
class Form extends Component
 | 
			
		||||
{
 | 
			
		||||
    public mixed $destination;
 | 
			
		||||
 | 
			
		||||
    protected $rules = [
 | 
			
		||||
        'destination.name' => 'required',
 | 
			
		||||
        'destination.network' => 'required',
 | 
			
		||||
        'destination.server.ip' => 'required',
 | 
			
		||||
    ];
 | 
			
		||||
 | 
			
		||||
    protected $validationAttributes = [
 | 
			
		||||
        'destination.name' => 'name',
 | 
			
		||||
        'destination.network' => 'network',
 | 
			
		||||
        'destination.server.ip' => 'IP Address/Domain',
 | 
			
		||||
    ];
 | 
			
		||||
 | 
			
		||||
    public function submit()
 | 
			
		||||
    {
 | 
			
		||||
        $this->validate();
 | 
			
		||||
        $this->destination->save();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function delete()
 | 
			
		||||
    {
 | 
			
		||||
        try {
 | 
			
		||||
            if ($this->destination->getMorphClass() === \App\Models\StandaloneDocker::class) {
 | 
			
		||||
                if ($this->destination->attachedTo()) {
 | 
			
		||||
                    return $this->dispatch('error', 'You must delete all resources before deleting this destination.');
 | 
			
		||||
                }
 | 
			
		||||
                instant_remote_process(["docker network disconnect {$this->destination->network} coolify-proxy"], $this->destination->server, throwError: false);
 | 
			
		||||
                instant_remote_process(['docker network rm -f '.$this->destination->network], $this->destination->server);
 | 
			
		||||
            }
 | 
			
		||||
            $this->destination->delete();
 | 
			
		||||
 | 
			
		||||
            return redirect()->route('destination.all');
 | 
			
		||||
        } catch (\Throwable $e) {
 | 
			
		||||
            return handleError($e, $this);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										23
									
								
								app/Livewire/Destination/Index.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										23
									
								
								app/Livewire/Destination/Index.php
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,23 @@
 | 
			
		||||
<?php
 | 
			
		||||
 | 
			
		||||
namespace App\Livewire\Destination;
 | 
			
		||||
 | 
			
		||||
use App\Models\Server;
 | 
			
		||||
use Livewire\Attributes\Locked;
 | 
			
		||||
use Livewire\Component;
 | 
			
		||||
 | 
			
		||||
class Index extends Component
 | 
			
		||||
{
 | 
			
		||||
    #[Locked]
 | 
			
		||||
    public $servers;
 | 
			
		||||
 | 
			
		||||
    public function mount()
 | 
			
		||||
    {
 | 
			
		||||
        $this->servers = Server::isUsable()->get();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function render()
 | 
			
		||||
    {
 | 
			
		||||
        return view('livewire.destination.index');
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -3,111 +3,89 @@
 | 
			
		||||
namespace App\Livewire\Destination\New;
 | 
			
		||||
 | 
			
		||||
use App\Models\Server;
 | 
			
		||||
use App\Models\StandaloneDocker as ModelsStandaloneDocker;
 | 
			
		||||
use App\Models\StandaloneDocker;
 | 
			
		||||
use App\Models\SwarmDocker;
 | 
			
		||||
use Illuminate\Support\Collection;
 | 
			
		||||
use Livewire\Attributes\Locked;
 | 
			
		||||
use Livewire\Attributes\Rule;
 | 
			
		||||
use Livewire\Component;
 | 
			
		||||
use Visus\Cuid2\Cuid2;
 | 
			
		||||
 | 
			
		||||
class Docker extends Component
 | 
			
		||||
{
 | 
			
		||||
    #[Locked]
 | 
			
		||||
    public $servers;
 | 
			
		||||
 | 
			
		||||
    #[Locked]
 | 
			
		||||
    public Server $selectedServer;
 | 
			
		||||
 | 
			
		||||
    #[Rule(['required', 'string'])]
 | 
			
		||||
    public string $name;
 | 
			
		||||
 | 
			
		||||
    #[Rule(['required', 'string'])]
 | 
			
		||||
    public string $network;
 | 
			
		||||
 | 
			
		||||
    public ?Collection $servers = null;
 | 
			
		||||
    #[Rule(['required', 'string'])]
 | 
			
		||||
    public string $serverId;
 | 
			
		||||
 | 
			
		||||
    public Server $server;
 | 
			
		||||
    #[Rule(['required', 'boolean'])]
 | 
			
		||||
    public bool $isSwarm = false;
 | 
			
		||||
 | 
			
		||||
    public ?int $server_id = null;
 | 
			
		||||
 | 
			
		||||
    public bool $is_swarm = false;
 | 
			
		||||
 | 
			
		||||
    protected $rules = [
 | 
			
		||||
        'name' => 'required|string',
 | 
			
		||||
        'network' => 'required|string',
 | 
			
		||||
        'server_id' => 'required|integer',
 | 
			
		||||
        'is_swarm' => 'boolean',
 | 
			
		||||
    ];
 | 
			
		||||
 | 
			
		||||
    protected $validationAttributes = [
 | 
			
		||||
        'name' => 'name',
 | 
			
		||||
        'network' => 'network',
 | 
			
		||||
        'server_id' => 'server',
 | 
			
		||||
        'is_swarm' => 'swarm',
 | 
			
		||||
    ];
 | 
			
		||||
 | 
			
		||||
    public function mount()
 | 
			
		||||
    public function mount(?string $server_id = null)
 | 
			
		||||
    {
 | 
			
		||||
        if (is_null($this->servers)) {
 | 
			
		||||
            $this->servers = Server::isReachable()->get();
 | 
			
		||||
        }
 | 
			
		||||
        if (request()->query('server_id')) {
 | 
			
		||||
            $this->server_id = request()->query('server_id');
 | 
			
		||||
        } else {
 | 
			
		||||
            if ($this->servers->count() > 0) {
 | 
			
		||||
                $this->server_id = $this->servers->first()->id;
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        if (request()->query('network_name')) {
 | 
			
		||||
            $this->network = request()->query('network_name');
 | 
			
		||||
        } else {
 | 
			
		||||
        $this->network = new Cuid2;
 | 
			
		||||
        $this->servers = Server::isUsable()->get();
 | 
			
		||||
        if ($server_id) {
 | 
			
		||||
            $this->selectedServer = $this->servers->find($server_id);
 | 
			
		||||
        } else {
 | 
			
		||||
            $this->selectedServer = $this->servers->first();
 | 
			
		||||
        }
 | 
			
		||||
        if ($this->servers->count() > 0) {
 | 
			
		||||
            $this->name = str("{$this->servers->first()->name}-{$this->network}")->kebab();
 | 
			
		||||
        }
 | 
			
		||||
        $this->generateName();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function generate_name()
 | 
			
		||||
    public function updatedServerId()
 | 
			
		||||
    {
 | 
			
		||||
        $this->server = Server::find($this->server_id);
 | 
			
		||||
        $this->name = str("{$this->server->name}-{$this->network}")->kebab();
 | 
			
		||||
        $this->selectedServer = $this->servers->find($this->serverId);
 | 
			
		||||
        $this->generateName();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function generateName()
 | 
			
		||||
    {
 | 
			
		||||
        $name = data_get($this->selectedServer, 'name', new Cuid2);
 | 
			
		||||
        $this->name = str("{$name}-{$this->network}")->kebab();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function submit()
 | 
			
		||||
    {
 | 
			
		||||
        $this->validate();
 | 
			
		||||
        try {
 | 
			
		||||
            $this->server = Server::find($this->server_id);
 | 
			
		||||
            if ($this->is_swarm) {
 | 
			
		||||
                $found = $this->server->swarmDockers()->where('network', $this->network)->first();
 | 
			
		||||
            $this->validate();
 | 
			
		||||
            if ($this->isSwarm) {
 | 
			
		||||
                $found = $this->selectedServer->swarmDockers()->where('network', $this->network)->first();
 | 
			
		||||
                if ($found) {
 | 
			
		||||
                    $this->dispatch('error', 'Network already added to this server.');
 | 
			
		||||
 | 
			
		||||
                    return;
 | 
			
		||||
                    throw new \Exception('Network already added to this server.');
 | 
			
		||||
                } else {
 | 
			
		||||
                    $docker = SwarmDocker::create([
 | 
			
		||||
                        'name' => $this->name,
 | 
			
		||||
                        'network' => $this->network,
 | 
			
		||||
                        'server_id' => $this->server_id,
 | 
			
		||||
                        'server_id' => $this->selectedServer->id,
 | 
			
		||||
                    ]);
 | 
			
		||||
                }
 | 
			
		||||
            } else {
 | 
			
		||||
                $found = $this->server->standaloneDockers()->where('network', $this->network)->first();
 | 
			
		||||
                $found = $this->selectedServer->standaloneDockers()->where('network', $this->network)->first();
 | 
			
		||||
                if ($found) {
 | 
			
		||||
                    $this->dispatch('error', 'Network already added to this server.');
 | 
			
		||||
 | 
			
		||||
                    return;
 | 
			
		||||
                    throw new \Exception('Network already added to this server.');
 | 
			
		||||
                } else {
 | 
			
		||||
                    $docker = ModelsStandaloneDocker::create([
 | 
			
		||||
                    $docker = StandaloneDocker::create([
 | 
			
		||||
                        'name' => $this->name,
 | 
			
		||||
                        'network' => $this->network,
 | 
			
		||||
                        'server_id' => $this->server_id,
 | 
			
		||||
                        'server_id' => $this->selectedServer->id,
 | 
			
		||||
                    ]);
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
            $this->createNetworkAndAttachToProxy();
 | 
			
		||||
 | 
			
		||||
            return redirect()->route('destination.show', $docker->uuid);
 | 
			
		||||
            $connectProxyToDockerNetworks = connectProxyToNetworks($this->selectedServer);
 | 
			
		||||
            instant_remote_process($connectProxyToDockerNetworks, $this->selectedServer, false);
 | 
			
		||||
            $this->dispatch('reloadWindow');
 | 
			
		||||
        } catch (\Throwable $e) {
 | 
			
		||||
            return handleError($e, $this);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private function createNetworkAndAttachToProxy()
 | 
			
		||||
    {
 | 
			
		||||
        $connectProxyToDockerNetworks = connectProxyToNetworks($this->server);
 | 
			
		||||
        instant_remote_process($connectProxyToDockerNetworks, $this->server, false);
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -5,71 +5,91 @@ namespace App\Livewire\Destination;
 | 
			
		||||
use App\Models\Server;
 | 
			
		||||
use App\Models\StandaloneDocker;
 | 
			
		||||
use App\Models\SwarmDocker;
 | 
			
		||||
use Illuminate\Support\Collection;
 | 
			
		||||
use Livewire\Attributes\Locked;
 | 
			
		||||
use Livewire\Attributes\Rule;
 | 
			
		||||
use Livewire\Component;
 | 
			
		||||
 | 
			
		||||
class Show extends Component
 | 
			
		||||
{
 | 
			
		||||
    public Server $server;
 | 
			
		||||
    #[Locked]
 | 
			
		||||
    public $destination;
 | 
			
		||||
 | 
			
		||||
    public Collection|array $networks = [];
 | 
			
		||||
    #[Rule(['string', 'required'])]
 | 
			
		||||
    public string $name;
 | 
			
		||||
 | 
			
		||||
    private function createNetworkAndAttachToProxy()
 | 
			
		||||
    #[Rule(['string', 'required'])]
 | 
			
		||||
    public string $network;
 | 
			
		||||
 | 
			
		||||
    #[Rule(['string', 'required'])]
 | 
			
		||||
    public string $serverIp;
 | 
			
		||||
 | 
			
		||||
    public function mount(string $destination_uuid)
 | 
			
		||||
    {
 | 
			
		||||
        $connectProxyToDockerNetworks = connectProxyToNetworks($this->server);
 | 
			
		||||
        instant_remote_process($connectProxyToDockerNetworks, $this->server, false);
 | 
			
		||||
    }
 | 
			
		||||
        try {
 | 
			
		||||
            $destination = StandaloneDocker::whereUuid($destination_uuid)->first() ??
 | 
			
		||||
                SwarmDocker::whereUuid($destination_uuid)->firstOrFail();
 | 
			
		||||
 | 
			
		||||
    public function add($name)
 | 
			
		||||
    {
 | 
			
		||||
        if ($this->server->isSwarm()) {
 | 
			
		||||
            $found = $this->server->swarmDockers()->where('network', $name)->first();
 | 
			
		||||
            if ($found) {
 | 
			
		||||
                $this->dispatch('error', 'Network already added to this server.');
 | 
			
		||||
 | 
			
		||||
                return;
 | 
			
		||||
            } else {
 | 
			
		||||
                SwarmDocker::create([
 | 
			
		||||
                    'name' => $this->server->name.'-'.$name,
 | 
			
		||||
                    'network' => $this->name,
 | 
			
		||||
                    'server_id' => $this->server->id,
 | 
			
		||||
                ]);
 | 
			
		||||
            $ownedByTeam = Server::ownedByCurrentTeam()->each(function ($server) use ($destination) {
 | 
			
		||||
                if ($server->standaloneDockers->contains($destination) || $server->swarmDockers->contains($destination)) {
 | 
			
		||||
                    $this->destination = $destination;
 | 
			
		||||
                    $this->syncData();
 | 
			
		||||
                }
 | 
			
		||||
        } else {
 | 
			
		||||
            $found = $this->server->standaloneDockers()->where('network', $name)->first();
 | 
			
		||||
            if ($found) {
 | 
			
		||||
                $this->dispatch('error', 'Network already added to this server.');
 | 
			
		||||
 | 
			
		||||
                return;
 | 
			
		||||
            } else {
 | 
			
		||||
                StandaloneDocker::create([
 | 
			
		||||
                    'name' => $this->server->name.'-'.$name,
 | 
			
		||||
                    'network' => $name,
 | 
			
		||||
                    'server_id' => $this->server->id,
 | 
			
		||||
                ]);
 | 
			
		||||
            }
 | 
			
		||||
            $this->createNetworkAndAttachToProxy();
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function scan()
 | 
			
		||||
    {
 | 
			
		||||
        if ($this->server->isSwarm()) {
 | 
			
		||||
            $alreadyAddedNetworks = $this->server->swarmDockers;
 | 
			
		||||
        } else {
 | 
			
		||||
            $alreadyAddedNetworks = $this->server->standaloneDockers;
 | 
			
		||||
        }
 | 
			
		||||
        $networks = instant_remote_process(['docker network ls --format "{{json .}}"'], $this->server, false);
 | 
			
		||||
        $this->networks = format_docker_command_output_to_json($networks)->filter(function ($network) {
 | 
			
		||||
            return $network['Name'] !== 'bridge' && $network['Name'] !== 'host' && $network['Name'] !== 'none';
 | 
			
		||||
        })->filter(function ($network) use ($alreadyAddedNetworks) {
 | 
			
		||||
            return ! $alreadyAddedNetworks->contains('network', $network['Name']);
 | 
			
		||||
            });
 | 
			
		||||
        if ($this->networks->count() === 0) {
 | 
			
		||||
            $this->dispatch('success', 'No new destinations found on this server.');
 | 
			
		||||
            if ($ownedByTeam === false) {
 | 
			
		||||
                return redirect()->route('destination.index');
 | 
			
		||||
            }
 | 
			
		||||
            $this->destination = $destination;
 | 
			
		||||
            $this->syncData();
 | 
			
		||||
        } catch (\Throwable $e) {
 | 
			
		||||
            return handleError($e, $this);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
        $this->dispatch('success', 'Scan done.');
 | 
			
		||||
    public function syncData(bool $toModel = false)
 | 
			
		||||
    {
 | 
			
		||||
        if ($toModel) {
 | 
			
		||||
            $this->validate();
 | 
			
		||||
            $this->destination->name = $this->name;
 | 
			
		||||
            $this->destination->network = $this->network;
 | 
			
		||||
            $this->destination->server->ip = $this->serverIp;
 | 
			
		||||
            $this->destination->save();
 | 
			
		||||
        } else {
 | 
			
		||||
            $this->name = $this->destination->name;
 | 
			
		||||
            $this->network = $this->destination->network;
 | 
			
		||||
            $this->serverIp = $this->destination->server->ip;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function submit()
 | 
			
		||||
    {
 | 
			
		||||
        try {
 | 
			
		||||
            $this->syncData(true);
 | 
			
		||||
            $this->dispatch('success', 'Destination saved.');
 | 
			
		||||
        } catch (\Throwable $e) {
 | 
			
		||||
            return handleError($e, $this);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function delete()
 | 
			
		||||
    {
 | 
			
		||||
        try {
 | 
			
		||||
            if ($this->destination->getMorphClass() === \App\Models\StandaloneDocker::class) {
 | 
			
		||||
                if ($this->destination->attachedTo()) {
 | 
			
		||||
                    return $this->dispatch('error', 'You must delete all resources before deleting this destination.');
 | 
			
		||||
                }
 | 
			
		||||
                instant_remote_process(["docker network disconnect {$this->destination->network} coolify-proxy"], $this->destination->server, throwError: false);
 | 
			
		||||
                instant_remote_process(['docker network rm -f '.$this->destination->network], $this->destination->server);
 | 
			
		||||
            }
 | 
			
		||||
            $this->destination->delete();
 | 
			
		||||
 | 
			
		||||
            return redirect()->route('destination.index');
 | 
			
		||||
        } catch (\Throwable $e) {
 | 
			
		||||
            return handleError($e, $this);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function render()
 | 
			
		||||
    {
 | 
			
		||||
        return view('livewire.destination.show');
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -5,55 +5,39 @@ namespace App\Livewire;
 | 
			
		||||
use DanHarrin\LivewireRateLimiting\WithRateLimiting;
 | 
			
		||||
use Illuminate\Notifications\Messages\MailMessage;
 | 
			
		||||
use Illuminate\Support\Facades\Http;
 | 
			
		||||
use Illuminate\Support\Facades\Route;
 | 
			
		||||
use Livewire\Attributes\Rule;
 | 
			
		||||
use Livewire\Component;
 | 
			
		||||
 | 
			
		||||
class Help extends Component
 | 
			
		||||
{
 | 
			
		||||
    use WithRateLimiting;
 | 
			
		||||
 | 
			
		||||
    #[Rule(['required', 'min:10', 'max:1000'])]
 | 
			
		||||
    public string $description;
 | 
			
		||||
 | 
			
		||||
    #[Rule(['required', 'min:3'])]
 | 
			
		||||
    public string $subject;
 | 
			
		||||
 | 
			
		||||
    public ?string $path = null;
 | 
			
		||||
 | 
			
		||||
    protected $rules = [
 | 
			
		||||
        'description' => 'required|min:10',
 | 
			
		||||
        'subject' => 'required|min:3',
 | 
			
		||||
    ];
 | 
			
		||||
 | 
			
		||||
    public function mount()
 | 
			
		||||
    {
 | 
			
		||||
        $this->path = Route::current()?->uri() ?? null;
 | 
			
		||||
        if (isDev()) {
 | 
			
		||||
            $this->description = "I'm having trouble with {$this->path}";
 | 
			
		||||
            $this->subject = "Help with {$this->path}";
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function submit()
 | 
			
		||||
    {
 | 
			
		||||
        try {
 | 
			
		||||
            $this->rateLimit(3, 30);
 | 
			
		||||
            $this->validate();
 | 
			
		||||
            $debug = "Route: {$this->path}";
 | 
			
		||||
            $this->rateLimit(3, 30);
 | 
			
		||||
 | 
			
		||||
            $settings = instanceSettings();
 | 
			
		||||
            $mail = new MailMessage;
 | 
			
		||||
            $mail->view(
 | 
			
		||||
                'emails.help',
 | 
			
		||||
                [
 | 
			
		||||
                    'description' => $this->description,
 | 
			
		||||
                    'debug' => $debug,
 | 
			
		||||
                ]
 | 
			
		||||
            );
 | 
			
		||||
            $mail->subject("[HELP]: {$this->subject}");
 | 
			
		||||
            $settings = instanceSettings();
 | 
			
		||||
            $type = set_transanctional_email_settings($settings);
 | 
			
		||||
            if (! $type) {
 | 
			
		||||
 | 
			
		||||
            // Sending feedback through Cloud API
 | 
			
		||||
            if ($type === false) {
 | 
			
		||||
                $url = 'https://app.coolify.io/api/feedback';
 | 
			
		||||
                if (isDev()) {
 | 
			
		||||
                    $url = 'http://localhost:80/api/feedback';
 | 
			
		||||
                }
 | 
			
		||||
                Http::post($url, [
 | 
			
		||||
                    'content' => 'User: `'.auth()->user()?->email.'` with subject: `'.$this->subject.'` has the following problem: `'.$this->description.'`',
 | 
			
		||||
                ]);
 | 
			
		||||
 
 | 
			
		||||
@@ -4,47 +4,94 @@ namespace App\Livewire\Notifications;
 | 
			
		||||
 | 
			
		||||
use App\Models\Team;
 | 
			
		||||
use App\Notifications\Test;
 | 
			
		||||
use Livewire\Attributes\Rule;
 | 
			
		||||
use Livewire\Component;
 | 
			
		||||
 | 
			
		||||
class Discord extends Component
 | 
			
		||||
{
 | 
			
		||||
    public Team $team;
 | 
			
		||||
 | 
			
		||||
    protected $rules = [
 | 
			
		||||
        'team.discord_enabled' => 'nullable|boolean',
 | 
			
		||||
        'team.discord_webhook_url' => 'required|url',
 | 
			
		||||
        'team.discord_notifications_test' => 'nullable|boolean',
 | 
			
		||||
        'team.discord_notifications_deployments' => 'nullable|boolean',
 | 
			
		||||
        'team.discord_notifications_status_changes' => 'nullable|boolean',
 | 
			
		||||
        'team.discord_notifications_database_backups' => 'nullable|boolean',
 | 
			
		||||
        'team.discord_notifications_scheduled_tasks' => 'nullable|boolean',
 | 
			
		||||
        'team.discord_notifications_server_disk_usage' => 'nullable|boolean',
 | 
			
		||||
    ];
 | 
			
		||||
    #[Rule(['boolean'])]
 | 
			
		||||
    public bool $discordEnabled = false;
 | 
			
		||||
 | 
			
		||||
    protected $validationAttributes = [
 | 
			
		||||
        'team.discord_webhook_url' => 'Discord Webhook',
 | 
			
		||||
    ];
 | 
			
		||||
    #[Rule(['url', 'nullable'])]
 | 
			
		||||
    public ?string $discordWebhookUrl = null;
 | 
			
		||||
 | 
			
		||||
    #[Rule(['boolean'])]
 | 
			
		||||
    public bool $discordNotificationsTest = false;
 | 
			
		||||
 | 
			
		||||
    #[Rule(['boolean'])]
 | 
			
		||||
    public bool $discordNotificationsDeployments = false;
 | 
			
		||||
 | 
			
		||||
    #[Rule(['boolean'])]
 | 
			
		||||
    public bool $discordNotificationsStatusChanges = false;
 | 
			
		||||
 | 
			
		||||
    #[Rule(['boolean'])]
 | 
			
		||||
    public bool $discordNotificationsDatabaseBackups = false;
 | 
			
		||||
 | 
			
		||||
    #[Rule(['boolean'])]
 | 
			
		||||
    public bool $discordNotificationsScheduledTasks = false;
 | 
			
		||||
 | 
			
		||||
    #[Rule(['boolean'])]
 | 
			
		||||
    public bool $discordNotificationsServerDiskUsage = false;
 | 
			
		||||
 | 
			
		||||
    public function mount()
 | 
			
		||||
    {
 | 
			
		||||
        try {
 | 
			
		||||
            $this->team = auth()->user()->currentTeam();
 | 
			
		||||
            $this->syncData();
 | 
			
		||||
        } catch (\Throwable $e) {
 | 
			
		||||
            handleError($e, $this);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function syncData(bool $toModel = false)
 | 
			
		||||
    {
 | 
			
		||||
        if ($toModel) {
 | 
			
		||||
            $this->validate();
 | 
			
		||||
            $this->team->discord_enabled = $this->discordEnabled;
 | 
			
		||||
            $this->team->discord_webhook_url = $this->discordWebhookUrl;
 | 
			
		||||
            $this->team->discord_notifications_test = $this->discordNotificationsTest;
 | 
			
		||||
            $this->team->discord_notifications_deployments = $this->discordNotificationsDeployments;
 | 
			
		||||
            $this->team->discord_notifications_status_changes = $this->discordNotificationsStatusChanges;
 | 
			
		||||
            $this->team->discord_notifications_database_backups = $this->discordNotificationsDatabaseBackups;
 | 
			
		||||
            $this->team->discord_notifications_scheduled_tasks = $this->discordNotificationsScheduledTasks;
 | 
			
		||||
            $this->team->discord_notifications_server_disk_usage = $this->discordNotificationsServerDiskUsage;
 | 
			
		||||
            try {
 | 
			
		||||
                $this->saveModel();
 | 
			
		||||
            } catch (\Throwable $e) {
 | 
			
		||||
                return handleError($e, $this);
 | 
			
		||||
            }
 | 
			
		||||
        } else {
 | 
			
		||||
            $this->discordEnabled = $this->team->discord_enabled;
 | 
			
		||||
            $this->discordWebhookUrl = $this->team->discord_webhook_url;
 | 
			
		||||
            $this->discordNotificationsTest = $this->team->discord_notifications_test;
 | 
			
		||||
            $this->discordNotificationsDeployments = $this->team->discord_notifications_deployments;
 | 
			
		||||
            $this->discordNotificationsStatusChanges = $this->team->discord_notifications_status_changes;
 | 
			
		||||
            $this->discordNotificationsDatabaseBackups = $this->team->discord_notifications_database_backups;
 | 
			
		||||
            $this->discordNotificationsScheduledTasks = $this->team->discord_notifications_scheduled_tasks;
 | 
			
		||||
            $this->discordNotificationsServerDiskUsage = $this->team->discord_notifications_server_disk_usage;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function instantSave()
 | 
			
		||||
    {
 | 
			
		||||
        try {
 | 
			
		||||
            $this->submit();
 | 
			
		||||
        } catch (\Throwable) {
 | 
			
		||||
            $this->team->discord_enabled = false;
 | 
			
		||||
            $this->validate();
 | 
			
		||||
            $this->syncData(true);
 | 
			
		||||
        } catch (\Throwable $e) {
 | 
			
		||||
            return handleError($e, $this);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function submit()
 | 
			
		||||
    {
 | 
			
		||||
        try {
 | 
			
		||||
            $this->resetErrorBag();
 | 
			
		||||
        $this->validate();
 | 
			
		||||
            $this->syncData(true);
 | 
			
		||||
            $this->saveModel();
 | 
			
		||||
        } catch (\Throwable $e) {
 | 
			
		||||
            return handleError($e, $this);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function saveModel()
 | 
			
		||||
@@ -56,8 +103,12 @@ class Discord extends Component
 | 
			
		||||
 | 
			
		||||
    public function sendTestNotification()
 | 
			
		||||
    {
 | 
			
		||||
        $this->team?->notify(new Test);
 | 
			
		||||
        try {
 | 
			
		||||
            $this->team->notify(new Test);
 | 
			
		||||
            $this->dispatch('success', 'Test notification sent.');
 | 
			
		||||
        } catch (\Throwable $e) {
 | 
			
		||||
            return handleError($e, $this);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function render()
 | 
			
		||||
 
 | 
			
		||||
@@ -3,24 +3,17 @@
 | 
			
		||||
namespace App\Livewire\Project;
 | 
			
		||||
 | 
			
		||||
use App\Models\Project;
 | 
			
		||||
use Livewire\Attributes\Rule;
 | 
			
		||||
use Livewire\Component;
 | 
			
		||||
 | 
			
		||||
class AddEmpty extends Component
 | 
			
		||||
{
 | 
			
		||||
    public string $name = '';
 | 
			
		||||
    #[Rule(['required', 'string', 'min:3'])]
 | 
			
		||||
    public string $name;
 | 
			
		||||
 | 
			
		||||
    #[Rule(['nullable', 'string'])]
 | 
			
		||||
    public string $description = '';
 | 
			
		||||
 | 
			
		||||
    protected $rules = [
 | 
			
		||||
        'name' => 'required|string|min:3',
 | 
			
		||||
        'description' => 'nullable|string',
 | 
			
		||||
    ];
 | 
			
		||||
 | 
			
		||||
    protected $validationAttributes = [
 | 
			
		||||
        'name' => 'Project Name',
 | 
			
		||||
        'description' => 'Project Description',
 | 
			
		||||
    ];
 | 
			
		||||
 | 
			
		||||
    public function submit()
 | 
			
		||||
    {
 | 
			
		||||
        try {
 | 
			
		||||
@@ -34,8 +27,6 @@ class AddEmpty extends Component
 | 
			
		||||
            return redirect()->route('project.show', $project->uuid);
 | 
			
		||||
        } catch (\Throwable $e) {
 | 
			
		||||
            return handleError($e, $this);
 | 
			
		||||
        } finally {
 | 
			
		||||
            $this->name = '';
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -1,44 +0,0 @@
 | 
			
		||||
<?php
 | 
			
		||||
 | 
			
		||||
namespace App\Livewire\Project;
 | 
			
		||||
 | 
			
		||||
use App\Models\Environment;
 | 
			
		||||
use App\Models\Project;
 | 
			
		||||
use Livewire\Component;
 | 
			
		||||
 | 
			
		||||
class AddEnvironment extends Component
 | 
			
		||||
{
 | 
			
		||||
    public Project $project;
 | 
			
		||||
 | 
			
		||||
    public string $name = '';
 | 
			
		||||
 | 
			
		||||
    public string $description = '';
 | 
			
		||||
 | 
			
		||||
    protected $rules = [
 | 
			
		||||
        'name' => '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.resource.index', [
 | 
			
		||||
                'project_uuid' => $this->project->uuid,
 | 
			
		||||
                'environment_name' => $environment->name,
 | 
			
		||||
            ]);
 | 
			
		||||
        } catch (\Throwable $e) {
 | 
			
		||||
            handleError($e, $this);
 | 
			
		||||
        } finally {
 | 
			
		||||
            $this->name = '';
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -4,55 +4,92 @@ namespace App\Livewire\Project\Application;
 | 
			
		||||
 | 
			
		||||
use App\Models\Application;
 | 
			
		||||
use App\Models\PrivateKey;
 | 
			
		||||
use Livewire\Attributes\Locked;
 | 
			
		||||
use Livewire\Attributes\Rule;
 | 
			
		||||
use Livewire\Component;
 | 
			
		||||
 | 
			
		||||
class Source extends Component
 | 
			
		||||
{
 | 
			
		||||
    public $applicationId;
 | 
			
		||||
 | 
			
		||||
    public Application $application;
 | 
			
		||||
 | 
			
		||||
    public $private_keys;
 | 
			
		||||
    #[Locked]
 | 
			
		||||
    public $privateKeys;
 | 
			
		||||
 | 
			
		||||
    protected $rules = [
 | 
			
		||||
        'application.git_repository' => 'required',
 | 
			
		||||
        'application.git_branch' => 'required',
 | 
			
		||||
        'application.git_commit_sha' => 'nullable',
 | 
			
		||||
    ];
 | 
			
		||||
    #[Rule(['nullable', 'string'])]
 | 
			
		||||
    public ?string $privateKeyName = null;
 | 
			
		||||
 | 
			
		||||
    protected $validationAttributes = [
 | 
			
		||||
        'application.git_repository' => 'repository',
 | 
			
		||||
        'application.git_branch' => 'branch',
 | 
			
		||||
        'application.git_commit_sha' => 'commit sha',
 | 
			
		||||
    ];
 | 
			
		||||
    #[Rule(['nullable', 'integer'])]
 | 
			
		||||
    public ?int $privateKeyId = null;
 | 
			
		||||
 | 
			
		||||
    #[Rule(['required', 'string'])]
 | 
			
		||||
    public string $gitRepository;
 | 
			
		||||
 | 
			
		||||
    #[Rule(['required', 'string'])]
 | 
			
		||||
    public string $gitBranch;
 | 
			
		||||
 | 
			
		||||
    #[Rule(['nullable', 'string'])]
 | 
			
		||||
    public ?string $gitCommitSha = null;
 | 
			
		||||
 | 
			
		||||
    public function mount()
 | 
			
		||||
    {
 | 
			
		||||
        $this->get_private_keys();
 | 
			
		||||
        try {
 | 
			
		||||
            $this->syncData();
 | 
			
		||||
            $this->getPrivateKeys();
 | 
			
		||||
        } catch (\Throwable $e) {
 | 
			
		||||
            handleError($e, $this);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private function get_private_keys()
 | 
			
		||||
    public function syncData(bool $toModel = false)
 | 
			
		||||
    {
 | 
			
		||||
        $this->private_keys = PrivateKey::whereTeamId(currentTeam()->id)->get()->reject(function ($key) {
 | 
			
		||||
            return $key->id == $this->application->private_key_id;
 | 
			
		||||
        if ($toModel) {
 | 
			
		||||
            $this->validate();
 | 
			
		||||
            $this->application->update([
 | 
			
		||||
                'git_repository' => $this->gitRepository,
 | 
			
		||||
                'git_branch' => $this->gitBranch,
 | 
			
		||||
                'git_commit_sha' => $this->gitCommitSha,
 | 
			
		||||
                'private_key_id' => $this->privateKeyId,
 | 
			
		||||
            ]);
 | 
			
		||||
        } else {
 | 
			
		||||
            $this->gitRepository = $this->application->git_repository;
 | 
			
		||||
            $this->gitBranch = $this->application->git_branch;
 | 
			
		||||
            $this->gitCommitSha = $this->application->git_commit_sha;
 | 
			
		||||
            $this->privateKeyId = $this->application->private_key_id;
 | 
			
		||||
            $this->privateKeyName = data_get($this->application, 'private_key.name');
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private function getPrivateKeys()
 | 
			
		||||
    {
 | 
			
		||||
        $this->privateKeys = PrivateKey::whereTeamId(currentTeam()->id)->get()->reject(function ($key) {
 | 
			
		||||
            return $key->id == $this->privateKeyId;
 | 
			
		||||
        });
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function setPrivateKey(int $private_key_id)
 | 
			
		||||
    public function setPrivateKey(int $privateKeyId)
 | 
			
		||||
    {
 | 
			
		||||
        $this->application->private_key_id = $private_key_id;
 | 
			
		||||
        $this->application->save();
 | 
			
		||||
        try {
 | 
			
		||||
            $this->privateKeyId = $privateKeyId;
 | 
			
		||||
            $this->syncData(true);
 | 
			
		||||
            $this->getPrivateKeys();
 | 
			
		||||
            $this->application->refresh();
 | 
			
		||||
        $this->get_private_keys();
 | 
			
		||||
            $this->privateKeyName = $this->application->private_key->name;
 | 
			
		||||
            $this->dispatch('success', 'Private key updated!');
 | 
			
		||||
        } catch (\Throwable $e) {
 | 
			
		||||
            return handleError($e, $this);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function submit()
 | 
			
		||||
    {
 | 
			
		||||
        $this->validate();
 | 
			
		||||
        if (! $this->application->git_commit_sha) {
 | 
			
		||||
            $this->application->git_commit_sha = 'HEAD';
 | 
			
		||||
        try {
 | 
			
		||||
            if (str($this->gitCommitSha)->isEmpty()) {
 | 
			
		||||
                $this->gitCommitSha = 'HEAD';
 | 
			
		||||
            }
 | 
			
		||||
        $this->application->save();
 | 
			
		||||
            $this->syncData(true);
 | 
			
		||||
            $this->dispatch('success', 'Application source updated!');
 | 
			
		||||
        } catch (\Throwable $e) {
 | 
			
		||||
            return handleError($e, $this);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -3,34 +3,47 @@
 | 
			
		||||
namespace App\Livewire\Project;
 | 
			
		||||
 | 
			
		||||
use App\Models\Project;
 | 
			
		||||
use Livewire\Attributes\Rule;
 | 
			
		||||
use Livewire\Component;
 | 
			
		||||
 | 
			
		||||
class Edit extends Component
 | 
			
		||||
{
 | 
			
		||||
    public Project $project;
 | 
			
		||||
 | 
			
		||||
    protected $rules = [
 | 
			
		||||
        'project.name' => 'required|min:3|max:255',
 | 
			
		||||
        'project.description' => 'nullable|string|max:255',
 | 
			
		||||
    ];
 | 
			
		||||
    #[Rule(['required', 'string', 'min:3', 'max:255'])]
 | 
			
		||||
    public string $name;
 | 
			
		||||
 | 
			
		||||
    public function mount()
 | 
			
		||||
    #[Rule(['nullable', 'string', 'max:255'])]
 | 
			
		||||
    public ?string $description = null;
 | 
			
		||||
 | 
			
		||||
    public function mount(string $project_uuid)
 | 
			
		||||
    {
 | 
			
		||||
        $projectUuid = request()->route('project_uuid');
 | 
			
		||||
        $teamId = currentTeam()->id;
 | 
			
		||||
        $project = Project::where('team_id', $teamId)->where('uuid', $projectUuid)->first();
 | 
			
		||||
        if (! $project) {
 | 
			
		||||
            return redirect()->route('dashboard');
 | 
			
		||||
        try {
 | 
			
		||||
            $this->project = Project::where('team_id', currentTeam()->id)->where('uuid', $project_uuid)->firstOrFail();
 | 
			
		||||
            $this->syncData();
 | 
			
		||||
        } catch (\Throwable $e) {
 | 
			
		||||
            return handleError($e, $this);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function syncData(bool $toModel = false)
 | 
			
		||||
    {
 | 
			
		||||
        if ($toModel) {
 | 
			
		||||
            $this->validate();
 | 
			
		||||
            $this->project->update([
 | 
			
		||||
                'name' => $this->name,
 | 
			
		||||
                'description' => $this->description,
 | 
			
		||||
            ]);
 | 
			
		||||
        } else {
 | 
			
		||||
            $this->name = $this->project->name;
 | 
			
		||||
            $this->description = $this->project->description;
 | 
			
		||||
        }
 | 
			
		||||
        $this->project = $project;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function submit()
 | 
			
		||||
    {
 | 
			
		||||
        try {
 | 
			
		||||
            $this->validate();
 | 
			
		||||
            $this->project->save();
 | 
			
		||||
            $this->dispatch('saved');
 | 
			
		||||
            $this->syncData(true);
 | 
			
		||||
            $this->dispatch('success', 'Project updated.');
 | 
			
		||||
        } catch (\Throwable $e) {
 | 
			
		||||
            return handleError($e, $this);
 | 
			
		||||
 
 | 
			
		||||
@@ -4,6 +4,8 @@ namespace App\Livewire\Project;
 | 
			
		||||
 | 
			
		||||
use App\Models\Application;
 | 
			
		||||
use App\Models\Project;
 | 
			
		||||
use Livewire\Attributes\Locked;
 | 
			
		||||
use Livewire\Attributes\Rule;
 | 
			
		||||
use Livewire\Component;
 | 
			
		||||
 | 
			
		||||
class EnvironmentEdit extends Component
 | 
			
		||||
@@ -12,29 +14,45 @@ class EnvironmentEdit extends Component
 | 
			
		||||
 | 
			
		||||
    public Application $application;
 | 
			
		||||
 | 
			
		||||
    #[Locked]
 | 
			
		||||
    public $environment;
 | 
			
		||||
 | 
			
		||||
    public array $parameters;
 | 
			
		||||
    #[Rule(['required', 'string', 'min:3', 'max:255'])]
 | 
			
		||||
    public string $name;
 | 
			
		||||
 | 
			
		||||
    protected $rules = [
 | 
			
		||||
        'environment.name' => 'required|min:3|max:255',
 | 
			
		||||
        'environment.description' => 'nullable|min:3|max:255',
 | 
			
		||||
    ];
 | 
			
		||||
    #[Rule(['nullable', 'string', 'max:255'])]
 | 
			
		||||
    public ?string $description = null;
 | 
			
		||||
 | 
			
		||||
    public function mount()
 | 
			
		||||
    public function mount(string $project_uuid, string $environment_name)
 | 
			
		||||
    {
 | 
			
		||||
        $this->parameters = get_route_parameters();
 | 
			
		||||
        $this->project = Project::ownedByCurrentTeam()->where('uuid', request()->route('project_uuid'))->first();
 | 
			
		||||
        $this->environment = $this->project->environments()->where('name', request()->route('environment_name'))->first();
 | 
			
		||||
        try {
 | 
			
		||||
            $this->project = Project::ownedByCurrentTeam()->where('uuid', $project_uuid)->firstOrFail();
 | 
			
		||||
            $this->environment = $this->project->environments()->where('name', $environment_name)->firstOrFail();
 | 
			
		||||
            $this->syncData();
 | 
			
		||||
        } catch (\Throwable $e) {
 | 
			
		||||
            return handleError($e, $this);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function syncData(bool $toModel = false)
 | 
			
		||||
    {
 | 
			
		||||
        if ($toModel) {
 | 
			
		||||
            $this->validate();
 | 
			
		||||
            $this->environment->update([
 | 
			
		||||
                'name' => $this->name,
 | 
			
		||||
                'description' => $this->description,
 | 
			
		||||
            ]);
 | 
			
		||||
        } else {
 | 
			
		||||
            $this->name = $this->environment->name;
 | 
			
		||||
            $this->description = $this->environment->description;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function submit()
 | 
			
		||||
    {
 | 
			
		||||
        $this->validate();
 | 
			
		||||
        try {
 | 
			
		||||
            $this->environment->save();
 | 
			
		||||
 | 
			
		||||
            return redirect()->route('project.environment.edit', ['project_uuid' => $this->project->uuid, 'environment_name' => $this->environment->name]);
 | 
			
		||||
            $this->syncData(true);
 | 
			
		||||
            $this->redirectRoute('project.environment.edit', ['environment_name' => $this->environment->name, 'project_uuid' => $this->project->uuid]);
 | 
			
		||||
        } catch (\Throwable $e) {
 | 
			
		||||
            return handleError($e, $this);
 | 
			
		||||
        }
 | 
			
		||||
 
 | 
			
		||||
@@ -2,27 +2,46 @@
 | 
			
		||||
 | 
			
		||||
namespace App\Livewire\Project;
 | 
			
		||||
 | 
			
		||||
use App\Models\Environment;
 | 
			
		||||
use App\Models\Project;
 | 
			
		||||
use Livewire\Attributes\Rule;
 | 
			
		||||
use Livewire\Component;
 | 
			
		||||
 | 
			
		||||
class Show extends Component
 | 
			
		||||
{
 | 
			
		||||
    public Project $project;
 | 
			
		||||
 | 
			
		||||
    public $environments;
 | 
			
		||||
    #[Rule(['required', 'string', 'min:3'])]
 | 
			
		||||
    public string $name;
 | 
			
		||||
 | 
			
		||||
    public function mount()
 | 
			
		||||
    #[Rule(['nullable', 'string'])]
 | 
			
		||||
    public ?string $description = null;
 | 
			
		||||
 | 
			
		||||
    public function mount(string $project_uuid)
 | 
			
		||||
    {
 | 
			
		||||
        $projectUuid = request()->route('project_uuid');
 | 
			
		||||
        $teamId = currentTeam()->id;
 | 
			
		||||
 | 
			
		||||
        $project = Project::where('team_id', $teamId)->where('uuid', $projectUuid)->first();
 | 
			
		||||
        if (! $project) {
 | 
			
		||||
            return redirect()->route('dashboard');
 | 
			
		||||
        try {
 | 
			
		||||
            $this->project = Project::where('team_id', currentTeam()->id)->where('uuid', $project_uuid)->firstOrFail();
 | 
			
		||||
        } catch (\Throwable $e) {
 | 
			
		||||
            return handleError($e, $this);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
        $this->environments = $project->environments->sortBy('created_at');
 | 
			
		||||
        $this->project = $project;
 | 
			
		||||
    public function submit()
 | 
			
		||||
    {
 | 
			
		||||
        try {
 | 
			
		||||
            $this->validate();
 | 
			
		||||
            $environment = Environment::create([
 | 
			
		||||
                'name' => $this->name,
 | 
			
		||||
                'project_id' => $this->project->id,
 | 
			
		||||
            ]);
 | 
			
		||||
 | 
			
		||||
            return redirect()->route('project.resource.index', [
 | 
			
		||||
                'project_uuid' => $this->project->uuid,
 | 
			
		||||
                'environment_name' => $environment->name,
 | 
			
		||||
            ]);
 | 
			
		||||
        } catch (\Throwable $e) {
 | 
			
		||||
            handleError($e, $this);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function render()
 | 
			
		||||
 
 | 
			
		||||
@@ -1,6 +1,6 @@
 | 
			
		||||
<?php
 | 
			
		||||
 | 
			
		||||
namespace App\Livewire\Server\Destination;
 | 
			
		||||
namespace App\Livewire\Server;
 | 
			
		||||
 | 
			
		||||
use App\Models\Server;
 | 
			
		||||
use App\Models\StandaloneDocker;
 | 
			
		||||
@@ -8,7 +8,7 @@ use App\Models\SwarmDocker;
 | 
			
		||||
use Illuminate\Support\Collection;
 | 
			
		||||
use Livewire\Component;
 | 
			
		||||
 | 
			
		||||
class Show extends Component
 | 
			
		||||
class Destinations extends Component
 | 
			
		||||
{
 | 
			
		||||
    public Server $server;
 | 
			
		||||
 | 
			
		||||
@@ -86,6 +86,6 @@ class Show extends Component
 | 
			
		||||
 | 
			
		||||
    public function render()
 | 
			
		||||
    {
 | 
			
		||||
        return view('livewire.server.destination.show');
 | 
			
		||||
        return view('livewire.server.destinations');
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -3,102 +3,83 @@
 | 
			
		||||
namespace App\Livewire;
 | 
			
		||||
 | 
			
		||||
use App\Models\InstanceSettings;
 | 
			
		||||
use Livewire\Attributes\Rule;
 | 
			
		||||
use Livewire\Component;
 | 
			
		||||
 | 
			
		||||
class SettingsEmail extends Component
 | 
			
		||||
{
 | 
			
		||||
    public InstanceSettings $settings;
 | 
			
		||||
 | 
			
		||||
    public string $emails;
 | 
			
		||||
    #[Rule(['boolean'])]
 | 
			
		||||
    public bool $smtpEnabled = false;
 | 
			
		||||
 | 
			
		||||
    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_from_address' => 'required|email',
 | 
			
		||||
        'settings.smtp_from_name' => 'required',
 | 
			
		||||
        'settings.resend_enabled' => 'nullable|boolean',
 | 
			
		||||
        'settings.resend_api_key' => 'nullable',
 | 
			
		||||
    #[Rule(['nullable', 'string'])]
 | 
			
		||||
    public ?string $smtpHost = null;
 | 
			
		||||
 | 
			
		||||
    ];
 | 
			
		||||
    #[Rule(['nullable', 'numeric', 'min:1', 'max:65535'])]
 | 
			
		||||
    public ?int $smtpPort = null;
 | 
			
		||||
 | 
			
		||||
    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_timeout' => 'Timeout',
 | 
			
		||||
        'settings.resend_api_key' => 'Resend API Key',
 | 
			
		||||
    ];
 | 
			
		||||
    #[Rule(['nullable', 'string'])]
 | 
			
		||||
    public ?string $smtpEncryption = null;
 | 
			
		||||
 | 
			
		||||
    #[Rule(['nullable', 'string'])]
 | 
			
		||||
    public ?string $smtpUsername = null;
 | 
			
		||||
 | 
			
		||||
    #[Rule(['nullable'])]
 | 
			
		||||
    public ?string $smtpPassword = null;
 | 
			
		||||
 | 
			
		||||
    #[Rule(['nullable', 'numeric'])]
 | 
			
		||||
    public ?int $smtpTimeout = null;
 | 
			
		||||
 | 
			
		||||
    #[Rule(['nullable', 'email'])]
 | 
			
		||||
    public ?string $smtpFromAddress = null;
 | 
			
		||||
 | 
			
		||||
    #[Rule(['nullable', 'string'])]
 | 
			
		||||
    public ?string $smtpFromName = null;
 | 
			
		||||
 | 
			
		||||
    #[Rule(['boolean'])]
 | 
			
		||||
    public bool $resendEnabled = false;
 | 
			
		||||
 | 
			
		||||
    #[Rule(['nullable', 'string'])]
 | 
			
		||||
    public ?string $resendApiKey = null;
 | 
			
		||||
 | 
			
		||||
    public function mount()
 | 
			
		||||
    {
 | 
			
		||||
        if (isInstanceAdmin()) {
 | 
			
		||||
            $this->settings = instanceSettings();
 | 
			
		||||
            $this->emails = auth()->user()->email;
 | 
			
		||||
        } else {
 | 
			
		||||
        if (isInstanceAdmin() === false) {
 | 
			
		||||
            return redirect()->route('dashboard');
 | 
			
		||||
        }
 | 
			
		||||
        $this->settings = instanceSettings();
 | 
			
		||||
        $this->syncData();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function submitFromFields()
 | 
			
		||||
    public function syncData(bool $toModel = false)
 | 
			
		||||
    {
 | 
			
		||||
        try {
 | 
			
		||||
            $this->resetErrorBag();
 | 
			
		||||
            $this->validate([
 | 
			
		||||
                'settings.smtp_from_address' => 'required|email',
 | 
			
		||||
                'settings.smtp_from_name' => 'required',
 | 
			
		||||
            ]);
 | 
			
		||||
        if ($toModel) {
 | 
			
		||||
            $this->validate();
 | 
			
		||||
            $this->settings->smtp_enabled = $this->smtpEnabled;
 | 
			
		||||
            $this->settings->smtp_host = $this->smtpHost;
 | 
			
		||||
            $this->settings->smtp_port = $this->smtpPort;
 | 
			
		||||
            $this->settings->smtp_encryption = $this->smtpEncryption;
 | 
			
		||||
            $this->settings->smtp_username = $this->smtpUsername;
 | 
			
		||||
            $this->settings->smtp_password = $this->smtpPassword;
 | 
			
		||||
            $this->settings->smtp_timeout = $this->smtpTimeout;
 | 
			
		||||
 | 
			
		||||
            $this->settings->resend_enabled = $this->resendEnabled;
 | 
			
		||||
            $this->settings->resend_api_key = $this->resendApiKey;
 | 
			
		||||
            $this->settings->save();
 | 
			
		||||
            $this->dispatch('success', 'Settings saved.');
 | 
			
		||||
        } catch (\Throwable $e) {
 | 
			
		||||
            return handleError($e, $this);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
        } else {
 | 
			
		||||
            $this->smtpEnabled = $this->settings->smtp_enabled;
 | 
			
		||||
            $this->smtpHost = $this->settings->smtp_host;
 | 
			
		||||
            $this->smtpPort = $this->settings->smtp_port;
 | 
			
		||||
            $this->smtpEncryption = $this->settings->smtp_encryption;
 | 
			
		||||
            $this->smtpUsername = $this->settings->smtp_username;
 | 
			
		||||
            $this->smtpPassword = $this->settings->smtp_password;
 | 
			
		||||
            $this->smtpTimeout = $this->settings->smtp_timeout;
 | 
			
		||||
            $this->smtpFromAddress = $this->settings->smtp_from_address;
 | 
			
		||||
            $this->smtpFromName = $this->settings->smtp_from_name;
 | 
			
		||||
 | 
			
		||||
    public function submitResend()
 | 
			
		||||
    {
 | 
			
		||||
        try {
 | 
			
		||||
            $this->resetErrorBag();
 | 
			
		||||
            $this->validate([
 | 
			
		||||
                'settings.smtp_from_address' => 'required|email',
 | 
			
		||||
                'settings.smtp_from_name' => 'required',
 | 
			
		||||
                'settings.resend_api_key' => 'required',
 | 
			
		||||
            ]);
 | 
			
		||||
            $this->settings->save();
 | 
			
		||||
            $this->dispatch('success', 'Settings saved.');
 | 
			
		||||
        } catch (\Throwable $e) {
 | 
			
		||||
            $this->settings->resend_enabled = false;
 | 
			
		||||
 | 
			
		||||
            return handleError($e, $this);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function instantSaveResend()
 | 
			
		||||
    {
 | 
			
		||||
        try {
 | 
			
		||||
            $this->settings->smtp_enabled = false;
 | 
			
		||||
            $this->submitResend();
 | 
			
		||||
        } catch (\Throwable $e) {
 | 
			
		||||
            return handleError($e, $this);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function instantSave()
 | 
			
		||||
    {
 | 
			
		||||
        try {
 | 
			
		||||
            $this->settings->resend_enabled = false;
 | 
			
		||||
            $this->submit();
 | 
			
		||||
        } catch (\Throwable $e) {
 | 
			
		||||
            return handleError($e, $this);
 | 
			
		||||
            $this->resendEnabled = $this->settings->resend_enabled;
 | 
			
		||||
            $this->resendApiKey = $this->settings->resend_api_key;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
@@ -106,20 +87,29 @@ class SettingsEmail extends Component
 | 
			
		||||
    {
 | 
			
		||||
        try {
 | 
			
		||||
            $this->resetErrorBag();
 | 
			
		||||
            $this->validate([
 | 
			
		||||
                'settings.smtp_from_address' => 'required|email',
 | 
			
		||||
                'settings.smtp_from_name' => 'required',
 | 
			
		||||
                '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',
 | 
			
		||||
            ]);
 | 
			
		||||
            $this->settings->save();
 | 
			
		||||
            $this->syncData(true);
 | 
			
		||||
            $this->dispatch('success', 'Settings saved.');
 | 
			
		||||
        } catch (\Throwable $e) {
 | 
			
		||||
            return handleError($e, $this);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function instantSave(string $type)
 | 
			
		||||
    {
 | 
			
		||||
        try {
 | 
			
		||||
            if ($type === 'SMTP') {
 | 
			
		||||
                $this->resendEnabled = false;
 | 
			
		||||
            } else {
 | 
			
		||||
                $this->smtpEnabled = false;
 | 
			
		||||
            }
 | 
			
		||||
            $this->syncData(true);
 | 
			
		||||
            if ($this->smtpEnabled || $this->resendEnabled) {
 | 
			
		||||
                $this->dispatch('success', "{$type} enabled.");
 | 
			
		||||
            } else {
 | 
			
		||||
                $this->dispatch('success', "{$type} disabled.");
 | 
			
		||||
            }
 | 
			
		||||
        } catch (\Throwable $e) {
 | 
			
		||||
            return handleError($e, $this);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -74,6 +74,9 @@ class AdminView extends Component
 | 
			
		||||
 | 
			
		||||
    public function delete($id, $password)
 | 
			
		||||
    {
 | 
			
		||||
        if (! isInstanceAdmin()) {
 | 
			
		||||
            return redirect()->route('dashboard');
 | 
			
		||||
        }
 | 
			
		||||
        if (! data_get(InstanceSettings::get(), 'disable_two_step_confirmation')) {
 | 
			
		||||
            if (! Hash::check($password, Auth::user()->password)) {
 | 
			
		||||
                $this->addError('password', 'The provided password is incorrect.');
 | 
			
		||||
 
 | 
			
		||||
@@ -117,14 +117,31 @@ class Application extends BaseModel
 | 
			
		||||
            if ($application->fqdn === '') {
 | 
			
		||||
                $application->fqdn = null;
 | 
			
		||||
            }
 | 
			
		||||
            $application->forceFill([
 | 
			
		||||
                'fqdn' => $application->fqdn,
 | 
			
		||||
                'install_command' => str($application->install_command)->trim(),
 | 
			
		||||
                'build_command' => str($application->build_command)->trim(),
 | 
			
		||||
                'start_command' => str($application->start_command)->trim(),
 | 
			
		||||
                'base_directory' => str($application->base_directory)->trim(),
 | 
			
		||||
                'publish_directory' => str($application->publish_directory)->trim(),
 | 
			
		||||
            ]);
 | 
			
		||||
            $payload = [];
 | 
			
		||||
            if ($application->isDirty('fqdn')) {
 | 
			
		||||
                $payload['fqdn'] = $application->fqdn;
 | 
			
		||||
            }
 | 
			
		||||
            if ($application->isDirty('install_command')) {
 | 
			
		||||
                $payload['install_command'] = str($application->install_command)->trim();
 | 
			
		||||
            }
 | 
			
		||||
            if ($application->isDirty('build_command')) {
 | 
			
		||||
                $payload['build_command'] = str($application->build_command)->trim();
 | 
			
		||||
            }
 | 
			
		||||
            if ($application->isDirty('start_command')) {
 | 
			
		||||
                $payload['start_command'] = str($application->start_command)->trim();
 | 
			
		||||
            }
 | 
			
		||||
            if ($application->isDirty('base_directory')) {
 | 
			
		||||
                $payload['base_directory'] = str($application->base_directory)->trim();
 | 
			
		||||
            }
 | 
			
		||||
            if ($application->isDirty('publish_directory')) {
 | 
			
		||||
                $payload['publish_directory'] = str($application->publish_directory)->trim();
 | 
			
		||||
            }
 | 
			
		||||
            if ($application->isDirty('status')) {
 | 
			
		||||
                $payload['last_online_at'] = now();
 | 
			
		||||
            }
 | 
			
		||||
            if (count($payload) > 0) {
 | 
			
		||||
                $application->forceFill($payload);
 | 
			
		||||
            }
 | 
			
		||||
        });
 | 
			
		||||
        static::created(function ($application) {
 | 
			
		||||
            ApplicationSetting::create([
 | 
			
		||||
 
 | 
			
		||||
@@ -28,6 +28,11 @@ class ApplicationPreview extends BaseModel
 | 
			
		||||
                });
 | 
			
		||||
            }
 | 
			
		||||
        });
 | 
			
		||||
        static::saving(function ($preview) {
 | 
			
		||||
            if ($preview->isDirty('status')) {
 | 
			
		||||
                $preview->forceFill(['last_online_at' => now()]);
 | 
			
		||||
            }
 | 
			
		||||
        });
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public static function findPreviewByApplicationAndPullId(int $application_id, int $pull_request_id)
 | 
			
		||||
 
 | 
			
		||||
@@ -507,20 +507,6 @@ $schema://$host {
 | 
			
		||||
        return Server::whereTeamId($teamId)->whereRelation('settings', 'is_reachable', true)->whereRelation('settings', 'is_build_server', true);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function skipServer()
 | 
			
		||||
    {
 | 
			
		||||
        if ($this->ip === '1.2.3.4') {
 | 
			
		||||
            // ray('skipping 1.2.3.4');
 | 
			
		||||
            return true;
 | 
			
		||||
        }
 | 
			
		||||
        if ($this->settings->force_disabled === true) {
 | 
			
		||||
            // ray('force_disabled');
 | 
			
		||||
            return true;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        return false;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function isForceDisabled()
 | 
			
		||||
    {
 | 
			
		||||
        return $this->settings->force_disabled;
 | 
			
		||||
@@ -691,7 +677,7 @@ $schema://$host {
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        } else {
 | 
			
		||||
            $containers = instant_remote_process(["docker container inspect $(docker container ls -q) --format '{{json .}}'"], $this, false);
 | 
			
		||||
            $containers = instant_remote_process(["docker container inspect $(docker container ls -aq) --format '{{json .}}'"], $this, false);
 | 
			
		||||
            $containers = format_docker_command_output_to_json($containers);
 | 
			
		||||
            $containerReplicates = collect([]);
 | 
			
		||||
        }
 | 
			
		||||
@@ -917,11 +903,23 @@ $schema://$host {
 | 
			
		||||
        return true;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function skipServer()
 | 
			
		||||
    {
 | 
			
		||||
        if ($this->ip === '1.2.3.4') {
 | 
			
		||||
            return true;
 | 
			
		||||
        }
 | 
			
		||||
        if ($this->settings->force_disabled === true) {
 | 
			
		||||
            return true;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        return false;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function isFunctional()
 | 
			
		||||
    {
 | 
			
		||||
        $isFunctional = $this->settings->is_reachable && $this->settings->is_usable && ! $this->settings->force_disabled;
 | 
			
		||||
        $isFunctional = $this->settings->is_reachable && $this->settings->is_usable && $this->settings->force_disabled === false && $this->ip !== '1.2.3.4';
 | 
			
		||||
 | 
			
		||||
        if (! $isFunctional) {
 | 
			
		||||
        if ($isFunctional === false) {
 | 
			
		||||
            Storage::disk('ssh-mux')->delete($this->muxFilename());
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
@@ -976,10 +974,10 @@ $schema://$host {
 | 
			
		||||
 | 
			
		||||
    public function serverStatus(): bool
 | 
			
		||||
    {
 | 
			
		||||
        if ($this->status() === false) {
 | 
			
		||||
        if ($this->isFunctional() === false) {
 | 
			
		||||
            return false;
 | 
			
		||||
        }
 | 
			
		||||
        if ($this->isFunctional() === false) {
 | 
			
		||||
        if ($this->status() === false) {
 | 
			
		||||
            return false;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
@@ -988,7 +986,7 @@ $schema://$host {
 | 
			
		||||
 | 
			
		||||
    public function status(): bool
 | 
			
		||||
    {
 | 
			
		||||
        if ($this->skipServer()) {
 | 
			
		||||
        if ($this->isFunctional() === false) {
 | 
			
		||||
            return false;
 | 
			
		||||
        }
 | 
			
		||||
        ['uptime' => $uptime] = $this->validateConnection(false);
 | 
			
		||||
 
 | 
			
		||||
@@ -19,6 +19,11 @@ class ServiceApplication extends BaseModel
 | 
			
		||||
            $service->persistentStorages()->delete();
 | 
			
		||||
            $service->fileStorages()->delete();
 | 
			
		||||
        });
 | 
			
		||||
        static::saving(function ($service) {
 | 
			
		||||
            if ($service->isDirty('status')) {
 | 
			
		||||
                $service->forceFill(['last_online_at' => now()]);
 | 
			
		||||
            }
 | 
			
		||||
        });
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function restart()
 | 
			
		||||
 
 | 
			
		||||
@@ -17,6 +17,11 @@ class ServiceDatabase extends BaseModel
 | 
			
		||||
            $service->persistentStorages()->delete();
 | 
			
		||||
            $service->fileStorages()->delete();
 | 
			
		||||
        });
 | 
			
		||||
        static::saving(function ($service) {
 | 
			
		||||
            if ($service->isDirty('status')) {
 | 
			
		||||
                $service->forceFill(['last_online_at' => now()]);
 | 
			
		||||
            }
 | 
			
		||||
        });
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function restart()
 | 
			
		||||
 
 | 
			
		||||
@@ -38,6 +38,11 @@ class StandaloneClickhouse extends BaseModel
 | 
			
		||||
            $database->environment_variables()->delete();
 | 
			
		||||
            $database->tags()->detach();
 | 
			
		||||
        });
 | 
			
		||||
        static::saving(function ($database) {
 | 
			
		||||
            if ($database->isDirty('status')) {
 | 
			
		||||
                $database->forceFill(['last_online_at' => now()]);
 | 
			
		||||
            }
 | 
			
		||||
        });
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    protected function serverStatus(): Attribute
 | 
			
		||||
 
 | 
			
		||||
@@ -38,6 +38,11 @@ class StandaloneDragonfly extends BaseModel
 | 
			
		||||
            $database->environment_variables()->delete();
 | 
			
		||||
            $database->tags()->detach();
 | 
			
		||||
        });
 | 
			
		||||
        static::saving(function ($database) {
 | 
			
		||||
            if ($database->isDirty('status')) {
 | 
			
		||||
                $database->forceFill(['last_online_at' => now()]);
 | 
			
		||||
            }
 | 
			
		||||
        });
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    protected function serverStatus(): Attribute
 | 
			
		||||
 
 | 
			
		||||
@@ -38,6 +38,11 @@ class StandaloneKeydb extends BaseModel
 | 
			
		||||
            $database->environment_variables()->delete();
 | 
			
		||||
            $database->tags()->detach();
 | 
			
		||||
        });
 | 
			
		||||
        static::saving(function ($database) {
 | 
			
		||||
            if ($database->isDirty('status')) {
 | 
			
		||||
                $database->forceFill(['last_online_at' => now()]);
 | 
			
		||||
            }
 | 
			
		||||
        });
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    protected function serverStatus(): Attribute
 | 
			
		||||
 
 | 
			
		||||
@@ -38,6 +38,11 @@ class StandaloneMariadb extends BaseModel
 | 
			
		||||
            $database->environment_variables()->delete();
 | 
			
		||||
            $database->tags()->detach();
 | 
			
		||||
        });
 | 
			
		||||
        static::saving(function ($database) {
 | 
			
		||||
            if ($database->isDirty('status')) {
 | 
			
		||||
                $database->forceFill(['last_online_at' => now()]);
 | 
			
		||||
            }
 | 
			
		||||
        });
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    protected function serverStatus(): Attribute
 | 
			
		||||
 
 | 
			
		||||
@@ -42,6 +42,11 @@ class StandaloneMongodb extends BaseModel
 | 
			
		||||
            $database->environment_variables()->delete();
 | 
			
		||||
            $database->tags()->detach();
 | 
			
		||||
        });
 | 
			
		||||
        static::saving(function ($database) {
 | 
			
		||||
            if ($database->isDirty('status')) {
 | 
			
		||||
                $database->forceFill(['last_online_at' => now()]);
 | 
			
		||||
            }
 | 
			
		||||
        });
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    protected function serverStatus(): Attribute
 | 
			
		||||
 
 | 
			
		||||
@@ -39,6 +39,11 @@ class StandaloneMysql extends BaseModel
 | 
			
		||||
            $database->environment_variables()->delete();
 | 
			
		||||
            $database->tags()->detach();
 | 
			
		||||
        });
 | 
			
		||||
        static::saving(function ($database) {
 | 
			
		||||
            if ($database->isDirty('status')) {
 | 
			
		||||
                $database->forceFill(['last_online_at' => now()]);
 | 
			
		||||
            }
 | 
			
		||||
        });
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    protected function serverStatus(): Attribute
 | 
			
		||||
 
 | 
			
		||||
@@ -39,6 +39,11 @@ class StandalonePostgresql extends BaseModel
 | 
			
		||||
            $database->environment_variables()->delete();
 | 
			
		||||
            $database->tags()->detach();
 | 
			
		||||
        });
 | 
			
		||||
        static::saving(function ($database) {
 | 
			
		||||
            if ($database->isDirty('status')) {
 | 
			
		||||
                $database->forceFill(['last_online_at' => now()]);
 | 
			
		||||
            }
 | 
			
		||||
        });
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function workdir()
 | 
			
		||||
 
 | 
			
		||||
@@ -34,6 +34,11 @@ class StandaloneRedis extends BaseModel
 | 
			
		||||
            $database->environment_variables()->delete();
 | 
			
		||||
            $database->tags()->detach();
 | 
			
		||||
        });
 | 
			
		||||
        static::saving(function ($database) {
 | 
			
		||||
            if ($database->isDirty('status')) {
 | 
			
		||||
                $database->forceFill(['last_online_at' => now()]);
 | 
			
		||||
            }
 | 
			
		||||
        });
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    protected function serverStatus(): Attribute
 | 
			
		||||
 
 | 
			
		||||
@@ -75,7 +75,8 @@ class FortifyServiceProvider extends ServiceProvider
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        Fortify::authenticateUsing(function (Request $request) {
 | 
			
		||||
            $user = User::where('email', $request->email)->with('teams')->first();
 | 
			
		||||
            $email = strtolower($request->email);
 | 
			
		||||
            $user = User::where('email', $email)->with('teams')->first();
 | 
			
		||||
            if (
 | 
			
		||||
                $user &&
 | 
			
		||||
                Hash::check($request->password, $user->password)
 | 
			
		||||
 
 | 
			
		||||
@@ -23,6 +23,8 @@ class Input extends Component
 | 
			
		||||
        public bool $isMultiline = false,
 | 
			
		||||
        public string $defaultClass = 'input',
 | 
			
		||||
        public string $autocomplete = 'off',
 | 
			
		||||
        public ?int $minlength = null,
 | 
			
		||||
        public ?int $maxlength = null,
 | 
			
		||||
    ) {}
 | 
			
		||||
 | 
			
		||||
    public function render(): View|Closure|string
 | 
			
		||||
 
 | 
			
		||||
@@ -30,7 +30,9 @@ class Textarea extends Component
 | 
			
		||||
        public bool $realtimeValidation = false,
 | 
			
		||||
        public bool $allowToPeak = true,
 | 
			
		||||
        public string $defaultClass = 'input scrollbar font-mono',
 | 
			
		||||
        public string $defaultClassInput = 'input'
 | 
			
		||||
        public string $defaultClassInput = 'input',
 | 
			
		||||
        public ?int $minlength = null,
 | 
			
		||||
        public ?int $maxlength = null,
 | 
			
		||||
    ) {
 | 
			
		||||
        //
 | 
			
		||||
    }
 | 
			
		||||
 
 | 
			
		||||
@@ -197,6 +197,7 @@ return [
 | 
			
		||||
        'production' => [
 | 
			
		||||
            's6' => [
 | 
			
		||||
                'autoScalingStrategy' => 'size',
 | 
			
		||||
                'minProcesses' => env('HORIZON_MIN_PROCESSES', 1),
 | 
			
		||||
                'maxProcesses' => env('HORIZON_MAX_PROCESSES', 6),
 | 
			
		||||
                'balanceMaxShift' => env('HORIZON_BALANCE_MAX_SHIFT', 1),
 | 
			
		||||
                'balanceCooldown' => env('HORIZON_BALANCE_COOLDOWN', 1),
 | 
			
		||||
@@ -206,6 +207,7 @@ return [
 | 
			
		||||
        'local' => [
 | 
			
		||||
            's6' => [
 | 
			
		||||
                'autoScalingStrategy' => 'size',
 | 
			
		||||
                'minProcesses' => env('HORIZON_MIN_PROCESSES', 1),
 | 
			
		||||
                'maxProcesses' => env('HORIZON_MAX_PROCESSES', 6),
 | 
			
		||||
                'balanceMaxShift' => env('HORIZON_BALANCE_MAX_SHIFT', 1),
 | 
			
		||||
                'balanceCooldown' => env('HORIZON_BALANCE_COOLDOWN', 1),
 | 
			
		||||
 
 | 
			
		||||
@@ -76,8 +76,8 @@ return [
 | 
			
		||||
    */
 | 
			
		||||
 | 
			
		||||
    'queue' => [
 | 
			
		||||
        'connection' => env('TELESCOPE_QUEUE_CONNECTION', null),
 | 
			
		||||
        'queue' => env('TELESCOPE_QUEUE', null),
 | 
			
		||||
        'connection' => env('TELESCOPE_QUEUE_CONNECTION', 'redis'),
 | 
			
		||||
        'queue' => env('TELESCOPE_QUEUE', 'default'),
 | 
			
		||||
    ],
 | 
			
		||||
 | 
			
		||||
    /*
 | 
			
		||||
 
 | 
			
		||||
@@ -0,0 +1,96 @@
 | 
			
		||||
<?php
 | 
			
		||||
 | 
			
		||||
use Illuminate\Database\Migrations\Migration;
 | 
			
		||||
use Illuminate\Database\Schema\Blueprint;
 | 
			
		||||
use Illuminate\Support\Facades\Schema;
 | 
			
		||||
 | 
			
		||||
return new class extends Migration
 | 
			
		||||
{
 | 
			
		||||
    /**
 | 
			
		||||
     * Run the migrations.
 | 
			
		||||
     */
 | 
			
		||||
    public function up(): void
 | 
			
		||||
    {
 | 
			
		||||
        Schema::table('applications', function (Blueprint $table) {
 | 
			
		||||
            $table->timestamp('last_online_at')->default(now())->after('updated_at');
 | 
			
		||||
        });
 | 
			
		||||
        Schema::table('application_previews', function (Blueprint $table) {
 | 
			
		||||
            $table->timestamp('last_online_at')->default(now())->after('updated_at');
 | 
			
		||||
        });
 | 
			
		||||
        Schema::table('service_applications', function (Blueprint $table) {
 | 
			
		||||
            $table->timestamp('last_online_at')->default(now())->after('updated_at');
 | 
			
		||||
        });
 | 
			
		||||
        Schema::table('service_databases', function (Blueprint $table) {
 | 
			
		||||
            $table->timestamp('last_online_at')->default(now())->after('updated_at');
 | 
			
		||||
        });
 | 
			
		||||
        Schema::table('standalone_postgresqls', function (Blueprint $table) {
 | 
			
		||||
            $table->timestamp('last_online_at')->default(now())->after('updated_at');
 | 
			
		||||
        });
 | 
			
		||||
        Schema::table('standalone_redis', function (Blueprint $table) {
 | 
			
		||||
            $table->timestamp('last_online_at')->default(now())->after('updated_at');
 | 
			
		||||
        });
 | 
			
		||||
        Schema::table('standalone_mongodbs', function (Blueprint $table) {
 | 
			
		||||
            $table->timestamp('last_online_at')->default(now())->after('updated_at');
 | 
			
		||||
        });
 | 
			
		||||
        Schema::table('standalone_mysqls', function (Blueprint $table) {
 | 
			
		||||
            $table->timestamp('last_online_at')->default(now())->after('updated_at');
 | 
			
		||||
        });
 | 
			
		||||
        Schema::table('standalone_mariadbs', function (Blueprint $table) {
 | 
			
		||||
            $table->timestamp('last_online_at')->default(now())->after('updated_at');
 | 
			
		||||
        });
 | 
			
		||||
        Schema::table('standalone_keydbs', function (Blueprint $table) {
 | 
			
		||||
            $table->timestamp('last_online_at')->default(now())->after('updated_at');
 | 
			
		||||
        });
 | 
			
		||||
        Schema::table('standalone_dragonflies', function (Blueprint $table) {
 | 
			
		||||
            $table->timestamp('last_online_at')->default(now())->after('updated_at');
 | 
			
		||||
        });
 | 
			
		||||
        Schema::table('standalone_clickhouses', function (Blueprint $table) {
 | 
			
		||||
            $table->timestamp('last_online_at')->default(now())->after('updated_at');
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Reverse the migrations.
 | 
			
		||||
     */
 | 
			
		||||
    public function down(): void
 | 
			
		||||
    {
 | 
			
		||||
        Schema::table('applications', function (Blueprint $table) {
 | 
			
		||||
            $table->dropColumn('last_online_at');
 | 
			
		||||
        });
 | 
			
		||||
        Schema::table('application_previews', function (Blueprint $table) {
 | 
			
		||||
            $table->dropColumn('last_online_at');
 | 
			
		||||
        });
 | 
			
		||||
        Schema::table('service_applications', function (Blueprint $table) {
 | 
			
		||||
            $table->dropColumn('last_online_at');
 | 
			
		||||
        });
 | 
			
		||||
        Schema::table('service_databases', function (Blueprint $table) {
 | 
			
		||||
            $table->dropColumn('last_online_at');
 | 
			
		||||
        });
 | 
			
		||||
        Schema::table('standalone_postgresqls', function (Blueprint $table) {
 | 
			
		||||
            $table->dropColumn('last_online_at');
 | 
			
		||||
        });
 | 
			
		||||
        Schema::table('standalone_redis', function (Blueprint $table) {
 | 
			
		||||
            $table->dropColumn('last_online_at');
 | 
			
		||||
        });
 | 
			
		||||
        Schema::table('standalone_mongodbs', function (Blueprint $table) {
 | 
			
		||||
            $table->dropColumn('last_online_at');
 | 
			
		||||
        });
 | 
			
		||||
        Schema::table('standalone_mysqls', function (Blueprint $table) {
 | 
			
		||||
            $table->dropColumn('last_online_at');
 | 
			
		||||
        });
 | 
			
		||||
        Schema::table('standalone_mariadbs', function (Blueprint $table) {
 | 
			
		||||
            $table->dropColumn('last_online_at');
 | 
			
		||||
        });
 | 
			
		||||
        Schema::table('standalone_keydbs', function (Blueprint $table) {
 | 
			
		||||
            $table->dropColumn('last_online_at');
 | 
			
		||||
        });
 | 
			
		||||
        Schema::table('standalone_dragonflies', function (Blueprint $table) {
 | 
			
		||||
            $table->dropColumn('last_online_at');
 | 
			
		||||
        });
 | 
			
		||||
        Schema::table('standalone_clickhouses', function (Blueprint $table) {
 | 
			
		||||
            $table->dropColumn('last_online_at');
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
    }
 | 
			
		||||
};
 | 
			
		||||
@@ -41,8 +41,9 @@
 | 
			
		||||
            @if ($id !== 'null') wire:model={{ $id }} @endif
 | 
			
		||||
            wire:dirty.class.remove='dark:focus:ring-coolgray-300 dark:ring-coolgray-300'
 | 
			
		||||
            wire:dirty.class="dark:focus:ring-warning dark:ring-warning" wire:loading.attr="disabled"
 | 
			
		||||
            type="{{ $type }}" @disabled($disabled)
 | 
			
		||||
            min="{{ $attributes->get('min') }}" max="{{ $attributes->get('max') }}"
 | 
			
		||||
            type="{{ $type }}" @disabled($disabled) min="{{ $attributes->get('min') }}"
 | 
			
		||||
            max="{{ $attributes->get('max') }}" minlength="{{ $attributes->get('minlength') }}"
 | 
			
		||||
            maxlength="{{ $attributes->get('maxlength') }}"
 | 
			
		||||
            @if ($id !== 'null') id={{ $id }} @endif name="{{ $name }}"
 | 
			
		||||
            placeholder="{{ $attributes->get('placeholder') }}">
 | 
			
		||||
    @endif
 | 
			
		||||
 
 | 
			
		||||
@@ -51,8 +51,8 @@
 | 
			
		||||
                    type="{{ $type }}" @readonly($readonly) @disabled($disabled) id="{{ $id }}"
 | 
			
		||||
                    name="{{ $name }}" placeholder="{{ $attributes->get('placeholder') }}"
 | 
			
		||||
                    aria-placeholder="{{ $attributes->get('placeholder') }}">
 | 
			
		||||
                <textarea x-cloak x-show="type !== 'password'" placeholder="{{ $placeholder }}"
 | 
			
		||||
                    {{ $attributes->merge(['class' => $defaultClass]) }}
 | 
			
		||||
                <textarea minlength="{{ $minlength }}" maxlength="{{ $maxlength }}" x-cloak x-show="type !== 'password'"
 | 
			
		||||
                    placeholder="{{ $placeholder }}" {{ $attributes->merge(['class' => $defaultClass]) }}
 | 
			
		||||
                    @if ($realtimeValidation) wire:model.debounce.200ms="{{ $id }}"
 | 
			
		||||
                @else
 | 
			
		||||
            wire:model={{ $value ?? $id }}
 | 
			
		||||
@@ -62,7 +62,8 @@
 | 
			
		||||
 | 
			
		||||
            </div>
 | 
			
		||||
        @else
 | 
			
		||||
            <textarea {{ $allowTab ? '@keydown.tab=handleKeydown' : '' }} placeholder="{{ $placeholder }}"
 | 
			
		||||
            <textarea minlength="{{ $minlength }}" maxlength="{{ $maxlength }}"
 | 
			
		||||
                {{ $allowTab ? '@keydown.tab=handleKeydown' : '' }} placeholder="{{ $placeholder }}"
 | 
			
		||||
                {{ !$spellcheck ? 'spellcheck=false' : '' }} {{ $attributes->merge(['class' => $defaultClass]) }}
 | 
			
		||||
                @if ($realtimeValidation) wire:model.debounce.200ms="{{ $id }}"
 | 
			
		||||
        @else
 | 
			
		||||
 
 | 
			
		||||
@@ -148,7 +148,7 @@
 | 
			
		||||
                    <li>
 | 
			
		||||
                        <a title="Destinations"
 | 
			
		||||
                            class="{{ request()->is('destination*') ? 'menu-item-active menu-item' : 'menu-item' }}"
 | 
			
		||||
                            href="{{ route('destination.all') }}">
 | 
			
		||||
                            href="{{ route('destination.index') }}" wire:navigate>
 | 
			
		||||
 | 
			
		||||
                            <svg xmlns="http://www.w3.org/2000/svg" class="icon" viewBox="0 0 24 24">
 | 
			
		||||
                                <path fill="none" stroke="currentColor" stroke-linecap="round"
 | 
			
		||||
 
 | 
			
		||||
@@ -1,44 +0,0 @@
 | 
			
		||||
<x-layout>
 | 
			
		||||
    <x-slot:title>
 | 
			
		||||
        Destinations | Coolify
 | 
			
		||||
    </x-slot>
 | 
			
		||||
    <div class="flex items-start gap-2">
 | 
			
		||||
        <h1>Destinations</h1>
 | 
			
		||||
        @if ($servers->count() > 0)
 | 
			
		||||
            <x-modal-input buttonTitle="+ Add" title="New Destination">
 | 
			
		||||
                <livewire:destination.new.docker :server_id="$server_id" />
 | 
			
		||||
            </x-modal-input>
 | 
			
		||||
        @endif
 | 
			
		||||
    </div>
 | 
			
		||||
    <div class="subtitle">Network endpoints to deploy your resources.</div>
 | 
			
		||||
    <div class="grid gap-2 lg:grid-cols-1">
 | 
			
		||||
        @forelse ($destinations as $destination)
 | 
			
		||||
            @if ($destination->getMorphClass() === 'App\Models\StandaloneDocker')
 | 
			
		||||
                <a class="box group"
 | 
			
		||||
                    href="{{ route('destination.show', ['destination_uuid' => data_get($destination, 'uuid')]) }}">
 | 
			
		||||
                    <div class="flex flex-col mx-6">
 | 
			
		||||
                        <div class="box-title">{{ $destination->name }}</div>
 | 
			
		||||
                        <div class="box-description">server: {{ $destination->server->name }}</div>
 | 
			
		||||
                    </div>
 | 
			
		||||
                </a>
 | 
			
		||||
            @endif
 | 
			
		||||
            @if ($destination->getMorphClass() === 'App\Models\SwarmDocker')
 | 
			
		||||
                <a class="box group"
 | 
			
		||||
                    href="{{ route('destination.show', ['destination_uuid' => data_get($destination, 'uuid')]) }}">
 | 
			
		||||
                    <div class="flex flex-col mx-6">
 | 
			
		||||
                        <div class="box-title">{{ $destination->name }}</div>
 | 
			
		||||
                        <div class="box-description">server: {{ $destination->server->name }}</div>
 | 
			
		||||
                    </div>
 | 
			
		||||
                </a>
 | 
			
		||||
            @endif
 | 
			
		||||
        @empty
 | 
			
		||||
            <div>
 | 
			
		||||
                @if ($servers->count() === 0)
 | 
			
		||||
                    <div>No servers found. Please add one first.</div>
 | 
			
		||||
                @else
 | 
			
		||||
                    <div>No destinations found.</div>
 | 
			
		||||
                @endif
 | 
			
		||||
            </div>
 | 
			
		||||
        @endforelse
 | 
			
		||||
    </div>
 | 
			
		||||
</x-layout>
 | 
			
		||||
@@ -1,3 +0,0 @@
 | 
			
		||||
<x-layout>
 | 
			
		||||
    <livewire:destination.form :destination="$destination" />
 | 
			
		||||
</x-layout>
 | 
			
		||||
@@ -2,4 +2,4 @@
 | 
			
		||||
 | 
			
		||||
{{ Illuminate\Mail\Markdown::parse('---') }}
 | 
			
		||||
 | 
			
		||||
{{ Illuminate\Mail\Markdown::parse($debug) }}
 | 
			
		||||
{{-- {{ Illuminate\Mail\Markdown::parse($debug) }} --}}
 | 
			
		||||
 
 | 
			
		||||
@@ -6,26 +6,25 @@
 | 
			
		||||
        <x-forms.input wire:model="search" placeholder="Search for a user" />
 | 
			
		||||
        <x-forms.button type="submit">Search</x-forms.button>
 | 
			
		||||
    </form>
 | 
			
		||||
    <h3 class="pt-4">Active Subscribers</h3>
 | 
			
		||||
    <div class="flex flex-wrap gap-2">
 | 
			
		||||
        @forelse ($active_subscribers as $user)
 | 
			
		||||
            <div class="flex gap-2 box" wire:click="switchUser('{{ $user->id }}')">
 | 
			
		||||
                <p>{{ $user->name }}</p>
 | 
			
		||||
                <p>{{ $user->email }}</p>
 | 
			
		||||
            </div>
 | 
			
		||||
        @empty
 | 
			
		||||
            <p>No active subscribers</p>
 | 
			
		||||
        @endforelse
 | 
			
		||||
    </div>
 | 
			
		||||
    <h3 class="pt-4">Inactive Subscribers</h3>
 | 
			
		||||
    <div class="flex flex-col flex-wrap gap-2">
 | 
			
		||||
        @forelse ($inactive_subscribers as $user)
 | 
			
		||||
            <div class="flex gap-2 box" wire:click="switchUser('{{ $user->id }}')">
 | 
			
		||||
                <p>{{ $user->name }}</p>
 | 
			
		||||
                <p>{{ $user->email }}</p>
 | 
			
		||||
            </div>
 | 
			
		||||
        @empty
 | 
			
		||||
            <p>No inactive subscribers</p>
 | 
			
		||||
        @endforelse
 | 
			
		||||
    <div class="pt-4">Active Subscribers : {{ $activeSubscribers }}</div>
 | 
			
		||||
    <div>Inactive Subscribers : {{ $inactiveSubscribers }}</div>
 | 
			
		||||
    @if ($search)
 | 
			
		||||
        @if ($foundUsers->count() > 0)
 | 
			
		||||
            <div class="flex flex-wrap gap-2 pt-4">
 | 
			
		||||
                @foreach ($foundUsers as $user)
 | 
			
		||||
                    <div class="box w-64 group">
 | 
			
		||||
                        <div class="flex flex-col gap-2">
 | 
			
		||||
                            <div class="box-title">{{ $user->name }}</div>
 | 
			
		||||
                            <div class="box-description">{{ $user->email }}</div>
 | 
			
		||||
                            <div class="box-description">Active:
 | 
			
		||||
                                {{ $user->teams()->whereRelation('subscription', 'stripe_subscription_id', '!=', null)->exists() ? 'Yes' : 'No' }}
 | 
			
		||||
                            </div>
 | 
			
		||||
                        </div>
 | 
			
		||||
                    </div>
 | 
			
		||||
                @endforeach
 | 
			
		||||
            </div>
 | 
			
		||||
        @else
 | 
			
		||||
            <div>No users found with {{ $search }}</div>
 | 
			
		||||
        @endif
 | 
			
		||||
    @endif
 | 
			
		||||
</div>
 | 
			
		||||
 
 | 
			
		||||
@@ -94,7 +94,7 @@
 | 
			
		||||
                @endforeach
 | 
			
		||||
            </div>
 | 
			
		||||
        @else
 | 
			
		||||
            @if ($private_keys->count() === 0)
 | 
			
		||||
            @if ($privateKeys->count() === 0)
 | 
			
		||||
                <div class="flex flex-col gap-1">
 | 
			
		||||
                    <div class='font-bold dark:text-warning'>No private keys found.</div>
 | 
			
		||||
                    <div class="flex items-center gap-1">Before you can add your server, first <x-modal-input
 | 
			
		||||
@@ -126,26 +126,17 @@
 | 
			
		||||
        <section>
 | 
			
		||||
            <div class="flex items-center gap-2">
 | 
			
		||||
                <h3 class="pb-2">Deployments</h3>
 | 
			
		||||
                @if (count($deployments_per_server) > 0)
 | 
			
		||||
                @if (count($deploymentsPerServer) > 0)
 | 
			
		||||
                    <x-loading />
 | 
			
		||||
                @endif
 | 
			
		||||
                <x-modal-confirmation
 | 
			
		||||
                    title="Confirm Cleanup Queues?"
 | 
			
		||||
                    buttonTitle="Cleanup Queues"
 | 
			
		||||
                    isErrorButton
 | 
			
		||||
                    submitAction="cleanup_queue"
 | 
			
		||||
                    :actions="['All running Deployment Queues will be cleaned up.']"
 | 
			
		||||
                    :confirmWithText="false"
 | 
			
		||||
                    :confirmWithPassword="false"
 | 
			
		||||
                    step2ButtonText="Permanently Cleanup Deployment Queues"
 | 
			
		||||
                    :dispatchEvent="true"
 | 
			
		||||
                    dispatchEventType="success"
 | 
			
		||||
                    dispatchEventMessage="Deployment Queues cleanup started."
 | 
			
		||||
                />
 | 
			
		||||
                <x-modal-confirmation title="Confirm Cleanup Queues?" buttonTitle="Cleanup Queues" isErrorButton
 | 
			
		||||
                    submitAction="cleanupQueue" :actions="['All running Deployment Queues will be cleaned up.']" :confirmWithText="false" :confirmWithPassword="false"
 | 
			
		||||
                    step2ButtonText="Permanently Cleanup Deployment Queues" :dispatchEvent="true"
 | 
			
		||||
                    dispatchEventType="success" dispatchEventMessage="Deployment Queues cleanup started." />
 | 
			
		||||
            </div>
 | 
			
		||||
            <div wire:poll.3000ms="get_deployments" class="grid grid-cols-1">
 | 
			
		||||
                @forelse ($deployments_per_server as $server_name => $deployments)
 | 
			
		||||
                    <h4 class="pb-2">{{ $server_name }}</h4>
 | 
			
		||||
            <div wire:poll.3000ms="loadDeployments" class="grid grid-cols-1">
 | 
			
		||||
                @forelse ($deploymentsPerServer as $serverName => $deployments)
 | 
			
		||||
                    <h4 class="pb-2">{{ $serverName }}</h4>
 | 
			
		||||
                    <div class="grid grid-cols-1 gap-2 lg:grid-cols-3">
 | 
			
		||||
                        @foreach ($deployments as $deployment)
 | 
			
		||||
                            <a href="{{ data_get($deployment, 'deployment_url') }}" @class([
 | 
			
		||||
@@ -187,7 +178,4 @@
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    </script>
 | 
			
		||||
    {{-- <x-forms.button wire:click='getIptables'>Get IPTABLES</x-forms.button> --}}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
</div>
 | 
			
		||||
 
 | 
			
		||||
@@ -1,29 +0,0 @@
 | 
			
		||||
<div>
 | 
			
		||||
    <form class="flex flex-col">
 | 
			
		||||
        <div class="flex items-center gap-2">
 | 
			
		||||
            <h1>Destination</h1>
 | 
			
		||||
            <x-forms.button wire:click.prevent='submit' type="submit">
 | 
			
		||||
                Save
 | 
			
		||||
            </x-forms.button>
 | 
			
		||||
            @if ($destination->network !== 'coolify')
 | 
			
		||||
                <x-modal-confirmation title="Confirm Destination Deletion?" buttonTitle="Delete Destination" isErrorButton
 | 
			
		||||
                    submitAction="delete" :actions="['This will delete the selected destination/network.']" confirmationText="{{ $destination->name }}"
 | 
			
		||||
                    confirmationLabel="Please confirm the execution of the actions by entering the Destination Name below"
 | 
			
		||||
                    shortConfirmationLabel="Destination Name" :confirmWithPassword="false" step2ButtonText="Permanently Delete" />
 | 
			
		||||
            @endif
 | 
			
		||||
        </div>
 | 
			
		||||
 | 
			
		||||
        @if ($destination->getMorphClass() === 'App\Models\StandaloneDocker')
 | 
			
		||||
            <div class="subtitle ">A Docker network in a non-swarm environment.</div>
 | 
			
		||||
        @else
 | 
			
		||||
            <div class="subtitle ">Your swarm docker network. WIP</div>
 | 
			
		||||
        @endif
 | 
			
		||||
        <div class="flex gap-2">
 | 
			
		||||
            <x-forms.input id="destination.name" label="Name" />
 | 
			
		||||
            <x-forms.input id="destination.server.ip" label="Server IP" readonly />
 | 
			
		||||
            @if ($destination->getMorphClass() === 'App\Models\StandaloneDocker')
 | 
			
		||||
                <x-forms.input id="destination.network" label="Docker Network" readonly />
 | 
			
		||||
            @endif
 | 
			
		||||
        </div>
 | 
			
		||||
    </form>
 | 
			
		||||
</div>
 | 
			
		||||
							
								
								
									
										44
									
								
								resources/views/livewire/destination/index.blade.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										44
									
								
								resources/views/livewire/destination/index.blade.php
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,44 @@
 | 
			
		||||
<div>
 | 
			
		||||
    <x-slot:title>
 | 
			
		||||
        Destinations | Coolify
 | 
			
		||||
    </x-slot>
 | 
			
		||||
    <div class="flex items-start gap-2">
 | 
			
		||||
        <h1>Destinations</h1>
 | 
			
		||||
        @if ($servers->count() > 0)
 | 
			
		||||
            <x-modal-input buttonTitle="+ Add" title="New Destination">
 | 
			
		||||
                <livewire:destination.new.docker />
 | 
			
		||||
            </x-modal-input>
 | 
			
		||||
        @endif
 | 
			
		||||
    </div>
 | 
			
		||||
    <div class="subtitle">Network endpoints to deploy your resources.</div>
 | 
			
		||||
    <div class="grid gap-2 lg:grid-cols-1">
 | 
			
		||||
        @forelse ($servers as $server)
 | 
			
		||||
            @forelse ($server->destinations() as $destination)
 | 
			
		||||
                @if ($destination->getMorphClass() === 'App\Models\StandaloneDocker')
 | 
			
		||||
                    <a class="box group"
 | 
			
		||||
                        href="{{ route('destination.show', ['destination_uuid' => data_get($destination, 'uuid')]) }}"
 | 
			
		||||
                        wire:navigate>
 | 
			
		||||
                        <div class="flex flex-col mx-6">
 | 
			
		||||
                            <div class="box-title">{{ $destination->name }}</div>
 | 
			
		||||
                            <div class="box-description">Server: {{ $destination->server->name }}</div>
 | 
			
		||||
                        </div>
 | 
			
		||||
                    </a>
 | 
			
		||||
                @endif
 | 
			
		||||
                @if ($destination->getMorphClass() === 'App\Models\SwarmDocker')
 | 
			
		||||
                    <a class="box group"
 | 
			
		||||
                        href="{{ route('destination.show', ['destination_uuid' => data_get($destination, 'uuid')]) }}"
 | 
			
		||||
                        wire:navigate>
 | 
			
		||||
                        <div class="flex flex-col mx-6">
 | 
			
		||||
                            <div class="box-title">{{ $destination->name }}</div>
 | 
			
		||||
                            <div class="box-description">server: {{ $destination->server->name }}</div>
 | 
			
		||||
                        </div>
 | 
			
		||||
                    </a>
 | 
			
		||||
                @endif
 | 
			
		||||
            @empty
 | 
			
		||||
                <div>No destinations found.</div>
 | 
			
		||||
            @endforelse
 | 
			
		||||
        @empty
 | 
			
		||||
            <div>No servers found.</div>
 | 
			
		||||
        @endforelse
 | 
			
		||||
    </div>
 | 
			
		||||
</div>
 | 
			
		||||
@@ -5,7 +5,7 @@
 | 
			
		||||
            <x-forms.input id="name" label="Name" required />
 | 
			
		||||
            <x-forms.input id="network" label="Network" required />
 | 
			
		||||
        </div>
 | 
			
		||||
        <x-forms.select id="server_id" label="Select a server" required wire:change="generate_name">
 | 
			
		||||
        <x-forms.select id="serverId" label="Select a server" required wire:change="generateName">
 | 
			
		||||
            <option disabled>Select a server</option>
 | 
			
		||||
            @foreach ($servers as $server)
 | 
			
		||||
                <option value="{{ $server->id }}">{{ $server->name }}</option>
 | 
			
		||||
 
 | 
			
		||||
@@ -1,42 +1,29 @@
 | 
			
		||||
<div>
 | 
			
		||||
    @if ($server->isFunctional())
 | 
			
		||||
        <div class="flex items-end gap-2">
 | 
			
		||||
            <h2>Destinations</h2>
 | 
			
		||||
            <x-modal-input buttonTitle="+ Add" title="New Destination">
 | 
			
		||||
                <livewire:destination.new.docker :server_id="$server->id" />
 | 
			
		||||
            </x-modal-input>
 | 
			
		||||
            <x-forms.button wire:click='scan'>Scan for Destinations</x-forms.button>
 | 
			
		||||
        </div>
 | 
			
		||||
        <div>Destinations are used to segregate resources by network.</div>
 | 
			
		||||
        <div class="flex gap-2 pt-6">
 | 
			
		||||
            Available for using:
 | 
			
		||||
            @forelse ($server->standaloneDockers as $docker)
 | 
			
		||||
                <a href="{{ route('destination.show', ['destination_uuid' => data_get($docker, 'uuid')]) }}">
 | 
			
		||||
                    <button class="dark:text-white btn-link">{{ data_get($docker, 'network') }} </button>
 | 
			
		||||
                </a>
 | 
			
		||||
            @empty
 | 
			
		||||
            @endforelse
 | 
			
		||||
            @forelse ($server->swarmDockers as $docker)
 | 
			
		||||
                <a href="{{ route('destination.show', ['destination_uuid' => data_get($docker, 'uuid')]) }}">
 | 
			
		||||
                    <button class="dark:text-white btn-link">{{ data_get($docker, 'network') }} </button>
 | 
			
		||||
                </a>
 | 
			
		||||
            @empty
 | 
			
		||||
            @endforelse
 | 
			
		||||
        </div>
 | 
			
		||||
        <div class="pt-2">
 | 
			
		||||
            @if (count($networks) > 0)
 | 
			
		||||
                <h3 class="pb-4">Found Destinations</h3>
 | 
			
		||||
    <form class="flex flex-col">
 | 
			
		||||
        <div class="flex items-center gap-2">
 | 
			
		||||
            <h1>Destination</h1>
 | 
			
		||||
            <x-forms.button wire:click.prevent='submit' type="submit">
 | 
			
		||||
                Save
 | 
			
		||||
            </x-forms.button>
 | 
			
		||||
            @if ($network !== 'coolify')
 | 
			
		||||
                <x-modal-confirmation title="Confirm Destination Deletion?" buttonTitle="Delete Destination" isErrorButton
 | 
			
		||||
                    submitAction="delete" :actions="['This will delete the selected destination/network.']" confirmationText="{{ $destination->name }}"
 | 
			
		||||
                    confirmationLabel="Please confirm the execution of the actions by entering the Destination Name below"
 | 
			
		||||
                    shortConfirmationLabel="Destination Name" :confirmWithPassword="false" step2ButtonText="Permanently Delete" />
 | 
			
		||||
            @endif
 | 
			
		||||
            <div class="flex flex-wrap gap-2 ">
 | 
			
		||||
                @foreach ($networks as $network)
 | 
			
		||||
                    <div class="min-w-fit">
 | 
			
		||||
                        <x-forms.button wire:click="add('{{ data_get($network, 'Name') }}')">Add
 | 
			
		||||
                            {{ data_get($network, 'Name') }}</x-forms.button>
 | 
			
		||||
                    </div>
 | 
			
		||||
                @endforeach
 | 
			
		||||
            </div>
 | 
			
		||||
        </div>
 | 
			
		||||
 | 
			
		||||
        @if ($destination->getMorphClass() === 'App\Models\StandaloneDocker')
 | 
			
		||||
            <div class="subtitle ">A simple Docker network.</div>
 | 
			
		||||
        @else
 | 
			
		||||
        <div>Server is not validated. Validate first.</div>
 | 
			
		||||
            <div class="subtitle ">A swarm Docker network. WIP</div>
 | 
			
		||||
        @endif
 | 
			
		||||
        <div class="flex gap-2">
 | 
			
		||||
            <x-forms.input id="name" label="Name" />
 | 
			
		||||
            <x-forms.input id="serverIp" label="Server IP" readonly />
 | 
			
		||||
            @if ($destination->getMorphClass() === 'App\Models\StandaloneDocker')
 | 
			
		||||
                <x-forms.input id="network" label="Docker Network" readonly />
 | 
			
		||||
            @endif
 | 
			
		||||
        </div>
 | 
			
		||||
    </form>
 | 
			
		||||
</div>
 | 
			
		||||
 
 | 
			
		||||
@@ -1,10 +1,11 @@
 | 
			
		||||
<div class="flex flex-col w-full gap-2">
 | 
			
		||||
    <div>Your feedback helps us to improve Coolify. Thank you! 💜</div>
 | 
			
		||||
    <form wire:submit="submit" class="flex flex-col gap-4 pt-4">
 | 
			
		||||
        <x-forms.input id="subject" label="Subject" placeholder="Summary of your problem."></x-forms.input>
 | 
			
		||||
        <x-forms.textarea rows="10" id="description" label="Description" class="font-sans" spellcheck
 | 
			
		||||
            placeholder="Please provide as much information as possible."></x-forms.textarea>
 | 
			
		||||
        <x-forms.input minlength="3" required id="subject" label="Subject" placeholder="Help with..."></x-forms.input>
 | 
			
		||||
        <x-forms.textarea minlength="10" maxlength="1000" required rows="10" id="description" label="Description"
 | 
			
		||||
            class="font-sans" spellcheck
 | 
			
		||||
            placeholder="Having trouble with... Please provide as much information as possible."></x-forms.textarea>
 | 
			
		||||
        <div></div>
 | 
			
		||||
        <x-forms.button class="w-full mt-4" type="submit" @click="modalOpen=false">Send</x-forms.button>
 | 
			
		||||
        <x-forms.button class="w-full mt-4" type="submit">Send</x-forms.button>
 | 
			
		||||
    </form>
 | 
			
		||||
</div>
 | 
			
		||||
 
 | 
			
		||||
@@ -9,7 +9,7 @@
 | 
			
		||||
            <x-forms.button type="submit">
 | 
			
		||||
                Save
 | 
			
		||||
            </x-forms.button>
 | 
			
		||||
            @if ($team->discord_enabled)
 | 
			
		||||
            @if ($discordEnabled)
 | 
			
		||||
                <x-forms.button class="normal-case dark:text-white btn btn-xs no-animation btn-primary"
 | 
			
		||||
                    wire:click="sendTestNotification">
 | 
			
		||||
                    Send Test Notifications
 | 
			
		||||
@@ -17,27 +17,26 @@
 | 
			
		||||
            @endif
 | 
			
		||||
        </div>
 | 
			
		||||
        <div class="w-32">
 | 
			
		||||
            <x-forms.checkbox instantSave id="team.discord_enabled" label="Enabled" />
 | 
			
		||||
            <x-forms.checkbox instantSave id="discordEnabled" label="Enabled" />
 | 
			
		||||
        </div>
 | 
			
		||||
        <x-forms.input type="password"
 | 
			
		||||
            helper="Generate a webhook in Discord.<br>Example: https://discord.com/api/webhooks/...." required
 | 
			
		||||
            id="team.discord_webhook_url" label="Webhook" />
 | 
			
		||||
            id="discordWebhookUrl" label="Webhook" />
 | 
			
		||||
    </form>
 | 
			
		||||
    @if (data_get($team, 'discord_enabled'))
 | 
			
		||||
    @if ($discordEnabled)
 | 
			
		||||
        <h2 class="mt-4">Subscribe to events</h2>
 | 
			
		||||
        <div class="w-64">
 | 
			
		||||
            @if (isDev())
 | 
			
		||||
                <x-forms.checkbox instantSave="saveModel" id="team.discord_notifications_test" label="Test" />
 | 
			
		||||
                <x-forms.checkbox instantSave="saveModel" id="discordNotificationsTest" label="Test" />
 | 
			
		||||
            @endif
 | 
			
		||||
            <x-forms.checkbox instantSave="saveModel" id="team.discord_notifications_status_changes"
 | 
			
		||||
            <x-forms.checkbox instantSave="saveModel" id="discordNotificationsStatusChanges"
 | 
			
		||||
                label="Container Status Changes" />
 | 
			
		||||
            <x-forms.checkbox instantSave="saveModel" id="team.discord_notifications_deployments"
 | 
			
		||||
            <x-forms.checkbox instantSave="saveModel" id="discordNotificationsDeployments"
 | 
			
		||||
                label="Application Deployments" />
 | 
			
		||||
            <x-forms.checkbox instantSave="saveModel" id="team.discord_notifications_database_backups"
 | 
			
		||||
                label="Backup Status" />
 | 
			
		||||
            <x-forms.checkbox instantSave="saveModel" id="team.discord_notifications_scheduled_tasks"
 | 
			
		||||
            <x-forms.checkbox instantSave="saveModel" id="discordNotificationsDatabaseBackups" label="Backup Status" />
 | 
			
		||||
            <x-forms.checkbox instantSave="saveModel" id="discordNotificationsScheduledTasks"
 | 
			
		||||
                label="Scheduled Tasks Status" />
 | 
			
		||||
            <x-forms.checkbox instantSave="saveModel" id="team.discord_notifications_server_disk_usage"
 | 
			
		||||
            <x-forms.checkbox instantSave="saveModel" id="discordNotificationsServerDiskUsage"
 | 
			
		||||
                label="Server Disk Usage" />
 | 
			
		||||
        </div>
 | 
			
		||||
    @endif
 | 
			
		||||
 
 | 
			
		||||
@@ -1,8 +1,9 @@
 | 
			
		||||
<form class="flex flex-col w-full gap-2 rounded" wire:submit='submit'>
 | 
			
		||||
    <x-forms.input placeholder="Your Cool Project" id="name" label="Name" required />
 | 
			
		||||
    <x-forms.input placeholder="This is my cool project everyone knows about" id="description" label="Description" />
 | 
			
		||||
    <div class="subtitle">New project will have a default production environment.</div>
 | 
			
		||||
    <x-forms.button type="submit" @click="slideOverOpen=false">
 | 
			
		||||
    <div class="subtitle">New project will have a default <span class="dark:text-warning font-bold">production</span>
 | 
			
		||||
        environment.</div>
 | 
			
		||||
    <x-forms.button type="submit">
 | 
			
		||||
        Continue
 | 
			
		||||
    </x-forms.button>
 | 
			
		||||
</form>
 | 
			
		||||
 
 | 
			
		||||
@@ -1,6 +0,0 @@
 | 
			
		||||
<form class="flex flex-col w-full gap-2 rounded" wire:submit='submit'>
 | 
			
		||||
    <x-forms.input placeholder="production" id="name" label="Name" required />
 | 
			
		||||
    <x-forms.button type="submit" @click="slideOverOpen=false">
 | 
			
		||||
        Save
 | 
			
		||||
    </x-forms.button>
 | 
			
		||||
</form>
 | 
			
		||||
@@ -27,24 +27,22 @@
 | 
			
		||||
 | 
			
		||||
        <div class="flex flex-col gap-2">
 | 
			
		||||
            <div class="flex gap-2">
 | 
			
		||||
                <x-forms.input placeholder="coollabsio/coolify-example" id="application.git_repository"
 | 
			
		||||
                    label="Repository" />
 | 
			
		||||
                <x-forms.input placeholder="main" id="application.git_branch" label="Branch" />
 | 
			
		||||
                <x-forms.input placeholder="coollabsio/coolify-example" id="gitRepository" label="Repository" />
 | 
			
		||||
                <x-forms.input placeholder="main" id="gitBranch" label="Branch" />
 | 
			
		||||
            </div>
 | 
			
		||||
            <div class="flex items-end gap-2">
 | 
			
		||||
                <x-forms.input placeholder="HEAD" id="application.git_commit_sha" placeholder="HEAD"
 | 
			
		||||
                    label="Commit SHA" />
 | 
			
		||||
                <x-forms.input placeholder="HEAD" id="gitCommitSha" placeholder="HEAD" label="Commit SHA" />
 | 
			
		||||
            </div>
 | 
			
		||||
        </div>
 | 
			
		||||
        @if (data_get($application, 'private_key_id'))
 | 
			
		||||
        @if ($privateKeyId)
 | 
			
		||||
            <h3 class="pt-4">Deploy Key</h3>
 | 
			
		||||
            <div class="py-2 pt-4">Currently attached Private Key: <span
 | 
			
		||||
                    class="dark:text-warning">{{ data_get($application, 'private_key.name') }}</span>
 | 
			
		||||
                    class="dark:text-warning">{{ $privateKeyName }}</span>
 | 
			
		||||
            </div>
 | 
			
		||||
 | 
			
		||||
            <h4 class="py-2 ">Select another Private Key</h4>
 | 
			
		||||
            <div class="flex flex-wrap gap-2">
 | 
			
		||||
                @foreach ($private_keys as $key)
 | 
			
		||||
                @foreach ($privateKeys as $key)
 | 
			
		||||
                    <x-forms.button wire:click.defer="setPrivateKey('{{ $key->id }}')">{{ $key->name }}
 | 
			
		||||
                    </x-forms.button>
 | 
			
		||||
                @endforeach
 | 
			
		||||
 
 | 
			
		||||
@@ -11,10 +11,9 @@
 | 
			
		||||
            </div>
 | 
			
		||||
        </div>
 | 
			
		||||
        <div class="pt-2 pb-10">Edit project details here.</div>
 | 
			
		||||
 | 
			
		||||
        <div class="flex gap-2">
 | 
			
		||||
            <x-forms.input label="Name" id="project.name" />
 | 
			
		||||
            <x-forms.input label="Description" id="project.description" />
 | 
			
		||||
            <x-forms.input label="Name" id="name" />
 | 
			
		||||
            <x-forms.input label="Description" id="description" />
 | 
			
		||||
        </div>
 | 
			
		||||
    </form>
 | 
			
		||||
</div>
 | 
			
		||||
 
 | 
			
		||||
@@ -13,7 +13,7 @@
 | 
			
		||||
                <li class="inline-flex items-center">
 | 
			
		||||
                    <div class="flex items-center">
 | 
			
		||||
                        <a class="text-xs truncate lg:text-sm"
 | 
			
		||||
                            href="{{ route('project.show', ['project_uuid' => data_get($parameters, 'project_uuid')]) }}">
 | 
			
		||||
                            href="{{ route('project.show', ['project_uuid' => $project->uuid]) }}">
 | 
			
		||||
                            {{ $project->name }}</a>
 | 
			
		||||
                        <svg aria-hidden="true" class="w-4 h-4 mx-1 font-bold dark:text-warning" fill="currentColor"
 | 
			
		||||
                            viewBox="0 0 20 20" xmlns="http://www.w3.org/2000/svg">
 | 
			
		||||
@@ -26,7 +26,9 @@
 | 
			
		||||
                <li>
 | 
			
		||||
                    <div class="flex items-center">
 | 
			
		||||
                        <a class="text-xs truncate lg:text-sm"
 | 
			
		||||
                            href="{{ route('project.resource.index', ['environment_name' => data_get($parameters, 'environment_name'), 'project_uuid' => data_get($parameters, 'project_uuid')]) }}">{{ data_get($parameters, 'environment_name') }}</a>
 | 
			
		||||
                            href="{{ route('project.resource.index', ['environment_name' => $environment->name, 'project_uuid' => $project->uuid]) }}">
 | 
			
		||||
                            {{ $environment->name }}
 | 
			
		||||
                        </a>
 | 
			
		||||
                    </div>
 | 
			
		||||
                </li>
 | 
			
		||||
                <li>
 | 
			
		||||
@@ -43,8 +45,8 @@
 | 
			
		||||
            </ol>
 | 
			
		||||
        </nav>
 | 
			
		||||
        <div class="flex gap-2">
 | 
			
		||||
            <x-forms.input label="Name" id="environment.name" />
 | 
			
		||||
            <x-forms.input label="Description" id="environment.description" />
 | 
			
		||||
            <x-forms.input label="Name" id="name" />
 | 
			
		||||
            <x-forms.input label="Description" id="description" />
 | 
			
		||||
        </div>
 | 
			
		||||
    </form>
 | 
			
		||||
</div>
 | 
			
		||||
 
 | 
			
		||||
@@ -5,13 +5,18 @@
 | 
			
		||||
    <div class="flex items-center gap-2">
 | 
			
		||||
        <h1>Environments</h1>
 | 
			
		||||
        <x-modal-input buttonTitle="+ Add" title="New Environment">
 | 
			
		||||
            <livewire:project.add-environment :project="$project" />
 | 
			
		||||
            <form class="flex flex-col w-full gap-2 rounded" wire:submit='submit'>
 | 
			
		||||
                <x-forms.input placeholder="production" id="name" label="Name" required />
 | 
			
		||||
                <x-forms.button type="submit">
 | 
			
		||||
                    Save
 | 
			
		||||
                </x-forms.button>
 | 
			
		||||
            </form>
 | 
			
		||||
        </x-modal-input>
 | 
			
		||||
        <livewire:project.delete-project :disabled="$project->resource_count() > 0" :project_id="$project->id" />
 | 
			
		||||
    </div>
 | 
			
		||||
    <div class="text-xs truncate subtitle lg:text-sm">{{ $project->name }}.</div>
 | 
			
		||||
    <div class="grid gap-2 lg:grid-cols-2">
 | 
			
		||||
        @forelse ($environments as $environment)
 | 
			
		||||
        @forelse ($project->environments->sortBy('created_at') as $environment)
 | 
			
		||||
            <div class="gap-2 border border-transparent cursor-pointer box group" x-data
 | 
			
		||||
                x-on:click="goto('{{ $project->uuid }}','{{ $environment->name }}')">
 | 
			
		||||
                <div class="flex flex-1 mx-6">
 | 
			
		||||
@@ -28,12 +33,6 @@
 | 
			
		||||
                        </a>
 | 
			
		||||
                    </div>
 | 
			
		||||
                </div>
 | 
			
		||||
                {{-- <div class="flex items-center justify-center gap-2 pt-4 pb-2 mr-4 text-xs lg:py-0 lg:justify-normal">
 | 
			
		||||
                    <a class="mx-4 font-bold hover:underline"
 | 
			
		||||
                        href="{{ route('project.environment.edit', ['project_uuid' => data_get($project, 'uuid'), 'environment_name' => $environment->name]) }}">
 | 
			
		||||
                        Settings
 | 
			
		||||
                    </a>
 | 
			
		||||
                </div> --}}
 | 
			
		||||
            </div>
 | 
			
		||||
        @empty
 | 
			
		||||
            <p>No environments found.</p>
 | 
			
		||||
 
 | 
			
		||||
@@ -1,4 +1,7 @@
 | 
			
		||||
<div>
 | 
			
		||||
    <x-slot:title>
 | 
			
		||||
        {{ data_get_str($server, 'name')->limit(10) }} > Advanced | Coolify
 | 
			
		||||
    </x-slot>
 | 
			
		||||
    <x-server.navbar :server="$server" />
 | 
			
		||||
    <div x-data="{ activeTab: window.location.hash ? window.location.hash.substring(1) : 'general' }" class="flex flex-col h-full gap-8 sm:flex-row">
 | 
			
		||||
        <x-server.sidebar :server="$server" activeMenu="advanced" />
 | 
			
		||||
 
 | 
			
		||||
@@ -1,6 +1,6 @@
 | 
			
		||||
<div>
 | 
			
		||||
    <x-slot:title>
 | 
			
		||||
        {{ data_get_str($server, 'name')->limit(10) }} > Server Destinations | Coolify
 | 
			
		||||
        {{ data_get_str($server, 'name')->limit(10) }} > Metrics | Coolify
 | 
			
		||||
    </x-slot>
 | 
			
		||||
    <x-server.navbar :server="$server" />
 | 
			
		||||
    <div class="flex flex-col h-full gap-8 sm:flex-row">
 | 
			
		||||
@@ -8,6 +8,7 @@
 | 
			
		||||
        <div class="w-full">
 | 
			
		||||
            <h2>Metrics</h2>
 | 
			
		||||
            <div class="pb-4">Basic metrics for your container.</div>
 | 
			
		||||
            @if ($server->isMetricsEnabled())
 | 
			
		||||
                <div @if ($poll) wire:poll.5000ms='pollData' @endif x-init="$wire.loadData()">
 | 
			
		||||
                    <x-forms.select label="Interval" wire:change="setInterval" id="interval">
 | 
			
		||||
                        <option value="5">5 minutes (live)</option>
 | 
			
		||||
@@ -248,6 +249,9 @@
 | 
			
		||||
 | 
			
		||||
                    </div>
 | 
			
		||||
                </div>
 | 
			
		||||
            @else
 | 
			
		||||
                <div>Metrics are disabled for this server.</div>
 | 
			
		||||
            @endif
 | 
			
		||||
        </div>
 | 
			
		||||
    </div>
 | 
			
		||||
</div>
 | 
			
		||||
 
 | 
			
		||||
@@ -1,6 +1,6 @@
 | 
			
		||||
<div>
 | 
			
		||||
    <x-slot:title>
 | 
			
		||||
        {{ data_get_str($server, 'name')->limit(10) }} > Server Destinations | Coolify
 | 
			
		||||
        {{ data_get_str($server, 'name')->limit(10) }} > Destinations | Coolify
 | 
			
		||||
    </x-slot>
 | 
			
		||||
    <x-server.navbar :server="$server" />
 | 
			
		||||
    <div class="flex flex-col h-full gap-8 sm:flex-row">
 | 
			
		||||
@@ -1,6 +1,6 @@
 | 
			
		||||
<div>
 | 
			
		||||
    <x-slot:title>
 | 
			
		||||
        {{ data_get_str($server, 'name')->limit(10) }} > Server Log Drains | Coolify
 | 
			
		||||
        {{ data_get_str($server, 'name')->limit(10) }} > Log Drains | Coolify
 | 
			
		||||
    </x-slot>
 | 
			
		||||
    <x-server.navbar :server="$server" />
 | 
			
		||||
    <div class="flex flex-col h-full gap-8 sm:flex-row">
 | 
			
		||||
 
 | 
			
		||||
@@ -1,6 +1,6 @@
 | 
			
		||||
<div>
 | 
			
		||||
    <x-slot:title>
 | 
			
		||||
        {{ data_get_str($server, 'name')->limit(10) }} > Server Connection | Coolify
 | 
			
		||||
        {{ data_get_str($server, 'name')->limit(10) }} > Private Key | Coolify
 | 
			
		||||
    </x-slot>
 | 
			
		||||
    <x-server.navbar :server="$server" />
 | 
			
		||||
    <div class="flex flex-col h-full gap-8 sm:flex-row">
 | 
			
		||||
 
 | 
			
		||||
@@ -1,6 +1,6 @@
 | 
			
		||||
<div>
 | 
			
		||||
    <x-slot:title>
 | 
			
		||||
        {{ data_get_str($server, 'name')->limit(10) }} > Server Configurations | Coolify
 | 
			
		||||
        {{ data_get_str($server, 'name')->limit(10) }} > General | Coolify
 | 
			
		||||
    </x-slot>
 | 
			
		||||
    <x-server.navbar :server="$server" />
 | 
			
		||||
    <div class="flex flex-col h-full gap-8 sm:flex-row">
 | 
			
		||||
 
 | 
			
		||||
@@ -1,19 +1,21 @@
 | 
			
		||||
<div>
 | 
			
		||||
    <x-slot:title>
 | 
			
		||||
        Settings | Coolify
 | 
			
		||||
        Transactional Email | Coolify
 | 
			
		||||
    </x-slot>
 | 
			
		||||
    <x-settings.navbar />
 | 
			
		||||
    <form wire:submit='submit' class="flex flex-col gap-2 pb-4">
 | 
			
		||||
        <div class="flex items-center gap-2">
 | 
			
		||||
            <h2>Transactional Email</h2>
 | 
			
		||||
    </div>
 | 
			
		||||
    <div class="pb-4 ">Email settings for password resets, invitations, etc.</div>
 | 
			
		||||
    <form wire:submit='submitFromFields' class="flex flex-col gap-2 pb-4">
 | 
			
		||||
        <x-forms.input required id="settings.smtp_from_name" helper="Name used in emails." label="From Name" />
 | 
			
		||||
        <x-forms.input required id="settings.smtp_from_address" helper="Email address used in emails."
 | 
			
		||||
            label="From Address" />
 | 
			
		||||
            <x-forms.button type="submit">
 | 
			
		||||
                Save
 | 
			
		||||
            </x-forms.button>
 | 
			
		||||
        </div>
 | 
			
		||||
        <div class="pb-4 ">Email settings for password resets, invitations, etc.</div>
 | 
			
		||||
        <div class="flex  gap-4">
 | 
			
		||||
            <x-forms.input required id="smtpFromName" helper="Name used in emails." label="From Name" />
 | 
			
		||||
            <x-forms.input required id="smtpFromAddress" helper="Email address used in emails." label="From Address" />
 | 
			
		||||
        </div>
 | 
			
		||||
 | 
			
		||||
    </form>
 | 
			
		||||
    <div class="flex flex-col gap-4">
 | 
			
		||||
        <div class="p-4 border dark:border-coolgray-300">
 | 
			
		||||
@@ -25,27 +27,26 @@
 | 
			
		||||
                    </x-forms.button>
 | 
			
		||||
                </div>
 | 
			
		||||
                <div class="w-32">
 | 
			
		||||
                    <x-forms.checkbox instantSave id="settings.smtp_enabled" label="Enabled" />
 | 
			
		||||
                    <x-forms.checkbox instantSave='instantSave("SMTP")' id="smtpEnabled" label="Enabled" />
 | 
			
		||||
                </div>
 | 
			
		||||
                <div class="flex flex-col gap-4">
 | 
			
		||||
                    <div class="flex flex-col w-full gap-2 xl:flex-row">
 | 
			
		||||
                        <x-forms.input required id="settings.smtp_host" placeholder="smtp.mailgun.org" label="Host" />
 | 
			
		||||
                        <x-forms.input required id="settings.smtp_port" placeholder="587" label="Port" />
 | 
			
		||||
                        <x-forms.input id="settings.smtp_encryption" helper="If SMTP uses SSL, set it to 'tls'."
 | 
			
		||||
                            placeholder="tls" label="Encryption" />
 | 
			
		||||
                        <x-forms.input required id="smtpHost" placeholder="smtp.mailgun.org" label="Host" />
 | 
			
		||||
                        <x-forms.input required id="smtpPort" placeholder="587" label="Port" />
 | 
			
		||||
                        <x-forms.input id="smtpEncryption" helper="If SMTP uses SSL, set it to 'tls'." placeholder="tls"
 | 
			
		||||
                            label="Encryption" />
 | 
			
		||||
                    </div>
 | 
			
		||||
                    <div class="flex flex-col w-full gap-2 xl:flex-row">
 | 
			
		||||
                        <x-forms.input id="settings.smtp_username" label="SMTP Username" />
 | 
			
		||||
                        <x-forms.input id="settings.smtp_password" type="password" label="SMTP Password"
 | 
			
		||||
                        <x-forms.input id="smtpUsername" label="SMTP Username" />
 | 
			
		||||
                        <x-forms.input id="smtpPassword" type="password" label="SMTP Password"
 | 
			
		||||
                            autocomplete="new-password" />
 | 
			
		||||
                        <x-forms.input id="settings.smtp_timeout" helper="Timeout value for sending emails."
 | 
			
		||||
                            label="Timeout" />
 | 
			
		||||
                        <x-forms.input id="smtpTimeout" helper="Timeout value for sending emails." label="Timeout" />
 | 
			
		||||
                    </div>
 | 
			
		||||
                </div>
 | 
			
		||||
            </form>
 | 
			
		||||
        </div>
 | 
			
		||||
        <div class="p-4 border dark:border-coolgray-300">
 | 
			
		||||
            <form wire:submit='submitResend' class="flex flex-col">
 | 
			
		||||
            <form wire:submit='submit' class="flex flex-col">
 | 
			
		||||
                <div class="flex gap-2">
 | 
			
		||||
                    <h3>Resend</h3>
 | 
			
		||||
                    <x-forms.button type="submit">
 | 
			
		||||
@@ -53,12 +54,12 @@
 | 
			
		||||
                    </x-forms.button>
 | 
			
		||||
                </div>
 | 
			
		||||
                <div class="w-32">
 | 
			
		||||
                    <x-forms.checkbox instantSave='instantSaveResend' id="settings.resend_enabled" label="Enabled" />
 | 
			
		||||
                    <x-forms.checkbox instantSave='instantSave("Resend")' id="resendEnabled" label="Enabled" />
 | 
			
		||||
                </div>
 | 
			
		||||
                <div class="flex flex-col gap-4">
 | 
			
		||||
                    <div class="flex flex-col w-full gap-2 xl:flex-row">
 | 
			
		||||
                        <x-forms.input type="password" id="settings.resend_api_key" placeholder="API key" required
 | 
			
		||||
                            label="Host" autocomplete="new-password" />
 | 
			
		||||
                        <x-forms.input type="password" id="resendApiKey" placeholder="API key" required label="API Key"
 | 
			
		||||
                            autocomplete="new-password" />
 | 
			
		||||
                    </div>
 | 
			
		||||
                </div>
 | 
			
		||||
            </form>
 | 
			
		||||
 
 | 
			
		||||
@@ -149,6 +149,7 @@ Route::group([
 | 
			
		||||
        }
 | 
			
		||||
        $data = request()->all();
 | 
			
		||||
 | 
			
		||||
        // \App\Jobs\ServerCheckNewJob::dispatch($server, $data);
 | 
			
		||||
        PushServerUpdateJob::dispatch($server, $data);
 | 
			
		||||
 | 
			
		||||
        return response()->json(['message' => 'ok'], 200);
 | 
			
		||||
 
 | 
			
		||||
@@ -7,6 +7,8 @@ use App\Http\Controllers\UploadController;
 | 
			
		||||
use App\Livewire\Admin\Index as AdminIndex;
 | 
			
		||||
use App\Livewire\Boarding\Index as BoardingIndex;
 | 
			
		||||
use App\Livewire\Dashboard;
 | 
			
		||||
use App\Livewire\Destination\Index as DestinationIndex;
 | 
			
		||||
use App\Livewire\Destination\Show as DestinationShow;
 | 
			
		||||
use App\Livewire\Dev\Compose as Compose;
 | 
			
		||||
use App\Livewire\ForcePasswordReset;
 | 
			
		||||
use App\Livewire\Notifications\Discord as NotificationDiscord;
 | 
			
		||||
@@ -38,7 +40,7 @@ use App\Livewire\Server\Advanced as ServerAdvanced;
 | 
			
		||||
use App\Livewire\Server\Charts as ServerCharts;
 | 
			
		||||
use App\Livewire\Server\CloudflareTunnels;
 | 
			
		||||
use App\Livewire\Server\Delete as DeleteServer;
 | 
			
		||||
use App\Livewire\Server\Destination\Show as DestinationShow;
 | 
			
		||||
use App\Livewire\Server\Destinations as ServerDestinations;
 | 
			
		||||
use App\Livewire\Server\Index as ServerIndex;
 | 
			
		||||
use App\Livewire\Server\LogDrains;
 | 
			
		||||
use App\Livewire\Server\PrivateKey\Show as PrivateKeyShow;
 | 
			
		||||
@@ -72,8 +74,6 @@ use App\Livewire\Terminal\Index as TerminalIndex;
 | 
			
		||||
use App\Models\GitlabApp;
 | 
			
		||||
use App\Models\ScheduledDatabaseBackupExecution;
 | 
			
		||||
use App\Models\Server;
 | 
			
		||||
use App\Models\StandaloneDocker;
 | 
			
		||||
use App\Models\SwarmDocker;
 | 
			
		||||
use App\Providers\RouteServiceProvider;
 | 
			
		||||
use Illuminate\Http\Request;
 | 
			
		||||
use Illuminate\Support\Facades\Route;
 | 
			
		||||
@@ -98,14 +98,14 @@ Route::middleware(['throttle:login'])->group(function () {
 | 
			
		||||
Route::get('/auth/{provider}/redirect', [OauthController::class, 'redirect'])->name('auth.redirect');
 | 
			
		||||
Route::get('/auth/{provider}/callback', [OauthController::class, 'callback'])->name('auth.callback');
 | 
			
		||||
 | 
			
		||||
Route::prefix('magic')->middleware(['auth'])->group(function () {
 | 
			
		||||
    Route::get('/servers', [MagicController::class, 'servers']);
 | 
			
		||||
    Route::get('/destinations', [MagicController::class, 'destinations']);
 | 
			
		||||
    Route::get('/projects', [MagicController::class, 'projects']);
 | 
			
		||||
    Route::get('/environments', [MagicController::class, 'environments']);
 | 
			
		||||
    Route::get('/project/new', [MagicController::class, 'newProject']);
 | 
			
		||||
    Route::get('/environment/new', [MagicController::class, 'newEnvironment']);
 | 
			
		||||
});
 | 
			
		||||
// Route::prefix('magic')->middleware(['auth'])->group(function () {
 | 
			
		||||
//     Route::get('/servers', [MagicController::class, 'servers']);
 | 
			
		||||
//     Route::get('/destinations', [MagicController::class, 'destinations']);
 | 
			
		||||
//     Route::get('/projects', [MagicController::class, 'projects']);
 | 
			
		||||
//     Route::get('/environments', [MagicController::class, 'environments']);
 | 
			
		||||
//     Route::get('/project/new', [MagicController::class, 'newProject']);
 | 
			
		||||
//     Route::get('/environment/new', [MagicController::class, 'newEnvironment']);
 | 
			
		||||
// });
 | 
			
		||||
 | 
			
		||||
Route::middleware(['auth', 'verified'])->group(function () {
 | 
			
		||||
    Route::middleware(['throttle:force-password-reset'])->group(function () {
 | 
			
		||||
@@ -212,7 +212,7 @@ Route::middleware(['auth', 'verified'])->group(function () {
 | 
			
		||||
        Route::get('/private-key', PrivateKeyShow::class)->name('server.private-key');
 | 
			
		||||
        Route::get('/resources', ResourcesShow::class)->name('server.resources');
 | 
			
		||||
        Route::get('/cloudflare-tunnels', CloudflareTunnels::class)->name('server.cloudflare-tunnels');
 | 
			
		||||
        Route::get('/destinations', DestinationShow::class)->name('server.destinations');
 | 
			
		||||
        Route::get('/destinations', ServerDestinations::class)->name('server.destinations');
 | 
			
		||||
        Route::get('/log-drains', LogDrains::class)->name('server.log-drains');
 | 
			
		||||
        Route::get('/metrics', ServerCharts::class)->name('server.charts');
 | 
			
		||||
        Route::get('/danger', DeleteServer::class)->name('server.delete');
 | 
			
		||||
@@ -221,6 +221,8 @@ Route::middleware(['auth', 'verified'])->group(function () {
 | 
			
		||||
        Route::get('/proxy/logs', ProxyLogs::class)->name('server.proxy.logs');
 | 
			
		||||
        Route::get('/terminal', ExecuteContainerCommand::class)->name('server.command');
 | 
			
		||||
    });
 | 
			
		||||
    Route::get('/destinations', DestinationIndex::class)->name('destination.index');
 | 
			
		||||
    Route::get('/destination/{destination_uuid}', DestinationShow::class)->name('destination.show');
 | 
			
		||||
 | 
			
		||||
    // Route::get('/security', fn () => view('security.index'))->name('security.index');
 | 
			
		||||
    Route::get('/security/private-key', SecurityPrivateKeyIndex::class)->name('security.private-key.index');
 | 
			
		||||
@@ -312,52 +314,7 @@ Route::middleware(['auth'])->group(function () {
 | 
			
		||||
            return response()->json(['message' => $e->getMessage()], 500);
 | 
			
		||||
        }
 | 
			
		||||
    })->name('download.backup');
 | 
			
		||||
    Route::get('/destinations', function () {
 | 
			
		||||
        $servers = Server::isUsable()->get();
 | 
			
		||||
        $destinations = collect([]);
 | 
			
		||||
        foreach ($servers as $server) {
 | 
			
		||||
            $destinations = $destinations->merge($server->destinations());
 | 
			
		||||
        }
 | 
			
		||||
        $pre_selected_server_uuid = data_get(request()->query(), 'server');
 | 
			
		||||
        if ($pre_selected_server_uuid) {
 | 
			
		||||
            $server = $servers->firstWhere('uuid', $pre_selected_server_uuid);
 | 
			
		||||
            if ($server) {
 | 
			
		||||
                $server_id = $server->id;
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        return view('destination.all', [
 | 
			
		||||
            'destinations' => $destinations,
 | 
			
		||||
            'servers' => $servers,
 | 
			
		||||
            'server_id' => $server_id ?? null,
 | 
			
		||||
        ]);
 | 
			
		||||
    })->name('destination.all');
 | 
			
		||||
    // Route::get('/destination/new', function () {
 | 
			
		||||
    //     $servers = Server::isUsable()->get();
 | 
			
		||||
    //     $pre_selected_server_uuid = data_get(request()->query(), 'server');
 | 
			
		||||
    //     if ($pre_selected_server_uuid) {
 | 
			
		||||
    //         $server = $servers->firstWhere('uuid', $pre_selected_server_uuid);
 | 
			
		||||
    //         if ($server) {
 | 
			
		||||
    //             $server_id = $server->id;
 | 
			
		||||
    //         }
 | 
			
		||||
    //     }
 | 
			
		||||
    //     return view('destination.new', [
 | 
			
		||||
    //         "servers" => $servers,
 | 
			
		||||
    //         "server_id" => $server_id ?? null,
 | 
			
		||||
    //     ]);
 | 
			
		||||
    // })->name('destination.new');
 | 
			
		||||
    Route::get('/destination/{destination_uuid}', function () {
 | 
			
		||||
        $standalone_dockers = StandaloneDocker::where('uuid', request()->destination_uuid)->first();
 | 
			
		||||
        $swarm_dockers = SwarmDocker::where('uuid', request()->destination_uuid)->first();
 | 
			
		||||
        if (! $standalone_dockers && ! $swarm_dockers) {
 | 
			
		||||
            abort(404);
 | 
			
		||||
        }
 | 
			
		||||
        $destination = $standalone_dockers ? $standalone_dockers : $swarm_dockers;
 | 
			
		||||
 | 
			
		||||
        return view('destination.show', [
 | 
			
		||||
            'destination' => $destination->load(['server']),
 | 
			
		||||
        ]);
 | 
			
		||||
    })->name('destination.show');
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
Route::any('/{any}', function () {
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user