diff --git a/README.md b/README.md index c82095a3b..27566cc67 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,10 @@ +![Latest Release Version](https://img.shields.io/badge/dynamic/json?labelColor=grey&color=6366f1&label=Latest_released_version&url=https%3A%2F%2Fcdn.coollabs.io%2Fcoolify%2Fversions.json&query=coolify.v4.version&style=for-the-badge +) + [![Bounty Issues](https://img.shields.io/static/v1?labelColor=grey&color=6366f1&label=Algora&message=%F0%9F%92%8E+Bounty+issues&style=for-the-badge)](https://console.algora.io/org/coollabsio/bounties/new) [![Open Bounties](https://img.shields.io/endpoint?url=https%3A%2F%2Fconsole.algora.io%2Fapi%2Fshields%2Fcoollabsio%2Fbounties%3Fstatus%3Dopen&style=for-the-badge)](https://console.algora.io/org/coollabsio/bounties?status=open) [![Rewarded Bounties](https://img.shields.io/endpoint?url=https%3A%2F%2Fconsole.algora.io%2Fapi%2Fshields%2Fcoollabsio%2Fbounties%3Fstatus%3Dcompleted&style=for-the-badge)](https://console.algora.io/org/coollabsio/bounties?status=completed) + # About the Project Coolify is an open-source & self-hostable alternative to Heroku / Netlify / Vercel / etc. diff --git a/app/Console/Commands/Init.php b/app/Console/Commands/Init.php index 50c9fe29b..7949b2457 100644 --- a/app/Console/Commands/Init.php +++ b/app/Console/Commands/Init.php @@ -5,6 +5,7 @@ namespace App\Console\Commands; use App\Enums\ApplicationDeploymentStatus; use App\Jobs\CleanupHelperContainersJob; use App\Models\ApplicationDeploymentQueue; +use App\Models\Environment; use App\Models\InstanceSettings; use App\Models\ScheduledDatabaseBackup; use App\Models\Server; @@ -24,6 +25,8 @@ class Init extends Command get_public_ips(); $full_cleanup = $this->option('full-cleanup'); $cleanup_deployments = $this->option('cleanup-deployments'); + + $this->replace_slash_in_environment_name(); if ($cleanup_deployments) { echo "Running cleanup deployments.\n"; $this->cleanup_in_progress_application_deployments(); @@ -150,4 +153,15 @@ class Init extends Command echo "Error: {$e->getMessage()}\n"; } } + + private function replace_slash_in_environment_name() + { + $environments = Environment::all(); + foreach ($environments as $environment) { + if (str_contains($environment->name, '/')) { + $environment->name = str_replace('/', '-', $environment->name); + $environment->save(); + } + } + } } diff --git a/app/Http/Controllers/Api/Applications.php b/app/Http/Controllers/Api/Applications.php new file mode 100644 index 000000000..a638adecd --- /dev/null +++ b/app/Http/Controllers/Api/Applications.php @@ -0,0 +1,183 @@ +get(); + $applications = collect(); + $applications->push($projects->pluck('applications')->flatten()); + $applications = $applications->flatten(); + + return response()->json($applications); + } + + public function application_by_uuid(Request $request) + { + $teamId = get_team_id_from_token(); + if (is_null($teamId)) { + return invalid_token(); + } + $uuid = $request->route('uuid'); + if (! $uuid) { + return response()->json(['error' => 'UUID is required.'], 400); + } + $application = Application::where('uuid', $uuid)->first(); + if (! $application) { + return response()->json(['error' => 'Application not found.'], 404); + } + + return response()->json($application); + } + + public function update_by_uuid(Request $request) + { + $teamId = get_team_id_from_token(); + if (is_null($teamId)) { + return invalid_token(); + } + + if ($request->collect()->count() == 0) { + return response()->json([ + 'message' => 'No data provided.', + ], 400); + } + $application = Application::where('uuid', $request->uuid)->first(); + + if (! $application) { + return response()->json([ + 'success' => false, + 'message' => 'Application not found', + ], 404); + } + ray($request->collect()); + + // if ($request->has('domains')) { + // $existingDomains = explode(',', $application->fqdn); + // $newDomains = $request->domains; + // $filteredNewDomains = array_filter($newDomains, function ($domain) use ($existingDomains) { + // return ! in_array($domain, $existingDomains); + // }); + // $mergedDomains = array_unique(array_merge($existingDomains, $filteredNewDomains)); + // $application->fqdn = implode(',', $mergedDomains); + // $application->custom_labels = base64_encode(implode("\n ", generateLabelsApplication($application))); + // $application->save(); + // } + + return response()->json([ + 'message' => 'Application updated successfully.', + 'application' => serialize_api_response($application), + ]); + } + + public function action_deploy(Request $request) + { + $teamId = get_team_id_from_token(); + if (is_null($teamId)) { + return invalid_token(); + } + $force = $request->query->get('force') ?? false; + $instant_deploy = $request->query->get('instant_deploy') ?? false; + $uuid = $request->route('uuid'); + if (! $uuid) { + return response()->json(['error' => 'UUID is required.'], 400); + } + $application = Application::where('uuid', $uuid)->first(); + if (! $application) { + return response()->json(['error' => 'Application not found.'], 404); + } + + $deployment_uuid = new Cuid2(7); + + queue_application_deployment( + application: $application, + deployment_uuid: $deployment_uuid, + force_rebuild: $force, + is_api: true, + no_questions_asked: $instant_deploy + ); + + return response()->json( + [ + 'message' => 'Deployment request queued.', + 'deployment_uuid' => $deployment_uuid->toString(), + 'deployment_api_url' => base_url().'/api/v1/deployment/'.$deployment_uuid->toString(), + ], + 200 + ); + } + + public function action_stop(Request $request) + { + $teamId = get_team_id_from_token(); + if (is_null($teamId)) { + return invalid_token(); + } + $uuid = $request->route('uuid'); + $sync = $request->query->get('sync') ?? false; + if (! $uuid) { + return response()->json(['error' => 'UUID is required.'], 400); + } + $application = Application::where('uuid', $uuid)->first(); + if (! $application) { + return response()->json(['error' => 'Application not found.'], 404); + } + if ($sync) { + StopApplication::run($application); + + return response()->json(['message' => 'Stopped the application.'], 200); + } else { + StopApplication::dispatch($application); + + return response()->json(['message' => 'Stopping request queued.'], 200); + } + } + + public function action_restart(Request $request) + { + $teamId = get_team_id_from_token(); + if (is_null($teamId)) { + return invalid_token(); + } + $uuid = $request->route('uuid'); + if (! $uuid) { + return response()->json(['error' => 'UUID is required.'], 400); + } + $application = Application::where('uuid', $uuid)->first(); + if (! $application) { + return response()->json(['error' => 'Application not found.'], 404); + } + + $deployment_uuid = new Cuid2(7); + + queue_application_deployment( + application: $application, + deployment_uuid: $deployment_uuid, + restart_only: true, + is_api: true, + ); + + return response()->json( + [ + 'message' => 'Restart request queued.', + 'deployment_uuid' => $deployment_uuid->toString(), + 'deployment_api_url' => base_url().'/api/v1/deployment/'.$deployment_uuid->toString(), + ], + 200 + ); + + } +} diff --git a/app/Http/Controllers/Api/Deploy.php b/app/Http/Controllers/Api/Deploy.php index d2abe2e31..f7a34facc 100644 --- a/app/Http/Controllers/Api/Deploy.php +++ b/app/Http/Controllers/Api/Deploy.php @@ -38,7 +38,25 @@ class Deploy extends Controller 'status', ])->sortBy('id')->toArray(); - return response()->json($deployments_per_server, 200); + return response()->json(serialize_api_response($deployments_per_server), 200); + } + + public function deployment_by_uuid(Request $request) + { + $teamId = get_team_id_from_token(); + if (is_null($teamId)) { + return invalid_token(); + } + $uuid = $request->route('uuid'); + if (! $uuid) { + return response()->json(['error' => 'UUID is required.'], 400); + } + $deployment = ApplicationDeploymentQueue::where('deployment_uuid', $uuid)->first()->makeHidden('logs'); + if (! $deployment) { + return response()->json(['error' => 'Deployment not found.'], 404); + } + + return response()->json(serialize_api_response($deployment), 200); } public function deploy(Request $request) diff --git a/app/Http/Controllers/Api/Domains.php b/app/Http/Controllers/Api/Domains.php index 86ff0cccc..6473b64de 100644 --- a/app/Http/Controllers/Api/Domains.php +++ b/app/Http/Controllers/Api/Domains.php @@ -4,161 +4,11 @@ namespace App\Http\Controllers\Api; use App\Http\Controllers\Controller; use App\Models\Application; -use App\Models\InstanceSettings; -use App\Models\Project as ModelsProject; use Illuminate\Http\Request; use Illuminate\Support\Facades\Validator; class Domains extends Controller { - public function domains(Request $request) - { - $teamId = get_team_id_from_token(); - if (is_null($teamId)) { - return invalid_token(); - } - $uuid = $request->query->get('uuid'); - if ($uuid) { - $domains = Application::getDomainsByUuid($uuid); - - return response()->json([ - 'uuid' => $uuid, - 'domains' => $domains, - ]); - } - $projects = ModelsProject::where('team_id', $teamId)->get(); - $domains = collect(); - $applications = $projects->pluck('applications')->flatten(); - $settings = InstanceSettings::get(); - if ($applications->count() > 0) { - foreach ($applications as $application) { - $ip = $application->destination->server->ip; - $fqdn = str($application->fqdn)->explode(',')->map(function ($fqdn) { - return str($fqdn)->replace('http://', '')->replace('https://', '')->replace('/', ''); - }); - if ($ip === 'host.docker.internal') { - if ($settings->public_ipv4) { - $domains->push([ - 'domain' => $fqdn, - 'ip' => $settings->public_ipv4, - ]); - } - if ($settings->public_ipv6) { - $domains->push([ - 'domain' => $fqdn, - 'ip' => $settings->public_ipv6, - ]); - } - if (! $settings->public_ipv4 && ! $settings->public_ipv6) { - $domains->push([ - 'domain' => $fqdn, - 'ip' => $ip, - ]); - } - } else { - $domains->push([ - 'domain' => $fqdn, - 'ip' => $ip, - ]); - } - } - } - $services = $projects->pluck('services')->flatten(); - if ($services->count() > 0) { - foreach ($services as $service) { - $service_applications = $service->applications; - if ($service_applications->count() > 0) { - foreach ($service_applications as $application) { - $fqdn = str($application->fqdn)->explode(',')->map(function ($fqdn) { - return str($fqdn)->replace('http://', '')->replace('https://', '')->replace('/', ''); - }); - if ($ip === 'host.docker.internal') { - if ($settings->public_ipv4) { - $domains->push([ - 'domain' => $fqdn, - 'ip' => $settings->public_ipv4, - ]); - } - if ($settings->public_ipv6) { - $domains->push([ - 'domain' => $fqdn, - 'ip' => $settings->public_ipv6, - ]); - } - if (! $settings->public_ipv4 && ! $settings->public_ipv6) { - $domains->push([ - 'domain' => $fqdn, - 'ip' => $ip, - ]); - } - } else { - $domains->push([ - 'domain' => $fqdn, - 'ip' => $ip, - ]); - } - } - } - } - } - $domains = $domains->groupBy('ip')->map(function ($domain) { - return $domain->pluck('domain')->flatten(); - })->map(function ($domain, $ip) { - return [ - 'ip' => $ip, - 'domains' => $domain, - ]; - })->values(); - - return response()->json($domains); - } - - public function updateDomains(Request $request) - { - $teamId = get_team_id_from_token(); - if (is_null($teamId)) { - return invalid_token(); - } - $validator = Validator::make($request->all(), [ - 'uuid' => 'required|string|exists:applications,uuid', - 'domains' => 'required|array', - 'domains.*' => 'required|string|distinct', - ]); - - if ($validator->fails()) { - return response()->json([ - 'success' => false, - 'message' => 'Validation failed', - 'errors' => $validator->errors(), - ], 422); - } - - $application = Application::where('uuid', $request->uuid)->first(); - - if (! $application) { - return response()->json([ - 'success' => false, - 'message' => 'Application not found', - ], 404); - } - - $existingDomains = explode(',', $application->fqdn); - $newDomains = $request->domains; - $filteredNewDomains = array_filter($newDomains, function ($domain) use ($existingDomains) { - return ! in_array($domain, $existingDomains); - }); - $mergedDomains = array_unique(array_merge($existingDomains, $filteredNewDomains)); - $application->fqdn = implode(',', $mergedDomains); - $application->custom_labels = base64_encode(implode("\n ", generateLabelsApplication($application))); - $application->save(); - - return response()->json([ - 'success' => true, - 'message' => 'Domains updated successfully', - 'application' => $application, - ]); - } - public function deleteDomains(Request $request) { $teamId = get_team_id_from_token(); diff --git a/app/Http/Controllers/Api/Server.php b/app/Http/Controllers/Api/Server.php index 9f88a3b28..7a6090bf8 100644 --- a/app/Http/Controllers/Api/Server.php +++ b/app/Http/Controllers/Api/Server.php @@ -3,6 +3,9 @@ namespace App\Http\Controllers\Api; use App\Http\Controllers\Controller; +use App\Models\Application; +use App\Models\InstanceSettings; +use App\Models\Project; use App\Models\Server as ModelsServer; use Illuminate\Http\Request; @@ -59,4 +62,106 @@ class Server extends Controller return response()->json($server); } + + public function get_domains_by_server(Request $request) + { + $teamId = get_team_id_from_token(); + if (is_null($teamId)) { + return invalid_token(); + } + $uuid = $request->query->get('uuid'); + if ($uuid) { + $domains = Application::getDomainsByUuid($uuid); + + return response()->json([ + 'uuid' => $uuid, + 'domains' => $domains, + ]); + } + $projects = Project::where('team_id', $teamId)->get(); + $domains = collect(); + $applications = $projects->pluck('applications')->flatten(); + $settings = InstanceSettings::get(); + if ($applications->count() > 0) { + foreach ($applications as $application) { + $ip = $application->destination->server->ip; + $fqdn = str($application->fqdn)->explode(',')->map(function ($fqdn) { + return str($fqdn)->replace('http://', '')->replace('https://', '')->replace('/', ''); + }); + if ($ip === 'host.docker.internal') { + if ($settings->public_ipv4) { + $domains->push([ + 'domain' => $fqdn, + 'ip' => $settings->public_ipv4, + ]); + } + if ($settings->public_ipv6) { + $domains->push([ + 'domain' => $fqdn, + 'ip' => $settings->public_ipv6, + ]); + } + if (! $settings->public_ipv4 && ! $settings->public_ipv6) { + $domains->push([ + 'domain' => $fqdn, + 'ip' => $ip, + ]); + } + } else { + $domains->push([ + 'domain' => $fqdn, + 'ip' => $ip, + ]); + } + } + } + $services = $projects->pluck('services')->flatten(); + if ($services->count() > 0) { + foreach ($services as $service) { + $service_applications = $service->applications; + if ($service_applications->count() > 0) { + foreach ($service_applications as $application) { + $fqdn = str($application->fqdn)->explode(',')->map(function ($fqdn) { + return str($fqdn)->replace('http://', '')->replace('https://', '')->replace('/', ''); + }); + if ($ip === 'host.docker.internal') { + if ($settings->public_ipv4) { + $domains->push([ + 'domain' => $fqdn, + 'ip' => $settings->public_ipv4, + ]); + } + if ($settings->public_ipv6) { + $domains->push([ + 'domain' => $fqdn, + 'ip' => $settings->public_ipv6, + ]); + } + if (! $settings->public_ipv4 && ! $settings->public_ipv6) { + $domains->push([ + 'domain' => $fqdn, + 'ip' => $ip, + ]); + } + } else { + $domains->push([ + 'domain' => $fqdn, + 'ip' => $ip, + ]); + } + } + } + } + } + $domains = $domains->groupBy('ip')->map(function ($domain) { + return $domain->pluck('domain')->flatten(); + })->map(function ($domain, $ip) { + return [ + 'ip' => $ip, + 'domains' => $domain, + ]; + })->values(); + + return response()->json($domains); + } } diff --git a/app/Http/Controllers/OauthController.php b/app/Http/Controllers/OauthController.php index 5b17fe926..9569e8cfa 100644 --- a/app/Http/Controllers/OauthController.php +++ b/app/Http/Controllers/OauthController.php @@ -2,8 +2,10 @@ namespace App\Http\Controllers; +use App\Models\InstanceSettings; use App\Models\User; use Illuminate\Support\Facades\Auth; +use Symfony\Component\HttpKernel\Exception\HttpException; class OauthController extends Controller { @@ -20,6 +22,11 @@ class OauthController extends Controller $oauthUser = get_socialite_provider($provider)->user(); $user = User::whereEmail($oauthUser->email)->first(); if (! $user) { + $settings = InstanceSettings::get(); + if (! $settings->is_registration_enabled) { + abort(403, 'Registration is disabled'); + } + $user = User::create([ 'name' => $oauthUser->name, 'email' => $oauthUser->email, @@ -31,7 +38,9 @@ class OauthController extends Controller } catch (\Exception $e) { ray($e->getMessage()); - return redirect()->route('login')->withErrors([__('auth.failed.callback')]); + $errorCode = $e instanceof HttpException ? 'auth.failed' : 'auth.failed.callback'; + + return redirect()->route('login')->withErrors([__($errorCode)]); } } } diff --git a/app/Livewire/Charts/ServerCpu.php b/app/Livewire/Charts/ServerCpu.php deleted file mode 100644 index 5f3283009..000000000 --- a/app/Livewire/Charts/ServerCpu.php +++ /dev/null @@ -1,59 +0,0 @@ -poll || $this->interval <= 10) { - $this->loadData(); - if ($this->interval > 10) { - $this->poll = false; - } - } - } - - public function loadData() - { - try { - $metrics = $this->server->getCpuMetrics($this->interval); - $metrics = collect($metrics)->map(function ($metric) { - return [$metric[0], $metric[1]]; - }); - $this->dispatch("refreshChartData-{$this->chartId}", [ - 'seriesData' => $metrics, - ]); - } catch (\Throwable $e) { - return handleError($e, $this); - } - } - - public function setInterval() - { - if ($this->interval <= 10) { - $this->poll = true; - } - $this->loadData(); - } -} diff --git a/app/Livewire/Project/New/Select.php b/app/Livewire/Project/New/Select.php index b8d186dab..b25290f71 100644 --- a/app/Livewire/Project/New/Select.php +++ b/app/Livewire/Project/New/Select.php @@ -176,10 +176,12 @@ class Select extends Component return; } - // if (count($this->servers) === 1) { - // $server = $this->servers->first(); - // $this->setServer($server); - // } + if (count($this->servers) === 1) { + $server = $this->servers->first(); + if ($server instanceof Server) { + $this->setServer($server); + } + } if (! is_null($this->server)) { $foundServer = $this->servers->where('id', $this->server->id)->first(); if ($foundServer) { @@ -195,6 +197,13 @@ class Select extends Component $this->server = $server; $this->standaloneDockers = $server->standaloneDockers; $this->swarmDockers = $server->swarmDockers; + $count = count($this->standaloneDockers) + count($this->swarmDockers); + if ($count === 1) { + $docker = $this->standaloneDockers->first() ?? $this->swarmDockers->first(); + if ($docker) { + $this->setDestination($docker->uuid); + } + } $this->current_step = 'destinations'; } diff --git a/app/Livewire/Project/Resource/EnvironmentSelect.php b/app/Livewire/Project/Resource/EnvironmentSelect.php new file mode 100644 index 000000000..efb1b6ca2 --- /dev/null +++ b/app/Livewire/Project/Resource/EnvironmentSelect.php @@ -0,0 +1,35 @@ +selectedEnvironment = request()->route('environment_name'); + $this->project_uuid = request()->route('project_uuid'); + } + + public function updatedSelectedEnvironment($value) + { + if ($value === 'edit') { + return redirect()->route('project.show', [ + 'project_uuid' => $this->project_uuid, + ]); + } else { + return redirect()->route('project.resource.index', [ + 'project_uuid' => $this->project_uuid, + 'environment_name' => $value, + ]); + } + } +} diff --git a/app/Livewire/Project/Service/EditCompose.php b/app/Livewire/Project/Service/EditCompose.php index fd4d684b1..c9bdf12fc 100644 --- a/app/Livewire/Project/Service/EditCompose.php +++ b/app/Livewire/Project/Service/EditCompose.php @@ -11,6 +11,8 @@ class EditCompose extends Component public $serviceId; + protected $listeners = ['refreshEnvs' => 'mount']; + protected $rules = [ 'service.docker_compose_raw' => 'required', 'service.docker_compose' => 'required', diff --git a/app/Livewire/Project/Service/StackForm.php b/app/Livewire/Project/Service/StackForm.php index 05917f895..a1b613a43 100644 --- a/app/Livewire/Project/Service/StackForm.php +++ b/app/Livewire/Project/Service/StackForm.php @@ -75,7 +75,6 @@ class StackForm extends Component $this->service->parse(); $this->service->refresh(); $this->service->saveComposeConfigs(); - $this->dispatch('refreshStacks'); $this->dispatch('refreshEnvs'); $this->dispatch('success', 'Service saved.'); } catch (\Throwable $e) { diff --git a/app/Livewire/Charts/ServerMemory.php b/app/Livewire/Server/Charts.php similarity index 53% rename from app/Livewire/Charts/ServerMemory.php rename to app/Livewire/Server/Charts.php index 911f267f6..0921c7fa4 100644 --- a/app/Livewire/Charts/ServerMemory.php +++ b/app/Livewire/Server/Charts.php @@ -1,15 +1,15 @@ poll || $this->interval <= 10) { @@ -37,13 +32,21 @@ class ServerMemory extends Component public function loadData() { try { - $metrics = $this->server->getMemoryMetrics($this->interval); - $metrics = collect($metrics)->map(function ($metric) { + $cpuMetrics = $this->server->getCpuMetrics($this->interval); + $memoryMetrics = $this->server->getMemoryMetrics($this->interval); + $cpuMetrics = collect($cpuMetrics)->map(function ($metric) { return [$metric[0], $metric[1]]; }); - $this->dispatch("refreshChartData-{$this->chartId}", [ - 'seriesData' => $metrics, + $memoryMetrics = collect($memoryMetrics)->map(function ($metric) { + return [$metric[0], $metric[1]]; + }); + $this->dispatch("refreshChartData-{$this->chartId}-cpu", [ + 'seriesData' => $cpuMetrics, ]); + $this->dispatch("refreshChartData-{$this->chartId}-memory", [ + 'seriesData' => $memoryMetrics, + ]); + } catch (\Throwable $e) { return handleError($e, $this); } diff --git a/app/Models/Application.php b/app/Models/Application.php index 859abe191..50c7760ff 100644 --- a/app/Models/Application.php +++ b/app/Models/Application.php @@ -228,7 +228,7 @@ class Application extends BaseModel public function gitCommitLink($link): string { - if (! is_null($this->source?->html_url) && ! is_null($this->git_repository) && ! is_null($this->git_branch)) { + if (! is_null(data_get($this, 'source.html_url')) && ! is_null(data_get($this, 'git_repository')) && ! is_null(data_get($this, 'git_branch'))) { if (str($this->source->html_url)->contains('bitbucket')) { return "{$this->source->html_url}/{$this->git_repository}/commits/{$link}"; } @@ -245,8 +245,11 @@ class Application extends BaseModel } if (strpos($this->git_repository, 'git@') === 0) { $git_repository = str_replace(['git@', ':', '.git'], ['', '/', ''], $this->git_repository); + if (data_get($this, 'source.html_url')) { + return "{$this->source->html_url}/{$git_repository}/commit/{$link}"; + } - return "https://{$git_repository}/commit/{$link}"; + return "{$git_repository}/commit/{$link}"; } return $this->git_repository; diff --git a/app/Models/Environment.php b/app/Models/Environment.php index e84b6989b..b2bb51092 100644 --- a/app/Models/Environment.php +++ b/app/Models/Environment.php @@ -109,7 +109,7 @@ class Environment extends Model protected function name(): Attribute { return Attribute::make( - set: fn (string $value) => strtolower($value), + set: fn (string $value) => str($value)->lower()->trim()->replace('/', '-')->toString(), ); } } diff --git a/app/Models/Project.php b/app/Models/Project.php index acc98e341..1cbce6cac 100644 --- a/app/Models/Project.php +++ b/app/Models/Project.php @@ -112,4 +112,14 @@ class Project extends BaseModel { return $this->postgresqls()->get()->merge($this->redis()->get())->merge($this->mongodbs()->get())->merge($this->mysqls()->get())->merge($this->mariadbs()->get())->merge($this->keydbs()->get())->merge($this->dragonflies()->get())->merge($this->clickhouses()->get()); } + + public function default_environment() + { + $default = $this->environments()->where('name', 'production')->first(); + if (! $default) { + $default = $this->environments()->sortBy('created_at')->first(); + } + + return $default; + } } diff --git a/app/Models/Service.php b/app/Models/Service.php index 8adca3424..c608e38dd 100644 --- a/app/Models/Service.php +++ b/app/Models/Service.php @@ -839,20 +839,33 @@ class Service extends BaseModel $commands[] = "cd $workdir"; $json = Yaml::parse($this->docker_compose); + $envs_from_coolify = $this->environment_variables()->get(); foreach ($json['services'] as $service => $config) { $envs = collect($config['environment']); $envs->push("COOLIFY_CONTAINER_NAME=$service-{$this->uuid}"); + foreach ($envs_from_coolify as $env) { + $envs = $envs->map(function ($value) use ($env) { + if (str($value)->startsWith($env->key)) { + return "{$env->key}={$env->real_value}"; + } + + return $value; + }); + } + $envs = $envs->unique(); data_set($json, "services.$service.environment", $envs->toArray()); } - $this->docker_compose = Yaml::dump($json); + + $this->docker_compose = Yaml::dump($json, 10, 2, Yaml::DUMP_MULTI_LINE_LITERAL_BLOCK); $docker_compose_base64 = base64_encode($this->docker_compose); + $commands[] = "echo $docker_compose_base64 | base64 -d | tee docker-compose.yml > /dev/null"; - $envs = $this->environment_variables()->get(); $commands[] = 'rm -f .env || true'; - foreach ($envs as $env) { + + foreach ($envs_from_coolify as $env) { $commands[] = "echo '{$env->key}={$env->real_value}' >> .env"; } - if ($envs->count() === 0) { + if ($envs_from_coolify->count() === 0) { $commands[] = 'touch .env'; } instant_remote_process($commands, $this->server); diff --git a/bootstrap/helpers/api.php b/bootstrap/helpers/api.php index 999de45c2..c278a5045 100644 --- a/bootstrap/helpers/api.php +++ b/bootstrap/helpers/api.php @@ -1,5 +1,7 @@ user()->currentAccessToken(); @@ -10,3 +12,27 @@ function invalid_token() { return response()->json(['error' => 'Invalid token.', 'docs' => 'https://coolify.io/docs/api-reference/authorization'], 400); } + +function serialize_api_response($data) +{ + if (! $data instanceof Collection) { + $data = collect($data); + } + $data = $data->sortKeys(); + $created_at = data_get($data, 'created_at'); + $updated_at = data_get($data, 'updated_at'); + if ($created_at) { + unset($data['created_at']); + $data['created_at'] = $created_at; + + } + if ($updated_at) { + unset($data['updated_at']); + $data['updated_at'] = $updated_at; + } + if (data_get($data, 'id')) { + $data = $data->prepend($data['id'], 'id'); + } + + return $data; +} diff --git a/bootstrap/helpers/applications.php b/bootstrap/helpers/applications.php index 816a13853..df891b824 100644 --- a/bootstrap/helpers/applications.php +++ b/bootstrap/helpers/applications.php @@ -8,7 +8,7 @@ use App\Models\Server; use App\Models\StandaloneDocker; use Spatie\Url\Url; -function queue_application_deployment(Application $application, string $deployment_uuid, ?int $pull_request_id = 0, string $commit = 'HEAD', bool $force_rebuild = false, bool $is_webhook = false, bool $restart_only = false, ?string $git_type = null, bool $no_questions_asked = false, ?Server $server = null, ?StandaloneDocker $destination = null, bool $only_this_server = false, bool $rollback = false) +function queue_application_deployment(Application $application, string $deployment_uuid, ?int $pull_request_id = 0, string $commit = 'HEAD', bool $force_rebuild = false, bool $is_webhook = false, bool $is_api = false, bool $restart_only = false, ?string $git_type = null, bool $no_questions_asked = false, ?Server $server = null, ?StandaloneDocker $destination = null, bool $only_this_server = false, bool $rollback = false) { $application_id = $application->id; $deployment_link = Url::fromString($application->link()."/deployment/{$deployment_uuid}"); @@ -35,6 +35,7 @@ function queue_application_deployment(Application $application, string $deployme 'pull_request_id' => $pull_request_id, 'force_rebuild' => $force_rebuild, 'is_webhook' => $is_webhook, + 'is_api' => $is_api, 'restart_only' => $restart_only, 'commit' => $commit, 'rollback' => $rollback, diff --git a/bootstrap/helpers/shared.php b/bootstrap/helpers/shared.php index b9dc685b7..aef362491 100644 --- a/bootstrap/helpers/shared.php +++ b/bootstrap/helpers/shared.php @@ -1254,6 +1254,18 @@ function parseDockerComposeFile(Service|Application $resource, bool $isNew = fal ]); } } + $envs_from_coolify = $resource->environment_variables()->get(); + $serviceVariables = $serviceVariables->map(function ($variable) use ($envs_from_coolify) { + $env_variable_key = str($variable)->before('='); + $env_variable_value = str($variable)->after('='); + $found_env = $envs_from_coolify->where('key', $env_variable_key)->first(); + if ($found_env) { + $env_variable_value = $found_env->value; + } + + return "$env_variable_key=$env_variable_value"; + }); + } // Add labels to the service if ($savedService->serviceType()) { @@ -1318,19 +1330,7 @@ function parseDockerComposeFile(Service|Application $resource, bool $isNew = fal data_forget($service, 'volumes.*.isDirectory'); data_forget($service, 'volumes.*.is_directory'); data_forget($service, 'exclude_from_hc'); - - // Remove unnecessary variables from service.environment - // $withoutServiceEnvs = collect([]); - // collect(data_get($service, 'environment'))->each(function ($value, $key) use ($withoutServiceEnvs) { - // ray($key, $value); - // if (!Str::of($key)->startsWith('$SERVICE_') && !Str::of($value)->startsWith('SERVICE_')) { - // $k = Str::of($value)->before("="); - // $v = Str::of($value)->after("="); - // $withoutServiceEnvs->put($k->value(), $v->value()); - // } - // }); - // ray($withoutServiceEnvs); - // data_set($service, 'environment', $withoutServiceEnvs->toArray()); + data_set($service, 'environment', $serviceVariables->toArray()); updateCompose($savedService); return $service; diff --git a/database/migrations/2024_06_21_143358_add_api_deployment_type.php b/database/migrations/2024_06_21_143358_add_api_deployment_type.php new file mode 100644 index 000000000..03f4d4796 --- /dev/null +++ b/database/migrations/2024_06_21_143358_add_api_deployment_type.php @@ -0,0 +1,28 @@ +boolean('is_api')->default(false); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::table('application_deployment_queues', function (Blueprint $table) { + $table->dropColumn('is_api'); + }); + } +}; diff --git a/resources/views/livewire/charts/server-cpu.blade.php b/resources/views/livewire/charts/server-cpu.blade.php deleted file mode 100644 index 3aab224f9..000000000 --- a/resources/views/livewire/charts/server-cpu.blade.php +++ /dev/null @@ -1,116 +0,0 @@ -
-

CPU (%)

- - - - - - - - - -
- - - -
diff --git a/resources/views/livewire/charts/server-memory.blade.php b/resources/views/livewire/charts/server-memory.blade.php deleted file mode 100644 index 851b9b975..000000000 --- a/resources/views/livewire/charts/server-memory.blade.php +++ /dev/null @@ -1,123 +0,0 @@ -
-

Memory (MB)

- - - - - - - - - -
- - - -
diff --git a/resources/views/livewire/dashboard.blade.php b/resources/views/livewire/dashboard.blade.php index d5e645a10..2df3b1cdb 100644 --- a/resources/views/livewire/dashboard.blade.php +++ b/resources/views/livewire/dashboard.blade.php @@ -23,9 +23,7 @@
@foreach ($projects as $project)
count() === 1) onclick="gotoProject('{{ data_get($project, 'uuid') }}', '{{ data_get($project, 'environments.0.name', 'production') }}')" - @else - onclick="window.location.href = '{{ route('project.show', ['project_uuid' => data_get($project, 'uuid')]) }}'" @endif> + onclick="window.location.href = '{{ route('project.resource.index', ['project_uuid' => data_get($project, 'uuid'), 'environment_name' => $project->default_environment()->name]) }}'">
{{ $project->name }}
diff --git a/resources/views/livewire/profile/index.blade.php b/resources/views/livewire/profile/index.blade.php index b2583519b..0648016b7 100644 --- a/resources/views/livewire/profile/index.blade.php +++ b/resources/views/livewire/profile/index.blade.php @@ -40,7 +40,7 @@ Validate 2FA
-
{!! request()->user()->twoFactorQrCodeSvg() !!}
+
{!! request()->user()->twoFactorQrCodeSvg() !!}