diff --git a/app/Actions/Server/InstallLogDrain.php b/app/Actions/Server/InstallLogDrain.php index 2a1b03260..86ca69a6e 100644 --- a/app/Actions/Server/InstallLogDrain.php +++ b/app/Actions/Server/InstallLogDrain.php @@ -128,9 +128,9 @@ class InstallLogDrain if ($type !== 'custom') { $parsers = base64_encode(" [PARSER] -Name empty_line_skipper -Format regex -Regex /^(?!\s*$).+/ + Name empty_line_skipper + Format regex + Regex /^(?!\s*$).+/ "); } $compose = base64_encode(" diff --git a/app/Http/Controllers/Api/Deploy.php b/app/Http/Controllers/Api/Deploy.php index f071f3b5b..21da51d66 100644 --- a/app/Http/Controllers/Api/Deploy.php +++ b/app/Http/Controllers/Api/Deploy.php @@ -18,8 +18,7 @@ class Deploy extends Controller { public function deploy(Request $request) { - $token = auth()->user()->currentAccessToken(); - $teamId = data_get($token, 'team_id'); + $teamId = get_team_id_from_token(); $uuids = $request->query->get('uuid'); $tags = $request->query->get('tag'); $force = $request->query->get('force') ?? false; diff --git a/app/Http/Controllers/Api/Project.php b/app/Http/Controllers/Api/Project.php new file mode 100644 index 000000000..fa2ba34bb --- /dev/null +++ b/app/Http/Controllers/Api/Project.php @@ -0,0 +1,39 @@ +json(['error' => 'Invalid token.', 'docs' => 'https://coolify.io/docs/api/authentication'], 400); + } + $projects = ModelsProject::whereTeamId($teamId)->select('id', 'name', 'uuid')->get(); + return response()->json($projects); + } + public function project_by_uuid(Request $request) + { + $teamId = get_team_id_from_token(); + if (is_null($teamId)) { + return response()->json(['error' => 'Invalid token.', 'docs' => 'https://coolify.io/docs/api/authentication'], 400); + } + $project = ModelsProject::whereTeamId($teamId)->whereUuid(request()->uuid)->first()->load(['environments']); + return response()->json($project); + } + public function environment_details(Request $request) + { + $teamId = get_team_id_from_token(); + if (is_null($teamId)) { + return response()->json(['error' => 'Invalid token.', 'docs' => 'https://coolify.io/docs/api/authentication'], 400); + } + $project = ModelsProject::whereTeamId($teamId)->whereUuid(request()->uuid)->first(); + $environment = $project->environments()->whereName(request()->environment_name)->first()->load(['applications', 'postgresqls', 'redis', 'mongodbs', 'mysqls', 'mariadbs', 'services']); + return response()->json($environment); + } +} diff --git a/app/Http/Controllers/Api/Server.php b/app/Http/Controllers/Api/Server.php new file mode 100644 index 000000000..e7b071a43 --- /dev/null +++ b/app/Http/Controllers/Api/Server.php @@ -0,0 +1,54 @@ +json(['error' => 'Invalid token.', 'docs' => 'https://coolify.io/docs/api/authentication'], 400); + } + $servers = ModelsServer::whereTeamId($teamId)->select('id', 'name', 'uuid', 'ip', 'user', 'port')->get()->load(['settings'])->map(function ($server) { + $server['is_reachable'] = $server->settings->is_reachable; + $server['is_usable'] = $server->settings->is_usable; + return $server; + }); + ray($servers); + return response()->json($servers); + } + public function server_by_uuid(Request $request) + { + $teamId = get_team_id_from_token(); + if (is_null($teamId)) { + return response()->json(['error' => 'Invalid token.', 'docs' => 'https://coolify.io/docs/api/authentication'], 400); + } + $server = ModelsServer::whereTeamId($teamId)->whereUuid(request()->uuid)->first(); + if (is_null($server)) { + return response()->json(['error' => 'Server not found.'], 404); + } + $server->load(['settings']); + $server['resources'] = $server->definedResources()->map(function ($resource) { + $payload = [ + 'id' => $resource->id, + 'uuid' => $resource->uuid, + 'name' => $resource->name, + 'type' => $resource->type(), + 'created_at' => $resource->created_at, + 'updated_at' => $resource->updated_at, + ]; + if ($resource->type() === 'service') { + $payload['status'] = $resource->status(); + } else { + $payload['status'] = $resource->status; + } + return $payload; + }); + return response()->json($server); + } +} diff --git a/app/Jobs/ApplicationDeploymentJob.php b/app/Jobs/ApplicationDeploymentJob.php index aa68c20ad..e135188a7 100644 --- a/app/Jobs/ApplicationDeploymentJob.php +++ b/app/Jobs/ApplicationDeploymentJob.php @@ -1022,11 +1022,15 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted $this->env_nixpacks_args = collect([]); if ($this->pull_request_id === 0) { foreach ($this->application->nixpacks_environment_variables as $env) { - $this->env_nixpacks_args->push("--env {$env->key}={$env->real_value}"); + if (!is_null($env->real_value)) { + $this->env_nixpacks_args->push("--env {$env->key}={$env->real_value}"); + } } } else { foreach ($this->application->nixpacks_environment_variables_preview as $env) { - $this->env_nixpacks_args->push("--env {$env->key}={$env->real_value}"); + if (!is_null($env->real_value)) { + $this->env_nixpacks_args->push("--env {$env->key}={$env->real_value}"); + } } } @@ -1037,11 +1041,15 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted $this->env_args = collect([]); if ($this->pull_request_id === 0) { foreach ($this->application->build_environment_variables as $env) { - $this->env_args->put($env->key, $env->real_value); + if (!is_null($env->real_value)) { + $this->env_args->put($env->key, $env->real_value); + } } } else { foreach ($this->application->build_environment_variables_preview as $env) { - $this->env_args->put($env->key, $env->real_value); + if (!is_null($env->real_value)) { + $this->env_args->put($env->key, $env->real_value); + } } } $this->env_args->put('SOURCE_COMMIT', $this->commit); diff --git a/app/Livewire/Project/Resource/Index.php b/app/Livewire/Project/Resource/Index.php index 9e0a5e9db..9524392dc 100644 --- a/app/Livewire/Project/Resource/Index.php +++ b/app/Livewire/Project/Resource/Index.php @@ -104,7 +104,7 @@ class Index extends Component 'environment_name' => data_get($service, 'environment.name'), 'service_uuid' => data_get($service, 'uuid') ]); - $service->status = serviceStatus($service); + $service->status = $service->status(); } return $service; }); diff --git a/app/Livewire/Server/Resources.php b/app/Livewire/Server/Resources.php new file mode 100644 index 000000000..99d988271 --- /dev/null +++ b/app/Livewire/Server/Resources.php @@ -0,0 +1,64 @@ +user()->currentTeam()->id; + return [ + "echo-private:team.{$teamId},ApplicationStatusChanged" => 'refreshStatus', + ]; + } + + public function startUnmanaged($id) { + $this->server->startUnmanaged($id); + $this->dispatch('success', 'Container started.'); + $this->loadUnmanagedContainers(); + } + public function restartUnmanaged($id) { + $this->server->restartUnmanaged($id); + $this->dispatch('success', 'Container restarted.'); + $this->loadUnmanagedContainers(); + } + public function stopUnmanaged($id) { + $this->server->stopUnmanaged($id); + $this->dispatch('success', 'Container stopped.'); + $this->loadUnmanagedContainers(); + } + public function refreshStatus() { + $this->server->refresh(); + $this->loadUnmanagedContainers(); + $this->dispatch('success', 'Resource statuses refreshed.'); + } + public function loadUnmanagedContainers() { + $this->unmanagedContainers = $this->server->loadUnmanagedContainers(); + } + public function mount() { + $this->unmanagedContainers = collect(); + $this->parameters = get_route_parameters(); + try { + $this->server = Server::ownedByCurrentTeam()->whereUuid(request()->server_uuid)->first(); + if (is_null($this->server)) { + return redirect()->route('server.index'); + } + } catch (\Throwable $e) { + return handleError($e, $this); + } + $this->loadUnmanagedContainers(); + } + public function render() + { + return view('livewire.server.resources'); + } +} diff --git a/app/Models/Application.php b/app/Models/Application.php index df1fb8038..a12be33fd 100644 --- a/app/Models/Application.php +++ b/app/Models/Application.php @@ -212,6 +212,9 @@ class Application extends BaseModel ); } + public function isExited() { + return (bool) str($this->status)->startsWith('exited'); + } public function realStatus() { return $this->getRawOriginal('status'); diff --git a/app/Models/Environment.php b/app/Models/Environment.php index b9451ac3f..efbfc70d9 100644 --- a/app/Models/Environment.php +++ b/app/Models/Environment.php @@ -26,7 +26,6 @@ class Environment extends Model { return $this->hasMany(Application::class); } - public function postgresqls() { return $this->hasMany(StandalonePostgresql::class); diff --git a/app/Models/ProjectSetting.php b/app/Models/ProjectSetting.php index 50192099e..d93bea05b 100644 --- a/app/Models/ProjectSetting.php +++ b/app/Models/ProjectSetting.php @@ -6,7 +6,10 @@ use Illuminate\Database\Eloquent\Model; class ProjectSetting extends Model { - protected $fillable = [ - 'project_id' - ]; + protected $guarded = []; + + public function project() + { + return $this->belongsTo(Project::class); + } } diff --git a/app/Models/Server.php b/app/Models/Server.php index f19943d05..24799fe6e 100644 --- a/app/Models/Server.php +++ b/app/Models/Server.php @@ -225,6 +225,29 @@ class Server extends BaseModel $services = $this->services(); return $applications->concat($databases)->concat($services->get()); } + public function stopUnmanaged($id) { + return instant_remote_process(["docker stop -t 0 $id"], $this); + } + public function restartUnmanaged($id) { + return instant_remote_process(["docker restart $id"], $this); + } + public function startUnmanaged($id) { + return instant_remote_process(["docker start $id"], $this); + } + public function loadUnmanagedContainers() + { + $containers = instant_remote_process(["docker ps -a --format '{{json .}}' "], $this); + $containers = format_docker_command_output_to_json($containers); + $containers = $containers->map(function ($container) { + $labels = data_get($container, 'Labels'); + if (!str($labels)->contains("coolify.managed")) { + return $container; + } + return null; + }); + $containers = $containers->filter(); + return collect($containers); + } public function hasDefinedResources() { $applications = $this->applications()->count() > 0; @@ -245,6 +268,8 @@ class Server extends BaseModel $mysqls = data_get($standaloneDocker, 'mysqls', collect([])); $mariadbs = data_get($standaloneDocker, 'mariadbs', collect([])); return $postgresqls->concat($redis)->concat($mongodbs)->concat($mysqls)->concat($mariadbs); + })->filter(function ($item) { + return data_get($item, 'name') === 'coolify-db'; })->flatten(); } public function applications() diff --git a/app/Models/Service.php b/app/Models/Service.php index 244964db1..246e812c2 100644 --- a/app/Models/Service.php +++ b/app/Models/Service.php @@ -28,6 +28,48 @@ class Service extends BaseModel { return $this->morphToMany(Tag::class, 'taggable'); } + public function status() { + $foundRunning = false; + $isDegraded = false; + $foundRestaring = false; + $applications = $this->applications; + $databases = $this->databases; + foreach ($applications as $application) { + if ($application->exclude_from_status) { + continue; + } + if (Str::of($application->status)->startsWith('running')) { + $foundRunning = true; + } else if (Str::of($application->status)->startsWith('restarting')) { + $foundRestaring = true; + } else { + $isDegraded = true; + } + } + foreach ($databases as $database) { + if ($database->exclude_from_status) { + continue; + } + if (Str::of($database->status)->startsWith('running')) { + $foundRunning = true; + } else if (Str::of($database->status)->startsWith('restarting')) { + $foundRestaring = true; + } else { + $isDegraded = true; + } + } + if ($foundRestaring) { + return 'degraded'; + } + if ($foundRunning && !$isDegraded) { + return 'running'; + } else if ($foundRunning && $isDegraded) { + return 'degraded'; + } else if (!$foundRunning && !$isDegraded) { + return 'exited'; + } + return 'exited'; + } public function extraFields() { $fields = collect([]); diff --git a/app/Models/StandaloneMariadb.php b/app/Models/StandaloneMariadb.php index 1143b018e..174397baa 100644 --- a/app/Models/StandaloneMariadb.php +++ b/app/Models/StandaloneMariadb.php @@ -82,6 +82,9 @@ class StandaloneMariadb extends BaseModel { return $this->morphToMany(Tag::class, 'taggable'); } + public function project() { + return data_get($this, 'environment.project'); + } public function team() { return data_get($this, 'environment.project.team'); diff --git a/app/Models/StandaloneMongodb.php b/app/Models/StandaloneMongodb.php index 610323f74..d6efaaa1e 100644 --- a/app/Models/StandaloneMongodb.php +++ b/app/Models/StandaloneMongodb.php @@ -85,6 +85,9 @@ class StandaloneMongodb extends BaseModel { return $this->morphToMany(Tag::class, 'taggable'); } + public function project() { + return data_get($this, 'environment.project'); + } public function team() { return data_get($this, 'environment.project.team'); diff --git a/app/Models/StandaloneMysql.php b/app/Models/StandaloneMysql.php index fa6bbe28f..f317196aa 100644 --- a/app/Models/StandaloneMysql.php +++ b/app/Models/StandaloneMysql.php @@ -82,6 +82,9 @@ class StandaloneMysql extends BaseModel { return $this->morphToMany(Tag::class, 'taggable'); } + public function project() { + return data_get($this, 'environment.project'); + } public function team() { return data_get($this, 'environment.project.team'); diff --git a/app/Models/StandalonePostgresql.php b/app/Models/StandalonePostgresql.php index bcc43843b..3d2317159 100644 --- a/app/Models/StandalonePostgresql.php +++ b/app/Models/StandalonePostgresql.php @@ -82,6 +82,10 @@ class StandalonePostgresql extends BaseModel { return $this->morphToMany(Tag::class, 'taggable'); } + public function project() + { + return data_get($this, 'environment.project'); + } public function link() { if (data_get($this, 'environment.project.uuid')) { diff --git a/app/Models/StandaloneRedis.php b/app/Models/StandaloneRedis.php index 59c53f882..6b6b6c415 100644 --- a/app/Models/StandaloneRedis.php +++ b/app/Models/StandaloneRedis.php @@ -77,6 +77,10 @@ class StandaloneRedis extends BaseModel { return $this->morphToMany(Tag::class, 'taggable'); } + public function project() + { + return data_get($this, 'environment.project'); + } public function team() { return data_get($this, 'environment.project.team'); diff --git a/app/View/Components/Status/Services.php b/app/View/Components/Status/Services.php index 3fc302acf..70db62172 100644 --- a/app/View/Components/Status/Services.php +++ b/app/View/Components/Status/Services.php @@ -15,8 +15,9 @@ class Services extends Component public function __construct( public Service $service, public string $complexStatus = 'exited', + public bool $showRefreshButton = true ) { - $this->complexStatus = serviceStatus($service); + $this->complexStatus = $service->status(); } /** diff --git a/bootstrap/helpers/api.php b/bootstrap/helpers/api.php new file mode 100644 index 000000000..94e9242cb --- /dev/null +++ b/bootstrap/helpers/api.php @@ -0,0 +1,7 @@ +user()->currentAccessToken(); + return data_get($token, 'team_id'); +} diff --git a/bootstrap/helpers/proxy.php b/bootstrap/helpers/proxy.php index a273a90e0..19403a505 100644 --- a/bootstrap/helpers/proxy.php +++ b/bootstrap/helpers/proxy.php @@ -103,6 +103,7 @@ function generate_default_proxy_configuration(Server $server) "traefik.http.routers.traefik.entrypoints=http", "traefik.http.routers.traefik.service=api@internal", "traefik.http.services.traefik.loadbalancer.server.port=8080", + "coolify.managed=true", ]; $config = [ "version" => "3.8", diff --git a/bootstrap/helpers/services.php b/bootstrap/helpers/services.php index 43a15444c..aa7d6fd54 100644 --- a/bootstrap/helpers/services.php +++ b/bootstrap/helpers/services.php @@ -21,49 +21,6 @@ function replaceVariables($variable) return $variable->replaceFirst('$', '')->replaceFirst('{', '')->replaceLast('}', ''); } -function serviceStatus(Service $service) -{ - $foundRunning = false; - $isDegraded = false; - $foundRestaring = false; - $applications = $service->applications; - $databases = $service->databases; - foreach ($applications as $application) { - if ($application->exclude_from_status) { - continue; - } - if (Str::of($application->status)->startsWith('running')) { - $foundRunning = true; - } else if (Str::of($application->status)->startsWith('restarting')) { - $foundRestaring = true; - } else { - $isDegraded = true; - } - } - foreach ($databases as $database) { - if ($database->exclude_from_status) { - continue; - } - if (Str::of($database->status)->startsWith('running')) { - $foundRunning = true; - } else if (Str::of($database->status)->startsWith('restarting')) { - $foundRestaring = true; - } else { - $isDegraded = true; - } - } - if ($foundRestaring) { - return 'degraded'; - } - if ($foundRunning && !$isDegraded) { - return 'running'; - } else if ($foundRunning && $isDegraded) { - return 'degraded'; - } else if (!$foundRunning && !$isDegraded) { - return 'exited'; - } - return 'exited'; -} function getFilesystemVolumesFromServer(ServiceApplication|ServiceDatabase $oneService, bool $isInit = false) { // TODO: make this async diff --git a/config/sentry.php b/config/sentry.php index 80b7ffe7f..bc708e23e 100644 --- a/config/sentry.php +++ b/config/sentry.php @@ -7,7 +7,7 @@ return [ // The release version of your application // Example with dynamic git hash: trim(exec('git --git-dir ' . base_path('.git') . ' log --pretty="%h" -n1 HEAD')) - 'release' => '4.0.0-beta.219', + 'release' => '4.0.0-beta.220', // When left empty or `null` the Laravel environment will be used 'environment' => config('app.env'), diff --git a/config/version.php b/config/version.php index 81f48a3c5..97edca729 100644 --- a/config/version.php +++ b/config/version.php @@ -1,3 +1,3 @@ + + + - @if (serviceStatus($service) === 'degraded') + @if ($service->status() === 'degraded') @endif - @if (serviceStatus($service) === 'running') + @if ($service->status() === 'running')