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/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 @@ public function mount() '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..f9f3af37c --- /dev/null +++ b/app/Livewire/Server/Resources.php @@ -0,0 +1,30 @@ +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); + } + } + public function render() + { + return view('livewire.server.resources'); + } +} 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 @@ public function applications() { 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 @@ 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..ce34486ed 100644 --- a/app/Models/Server.php +++ b/app/Models/Server.php @@ -245,6 +245,8 @@ public function databases() $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 @@ public function tags() { 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 @@ public function tags() { 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 @@ public function tags() { 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 @@ public function tags() { 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 @@ public function tags() { 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 @@ public function tags() { 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..f81ca9703 100644 --- a/app/View/Components/Status/Services.php +++ b/app/View/Components/Status/Services.php @@ -16,7 +16,7 @@ public function __construct( public Service $service, public string $complexStatus = 'exited', ) { - $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/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/resources/views/components/server/navbar.blade.php b/resources/views/components/server/navbar.blade.php index a6a2dd74a..08a8019a8 100644 --- a/resources/views/components/server/navbar.blade.php +++ b/resources/views/components/server/navbar.blade.php @@ -18,6 +18,12 @@ ]) }}"> General + + Resources + - @if (serviceStatus($service) === 'degraded') + @if ($service->status() === 'degraded') @@ -26,7 +26,7 @@ class="flex items-center gap-2 cursor-pointer hover:text-white text-neutral-400" Stop @endif - @if (serviceStatus($service) === 'running') + @if ($service->status() === 'running') @endif - @if (serviceStatus($service) === 'exited') + @if ($service->status() === 'exited') diff --git a/resources/views/components/status/index.blade.php b/resources/views/components/status/index.blade.php index 966bddc01..12827eaca 100644 --- a/resources/views/components/status/index.blade.php +++ b/resources/views/components/status/index.blade.php @@ -1,11 +1,15 @@ +@props(['status', 'showRefreshButton' => true]) @if (str($status)->startsWith('running')) -@elseif(str($status)->startsWith('restarting') || str($status)->startsWith('starting') || str($status)->startsWith('degraded')) +@elseif(str($status)->startsWith('restarting') || + str($status)->startsWith('starting') || + str($status)->startsWith('degraded')) @else - + @endif -@if (!str($status)->contains('exited')) + +@if (!str($status)->contains('exited') && $showRefreshButton) link()) - {{ str($resource->type())->headline() }} - {{ $resource->name }} + + @if ($resource->type() === 'service') + + @else + + @endif + + {{ $resource->name }}({{ str($resource->type())->headline() }}) @else - {{ str($resource->type())->headline() }} - {{ $resource->name }} + + @if ($resource->type() === 'service') + + @else + + @endif + + {{ $resource->name }}({{ str($resource->type())->headline() }}) @endif @empty @endforelse @else - - @forelse ($server->definedResources() as $resource) - @if ($loop->first) - Resources - @endif - @if ($resource->link()) - - {{ str($resource->type())->headline() }} - {{ $resource->name }} - - @else - - {{ str($resource->type())->headline() }} - {{ $resource->name }} - - @endif - @empty - @endforelse - + @endif diff --git a/resources/views/livewire/server/resources.blade.php b/resources/views/livewire/server/resources.blade.php new file mode 100644 index 000000000..f8f3990d6 --- /dev/null +++ b/resources/views/livewire/server/resources.blade.php @@ -0,0 +1,46 @@ + + + Resources + Here you can find all resources for this server. + + + + + + + + + Project Name + Name + Type + Status + + + + @forelse ($server->definedResources()->sortBy('name',SORT_NATURAL) as $resource) + + + {{ data_get($resource->project(), 'name') }} + + {{ $resource->name }} + + {{ str($resource->type())->headline() }} + + @if ($resource->type() === 'service') + + @else + + @endif + + + @empty + @endforelse + + + + + + + + diff --git a/routes/api.php b/routes/api.php index 30dddd2d4..4ef5500f8 100644 --- a/routes/api.php +++ b/routes/api.php @@ -1,36 +1,11 @@ json(['message' => 'Feedback sent.'], 200); }); -// Route::group([ -// 'middleware' => $middlewares, -// 'prefix' => 'v1' -// ], function () { -// Route::get('/deployments', function () { -// return ApplicationDeploymentQueue::whereIn("status", ["in_progress", "queued"])->get([ -// "id", -// "server_id", -// "status" -// ])->groupBy("server_id")->map(function ($item) { -// return $item; -// })->toArray(); -// }); -// }); + Route::group([ 'middleware' => ['auth:sanctum'], 'prefix' => 'v1' ], function () { + Route::get('/version', function () { + return response(config('version')); + }); Route::get('/deploy', [Deploy::class, 'deploy']); - - + Route::get('/servers', [Server::class, 'servers']); + Route::get('/server/{uuid}', [Server::class, 'server_by_uuid']); + Route::get('/projects', [Project::class, 'projects']); + Route::get('/project/{uuid}', [Project::class, 'project_by_uuid']); + Route::get('/project/{uuid}/{environment_name}', [Project::class, 'environment_details']); }); -Route::middleware(['throttle:5'])->group(function () { - Route::get('/unsubscribe/{token}', function () { - try { - $token = request()->token; - $email = decrypt($token); - if (!User::whereEmail($email)->exists()) { - return redirect(RouteServiceProvider::HOME); - } - if (User::whereEmail($email)->first()->marketing_emails === false) { - return 'You have already unsubscribed from marketing emails.'; - } - User::whereEmail($email)->update(['marketing_emails' => false]); - return 'You have been unsubscribed from marketing emails.'; - } catch (\Throwable $e) { - return 'Something went wrong. Please try again or contact support.'; - } - })->name('unsubscribe.marketing.emails'); -}); +Route::get('/{any}', function () { + return response()->json(['error' => 'Not found.'], 404); +})->where('any', '.*'); + +// Route::middleware(['throttle:5'])->group(function () { +// Route::get('/unsubscribe/{token}', function () { +// try { +// $token = request()->token; +// $email = decrypt($token); +// if (!User::whereEmail($email)->exists()) { +// return redirect(RouteServiceProvider::HOME); +// } +// if (User::whereEmail($email)->first()->marketing_emails === false) { +// return 'You have already unsubscribed from marketing emails.'; +// } +// User::whereEmail($email)->update(['marketing_emails' => false]); +// return 'You have been unsubscribed from marketing emails.'; +// } catch (\Throwable $e) { +// return 'Something went wrong. Please try again or contact support.'; +// } +// })->name('unsubscribe.marketing.emails'); +// }); diff --git a/routes/web.php b/routes/web.php index 80165f9f8..9a6a6bbef 100644 --- a/routes/web.php +++ b/routes/web.php @@ -1,5 +1,6 @@ group(function () { Route::get('/', ServerShow::class)->name('server.show'); + Route::get('/resources', ResourcesShow::class)->name('server.resources'); Route::get('/proxy', ProxyShow::class)->name('server.proxy'); Route::get('/proxy/logs', ProxyLogs::class)->name('server.proxy.logs'); Route::get('/private-key', PrivateKeyShow::class)->name('server.private-key');