Merge remote-tracking branch 'upstream/next' into next

This commit is contained in:
Leonardo Cabeza 2024-06-24 17:54:12 -05:00
commit 408c24c700
167 changed files with 3101 additions and 621 deletions

View File

@ -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) [![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) [![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) [![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 # About the Project
Coolify is an open-source & self-hostable alternative to Heroku / Netlify / Vercel / etc. Coolify is an open-source & self-hostable alternative to Heroku / Netlify / Vercel / etc.
@ -34,13 +38,17 @@ Thank you so much!
Special thanks to our biggest sponsors! Special thanks to our biggest sponsors!
<a href="https://cccareers.org/" target="_blank"><img src="./other/logos/ccc-logo.webp" alt="cccareers logo" width="200"/></a> <a href="https://cccareers.org/" target="_blank"><img src="./other/logos/ccc-logo.webp" alt="cccareers logo" width="200"/></a>
<a href="http://htznr.li/CoolifyXHetzner" target="_blank"><img src="./other/logos/hetzner.jpg" alt="hetzner logo" width="200"/></a> <a href="http://htznr.li/CoolifyXHetzner" target="_blank"><img src="./other/logos/hetzner.jpg" alt="hetzner logo" width="150"/></a>
<a href="https://logto.io/?ref=coolify" target="_blank"><img src="./other/logos/logto.webp" alt="logto logo" width="200"/></a> <a href="https://logto.io/?ref=coolify" target="_blank"><img src="./other/logos/logto.webp" alt="logto logo" width="150"/></a>
<a href="https://bc.direct/?ref=coolify.io" target="_blank"><img src="./other/logos/bc.png" alt="bc direct logo" width="200"/></a> <a href="https://bc.direct/?ref=coolify.io" target="_blank"><img src="./other/logos/bc.png" alt="bc direct logo" width="200"/></a>
<a href="https://www.quantcdn.io/?ref=coolify.io" target="_blank"><img src="./other/logos/quant.svg" alt="quantcdn logo" width="200"/></a> <a href="https://www.quantcdn.io/?ref=coolify.io" target="_blank"><img src="./other/logos/quant.svg" alt="quantcdn logo" width="150"/></a>
<a href="https://arcjet.com/?ref=coolify.io" target="_blank"><img src="./other/logos/arcjet.svg" alt="arcjet logo" width="200"/></a> <a href="https://arcjet.com/?ref=coolify.io" target="_blank"><img src="./other/logos/arcjet.svg" alt="arcjet logo" width="200"/></a>
<a href="https://supa.guide/?ref=coolify.io" target="_blank"><img src="./other/logos/supaguide.png" alt="supaguide logo" width="200"/></a> <a href="https://supa.guide/?ref=coolify.io" target="_blank"><img src="./other/logos/supaguide.png" alt="supaguide logo" width="200"/></a>
<a href="https://tigrisdata.com/?ref=coolify.io" target="_blank"><img src="./other/logos/tigris.svg" alt="tigris logo" width="200"/></a> <a href="https://tigrisdata.com/?ref=coolify.io" target="_blank"><img src="./other/logos/tigris.svg" alt="tigris logo" width="140"/></a>
<a href="https://fractalnetworks.co/?ref=coolify.io" target="_blank"><img src="./other/logos/fractal.svg" alt="fractal logo" width="180"/></a>
<a href="https://coolify.ad.vin/?ref=coolify.io" target="_blank"><img src="./other/logos/advin.png" alt="advin logo" width="250"/></a>
<a href="https://trieve.ai/?ref=coolify.io" target="_blank"><img src="./other/logos/trieve_bg.png" alt="trieve logo" width="180"/></a>
<a href="https://blacksmith.sh/?ref=coolify.io" target="_blank"><img src="./other/logos/blacksmith.svg" alt="blacksmith logo" width="200"/></a>
## Github Sponsors ($40+) ## Github Sponsors ($40+)
<a href="https://serpapi.com/?ref=coolify.io"><img width="60px" alt="SerpAPI" src="https://github.com/serpapi.png"/></a> <a href="https://serpapi.com/?ref=coolify.io"><img width="60px" alt="SerpAPI" src="https://github.com/serpapi.png"/></a>
@ -51,6 +59,7 @@ Special thanks to our biggest sponsors!
<a href="https://www.flint.sh/en/home?ref=coolify.io"> <img src="https://github.com/Flint-company.png" width="60px" alt="FlintCompany"/></a> <a href="https://www.flint.sh/en/home?ref=coolify.io"> <img src="https://github.com/Flint-company.png" width="60px" alt="FlintCompany"/></a>
<a href="https://americancloud.com/?ref=coolify.io"><img src="https://github.com/American-Cloud.png" width="60px" alt="American Cloud"/></a> <a href="https://americancloud.com/?ref=coolify.io"><img src="https://github.com/American-Cloud.png" width="60px" alt="American Cloud"/></a>
<a href="https://cryptojobslist.com/?ref=coolify.io"><img src="https://github.com/cryptojobslist.png" width="60px" alt="CryptoJobsList" /></a> <a href="https://cryptojobslist.com/?ref=coolify.io"><img src="https://github.com/cryptojobslist.png" width="60px" alt="CryptoJobsList" /></a>
<a href="https://codext.link/coolify-io?ref=coolify.io"><img src="./other/logos/codext.jpg" width="60px" alt="Codext" /></a>
<a href="https://x.com/mrsmith9ja?ref=coolify.io"><img width="60px" alt="Thompson Edolo" src="https://github.com/verygreenboi.png"/></a> <a href="https://x.com/mrsmith9ja?ref=coolify.io"><img width="60px" alt="Thompson Edolo" src="https://github.com/verygreenboi.png"/></a>
<a href="https://www.uxwizz.com/?ref=coolify.io"><img width="60px" alt="UXWizz" src="https://github.com/UXWizz.png"/></a> <a href="https://www.uxwizz.com/?ref=coolify.io"><img width="60px" alt="UXWizz" src="https://github.com/UXWizz.png"/></a>
<a href="https://github.com/Flowko"><img src="https://barrad.me/_ipx/f_webp&s_300x300/younes.jpg" width="60px" alt="Younes Barrad" /></a> <a href="https://github.com/Flowko"><img src="https://barrad.me/_ipx/f_webp&s_300x300/younes.jpg" width="60px" alt="Younes Barrad" /></a>

View File

@ -2,6 +2,7 @@
namespace App\Actions\Database; namespace App\Actions\Database;
use App\Events\DatabaseStatusChanged;
use App\Models\ServiceDatabase; use App\Models\ServiceDatabase;
use App\Models\StandaloneClickhouse; use App\Models\StandaloneClickhouse;
use App\Models\StandaloneDragonfly; use App\Models\StandaloneDragonfly;
@ -28,5 +29,6 @@ class StopDatabaseProxy
instant_remote_process(["docker rm -f {$uuid}-proxy"], $server); instant_remote_process(["docker rm -f {$uuid}-proxy"], $server);
$database->is_public = false; $database->is_public = false;
$database->save(); $database->save();
DatabaseStatusChanged::dispatch();
} }
} }

View File

@ -12,12 +12,15 @@ class StartSentinel
public function handle(Server $server, $version = 'latest', bool $restart = false) public function handle(Server $server, $version = 'latest', bool $restart = false)
{ {
if ($restart) { if ($restart) {
instant_remote_process(['docker rm -f coolify-sentinel'], $server, false); StopSentinel::run($server);
} }
$metrics_history = $server->settings->metrics_history_days;
$refresh_rate = $server->settings->metrics_refresh_rate_seconds;
$token = $server->settings->metrics_token;
instant_remote_process([ instant_remote_process([
"docker run --rm --pull always -d -e \"SCHEDULER=true\" --name coolify-sentinel -v /var/run/docker.sock:/var/run/docker.sock -v /data/coolify/metrics:/app/metrics -v /data/coolify/logs:/app/logs --pid host --health-cmd \"curl --fail http://127.0.0.1:8888/api/health || exit 1\" --health-interval 10s --health-retries 3 ghcr.io/coollabsio/sentinel:$version", "docker run --rm --pull always -d -e \"TOKEN={$token}\" -e \"SCHEDULER=true\" -e \"METRICS_HISTORY={$metrics_history}\" -e \"REFRESH_RATE={$refresh_rate}\" --name coolify-sentinel -v /var/run/docker.sock:/var/run/docker.sock -v /data/coolify/metrics:/app/metrics -v /data/coolify/logs:/app/logs --pid host --health-cmd \"curl --fail http://127.0.0.1:8888/api/health || exit 1\" --health-interval 10s --health-retries 3 ghcr.io/coollabsio/sentinel:$version",
'chown -R 9999:root /data/coolify/metrics /data/coolify/logs', 'chown -R 9999:root /data/coolify/metrics /data/coolify/logs',
'chmod -R 700 /data/coolify/metrics /data/coolify/logs', 'chmod -R 700 /data/coolify/metrics /data/coolify/logs',
], $server, false); ], $server, true);
} }
} }

View File

@ -0,0 +1,16 @@
<?php
namespace App\Actions\Server;
use App\Models\Server;
use Lorisleiva\Actions\Concerns\AsAction;
class StopSentinel
{
use AsAction;
public function handle(Server $server)
{
instant_remote_process(['docker rm -f coolify-sentinel'], $server, false);
}
}

View File

@ -5,6 +5,7 @@ namespace App\Console\Commands;
use App\Enums\ApplicationDeploymentStatus; use App\Enums\ApplicationDeploymentStatus;
use App\Jobs\CleanupHelperContainersJob; use App\Jobs\CleanupHelperContainersJob;
use App\Models\ApplicationDeploymentQueue; use App\Models\ApplicationDeploymentQueue;
use App\Models\Environment;
use App\Models\InstanceSettings; use App\Models\InstanceSettings;
use App\Models\ScheduledDatabaseBackup; use App\Models\ScheduledDatabaseBackup;
use App\Models\Server; use App\Models\Server;
@ -24,6 +25,8 @@ class Init extends Command
get_public_ips(); get_public_ips();
$full_cleanup = $this->option('full-cleanup'); $full_cleanup = $this->option('full-cleanup');
$cleanup_deployments = $this->option('cleanup-deployments'); $cleanup_deployments = $this->option('cleanup-deployments');
$this->replace_slash_in_environment_name();
if ($cleanup_deployments) { if ($cleanup_deployments) {
echo "Running cleanup deployments.\n"; echo "Running cleanup deployments.\n";
$this->cleanup_in_progress_application_deployments(); $this->cleanup_in_progress_application_deployments();
@ -150,4 +153,15 @@ class Init extends Command
echo "Error: {$e->getMessage()}\n"; 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();
}
}
}
} }

View File

@ -61,7 +61,7 @@ class Kernel extends ConsoleKernel
{ {
$servers = $this->all_servers->where('settings.is_usable', true)->where('settings.is_reachable', true)->where('ip', '!=', '1.2.3.4'); $servers = $this->all_servers->where('settings.is_usable', true)->where('settings.is_reachable', true)->where('ip', '!=', '1.2.3.4');
foreach ($servers as $server) { foreach ($servers as $server) {
if (config('coolify.is_sentinel_enabled')) { if ($server->isSentinelEnabled()) {
$schedule->job(new PullSentinelImageJob($server))->everyFiveMinutes()->onOneServer(); $schedule->job(new PullSentinelImageJob($server))->everyFiveMinutes()->onOneServer();
} }
$schedule->job(new PullHelperImageJob($server))->everyFiveMinutes()->onOneServer(); $schedule->job(new PullHelperImageJob($server))->everyFiveMinutes()->onOneServer();

View File

@ -11,6 +11,5 @@ class ServerMetadata extends Data
public function __construct( public function __construct(
public ?ProxyTypes $type, public ?ProxyTypes $type,
public ?ProxyStatus $status public ?ProxyStatus $status
) { ) {}
}
} }

View File

@ -10,8 +10,5 @@ class ProxyStarted
{ {
use Dispatchable, InteractsWithSockets, SerializesModels; use Dispatchable, InteractsWithSockets, SerializesModels;
public function __construct(public $data) public function __construct(public $data) {}
{
}
} }

View File

@ -4,6 +4,4 @@ namespace App\Exceptions;
use Exception; use Exception;
class ProcessException extends Exception class ProcessException extends Exception {}
{
}

View File

@ -0,0 +1,183 @@
<?php
namespace App\Http\Controllers\Api;
use App\Actions\Application\StopApplication;
use App\Http\Controllers\Controller;
use App\Models\Application;
use App\Models\Project;
use Illuminate\Http\Request;
use Visus\Cuid2\Cuid2;
class Applications extends Controller
{
public function applications(Request $request)
{
$teamId = get_team_id_from_token();
if (is_null($teamId)) {
return invalid_token();
}
$projects = Project::where('team_id', $teamId)->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
);
}
}

View File

@ -38,7 +38,25 @@ class Deploy extends Controller
'status', 'status',
])->sortBy('id')->toArray(); ])->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) public function deploy(Request $request)

View File

@ -3,102 +3,52 @@
namespace App\Http\Controllers\Api; namespace App\Http\Controllers\Api;
use App\Http\Controllers\Controller; use App\Http\Controllers\Controller;
use App\Models\InstanceSettings; use App\Models\Application;
use App\Models\Project as ModelsProject;
use Illuminate\Http\Request; use Illuminate\Http\Request;
use Illuminate\Support\Facades\Validator;
class Domains extends Controller class Domains extends Controller
{ {
public function domains(Request $request) public function deleteDomains(Request $request)
{ {
$teamId = get_team_id_from_token(); $teamId = get_team_id_from_token();
if (is_null($teamId)) { if (is_null($teamId)) {
return invalid_token(); return invalid_token();
} }
$projects = ModelsProject::where('team_id', $teamId)->get(); $validator = Validator::make($request->all(), [
$domains = collect(); 'uuid' => 'required|string|exists:applications,uuid',
$applications = $projects->pluck('applications')->flatten(); 'domains' => 'required|array',
$settings = InstanceSettings::get(); 'domains.*' => 'required|string|distinct',
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); 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);
$domainsToDelete = $request->domains;
$updatedDomains = array_diff($existingDomains, $domainsToDelete);
$application->fqdn = implode(',', $updatedDomains);
$application->custom_labels = base64_encode(implode("\n ", generateLabelsApplication($application)));
$application->save();
return response()->json([
'success' => true,
'message' => 'Domains updated successfully',
'application' => $application,
]);
} }
} }

View File

@ -3,6 +3,9 @@
namespace App\Http\Controllers\Api; namespace App\Http\Controllers\Api;
use App\Http\Controllers\Controller; use App\Http\Controllers\Controller;
use App\Models\Application;
use App\Models\InstanceSettings;
use App\Models\Project;
use App\Models\Server as ModelsServer; use App\Models\Server as ModelsServer;
use Illuminate\Http\Request; use Illuminate\Http\Request;
@ -59,4 +62,106 @@ class Server extends Controller
return response()->json($server); 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);
}
} }

View File

@ -2,8 +2,10 @@
namespace App\Http\Controllers; namespace App\Http\Controllers;
use App\Models\InstanceSettings;
use App\Models\User; use App\Models\User;
use Illuminate\Support\Facades\Auth; use Illuminate\Support\Facades\Auth;
use Symfony\Component\HttpKernel\Exception\HttpException;
class OauthController extends Controller class OauthController extends Controller
{ {
@ -20,6 +22,11 @@ class OauthController extends Controller
$oauthUser = get_socialite_provider($provider)->user(); $oauthUser = get_socialite_provider($provider)->user();
$user = User::whereEmail($oauthUser->email)->first(); $user = User::whereEmail($oauthUser->email)->first();
if (! $user) { if (! $user) {
$settings = InstanceSettings::get();
if (! $settings->is_registration_enabled) {
abort(403, 'Registration is disabled');
}
$user = User::create([ $user = User::create([
'name' => $oauthUser->name, 'name' => $oauthUser->name,
'email' => $oauthUser->email, 'email' => $oauthUser->email,
@ -31,7 +38,9 @@ class OauthController extends Controller
} catch (\Exception $e) { } catch (\Exception $e) {
ray($e->getMessage()); 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)]);
} }
} }
} }

View File

@ -72,14 +72,14 @@ class Stripe extends Controller
} }
$subscription = Subscription::where('team_id', $teamId)->first(); $subscription = Subscription::where('team_id', $teamId)->first();
if ($subscription) { if ($subscription) {
send_internal_notification('Old subscription activated for team: '.$teamId); // send_internal_notification('Old subscription activated for team: '.$teamId);
$subscription->update([ $subscription->update([
'stripe_subscription_id' => $subscriptionId, 'stripe_subscription_id' => $subscriptionId,
'stripe_customer_id' => $customerId, 'stripe_customer_id' => $customerId,
'stripe_invoice_paid' => true, 'stripe_invoice_paid' => true,
]); ]);
} else { } else {
send_internal_notification('New subscription for team: '.$teamId); // send_internal_notification('New subscription for team: '.$teamId);
Subscription::create([ Subscription::create([
'team_id' => $teamId, 'team_id' => $teamId,
'stripe_subscription_id' => $subscriptionId, 'stripe_subscription_id' => $subscriptionId,
@ -92,7 +92,7 @@ class Stripe extends Controller
$customerId = data_get($data, 'customer'); $customerId = data_get($data, 'customer');
$planId = data_get($data, 'lines.data.0.plan.id'); $planId = data_get($data, 'lines.data.0.plan.id');
if (Str::contains($excludedPlans, $planId)) { if (Str::contains($excludedPlans, $planId)) {
send_internal_notification('Subscription excluded.'); // send_internal_notification('Subscription excluded.');
break; break;
} }
$subscription = Subscription::where('stripe_customer_id', $customerId)->first(); $subscription = Subscription::where('stripe_customer_id', $customerId)->first();
@ -108,33 +108,33 @@ class Stripe extends Controller
$customerId = data_get($data, 'customer'); $customerId = data_get($data, 'customer');
$subscription = Subscription::where('stripe_customer_id', $customerId)->first(); $subscription = Subscription::where('stripe_customer_id', $customerId)->first();
if (! $subscription) { if (! $subscription) {
send_internal_notification('invoice.payment_failed failed but no subscription found in Coolify for customer: '.$customerId); // send_internal_notification('invoice.payment_failed failed but no subscription found in Coolify for customer: '.$customerId);
return response('No subscription found in Coolify.'); return response('No subscription found in Coolify.');
} }
$team = data_get($subscription, 'team'); $team = data_get($subscription, 'team');
if (! $team) { if (! $team) {
send_internal_notification('invoice.payment_failed failed but no team found in Coolify for customer: '.$customerId); // send_internal_notification('invoice.payment_failed failed but no team found in Coolify for customer: '.$customerId);
return response('No team found in Coolify.'); return response('No team found in Coolify.');
} }
if (! $subscription->stripe_invoice_paid) { if (! $subscription->stripe_invoice_paid) {
SubscriptionInvoiceFailedJob::dispatch($team); SubscriptionInvoiceFailedJob::dispatch($team);
send_internal_notification('Invoice payment failed: '.$customerId); // send_internal_notification('Invoice payment failed: '.$customerId);
} else { } else {
send_internal_notification('Invoice payment failed but already paid: '.$customerId); // send_internal_notification('Invoice payment failed but already paid: '.$customerId);
} }
break; break;
case 'payment_intent.payment_failed': case 'payment_intent.payment_failed':
$customerId = data_get($data, 'customer'); $customerId = data_get($data, 'customer');
$subscription = Subscription::where('stripe_customer_id', $customerId)->first(); $subscription = Subscription::where('stripe_customer_id', $customerId)->first();
if (! $subscription) { if (! $subscription) {
send_internal_notification('payment_intent.payment_failed, no subscription found in Coolify for customer: '.$customerId); // send_internal_notification('payment_intent.payment_failed, no subscription found in Coolify for customer: '.$customerId);
return response('No subscription found in Coolify.'); return response('No subscription found in Coolify.');
} }
if ($subscription->stripe_invoice_paid) { if ($subscription->stripe_invoice_paid) {
send_internal_notification('payment_intent.payment_failed but invoice is active for customer: '.$customerId); // send_internal_notification('payment_intent.payment_failed but invoice is active for customer: '.$customerId);
return; return;
} }
@ -146,7 +146,7 @@ class Stripe extends Controller
$subscriptionId = data_get($data, 'items.data.0.subscription'); $subscriptionId = data_get($data, 'items.data.0.subscription');
$planId = data_get($data, 'items.data.0.plan.id'); $planId = data_get($data, 'items.data.0.plan.id');
if (Str::contains($excludedPlans, $planId)) { if (Str::contains($excludedPlans, $planId)) {
send_internal_notification('Subscription excluded.'); // send_internal_notification('Subscription excluded.');
break; break;
} }
$subscription = Subscription::where('stripe_customer_id', $customerId)->first(); $subscription = Subscription::where('stripe_customer_id', $customerId)->first();
@ -156,11 +156,11 @@ class Stripe extends Controller
} }
if (! $subscription) { if (! $subscription) {
if ($status === 'incomplete_expired') { if ($status === 'incomplete_expired') {
send_internal_notification('Subscription incomplete expired for customer: '.$customerId); // send_internal_notification('Subscription incomplete expired for customer: '.$customerId);
return response('Subscription incomplete expired', 200); return response('Subscription incomplete expired', 200);
} }
send_internal_notification('No subscription found for: '.$customerId); // send_internal_notification('No subscription found for: '.$customerId);
return response('No subscription found', 400); return response('No subscription found', 400);
} }
@ -194,7 +194,7 @@ class Stripe extends Controller
$subscription->update([ $subscription->update([
'stripe_invoice_paid' => false, 'stripe_invoice_paid' => false,
]); ]);
send_internal_notification('Subscription paused or incomplete for customer: '.$customerId); // send_internal_notification('Subscription paused or incomplete for customer: '.$customerId);
} }
// Trial ended but subscribed, reactive servers // Trial ended but subscribed, reactive servers
@ -208,13 +208,13 @@ class Stripe extends Controller
if ($comment) { if ($comment) {
$reason .= ' with comment: \''.$comment."'"; $reason .= ' with comment: \''.$comment."'";
} }
send_internal_notification($reason); // send_internal_notification($reason);
} }
if ($alreadyCancelAtPeriodEnd !== $cancelAtPeriodEnd) { if ($alreadyCancelAtPeriodEnd !== $cancelAtPeriodEnd) {
if ($cancelAtPeriodEnd) { if ($cancelAtPeriodEnd) {
// send_internal_notification('Subscription cancelled at period end for team: ' . $subscription->team->id); // send_internal_notification('Subscription cancelled at period end for team: ' . $subscription->team->id);
} else { } else {
send_internal_notification('customer.subscription.updated for customer: '.$customerId); // send_internal_notification('customer.subscription.updated for customer: '.$customerId);
} }
} }
break; break;
@ -233,7 +233,7 @@ class Stripe extends Controller
'stripe_invoice_paid' => false, 'stripe_invoice_paid' => false,
'stripe_trial_already_ended' => true, 'stripe_trial_already_ended' => true,
]); ]);
send_internal_notification('customer.subscription.deleted for customer: '.$customerId); // send_internal_notification('customer.subscription.deleted for customer: '.$customerId);
break; break;
case 'customer.subscription.trial_will_end': case 'customer.subscription.trial_will_end':
// Not used for now // Not used for now
@ -258,7 +258,7 @@ class Stripe extends Controller
'stripe_invoice_paid' => false, 'stripe_invoice_paid' => false,
]); ]);
SubscriptionTrialEndedJob::dispatch($team); SubscriptionTrialEndedJob::dispatch($team);
send_internal_notification('Subscription paused for customer: '.$customerId); // send_internal_notification('Subscription paused for customer: '.$customerId);
break; break;
default: default:
// Unhandled event type // Unhandled event type

View File

@ -340,7 +340,7 @@ class ApplicationDeploymentJob implements ShouldBeEncrypted, ShouldQueue
private function post_deployment() private function post_deployment()
{ {
if ($this->server->isProxyShouldRun()) { if ($this->server->isProxyShouldRun()) {
GetContainersStatus::dispatch($this->server); GetContainersStatus::dispatch($this->server)->onQueue('high');
// dispatch(new ContainerStatusJob($this->server)); // dispatch(new ContainerStatusJob($this->server));
} }
$this->next(ApplicationDeploymentStatus::FINISHED->value); $this->next(ApplicationDeploymentStatus::FINISHED->value);
@ -828,6 +828,9 @@ class ApplicationDeploymentJob implements ShouldBeEncrypted, ShouldQueue
if ($this->application->environment_variables_preview->where('key', 'COOLIFY_BRANCH')->isEmpty()) { if ($this->application->environment_variables_preview->where('key', 'COOLIFY_BRANCH')->isEmpty()) {
$envs->push("COOLIFY_BRANCH={$local_branch}"); $envs->push("COOLIFY_BRANCH={$local_branch}");
} }
if ($this->application->environment_variables_preview->where('key', 'COOLIFY_CONTAINER_NAME')->isEmpty()) {
$envs->push("COOLIFY_CONTAINER_NAME={$this->container_name}");
}
foreach ($sorted_environment_variables_preview as $env) { foreach ($sorted_environment_variables_preview as $env) {
$real_value = $env->real_value; $real_value = $env->real_value;
if ($env->version === '4.0.0-beta.239') { if ($env->version === '4.0.0-beta.239') {
@ -869,6 +872,9 @@ class ApplicationDeploymentJob implements ShouldBeEncrypted, ShouldQueue
if ($this->application->environment_variables->where('key', 'COOLIFY_BRANCH')->isEmpty()) { if ($this->application->environment_variables->where('key', 'COOLIFY_BRANCH')->isEmpty()) {
$envs->push("COOLIFY_BRANCH={$local_branch}"); $envs->push("COOLIFY_BRANCH={$local_branch}");
} }
if ($this->application->environment_variables->where('key', 'COOLIFY_CONTAINER_NAME')->isEmpty()) {
$envs->push("COOLIFY_CONTAINER_NAME={$this->container_name}");
}
foreach ($sorted_environment_variables as $env) { foreach ($sorted_environment_variables as $env) {
$real_value = $env->real_value; $real_value = $env->real_value;
if ($env->version === '4.0.0-beta.239') { if ($env->version === '4.0.0-beta.239') {
@ -1863,12 +1869,12 @@ COPY ./nginx.conf /etc/nginx/conf.d/default.conf");
$this->execute_remote_command([ $this->execute_remote_command([
executeInDocker($this->deployment_uuid, "nixpacks build -c /artifacts/thegameplan.json --no-cache --no-error-without-start -n {$this->build_image_name} {$this->workdir} -o {$this->workdir}"), 'hidden' => true, executeInDocker($this->deployment_uuid, "nixpacks build -c /artifacts/thegameplan.json --no-cache --no-error-without-start -n {$this->build_image_name} {$this->workdir} -o {$this->workdir}"), 'hidden' => true,
]); ]);
$build_command = "docker build --no-cache {$this->addHosts} --network host -f {$this->workdir}/.nixpacks/Dockerfile {$this->build_args} --progress plain -t {$this->production_image_name} {$this->workdir}"; $build_command = "docker build --no-cache {$this->addHosts} --network host -f {$this->workdir}/.nixpacks/Dockerfile {$this->build_args} --progress plain -t {$this->build_image_name} {$this->workdir}";
} else { } else {
$this->execute_remote_command([ $this->execute_remote_command([
executeInDocker($this->deployment_uuid, "nixpacks build -c /artifacts/thegameplan.json --cache-key '{$this->application->uuid}' --no-error-without-start -n {$this->build_image_name} {$this->workdir} -o {$this->workdir}"), 'hidden' => true, executeInDocker($this->deployment_uuid, "nixpacks build -c /artifacts/thegameplan.json --cache-key '{$this->application->uuid}' --no-error-without-start -n {$this->build_image_name} {$this->workdir} -o {$this->workdir}"), 'hidden' => true,
]); ]);
$build_command = "docker build {$this->addHosts} --network host -f {$this->workdir}/.nixpacks/Dockerfile {$this->build_args} --progress plain -t {$this->production_image_name} {$this->workdir}"; $build_command = "docker build {$this->addHosts} --network host -f {$this->workdir}/.nixpacks/Dockerfile {$this->build_args} --progress plain -t {$this->build_image_name} {$this->workdir}";
} }
$base64_build_command = base64_encode($build_command); $base64_build_command = base64_encode($build_command);
@ -1898,7 +1904,6 @@ COPY ./nginx.conf /etc/nginx/conf.d/default.conf");
] ]
); );
} }
$dockerfile = base64_encode("FROM {$this->application->static_image} $dockerfile = base64_encode("FROM {$this->application->static_image}
WORKDIR /usr/share/nginx/html/ WORKDIR /usr/share/nginx/html/
LABEL coolify.deploymentId={$this->deployment_uuid} LABEL coolify.deploymentId={$this->deployment_uuid}

View File

@ -25,8 +25,7 @@ class ApplicationPullRequestUpdateJob implements ShouldBeEncrypted, ShouldQueue
public ApplicationPreview $preview, public ApplicationPreview $preview,
public ProcessStatus $status, public ProcessStatus $status,
public ?string $deployment_uuid = null public ?string $deployment_uuid = null
) { ) {}
}
public function handle() public function handle()
{ {

View File

@ -19,9 +19,7 @@ class CheckLogDrainContainerJob implements ShouldBeEncrypted, ShouldQueue
{ {
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels; use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
public function __construct(public Server $server) public function __construct(public Server $server) {}
{
}
public function middleware(): array public function middleware(): array
{ {

View File

@ -14,9 +14,7 @@ class CheckResaleLicenseJob implements ShouldBeEncrypted, ShouldQueue
{ {
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels; use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
public function __construct() public function __construct() {}
{
}
public function handle(): void public function handle(): void
{ {

View File

@ -15,9 +15,7 @@ class CleanupHelperContainersJob implements ShouldBeEncrypted, ShouldBeUnique, S
{ {
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels; use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
public function __construct(public Server $server) public function __construct(public Server $server) {}
{
}
public function handle(): void public function handle(): void
{ {

View File

@ -16,10 +16,7 @@ class CleanupInstanceStuffsJob implements ShouldBeEncrypted, ShouldBeUnique, Sho
{ {
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels; use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
public function __construct() public function __construct() {}
{
}
// public function uniqueId(): string // public function uniqueId(): string
// { // {

View File

@ -23,9 +23,7 @@ class ContainerStatusJob implements ShouldBeEncrypted, ShouldQueue
return isDev() ? 1 : 3; return isDev() ? 1 : 3;
} }
public function __construct(public Server $server) public function __construct(public Server $server) {}
{
}
public function middleware(): array public function middleware(): array
{ {

View File

@ -23,8 +23,7 @@ class CoolifyTask implements ShouldBeEncrypted, ShouldQueue
public bool $ignore_errors = false, public bool $ignore_errors = false,
public $call_event_on_finish = null, public $call_event_on_finish = null,
public $call_event_data = null public $call_event_data = null
) { ) {}
}
/** /**
* Execute the job. * Execute the job.

View File

@ -18,9 +18,7 @@ class DatabaseBackupStatusJob implements ShouldBeEncrypted, ShouldQueue
public $tries = 1; public $tries = 1;
public function __construct() public function __construct() {}
{
}
public function handle() public function handle()
{ {

View File

@ -28,9 +28,7 @@ class DeleteResourceJob implements ShouldBeEncrypted, ShouldQueue
{ {
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels; use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
public function __construct(public Application|Service|StandalonePostgresql|StandaloneRedis|StandaloneMongodb|StandaloneMysql|StandaloneMariadb|StandaloneKeydb|StandaloneDragonfly|StandaloneClickhouse $resource, public bool $deleteConfigurations = false) public function __construct(public Application|Service|StandalonePostgresql|StandaloneRedis|StandaloneMongodb|StandaloneMysql|StandaloneMariadb|StandaloneKeydb|StandaloneDragonfly|StandaloneClickhouse $resource, public bool $deleteConfigurations = false) {}
{
}
public function handle() public function handle()
{ {

View File

@ -22,9 +22,7 @@ class DockerCleanupJob implements ShouldBeEncrypted, ShouldQueue
public ?int $usageBefore = null; public ?int $usageBefore = null;
public function __construct(public Server $server) public function __construct(public Server $server) {}
{
}
public function handle(): void public function handle(): void
{ {
@ -62,7 +60,7 @@ class DockerCleanupJob implements ShouldBeEncrypted, ShouldQueue
Log::info('No need to clean up '.$this->server->name); Log::info('No need to clean up '.$this->server->name);
} }
} catch (\Throwable $e) { } catch (\Throwable $e) {
send_internal_notification('DockerCleanupJob failed with: '.$e->getMessage()); // send_internal_notification('DockerCleanupJob failed with: '.$e->getMessage());
ray($e->getMessage()); ray($e->getMessage());
throw $e; throw $e;
} }

View File

@ -23,9 +23,7 @@ class GithubAppPermissionJob implements ShouldBeEncrypted, ShouldQueue
return isDev() ? 1 : 3; return isDev() ? 1 : 3;
} }
public function __construct(public GithubApp $github_app) public function __construct(public GithubApp $github_app) {}
{
}
public function middleware(): array public function middleware(): array
{ {

View File

@ -19,9 +19,7 @@ class InstanceAutoUpdateJob implements ShouldBeEncrypted, ShouldBeUnique, Should
public $tries = 1; public $tries = 1;
public function __construct() public function __construct() {}
{
}
public function handle(): void public function handle(): void
{ {

View File

@ -19,9 +19,7 @@ class PullCoolifyImageJob implements ShouldBeEncrypted, ShouldQueue
public $timeout = 1000; public $timeout = 1000;
public function __construct() public function __construct() {}
{
}
public function handle(): void public function handle(): void
{ {

View File

@ -27,9 +27,7 @@ class PullHelperImageJob implements ShouldBeEncrypted, ShouldQueue
return $this->server->uuid; return $this->server->uuid;
} }
public function __construct(public Server $server) public function __construct(public Server $server) {}
{
}
public function handle(): void public function handle(): void
{ {

View File

@ -28,9 +28,7 @@ class PullSentinelImageJob implements ShouldBeEncrypted, ShouldQueue
return $this->server->uuid; return $this->server->uuid;
} }
public function __construct(public Server $server) public function __construct(public Server $server) {}
{
}
public function handle(): void public function handle(): void
{ {
@ -52,7 +50,7 @@ class PullSentinelImageJob implements ShouldBeEncrypted, ShouldQueue
} }
ray('Sentinel image is up to date'); ray('Sentinel image is up to date');
} catch (\Throwable $e) { } catch (\Throwable $e) {
send_internal_notification('PullSentinelImageJob failed with: '.$e->getMessage()); // send_internal_notification('PullSentinelImageJob failed with: '.$e->getMessage());
ray($e->getMessage()); ray($e->getMessage());
throw $e; throw $e;
} }

View File

@ -17,9 +17,7 @@ class PullTemplatesFromCDN implements ShouldBeEncrypted, ShouldQueue
public $timeout = 10; public $timeout = 10;
public function __construct() public function __construct() {}
{
}
public function handle(): void public function handle(): void
{ {

View File

@ -17,9 +17,7 @@ class PullVersionsFromCDN implements ShouldBeEncrypted, ShouldQueue
public $timeout = 10; public $timeout = 10;
public function __construct() public function __construct() {}
{
}
public function handle(): void public function handle(): void
{ {

View File

@ -14,9 +14,7 @@ class SendConfirmationForWaitlistJob implements ShouldBeEncrypted, ShouldQueue
{ {
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels; use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
public function __construct(public string $email, public string $uuid) public function __construct(public string $email, public string $uuid) {}
{
}
public function handle() public function handle()
{ {

View File

@ -31,8 +31,7 @@ class SendMessageToDiscordJob implements ShouldBeEncrypted, ShouldQueue
public function __construct( public function __construct(
public string $text, public string $text,
public string $webhookUrl public string $webhookUrl
) { ) {}
}
/** /**
* Execute the job. * Execute the job.

View File

@ -33,8 +33,7 @@ class SendMessageToTelegramJob implements ShouldBeEncrypted, ShouldQueue
public string $token, public string $token,
public string $chatId, public string $chatId,
public ?string $topicId = null, public ?string $topicId = null,
) { ) {}
}
/** /**
* Execute the job. * Execute the job.

View File

@ -16,9 +16,7 @@ class ServerFilesFromServerJob implements ShouldBeEncrypted, ShouldQueue
{ {
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels; use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
public function __construct(public ServiceApplication|ServiceDatabase|Application $resource) public function __construct(public ServiceApplication|ServiceDatabase|Application $resource) {}
{
}
public function handle() public function handle()
{ {

View File

@ -24,9 +24,7 @@ class ServerLimitCheckJob implements ShouldBeEncrypted, ShouldQueue
return isDev() ? 1 : 3; return isDev() ? 1 : 3;
} }
public function __construct(public Team $team) public function __construct(public Team $team) {}
{
}
public function middleware(): array public function middleware(): array
{ {

View File

@ -25,9 +25,7 @@ class ServerStatusJob implements ShouldBeEncrypted, ShouldQueue
return isDev() ? 1 : 3; return isDev() ? 1 : 3;
} }
public function __construct(public Server $server) public function __construct(public Server $server) {}
{
}
public function middleware(): array public function middleware(): array
{ {
@ -48,7 +46,7 @@ class ServerStatusJob implements ShouldBeEncrypted, ShouldQueue
if ($this->server->isFunctional()) { if ($this->server->isFunctional()) {
$this->cleanup(notify: false); $this->cleanup(notify: false);
$this->remove_unnecessary_coolify_yaml(); $this->remove_unnecessary_coolify_yaml();
if (config('coolify.is_sentinel_enabled')) { if ($this->server->isSentinelEnabled()) {
$this->server->checkSentinel(); $this->server->checkSentinel();
} }
} }

View File

@ -14,9 +14,7 @@ class ServerStorageSaveJob implements ShouldBeEncrypted, ShouldQueue
{ {
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels; use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
public function __construct(public LocalFileVolume $localFileVolume) public function __construct(public LocalFileVolume $localFileVolume) {}
{
}
public function handle() public function handle()
{ {

View File

@ -15,9 +15,7 @@ class SubscriptionInvoiceFailedJob implements ShouldBeEncrypted, ShouldQueue
{ {
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels; use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
public function __construct(protected Team $team) public function __construct(protected Team $team) {}
{
}
public function handle() public function handle()
{ {

View File

@ -17,8 +17,7 @@ class SubscriptionTrialEndedJob implements ShouldBeEncrypted, ShouldQueue
public function __construct( public function __construct(
public Team $team public Team $team
) { ) {}
}
public function handle(): void public function handle(): void
{ {

View File

@ -17,8 +17,7 @@ class SubscriptionTrialEndsSoonJob implements ShouldBeEncrypted, ShouldQueue
public function __construct( public function __construct(
public Team $team public Team $team
) { ) {}
}
public function handle(): void public function handle(): void
{ {

View File

@ -9,9 +9,7 @@ use Symfony\Component\HttpFoundation\Request as SymfonyRequest;
class MaintenanceModeDisabledNotification class MaintenanceModeDisabledNotification
{ {
public function __construct() public function __construct() {}
{
}
public function handle(EventsMaintenanceModeDisabled $event): void public function handle(EventsMaintenanceModeDisabled $event): void
{ {

View File

@ -9,9 +9,7 @@ class ProxyStartedNotification
{ {
public Server $server; public Server $server;
public function __construct() public function __construct() {}
{
}
public function handle(ProxyStarted $event): void public function handle(ProxyStarted $event): void
{ {

View File

@ -12,7 +12,7 @@ use Livewire\Component;
class Index extends Component class Index extends Component
{ {
protected $listeners = ['serverInstalled' => 'validateServer']; protected $listeners = ['refreshBoardingIndex' => 'validateServer'];
public string $currentState = 'welcome'; public string $currentState = 'welcome';

View File

@ -0,0 +1,51 @@
<?php
namespace App\Livewire;
//use Livewire\Component;
use Illuminate\View\Component;
use Visus\Cuid2\Cuid2;
class MonacoEditor extends Component
{
protected $listeners = [
'configurationChanged' => '$refresh',
];
public function __construct(
public ?string $id,
public ?string $name,
public ?string $type,
public ?string $monacoContent,
public ?string $value,
public ?string $label,
public ?string $placeholder,
public bool $required,
public bool $disabled,
public bool $readonly,
public bool $allowTab,
public bool $spellcheck,
public ?string $helper,
public bool $realtimeValidation,
public bool $allowToPeak,
public string $defaultClass,
public string $defaultClassInput,
public ?string $language
) {
//
}
public function render()
{
if (is_null($this->id)) {
$this->id = new Cuid2(7);
}
if (is_null($this->name)) {
$this->name = $this->id;
}
return view('components.forms.monaco-editor');
}
}

View File

@ -347,7 +347,9 @@ class General extends Component
public function submit($showToaster = true) public function submit($showToaster = true)
{ {
try { try {
$this->set_redirect(); if ($this->application->isDirty('redirect')) {
$this->set_redirect();
}
$this->application->fqdn = str($this->application->fqdn)->replaceEnd(',', '')->trim(); $this->application->fqdn = str($this->application->fqdn)->replaceEnd(',', '')->trim();
$this->application->fqdn = str($this->application->fqdn)->replaceStart(',', '')->trim(); $this->application->fqdn = str($this->application->fqdn)->replaceStart(',', '')->trim();
$this->application->fqdn = str($this->application->fqdn)->trim()->explode(',')->map(function ($domain) { $this->application->fqdn = str($this->application->fqdn)->trim()->explode(',')->map(function ($domain) {

View File

@ -45,7 +45,7 @@ class Heading extends Component
public function check_status($showNotification = false) public function check_status($showNotification = false)
{ {
if ($this->application->destination->server->isFunctional()) { if ($this->application->destination->server->isFunctional()) {
GetContainersStatus::dispatch($this->application->destination->server); GetContainersStatus::dispatch($this->application->destination->server)->onQueue('high');
// dispatch(new ContainerStatusJob($this->application->destination->server)); // dispatch(new ContainerStatusJob($this->application->destination->server));
} else { } else {
dispatch(new ServerStatusJob($this->application->destination->server)); dispatch(new ServerStatusJob($this->application->destination->server));

View File

@ -186,7 +186,7 @@ class Previews extends Component
instant_remote_process(["docker rm -f $name"], $this->application->destination->server, throwError: false); instant_remote_process(["docker rm -f $name"], $this->application->destination->server, throwError: false);
} }
} }
GetContainersStatus::dispatchSync($this->application->destination->server); GetContainersStatus::dispatchSync($this->application->destination->server)->onQueue('high');
$this->dispatch('reloadWindow'); $this->dispatch('reloadWindow');
} catch (\Throwable $e) { } catch (\Throwable $e) {
return handleError($e, $this); return handleError($e, $this);

View File

@ -12,7 +12,6 @@ use App\Actions\Database\StartPostgresql;
use App\Actions\Database\StartRedis; use App\Actions\Database\StartRedis;
use App\Actions\Database\StopDatabase; use App\Actions\Database\StopDatabase;
use App\Actions\Docker\GetContainersStatus; use App\Actions\Docker\GetContainersStatus;
use App\Jobs\ContainerStatusJob;
use Livewire\Component; use Livewire\Component;
class Heading extends Component class Heading extends Component

View File

@ -25,7 +25,17 @@ class General extends Component
public ?string $db_url_public = null; public ?string $db_url_public = null;
protected $listeners = ['refresh', 'save_init_script', 'delete_init_script']; public function getListeners()
{
$userId = auth()->user()->id;
return [
"echo-private:user.{$userId},DatabaseStatusChanged" => 'database_stopped',
'refresh',
'save_init_script',
'delete_init_script',
];
}
protected $rules = [ protected $rules = [
'database.name' => 'required', 'database.name' => 'required',
@ -69,6 +79,11 @@ class General extends Component
$this->server = data_get($this->database, 'destination.server'); $this->server = data_get($this->database, 'destination.server');
} }
public function database_stopped()
{
$this->dispatch('success', 'Database proxy stopped. Database is no longer publicly accessible.');
}
public function instantSaveAdvanced() public function instantSaveAdvanced()
{ {
try { try {

View File

@ -176,10 +176,12 @@ class Select extends Component
return; return;
} }
// if (count($this->servers) === 1) { if (count($this->servers) === 1) {
// $server = $this->servers->first(); $server = $this->servers->first();
// $this->setServer($server); if ($server instanceof Server) {
// } $this->setServer($server);
}
}
if (! is_null($this->server)) { if (! is_null($this->server)) {
$foundServer = $this->servers->where('id', $this->server->id)->first(); $foundServer = $this->servers->where('id', $this->server->id)->first();
if ($foundServer) { if ($foundServer) {
@ -195,6 +197,13 @@ class Select extends Component
$this->server = $server; $this->server = $server;
$this->standaloneDockers = $server->standaloneDockers; $this->standaloneDockers = $server->standaloneDockers;
$this->swarmDockers = $server->swarmDockers; $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'; $this->current_step = 'destinations';
} }

View File

@ -0,0 +1,35 @@
<?php
namespace App\Livewire\Project\Resource;
use Illuminate\Database\Eloquent\Collection;
use Livewire\Component;
class EnvironmentSelect extends Component
{
public Collection $environments;
public string $project_uuid = '';
public string $selectedEnvironment = '';
public function mount()
{
$this->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,
]);
}
}
}

View File

@ -11,6 +11,8 @@ class EditCompose extends Component
public $serviceId; public $serviceId;
protected $listeners = ['refreshEnvs' => 'mount'];
protected $rules = [ protected $rules = [
'service.docker_compose_raw' => 'required', 'service.docker_compose_raw' => 'required',
'service.docker_compose' => 'required', 'service.docker_compose' => 'required',

View File

@ -75,14 +75,12 @@ class StackForm extends Component
$this->service->parse(); $this->service->parse();
$this->service->refresh(); $this->service->refresh();
$this->service->saveComposeConfigs(); $this->service->saveComposeConfigs();
$this->dispatch('refreshStacks');
$this->dispatch('refreshEnvs'); $this->dispatch('refreshEnvs');
$this->dispatch('success', 'Service saved.'); $this->dispatch('success', 'Service saved.');
} catch (\Throwable $e) { } catch (\Throwable $e) {
return handleError($e, $this); return handleError($e, $this);
} finally { } finally {
if (is_null($this->service->config_hash)) { if (is_null($this->service->config_hash)) {
ray('asdf');
$this->service->isConfigurationChanged(true); $this->service->isConfigurationChanged(true);
} else { } else {
$this->dispatch('configurationChanged'); $this->dispatch('configurationChanged');

View File

@ -59,15 +59,6 @@ class Logs extends Component
} }
} }
public function loadMetrics()
{
return;
$server = data_get($this->resource, 'destination.server');
if ($server->isFunctional()) {
$this->cpu = $server->getMetrics();
}
}
public function mount() public function mount()
{ {
try { try {
@ -122,7 +113,6 @@ class Logs extends Component
} }
$this->loadMetrics();
} catch (\Exception $e) { } catch (\Exception $e) {
return handleError($e, $this); return handleError($e, $this);
} }

View File

@ -0,0 +1,64 @@
<?php
namespace App\Livewire\Project\Shared;
use Livewire\Component;
class Metrics extends Component
{
public $resource;
public $chartId = 'container-cpu';
public $data;
public $categories;
public int $interval = 5;
public bool $poll = true;
public function pollData()
{
if ($this->poll || $this->interval <= 10) {
$this->loadData();
if ($this->interval > 10) {
$this->poll = false;
}
}
}
public function loadData()
{
try {
$metrics = $this->resource->getMetrics($this->interval);
$cpuMetrics = collect($metrics)->map(function ($metric) {
return [$metric[0], $metric[1]];
});
$memoryMetrics = collect($metrics)->map(function ($metric) {
return [$metric[0], $metric[2]];
});
$this->dispatch("refreshChartData-{$this->chartId}-cpu", [
'seriesData' => $cpuMetrics,
]);
$this->dispatch("refreshChartData-{$this->chartId}-memory", [
'seriesData' => $memoryMetrics,
]);
} catch (\Throwable $e) {
return handleError($e, $this);
}
}
public function setInterval()
{
if ($this->interval <= 10) {
$this->poll = true;
}
$this->loadData();
}
public function render()
{
return view('livewire.project.shared.metrics');
}
}

View File

@ -9,6 +9,8 @@ class Show extends Component
{ {
public Project $project; public Project $project;
public $environments;
public function mount() public function mount()
{ {
$projectUuid = request()->route('project_uuid'); $projectUuid = request()->route('project_uuid');
@ -18,7 +20,8 @@ class Show extends Component
if (! $project) { if (! $project) {
return redirect()->route('dashboard'); return redirect()->route('dashboard');
} }
$project->load(['environments']);
$this->environments = $project->environments->sortBy('created_at');
$this->project = $project; $this->project = $project;
} }

View File

@ -0,0 +1,62 @@
<?php
namespace App\Livewire\Server;
use App\Models\Server;
use Livewire\Component;
class Charts extends Component
{
public Server $server;
public $chartId = 'server';
public $data;
public $categories;
public int $interval = 5;
public bool $poll = true;
public function pollData()
{
if ($this->poll || $this->interval <= 10) {
$this->loadData();
if ($this->interval > 10) {
$this->poll = false;
}
}
}
public function loadData()
{
try {
$cpuMetrics = $this->server->getCpuMetrics($this->interval);
$memoryMetrics = $this->server->getMemoryMetrics($this->interval);
$cpuMetrics = collect($cpuMetrics)->map(function ($metric) {
return [$metric[0], $metric[1]];
});
$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);
}
}
public function setInterval()
{
if ($this->interval <= 10) {
$this->poll = true;
}
$this->loadData();
}
}

View File

@ -21,7 +21,7 @@ class ConfigureCloudflareTunnels extends Component
$server->settings->is_cloudflare_tunnel = true; $server->settings->is_cloudflare_tunnel = true;
$server->settings->save(); $server->settings->save();
$this->dispatch('success', 'Cloudflare Tunnels configured successfully.'); $this->dispatch('success', 'Cloudflare Tunnels configured successfully.');
$this->dispatch('serverInstalled'); $this->dispatch('refreshServerShow');
} catch (\Throwable $e) { } catch (\Throwable $e) {
return handleError($e, $this); return handleError($e, $this);
} }
@ -37,7 +37,7 @@ class ConfigureCloudflareTunnels extends Component
$server->save(); $server->save();
$server->settings->save(); $server->settings->save();
$this->dispatch('success', 'Cloudflare Tunnels configured successfully.'); $this->dispatch('success', 'Cloudflare Tunnels configured successfully.');
$this->dispatch('serverInstalled'); $this->dispatch('refreshServerShow');
} catch (\Throwable $e) { } catch (\Throwable $e) {
return handleError($e, $this); return handleError($e, $this);
} }

View File

@ -2,6 +2,9 @@
namespace App\Livewire\Server; namespace App\Livewire\Server;
use App\Actions\Server\StartSentinel;
use App\Actions\Server\StopSentinel;
use App\Jobs\PullSentinelImageJob;
use App\Models\Server; use App\Models\Server;
use Livewire\Component; use Livewire\Component;
@ -36,7 +39,12 @@ class Form extends Component
'server.settings.is_build_server' => 'required|boolean', 'server.settings.is_build_server' => 'required|boolean',
'server.settings.concurrent_builds' => 'required|integer|min:1', 'server.settings.concurrent_builds' => 'required|integer|min:1',
'server.settings.dynamic_timeout' => 'required|integer|min:1', 'server.settings.dynamic_timeout' => 'required|integer|min:1',
'server.settings.is_metrics_enabled' => 'required|boolean',
'server.settings.metrics_token' => 'required',
'server.settings.metrics_refresh_rate_seconds' => 'required|integer|min:1',
'server.settings.metrics_history_days' => 'required|integer|min:1',
'wildcard_domain' => 'nullable|url', 'wildcard_domain' => 'nullable|url',
'server.settings.is_server_api_enabled' => 'required|boolean',
]; ];
protected $validationAttributes = [ protected $validationAttributes = [
@ -52,7 +60,11 @@ class Form extends Component
'server.settings.is_build_server' => 'Build Server', 'server.settings.is_build_server' => 'Build Server',
'server.settings.concurrent_builds' => 'Concurrent Builds', 'server.settings.concurrent_builds' => 'Concurrent Builds',
'server.settings.dynamic_timeout' => 'Dynamic Timeout', 'server.settings.dynamic_timeout' => 'Dynamic Timeout',
'server.settings.is_metrics_enabled' => 'Metrics',
'server.settings.metrics_token' => 'Metrics Token',
'server.settings.metrics_refresh_rate_seconds' => 'Metrics Interval',
'server.settings.metrics_history_days' => 'Metrics History',
'server.settings.is_server_api_enabled' => 'Server API',
]; ];
public function mount() public function mount()
@ -69,18 +81,59 @@ class Form extends Component
public function updatedServerSettingsIsBuildServer() public function updatedServerSettingsIsBuildServer()
{ {
$this->dispatch('serverInstalled'); $this->dispatch('refreshServerShow');
$this->dispatch('serverRefresh'); $this->dispatch('serverRefresh');
$this->dispatch('proxyStatusUpdated'); $this->dispatch('proxyStatusUpdated');
} }
public function checkPortForServerApi()
{
try {
if ($this->server->settings->is_server_api_enabled === true) {
$this->server->checkServerApi();
$this->dispatch('success', 'Server API is reachable.');
}
} catch (\Throwable $e) {
return handleError($e, $this);
}
}
public function instantSave() public function instantSave()
{ {
try { try {
refresh_server_connection($this->server->privateKey); refresh_server_connection($this->server->privateKey);
$this->validateServer(false); $this->validateServer(false);
$this->server->settings->save(); $this->server->settings->save();
$this->server->save();
$this->dispatch('success', 'Server updated.'); $this->dispatch('success', 'Server updated.');
$this->dispatch('refreshServerShow');
if ($this->server->isSentinelEnabled()) {
PullSentinelImageJob::dispatchSync($this->server);
ray('Sentinel is enabled');
if ($this->server->settings->isDirty('is_metrics_enabled')) {
$this->dispatch('reloadWindow');
}
if ($this->server->settings->isDirty('is_server_api_enabled') && $this->server->settings->is_server_api_enabled === true) {
ray('Starting sentinel');
}
} else {
ray('Sentinel is not enabled');
StopSentinel::dispatch($this->server);
}
// $this->checkPortForServerApi();
} catch (\Throwable $e) {
return handleError($e, $this);
}
}
public function restartSentinel()
{
try {
$version = get_latest_sentinel_version();
StartSentinel::run($this->server, $version, true);
$this->dispatch('success', 'Sentinel restarted.');
} catch (\Throwable $e) { } catch (\Throwable $e) {
return handleError($e, $this); return handleError($e, $this);
} }

View File

@ -14,7 +14,7 @@ class Show extends Component
public $parameters = []; public $parameters = [];
protected $listeners = ['serverInstalled' => '$refresh']; protected $listeners = ['refreshServerShow' => '$refresh'];
public function mount() public function mount()
{ {

View File

@ -143,7 +143,8 @@ class ValidateAndInstall extends Component
} else { } else {
$this->docker_version = $this->server->validateDockerEngineVersion(); $this->docker_version = $this->server->validateDockerEngineVersion();
if ($this->docker_version) { if ($this->docker_version) {
$this->dispatch('serverInstalled'); $this->dispatch('refreshServerShow');
$this->dispatch('refreshBoardingIndex');
$this->dispatch('success', 'Server validated.'); $this->dispatch('success', 'Server validated.');
} else { } else {
$this->error = 'Docker Engine version is not 22+. Please install Docker manually before continuing: <a target="_blank" class="underline" href="https://docs.docker.com/engine/install/#server">documentation</a>.'; $this->error = 'Docker Engine version is not 22+. Please install Docker manually before continuing: <a target="_blank" class="underline" href="https://docs.docker.com/engine/install/#server">documentation</a>.';

View File

@ -29,6 +29,7 @@ class Configuration extends Component
'settings.public_port_min' => 'required', 'settings.public_port_min' => 'required',
'settings.public_port_max' => 'required', 'settings.public_port_max' => 'required',
'settings.custom_dns_servers' => 'nullable', 'settings.custom_dns_servers' => 'nullable',
'settings.instance_name' => 'nullable',
]; ];
protected $validationAttributes = [ protected $validationAttributes = [

View File

@ -228,7 +228,7 @@ class Application extends BaseModel
public function gitCommitLink($link): string 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')) { if (str($this->source->html_url)->contains('bitbucket')) {
return "{$this->source->html_url}/{$this->git_repository}/commits/{$link}"; 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) { if (strpos($this->git_repository, 'git@') === 0) {
$git_repository = str_replace(['git@', ':', '.git'], ['', '/', ''], $this->git_repository); $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; return $this->git_repository;
@ -1167,4 +1170,44 @@ class Application extends BaseModel
return $preview; return $preview;
} }
public static function getDomainsByUuid(string $uuid): array
{
$application = self::where('uuid', $uuid)->first();
if ($application) {
return $application->fqdns;
}
return [];
}
public function getMetrics(int $mins = 5)
{
$server = $this->destination->server;
$container_name = $this->uuid;
if ($server->isMetricsEnabled()) {
$from = now()->subMinutes($mins)->toIso8601ZuluString();
$metrics = instant_remote_process(["docker exec coolify-sentinel sh -c 'curl -H \"Authorization: Bearer {$server->settings->metrics_token}\" http://localhost:8888/api/container/{$container_name}/metrics/history?from=$from'"], $server, false);
if (str($metrics)->contains('error')) {
$error = json_decode($metrics, true);
$error = data_get($error, 'error', 'Something is not okay, are you okay?');
if ($error == 'Unauthorized') {
$error = 'Unauthorized, please check your metrics token or restart Sentinel to set a new token.';
}
throw new \Exception($error);
}
$metrics = str($metrics)->explode("\n")->skip(1)->all();
$parsedCollection = collect($metrics)->flatMap(function ($item) {
return collect(explode("\n", trim($item)))->map(function ($line) {
[$time, $cpu_usage_percent, $memory_usage, $memory_usage_percent] = explode(',', trim($line));
$cpu_usage_percent = number_format($cpu_usage_percent, 2);
return [(int) $time, (float) $cpu_usage_percent, (int) $memory_usage];
});
});
return $parsedCollection->toArray();
}
}
} }

View File

@ -109,7 +109,7 @@ class Environment extends Model
protected function name(): Attribute protected function name(): Attribute
{ {
return Attribute::make( return Attribute::make(
set: fn (string $value) => strtolower($value), set: fn (string $value) => str($value)->lower()->trim()->replace('/', '-')->toString(),
); );
} }
} }

View File

@ -47,4 +47,14 @@ class InstanceSettings extends Model implements SendsEmail
return explode(',', $recipients); return explode(',', $recipients);
} }
public function getTitleDisplayName(): string
{
$instanceName = $this->instance_name;
if (! $instanceName) {
return '';
}
return "[{$instanceName}]";
}
} }

View File

@ -2,6 +2,4 @@
namespace App\Models; namespace App\Models;
class Kubernetes extends BaseModel class Kubernetes extends BaseModel {}
{
}

View File

@ -112,4 +112,18 @@ 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()); 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) {
return $default->name;
}
$default = $this->environments()->get();
if ($default->count() > 0) {
return $default->sortBy('created_at')->first()->name;
}
return null;
}
} }

View File

@ -5,12 +5,11 @@ namespace App\Models;
use App\Actions\Server\InstallDocker; use App\Actions\Server\InstallDocker;
use App\Enums\ProxyTypes; use App\Enums\ProxyTypes;
use App\Jobs\PullSentinelImageJob; use App\Jobs\PullSentinelImageJob;
use App\Notifications\Server\Revived;
use App\Notifications\Server\Unreachable;
use Illuminate\Database\Eloquent\Builder; use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Casts\Attribute; use Illuminate\Database\Eloquent\Casts\Attribute;
use Illuminate\Support\Collection; use Illuminate\Support\Collection;
use Illuminate\Support\Facades\DB; use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Process;
use Illuminate\Support\Facades\Storage; use Illuminate\Support\Facades\Storage;
use Illuminate\Support\Str; use Illuminate\Support\Str;
use Illuminate\Support\Stringable; use Illuminate\Support\Stringable;
@ -462,10 +461,44 @@ $schema://$host {
Storage::disk('ssh-mux')->delete($this->muxFilename()); Storage::disk('ssh-mux')->delete($this->muxFilename());
} }
public function isSentinelEnabled()
{
return $this->isMetricsEnabled() || $this->isServerApiEnabled();
}
public function isMetricsEnabled()
{
return $this->settings->is_metrics_enabled;
}
public function isServerApiEnabled()
{
return $this->settings->is_server_api_enabled;
}
public function checkServerApi()
{
if ($this->isServerApiEnabled()) {
$server_ip = $this->ip;
if (isDev()) {
if ($this->id === 0) {
$server_ip = 'localhost';
}
}
$command = "curl -s http://{$server_ip}:12172/api/health";
$process = Process::timeout(5)->run($command);
if ($process->failed()) {
ray($process->exitCode(), $process->output(), $process->errorOutput());
throw new \Exception("Server API is not reachable on http://{$server_ip}:12172");
}
}
}
public function checkSentinel() public function checkSentinel()
{ {
ray("Checking sentinel on server: {$this->name}"); ray("Checking sentinel on server: {$this->name}");
if ($this->is_metrics_enabled) { if ($this->isSentinelEnabled()) {
$sentinel_found = instant_remote_process(['docker inspect coolify-sentinel'], $this, false); $sentinel_found = instant_remote_process(['docker inspect coolify-sentinel'], $this, false);
$sentinel_found = json_decode($sentinel_found, true); $sentinel_found = json_decode($sentinel_found, true);
$status = data_get($sentinel_found, '0.State.Status', 'exited'); $status = data_get($sentinel_found, '0.State.Status', 'exited');
@ -478,21 +511,57 @@ $schema://$host {
} }
} }
public function getMetrics() public function getCpuMetrics(int $mins = 5)
{ {
if ($this->is_metrics_enabled) { if ($this->isMetricsEnabled()) {
$from = now()->subMinutes(5)->toIso8601ZuluString(); $from = now()->subMinutes($mins)->toIso8601ZuluString();
$cpu = instant_remote_process(["docker exec coolify-sentinel sh -c 'curl http://localhost:8888/api/cpu/history?from=$from'"], $this, false); $cpu = instant_remote_process(["docker exec coolify-sentinel sh -c 'curl -H \"Authorization: Bearer {$this->settings->metrics_token}\" http://localhost:8888/api/cpu/history?from=$from'"], $this, false);
if (str($cpu)->contains('error')) {
$error = json_decode($cpu, true);
$error = data_get($error, 'error', 'Something is not okay, are you okay?');
if ($error == 'Unauthorized') {
$error = 'Unauthorized, please check your metrics token or restart Sentinel to set a new token.';
}
throw new \Exception($error);
}
$cpu = str($cpu)->explode("\n")->skip(1)->all(); $cpu = str($cpu)->explode("\n")->skip(1)->all();
$parsedCollection = collect($cpu)->flatMap(function ($item) { $parsedCollection = collect($cpu)->flatMap(function ($item) {
return collect(explode("\n", trim($item)))->map(function ($line) { return collect(explode("\n", trim($item)))->map(function ($line) {
[$time, $value] = explode(',', trim($line)); [$time, $cpu_usage_percent] = explode(',', trim($line));
$cpu_usage_percent = number_format($cpu_usage_percent, 0);
return [(int) $time, (float) $value]; return [(int) $time, (float) $cpu_usage_percent];
}); });
})->toArray(); });
return $parsedCollection; return $parsedCollection->toArray();
}
}
public function getMemoryMetrics(int $mins = 5)
{
if ($this->isMetricsEnabled()) {
$from = now()->subMinutes($mins)->toIso8601ZuluString();
$memory = instant_remote_process(["docker exec coolify-sentinel sh -c 'curl -H \"Authorization: Bearer {$this->settings->metrics_token}\" http://localhost:8888/api/memory/history?from=$from'"], $this, false);
if (str($memory)->contains('error')) {
$error = json_decode($memory, true);
$error = data_get($error, 'error', 'Something is not okay, are you okay?');
if ($error == 'Unauthorized') {
$error = 'Unauthorized, please check your metrics token or restart Sentinel to set a new token.';
}
throw new \Exception($error);
}
$memory = str($memory)->explode("\n")->skip(1)->all();
$parsedCollection = collect($memory)->flatMap(function ($item) {
return collect(explode("\n", trim($item)))->map(function ($line) {
[$time, $used, $free, $usedPercent] = explode(',', trim($line));
$usedPercent = number_format($usedPercent, 0);
return [(int) $time, (float) $usedPercent];
});
});
return $parsedCollection->toArray();
} }
} }

View File

@ -6,6 +6,7 @@ use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Relations\HasMany; use Illuminate\Database\Eloquent\Relations\HasMany;
use Illuminate\Database\Eloquent\SoftDeletes; use Illuminate\Database\Eloquent\SoftDeletes;
use Illuminate\Support\Collection; use Illuminate\Support\Collection;
use Symfony\Component\Yaml\Yaml;
class Service extends BaseModel class Service extends BaseModel
{ {
@ -837,14 +838,38 @@ class Service extends BaseModel
$commands[] = "mkdir -p $workdir"; $commands[] = "mkdir -p $workdir";
$commands[] = "cd $workdir"; $commands[] = "cd $workdir";
$json = Yaml::parse($this->docker_compose);
$envs_from_coolify = $this->environment_variables()->get();
// foreach ($json['services'] as $service => $config) {
// if (data_get($config, 'environment') === null) {
// data_set($json, "services.$service.environment", []);
// $envs = collect([]);
// } else {
// $envs = collect($config['environment']);
// }
// // $envs->put('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, 10, 2, Yaml::DUMP_MULTI_LINE_LITERAL_BLOCK);
$docker_compose_base64 = base64_encode($this->docker_compose); $docker_compose_base64 = base64_encode($this->docker_compose);
$commands[] = "echo $docker_compose_base64 | base64 -d | tee docker-compose.yml > /dev/null"; $commands[] = "echo $docker_compose_base64 | base64 -d | tee docker-compose.yml > /dev/null";
$envs = $this->environment_variables()->get();
$commands[] = 'rm -f .env || true'; $commands[] = 'rm -f .env || true';
foreach ($envs as $env) {
foreach ($envs_from_coolify as $env) {
$commands[] = "echo '{$env->key}={$env->real_value}' >> .env"; $commands[] = "echo '{$env->key}={$env->real_value}' >> .env";
} }
if ($envs->count() === 0) { if ($envs_from_coolify->count() === 0) {
$commands[] = 'touch .env'; $commands[] = 'touch .env';
} }
instant_remote_process($commands, $this->server); instant_remote_process($commands, $this->server);

View File

@ -226,4 +226,33 @@ class StandaloneClickhouse extends BaseModel
{ {
return $this->morphMany(ScheduledDatabaseBackup::class, 'database'); return $this->morphMany(ScheduledDatabaseBackup::class, 'database');
} }
public function getMetrics(int $mins = 5)
{
$server = $this->destination->server;
$container_name = $this->uuid;
if ($server->isMetricsEnabled()) {
$from = now()->subMinutes($mins)->toIso8601ZuluString();
$metrics = instant_remote_process(["docker exec coolify-sentinel sh -c 'curl -H \"Authorization: Bearer {$server->settings->metrics_token}\" http://localhost:8888/api/container/{$container_name}/metrics/history?from=$from'"], $server, false);
if (str($metrics)->contains('error')) {
$error = json_decode($metrics, true);
$error = data_get($error, 'error', 'Something is not okay, are you okay?');
if ($error == 'Unauthorized') {
$error = 'Unauthorized, please check your metrics token or restart Sentinel to set a new token.';
}
throw new \Exception($error);
}
$metrics = str($metrics)->explode("\n")->skip(1)->all();
$parsedCollection = collect($metrics)->flatMap(function ($item) {
return collect(explode("\n", trim($item)))->map(function ($line) {
[$time, $cpu_usage_percent, $memory_usage, $memory_usage_percent] = explode(',', trim($line));
$cpu_usage_percent = number_format($cpu_usage_percent, 2);
return [(int) $time, (float) $cpu_usage_percent, (int) $memory_usage];
});
});
return $parsedCollection->toArray();
}
}
} }

View File

@ -226,4 +226,33 @@ class StandaloneDragonfly extends BaseModel
{ {
return $this->morphMany(ScheduledDatabaseBackup::class, 'database'); return $this->morphMany(ScheduledDatabaseBackup::class, 'database');
} }
public function getMetrics(int $mins = 5)
{
$server = $this->destination->server;
$container_name = $this->uuid;
if ($server->isMetricsEnabled()) {
$from = now()->subMinutes($mins)->toIso8601ZuluString();
$metrics = instant_remote_process(["docker exec coolify-sentinel sh -c 'curl -H \"Authorization: Bearer {$server->settings->metrics_token}\" http://localhost:8888/api/container/{$container_name}/metrics/history?from=$from'"], $server, false);
if (str($metrics)->contains('error')) {
$error = json_decode($metrics, true);
$error = data_get($error, 'error', 'Something is not okay, are you okay?');
if ($error == 'Unauthorized') {
$error = 'Unauthorized, please check your metrics token or restart Sentinel to set a new token.';
}
throw new \Exception($error);
}
$metrics = str($metrics)->explode("\n")->skip(1)->all();
$parsedCollection = collect($metrics)->flatMap(function ($item) {
return collect(explode("\n", trim($item)))->map(function ($line) {
[$time, $cpu_usage_percent, $memory_usage, $memory_usage_percent] = explode(',', trim($line));
$cpu_usage_percent = number_format($cpu_usage_percent, 2);
return [(int) $time, (float) $cpu_usage_percent, (int) $memory_usage];
});
});
return $parsedCollection->toArray();
}
}
} }

View File

@ -226,4 +226,33 @@ class StandaloneKeydb extends BaseModel
{ {
return $this->morphMany(ScheduledDatabaseBackup::class, 'database'); return $this->morphMany(ScheduledDatabaseBackup::class, 'database');
} }
public function getMetrics(int $mins = 5)
{
$server = $this->destination->server;
$container_name = $this->uuid;
if ($server->isMetricsEnabled()) {
$from = now()->subMinutes($mins)->toIso8601ZuluString();
$metrics = instant_remote_process(["docker exec coolify-sentinel sh -c 'curl -H \"Authorization: Bearer {$server->settings->metrics_token}\" http://localhost:8888/api/container/{$container_name}/metrics/history?from=$from'"], $server, false);
if (str($metrics)->contains('error')) {
$error = json_decode($metrics, true);
$error = data_get($error, 'error', 'Something is not okay, are you okay?');
if ($error == 'Unauthorized') {
$error = 'Unauthorized, please check your metrics token or restart Sentinel to set a new token.';
}
throw new \Exception($error);
}
$metrics = str($metrics)->explode("\n")->skip(1)->all();
$parsedCollection = collect($metrics)->flatMap(function ($item) {
return collect(explode("\n", trim($item)))->map(function ($line) {
[$time, $cpu_usage_percent, $memory_usage, $memory_usage_percent] = explode(',', trim($line));
$cpu_usage_percent = number_format($cpu_usage_percent, 2);
return [(int) $time, (float) $cpu_usage_percent, (int) $memory_usage];
});
});
return $parsedCollection->toArray();
}
}
} }

View File

@ -226,4 +226,33 @@ class StandaloneMariadb extends BaseModel
{ {
return $this->morphMany(ScheduledDatabaseBackup::class, 'database'); return $this->morphMany(ScheduledDatabaseBackup::class, 'database');
} }
public function getMetrics(int $mins = 5)
{
$server = $this->destination->server;
$container_name = $this->uuid;
if ($server->isMetricsEnabled()) {
$from = now()->subMinutes($mins)->toIso8601ZuluString();
$metrics = instant_remote_process(["docker exec coolify-sentinel sh -c 'curl -H \"Authorization: Bearer {$server->settings->metrics_token}\" http://localhost:8888/api/container/{$container_name}/metrics/history?from=$from'"], $server, false);
if (str($metrics)->contains('error')) {
$error = json_decode($metrics, true);
$error = data_get($error, 'error', 'Something is not okay, are you okay?');
if ($error == 'Unauthorized') {
$error = 'Unauthorized, please check your metrics token or restart Sentinel to set a new token.';
}
throw new \Exception($error);
}
$metrics = str($metrics)->explode("\n")->skip(1)->all();
$parsedCollection = collect($metrics)->flatMap(function ($item) {
return collect(explode("\n", trim($item)))->map(function ($line) {
[$time, $cpu_usage_percent, $memory_usage, $memory_usage_percent] = explode(',', trim($line));
$cpu_usage_percent = number_format($cpu_usage_percent, 2);
return [(int) $time, (float) $cpu_usage_percent, (int) $memory_usage];
});
});
return $parsedCollection->toArray();
}
}
} }

View File

@ -246,4 +246,33 @@ class StandaloneMongodb extends BaseModel
{ {
return $this->morphMany(ScheduledDatabaseBackup::class, 'database'); return $this->morphMany(ScheduledDatabaseBackup::class, 'database');
} }
public function getMetrics(int $mins = 5)
{
$server = $this->destination->server;
$container_name = $this->uuid;
if ($server->isMetricsEnabled()) {
$from = now()->subMinutes($mins)->toIso8601ZuluString();
$metrics = instant_remote_process(["docker exec coolify-sentinel sh -c 'curl -H \"Authorization: Bearer {$server->settings->metrics_token}\" http://localhost:8888/api/container/{$container_name}/metrics/history?from=$from'"], $server, false);
if (str($metrics)->contains('error')) {
$error = json_decode($metrics, true);
$error = data_get($error, 'error', 'Something is not okay, are you okay?');
if ($error == 'Unauthorized') {
$error = 'Unauthorized, please check your metrics token or restart Sentinel to set a new token.';
}
throw new \Exception($error);
}
$metrics = str($metrics)->explode("\n")->skip(1)->all();
$parsedCollection = collect($metrics)->flatMap(function ($item) {
return collect(explode("\n", trim($item)))->map(function ($line) {
[$time, $cpu_usage_percent, $memory_usage, $memory_usage_percent] = explode(',', trim($line));
$cpu_usage_percent = number_format($cpu_usage_percent, 2);
return [(int) $time, (float) $cpu_usage_percent, (int) $memory_usage];
});
});
return $parsedCollection->toArray();
}
}
} }

View File

@ -227,4 +227,33 @@ class StandaloneMysql extends BaseModel
{ {
return $this->morphMany(ScheduledDatabaseBackup::class, 'database'); return $this->morphMany(ScheduledDatabaseBackup::class, 'database');
} }
public function getMetrics(int $mins = 5)
{
$server = $this->destination->server;
$container_name = $this->uuid;
if ($server->isMetricsEnabled()) {
$from = now()->subMinutes($mins)->toIso8601ZuluString();
$metrics = instant_remote_process(["docker exec coolify-sentinel sh -c 'curl -H \"Authorization: Bearer {$server->settings->metrics_token}\" http://localhost:8888/api/container/{$container_name}/metrics/history?from=$from'"], $server, false);
if (str($metrics)->contains('error')) {
$error = json_decode($metrics, true);
$error = data_get($error, 'error', 'Something is not okay, are you okay?');
if ($error == 'Unauthorized') {
$error = 'Unauthorized, please check your metrics token or restart Sentinel to set a new token.';
}
throw new \Exception($error);
}
$metrics = str($metrics)->explode("\n")->skip(1)->all();
$parsedCollection = collect($metrics)->flatMap(function ($item) {
return collect(explode("\n", trim($item)))->map(function ($line) {
[$time, $cpu_usage_percent, $memory_usage, $memory_usage_percent] = explode(',', trim($line));
$cpu_usage_percent = number_format($cpu_usage_percent, 2);
return [(int) $time, (float) $cpu_usage_percent, (int) $memory_usage];
});
});
return $parsedCollection->toArray();
}
}
} }

View File

@ -227,4 +227,33 @@ class StandalonePostgresql extends BaseModel
{ {
return $this->morphMany(ScheduledDatabaseBackup::class, 'database'); return $this->morphMany(ScheduledDatabaseBackup::class, 'database');
} }
public function getMetrics(int $mins = 5)
{
$server = $this->destination->server;
$container_name = $this->uuid;
if ($server->isMetricsEnabled()) {
$from = now()->subMinutes($mins)->toIso8601ZuluString();
$metrics = instant_remote_process(["docker exec coolify-sentinel sh -c 'curl -H \"Authorization: Bearer {$server->settings->metrics_token}\" http://localhost:8888/api/container/{$container_name}/metrics/history?from=$from'"], $server, false);
if (str($metrics)->contains('error')) {
$error = json_decode($metrics, true);
$error = data_get($error, 'error', 'Something is not okay, are you okay?');
if ($error == 'Unauthorized') {
$error = 'Unauthorized, please check your metrics token or restart Sentinel to set a new token.';
}
throw new \Exception($error);
}
$metrics = str($metrics)->explode("\n")->skip(1)->all();
$parsedCollection = collect($metrics)->flatMap(function ($item) {
return collect(explode("\n", trim($item)))->map(function ($line) {
[$time, $cpu_usage_percent, $memory_usage, $memory_usage_percent] = explode(',', trim($line));
$cpu_usage_percent = number_format($cpu_usage_percent, 2);
return [(int) $time, (float) $cpu_usage_percent, (int) $memory_usage];
});
});
return $parsedCollection->toArray();
}
}
} }

View File

@ -222,4 +222,33 @@ class StandaloneRedis extends BaseModel
{ {
return $this->morphMany(ScheduledDatabaseBackup::class, 'database'); return $this->morphMany(ScheduledDatabaseBackup::class, 'database');
} }
public function getMetrics(int $mins = 5)
{
$server = $this->destination->server;
$container_name = $this->uuid;
if ($server->isMetricsEnabled()) {
$from = now()->subMinutes($mins)->toIso8601ZuluString();
$metrics = instant_remote_process(["docker exec coolify-sentinel sh -c 'curl -H \"Authorization: Bearer {$server->settings->metrics_token}\" http://localhost:8888/api/container/{$container_name}/metrics/history?from=$from'"], $server, false);
if (str($metrics)->contains('error')) {
$error = json_decode($metrics, true);
$error = data_get($error, 'error', 'Something is not okay, are you okay?');
if ($error == 'Unauthorized') {
$error = 'Unauthorized, please check your metrics token or restart Sentinel to set a new token.';
}
throw new \Exception($error);
}
$metrics = str($metrics)->explode("\n")->skip(1)->all();
$parsedCollection = collect($metrics)->flatMap(function ($item) {
return collect(explode("\n", trim($item)))->map(function ($line) {
[$time, $cpu_usage_percent, $memory_usage, $memory_usage_percent] = explode(',', trim($line));
$cpu_usage_percent = number_format($cpu_usage_percent, 2);
return [(int) $time, (float) $cpu_usage_percent, (int) $memory_usage];
});
});
return $parsedCollection->toArray();
}
}
} }

View File

@ -14,9 +14,7 @@ class ContainerRestarted extends Notification implements ShouldQueue
public $tries = 1; public $tries = 1;
public function __construct(public string $name, public Server $server, public ?string $url = null) public function __construct(public string $name, public Server $server, public ?string $url = null) {}
{
}
public function via(object $notifiable): array public function via(object $notifiable): array
{ {

View File

@ -14,9 +14,7 @@ class ContainerStopped extends Notification implements ShouldQueue
public $tries = 1; public $tries = 1;
public function __construct(public string $name, public Server $server, public ?string $url = null) public function __construct(public string $name, public Server $server, public ?string $url = null) {}
{
}
public function via(object $notifiable): array public function via(object $notifiable): array
{ {

View File

@ -16,9 +16,7 @@ class DailyBackup extends Notification implements ShouldQueue
public $tries = 1; public $tries = 1;
public function __construct(public $databases) public function __construct(public $databases) {}
{
}
public function via(object $notifiable): array public function via(object $notifiable): array
{ {

View File

@ -14,9 +14,7 @@ class GeneralNotification extends Notification implements ShouldQueue
public $tries = 1; public $tries = 1;
public function __construct(public string $message) public function __construct(public string $message) {}
{
}
public function via(object $notifiable): array public function via(object $notifiable): array
{ {

View File

@ -15,9 +15,7 @@ class DockerCleanup extends Notification implements ShouldQueue
public $tries = 1; public $tries = 1;
public function __construct(public Server $server, public string $message) public function __construct(public Server $server, public string $message) {}
{
}
public function via(object $notifiable): array public function via(object $notifiable): array
{ {

View File

@ -17,9 +17,7 @@ class ForceDisabled extends Notification implements ShouldQueue
public $tries = 1; public $tries = 1;
public function __construct(public Server $server) public function __construct(public Server $server) {}
{
}
public function via(object $notifiable): array public function via(object $notifiable): array
{ {

View File

@ -17,9 +17,7 @@ class ForceEnabled extends Notification implements ShouldQueue
public $tries = 1; public $tries = 1;
public function __construct(public Server $server) public function __construct(public Server $server) {}
{
}
public function via(object $notifiable): array public function via(object $notifiable): array
{ {

View File

@ -17,9 +17,7 @@ class HighDiskUsage extends Notification implements ShouldQueue
public $tries = 1; public $tries = 1;
public function __construct(public Server $server, public int $disk_usage, public int $cleanup_after_percentage) public function __construct(public Server $server, public int $disk_usage, public int $cleanup_after_percentage) {}
{
}
public function via(object $notifiable): array public function via(object $notifiable): array
{ {

View File

@ -24,7 +24,7 @@ class Revived extends Notification implements ShouldQueue
if ($this->server->unreachable_notification_sent === false) { if ($this->server->unreachable_notification_sent === false) {
return; return;
} }
GetContainersStatus::dispatch($server); GetContainersStatus::dispatch($server)->onQueue('high');
// dispatch(new ContainerStatusJob($server)); // dispatch(new ContainerStatusJob($server));
} }

View File

@ -17,10 +17,7 @@ class Unreachable extends Notification implements ShouldQueue
public $tries = 1; public $tries = 1;
public function __construct(public Server $server) public function __construct(public Server $server) {}
{
}
public function via(object $notifiable): array public function via(object $notifiable): array
{ {

View File

@ -13,9 +13,7 @@ class Test extends Notification implements ShouldQueue
public $tries = 5; public $tries = 5;
public function __construct(public ?string $emails = null) public function __construct(public ?string $emails = null) {}
{
}
public function via(object $notifiable): array public function via(object $notifiable): array
{ {

View File

@ -22,9 +22,7 @@ class InvitationLink extends Notification implements ShouldQueue
return [TransactionalEmailChannel::class]; return [TransactionalEmailChannel::class];
} }
public function __construct(public User $user) public function __construct(public User $user) {}
{
}
public function toMail(): MailMessage public function toMail(): MailMessage
{ {

View File

@ -14,9 +14,7 @@ class Test extends Notification implements ShouldQueue
public $tries = 5; public $tries = 5;
public function __construct(public string $emails) public function __construct(public string $emails) {}
{
}
public function via(): array public function via(): array
{ {

View File

@ -9,9 +9,7 @@ use Laravel\Sanctum\Sanctum;
class AppServiceProvider extends ServiceProvider class AppServiceProvider extends ServiceProvider
{ {
public function register(): void public function register(): void {}
{
}
public function boot(): void public function boot(): void
{ {

View File

@ -22,8 +22,7 @@ class Input extends Component
public bool $allowToPeak = true, public bool $allowToPeak = true,
public bool $isMultiline = false, public bool $isMultiline = false,
public string $defaultClass = 'input', public string $defaultClass = 'input',
) { ) {}
}
public function render(): View|Closure|string public function render(): View|Closure|string
{ {

View File

@ -19,6 +19,8 @@ class Textarea extends Component
public ?string $value = null, public ?string $value = null,
public ?string $label = null, public ?string $label = null,
public ?string $placeholder = null, public ?string $placeholder = null,
public ?string $monacoEditorLanguage = '',
public bool $useMonacoEditor = false,
public bool $required = false, public bool $required = false,
public bool $disabled = false, public bool $disabled = false,
public bool $readonly = false, public bool $readonly = false,

View File

@ -16,9 +16,7 @@ class ResourceView extends Component
public ?string $logo = null, public ?string $logo = null,
public ?string $documentation = null, public ?string $documentation = null,
public bool $upgrade = false, public bool $upgrade = false,
) { ) {}
}
/** /**
* Get the view / contents that represent the component. * Get the view / contents that represent the component.

View File

@ -14,8 +14,7 @@ class Index extends Component
public function __construct( public function __construct(
public $resource = null, public $resource = null,
public bool $showRefreshButton = true, public bool $showRefreshButton = true,
) { ) {}
}
/** /**
* Get the view / contents that represent the component. * Get the view / contents that represent the component.

View File

@ -1,5 +1,7 @@
<?php <?php
use Illuminate\Database\Eloquent\Collection;
function get_team_id_from_token() function get_team_id_from_token()
{ {
$token = auth()->user()->currentAccessToken(); $token = auth()->user()->currentAccessToken();
@ -10,3 +12,27 @@ function invalid_token()
{ {
return response()->json(['error' => 'Invalid token.', 'docs' => 'https://coolify.io/docs/api-reference/authorization'], 400); 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;
}

View File

@ -8,7 +8,7 @@ use App\Models\Server;
use App\Models\StandaloneDocker; use App\Models\StandaloneDocker;
use Spatie\Url\Url; 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; $application_id = $application->id;
$deployment_link = Url::fromString($application->link()."/deployment/{$deployment_uuid}"); $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, 'pull_request_id' => $pull_request_id,
'force_rebuild' => $force_rebuild, 'force_rebuild' => $force_rebuild,
'is_webhook' => $is_webhook, 'is_webhook' => $is_webhook,
'is_api' => $is_api,
'restart_only' => $restart_only, 'restart_only' => $restart_only,
'commit' => $commit, 'commit' => $commit,
'rollback' => $rollback, 'rollback' => $rollback,
@ -45,11 +46,11 @@ function queue_application_deployment(Application $application, string $deployme
if ($no_questions_asked) { if ($no_questions_asked) {
dispatch(new ApplicationDeploymentJob( dispatch(new ApplicationDeploymentJob(
application_deployment_queue_id: $deployment->id, application_deployment_queue_id: $deployment->id,
)); ))->onQueue('high');
} elseif (next_queuable($server_id, $application_id)) { } elseif (next_queuable($server_id, $application_id)) {
dispatch(new ApplicationDeploymentJob( dispatch(new ApplicationDeploymentJob(
application_deployment_queue_id: $deployment->id, application_deployment_queue_id: $deployment->id,
)); ))->onQueue('high');
} }
} }
function force_start_deployment(ApplicationDeploymentQueue $deployment) function force_start_deployment(ApplicationDeploymentQueue $deployment)
@ -60,7 +61,7 @@ function force_start_deployment(ApplicationDeploymentQueue $deployment)
dispatch(new ApplicationDeploymentJob( dispatch(new ApplicationDeploymentJob(
application_deployment_queue_id: $deployment->id, application_deployment_queue_id: $deployment->id,
)); ))->onQueue('high');
} }
function queue_next_deployment(Application $application) function queue_next_deployment(Application $application)
{ {
@ -73,7 +74,7 @@ function queue_next_deployment(Application $application)
dispatch(new ApplicationDeploymentJob( dispatch(new ApplicationDeploymentJob(
application_deployment_queue_id: $next_found->id, application_deployment_queue_id: $next_found->id,
)); ))->onQueue('high');
} }
} }
@ -114,7 +115,7 @@ function next_after_cancel(?Server $server = null)
dispatch(new ApplicationDeploymentJob( dispatch(new ApplicationDeploymentJob(
application_deployment_queue_id: $next->id, application_deployment_queue_id: $next->id,
)); ))->onQueue('high');
} }
break; break;
} }

Some files were not shown because too many files have changed in this diff Show More