commit
5700f2f78a
@ -35,6 +35,10 @@ # Donations
|
|||||||
## Github Sponsors ($40+)
|
## Github Sponsors ($40+)
|
||||||
<a href="https://bc.direct"><img width="60px" alt="BC Direct" src="https://github.com/coollabsio/coolify/assets/5845193/a4063c41-95ed-4a32-8814-cd1475572e37"/></a>
|
<a href="https://bc.direct"><img width="60px" alt="BC Direct" src="https://github.com/coollabsio/coolify/assets/5845193/a4063c41-95ed-4a32-8814-cd1475572e37"/></a>
|
||||||
<a href="https://typebot.io/?utm_source=coolify.io"><img src="https://pbs.twimg.com/profile_images/1509194008366657543/9I-C7uWT_400x400.jpg" width="60px" alt="typebot"/></a>
|
<a href="https://typebot.io/?utm_source=coolify.io"><img src="https://pbs.twimg.com/profile_images/1509194008366657543/9I-C7uWT_400x400.jpg" width="60px" alt="typebot"/></a>
|
||||||
|
<a href="https://www.quantcdn.io/?utm_source=coolify.io"><img src="https://github.com/quantcdn.png" width="60px" alt="QuantCDN"/></a>
|
||||||
|
<a href="https://www.runpod.io/?utm_source=coolify.io">
|
||||||
|
<svg style="width:60px;height:60px;background:#fff;" xmlns="http://www.w3.org/2000/svg" version="1.0" viewBox="0 0 200 200"><g><path d="M74.5 51.1c-25.4 14.9-27 16-29.6 20.2-1.8 3-1.9 5.3-1.9 32.3 0 21.7.3 29.4 1.3 30.6 1.9 2.5 46.7 27.9 48.5 27.6 1.5-.3 1.7-3.1 2-27.7.2-21.9 0-27.8-1.1-29.5-.8-1.2-9.9-6.8-20.2-12.6-10.3-5.8-19.4-11.5-20.2-12.7-1.8-2.6-.9-5.9 1.8-7.4 1.6-.8 6.3 0 21.8 4C87.8 78.7 98 81 99.6 81c4.4 0 49.9-25.9 49.9-28.4 0-1.6-3.4-2.8-24-8.2-13.2-3.5-25.1-6.3-26.5-6.3-1.4.1-12.4 5.9-24.5 13z"></path><path d="m137.2 68.1-3.3 2.1 6.3 3.7c3.5 2 6.3 4.3 6.3 5.1 0 .9-8 6.1-19.4 12.6-10.6 6-20 11.9-20.7 12.9-1.2 1.6-1.4 7.2-1.2 29.4.3 24.8.5 27.6 2 27.9 1.8.3 46.6-25.1 48.6-27.6.9-1.2 1.2-8.8 1.2-30.2s-.3-29-1.2-30.2c-1.6-1.9-12.1-7.8-13.9-7.8-.8 0-2.9 1-4.7 2.1z"></path></g></svg></a>
|
||||||
|
<a href="https://www.flint.sh/en/home?utm_source=coolify.io"> <img src="https://github.com/Flint-company.png" width="60px" alt="FlintCompany"/></a>
|
||||||
<a href="https://americancloud.com/?utm_source=coolify.io"><img src="https://github.com/American-Cloud.png" width="60px" alt="American Cloud"/></a>
|
<a href="https://americancloud.com/?utm_source=coolify.io"><img src="https://github.com/American-Cloud.png" width="60px" alt="American Cloud"/></a>
|
||||||
<a href="https://cryptojobslist.com/?utm_source=coolify.io"><img src="https://github.com/cryptojobslist.png" width="60px" alt="CryptoJobsList" /></a>
|
<a href="https://cryptojobslist.com/?utm_source=coolify.io"><img src="https://github.com/cryptojobslist.png" width="60px" alt="CryptoJobsList" /></a>
|
||||||
<a href="https://www.uxwizz.com/?utm_source=coolify.io"><img width="60px" alt="UXWizz" src="https://github.com/UXWizz.png"/></a>
|
<a href="https://www.uxwizz.com/?utm_source=coolify.io"><img width="60px" alt="UXWizz" src="https://github.com/UXWizz.png"/></a>
|
||||||
@ -62,6 +66,7 @@ ## Organizations
|
|||||||
<a href="https://opencollective.com/coollabsio/organization/8/website"><img src="https://opencollective.com/coollabsio/organization/8/avatar.svg"></a>
|
<a href="https://opencollective.com/coollabsio/organization/8/website"><img src="https://opencollective.com/coollabsio/organization/8/avatar.svg"></a>
|
||||||
<a href="https://opencollective.com/coollabsio/organization/9/website"><img src="https://opencollective.com/coollabsio/organization/9/avatar.svg"></a>
|
<a href="https://opencollective.com/coollabsio/organization/9/website"><img src="https://opencollective.com/coollabsio/organization/9/avatar.svg"></a>
|
||||||
|
|
||||||
|
|
||||||
## Individuals
|
## Individuals
|
||||||
|
|
||||||
<a href="https://opencollective.com/coollabsio"><img src="https://opencollective.com/coollabsio/individuals.svg?width=890"></a>
|
<a href="https://opencollective.com/coollabsio"><img src="https://opencollective.com/coollabsio/individuals.svg?width=890"></a>
|
||||||
|
@ -339,7 +339,7 @@ private function sentinel()
|
|||||||
instant_remote_process($connectProxyToDockerNetworks, $this->server, false);
|
instant_remote_process($connectProxyToDockerNetworks, $this->server, false);
|
||||||
}
|
}
|
||||||
} catch (\Exception $e) {
|
} catch (\Exception $e) {
|
||||||
send_internal_notification("ContainerStatusJob failed on ({$this->server->id}) with: " . $e->getMessage());
|
// send_internal_notification("ContainerStatusJob failed on ({$this->server->id}) with: " . $e->getMessage());
|
||||||
ray($e->getMessage());
|
ray($e->getMessage());
|
||||||
return handleError($e);
|
return handleError($e);
|
||||||
}
|
}
|
||||||
|
54
app/Console/Commands/AdminRemoveUser.php
Normal file
54
app/Console/Commands/AdminRemoveUser.php
Normal file
@ -0,0 +1,54 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Console\Commands;
|
||||||
|
|
||||||
|
use App\Models\Server;
|
||||||
|
use App\Models\User;
|
||||||
|
use Illuminate\Console\Command;
|
||||||
|
|
||||||
|
class AdminRemoveUser extends Command
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* The name and signature of the console command.
|
||||||
|
*
|
||||||
|
* @var string
|
||||||
|
*/
|
||||||
|
protected $signature = 'admin:remove-user {email}';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The console command description.
|
||||||
|
*
|
||||||
|
* @var string
|
||||||
|
*/
|
||||||
|
protected $description = 'Remove User from database';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Execute the console command.
|
||||||
|
*/
|
||||||
|
public function handle()
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
$email = $this->argument('email');
|
||||||
|
$confirm = $this->confirm('Are you sure you want to remove user with email: ' . $email . '?');
|
||||||
|
if (!$confirm) {
|
||||||
|
$this->info('User removal cancelled.');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
$this->info("Removing user with email: $email");
|
||||||
|
$user = User::whereEmail($email)->firstOrFail();
|
||||||
|
$teams = $user->teams;
|
||||||
|
foreach ($teams as $team) {
|
||||||
|
if ($team->members->count() > 1) {
|
||||||
|
$this->error('User is a member of a team with more than one member. Please remove user from team first.');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
$team->delete();
|
||||||
|
}
|
||||||
|
$user->delete();
|
||||||
|
} catch (\Exception $e) {
|
||||||
|
$this->error('Failed to remove user.');
|
||||||
|
$this->error($e->getMessage());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -1,33 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
namespace App\Console\Commands;
|
|
||||||
|
|
||||||
use App\Models\Server;
|
|
||||||
use Illuminate\Console\Command;
|
|
||||||
|
|
||||||
class Cloud extends Command
|
|
||||||
{
|
|
||||||
/**
|
|
||||||
* The name and signature of the console command.
|
|
||||||
*
|
|
||||||
* @var string
|
|
||||||
*/
|
|
||||||
protected $signature = 'cloud:unused-servers';
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The console command description.
|
|
||||||
*
|
|
||||||
* @var string
|
|
||||||
*/
|
|
||||||
protected $description = 'Get Unused Servers from Cloud';
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Execute the console command.
|
|
||||||
*/
|
|
||||||
public function handle()
|
|
||||||
{
|
|
||||||
Server::all()->whereNotNull('team.subscription')->where('team.subscription.stripe_trial_already_ended',true)->each(function($server){
|
|
||||||
$this->info($server->name);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
@ -29,7 +29,6 @@ class RootResetPassword extends Command
|
|||||||
*/
|
*/
|
||||||
public function handle()
|
public function handle()
|
||||||
{
|
{
|
||||||
//
|
|
||||||
$this->info('You are about to reset the root password.');
|
$this->info('You are about to reset the root password.');
|
||||||
$password = password('Give me a new password for root user: ');
|
$password = password('Give me a new password for root user: ');
|
||||||
$passwordAgain = password('Again');
|
$passwordAgain = password('Again');
|
||||||
|
@ -18,6 +18,7 @@
|
|||||||
use App\Models\Team;
|
use App\Models\Team;
|
||||||
use Illuminate\Console\Scheduling\Schedule;
|
use Illuminate\Console\Scheduling\Schedule;
|
||||||
use Illuminate\Foundation\Console\Kernel as ConsoleKernel;
|
use Illuminate\Foundation\Console\Kernel as ConsoleKernel;
|
||||||
|
use Illuminate\Support\Sleep;
|
||||||
|
|
||||||
class Kernel extends ConsoleKernel
|
class Kernel extends ConsoleKernel
|
||||||
{
|
{
|
||||||
@ -76,13 +77,28 @@ private function check_resources($schedule)
|
|||||||
$containerServers = $servers->where('settings.is_swarm_worker', false)->where('settings.is_build_server', false);
|
$containerServers = $servers->where('settings.is_swarm_worker', false)->where('settings.is_build_server', false);
|
||||||
}
|
}
|
||||||
foreach ($containerServers as $server) {
|
foreach ($containerServers as $server) {
|
||||||
$schedule->job(new ContainerStatusJob($server))->everyMinute()->onOneServer();
|
$schedule->job(new ContainerStatusJob($server))->everyTwoMinutes()->onOneServer()->before(function () {
|
||||||
|
if (isCloud()) {
|
||||||
|
$wait = rand(5, 20);
|
||||||
|
Sleep::for($wait)->seconds();
|
||||||
|
}
|
||||||
|
});
|
||||||
if ($server->isLogDrainEnabled()) {
|
if ($server->isLogDrainEnabled()) {
|
||||||
$schedule->job(new CheckLogDrainContainerJob($server))->everyMinute()->onOneServer();
|
$schedule->job(new CheckLogDrainContainerJob($server))->everyTwoMinutes()->onOneServer()->before(function () {
|
||||||
|
if (isCloud()) {
|
||||||
|
$wait = rand(5, 20);
|
||||||
|
Sleep::for($wait)->seconds();
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
foreach ($servers as $server) {
|
foreach ($servers as $server) {
|
||||||
$schedule->job(new ServerStatusJob($server))->everyMinute()->onOneServer();
|
$schedule->job(new ServerStatusJob($server))->everyTwoMinutes()->onOneServer()->before(function () {
|
||||||
|
if (isCloud()) {
|
||||||
|
$wait = rand(5, 20);
|
||||||
|
Sleep::for($wait)->seconds();
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
private function instance_auto_update($schedule)
|
private function instance_auto_update($schedule)
|
||||||
@ -138,7 +154,16 @@ private function check_scheduled_tasks($schedule)
|
|||||||
$scheduled_task->delete();
|
$scheduled_task->delete();
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
if ($application) {
|
||||||
|
if (str($application->status)->contains('running') === false) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if ($service) {
|
||||||
|
if (str($service->status())->contains('running') === false) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
if (isset(VALID_CRON_STRINGS[$scheduled_task->frequency])) {
|
if (isset(VALID_CRON_STRINGS[$scheduled_task->frequency])) {
|
||||||
$scheduled_task->frequency = VALID_CRON_STRINGS[$scheduled_task->frequency];
|
$scheduled_task->frequency = VALID_CRON_STRINGS[$scheduled_task->frequency];
|
||||||
}
|
}
|
||||||
|
@ -47,7 +47,7 @@ public function manual(Request $request)
|
|||||||
if ($x_bitbucket_event === 'repo:push') {
|
if ($x_bitbucket_event === 'repo:push') {
|
||||||
$branch = data_get($payload, 'push.changes.0.new.name');
|
$branch = data_get($payload, 'push.changes.0.new.name');
|
||||||
$full_name = data_get($payload, 'repository.full_name');
|
$full_name = data_get($payload, 'repository.full_name');
|
||||||
|
$commit = data_get($payload, 'push.changes.0.new.target.hash');
|
||||||
if (!$branch) {
|
if (!$branch) {
|
||||||
return response([
|
return response([
|
||||||
'status' => 'failed',
|
'status' => 'failed',
|
||||||
@ -104,6 +104,7 @@ public function manual(Request $request)
|
|||||||
queue_application_deployment(
|
queue_application_deployment(
|
||||||
application: $application,
|
application: $application,
|
||||||
deployment_uuid: $deployment_uuid,
|
deployment_uuid: $deployment_uuid,
|
||||||
|
commit: $commit,
|
||||||
force_rebuild: false,
|
force_rebuild: false,
|
||||||
is_webhook: true
|
is_webhook: true
|
||||||
);
|
);
|
||||||
|
@ -129,6 +129,7 @@ public function manual(Request $request)
|
|||||||
application: $application,
|
application: $application,
|
||||||
deployment_uuid: $deployment_uuid,
|
deployment_uuid: $deployment_uuid,
|
||||||
force_rebuild: false,
|
force_rebuild: false,
|
||||||
|
commit: data_get($payload, 'after', 'HEAD'),
|
||||||
is_webhook: true,
|
is_webhook: true,
|
||||||
);
|
);
|
||||||
$return_payloads->push([
|
$return_payloads->push([
|
||||||
@ -177,6 +178,7 @@ public function manual(Request $request)
|
|||||||
pull_request_id: $pull_request_id,
|
pull_request_id: $pull_request_id,
|
||||||
deployment_uuid: $deployment_uuid,
|
deployment_uuid: $deployment_uuid,
|
||||||
force_rebuild: false,
|
force_rebuild: false,
|
||||||
|
commit: data_get($payload, 'head.sha', 'HEAD'),
|
||||||
is_webhook: true,
|
is_webhook: true,
|
||||||
git_type: 'github'
|
git_type: 'github'
|
||||||
);
|
);
|
||||||
@ -338,6 +340,7 @@ public function normal(Request $request)
|
|||||||
queue_application_deployment(
|
queue_application_deployment(
|
||||||
application: $application,
|
application: $application,
|
||||||
deployment_uuid: $deployment_uuid,
|
deployment_uuid: $deployment_uuid,
|
||||||
|
commit: data_get($payload, 'after', 'HEAD'),
|
||||||
force_rebuild: false,
|
force_rebuild: false,
|
||||||
is_webhook: true,
|
is_webhook: true,
|
||||||
);
|
);
|
||||||
@ -387,6 +390,7 @@ public function normal(Request $request)
|
|||||||
pull_request_id: $pull_request_id,
|
pull_request_id: $pull_request_id,
|
||||||
deployment_uuid: $deployment_uuid,
|
deployment_uuid: $deployment_uuid,
|
||||||
force_rebuild: false,
|
force_rebuild: false,
|
||||||
|
commit: data_get($payload, 'head.sha', 'HEAD'),
|
||||||
is_webhook: true,
|
is_webhook: true,
|
||||||
git_type: 'github'
|
git_type: 'github'
|
||||||
);
|
);
|
||||||
|
@ -38,6 +38,15 @@ public function manual(Request $request)
|
|||||||
$headers = $request->headers->all();
|
$headers = $request->headers->all();
|
||||||
$x_gitlab_token = data_get($headers, 'x-gitlab-token.0');
|
$x_gitlab_token = data_get($headers, 'x-gitlab-token.0');
|
||||||
$x_gitlab_event = data_get($payload, 'object_kind');
|
$x_gitlab_event = data_get($payload, 'object_kind');
|
||||||
|
$allowed_events = ['push', 'merge_request'];
|
||||||
|
if (!in_array($x_gitlab_event, $allowed_events)) {
|
||||||
|
$return_payloads->push([
|
||||||
|
'status' => 'failed',
|
||||||
|
'message' => 'Event not allowed. Only push and merge_request events are allowed.',
|
||||||
|
]);
|
||||||
|
return response($return_payloads);
|
||||||
|
}
|
||||||
|
|
||||||
if ($x_gitlab_event === 'push') {
|
if ($x_gitlab_event === 'push') {
|
||||||
$branch = data_get($payload, 'ref');
|
$branch = data_get($payload, 'ref');
|
||||||
$full_name = data_get($payload, 'project.path_with_namespace');
|
$full_name = data_get($payload, 'project.path_with_namespace');
|
||||||
@ -124,6 +133,7 @@ public function manual(Request $request)
|
|||||||
queue_application_deployment(
|
queue_application_deployment(
|
||||||
application: $application,
|
application: $application,
|
||||||
deployment_uuid: $deployment_uuid,
|
deployment_uuid: $deployment_uuid,
|
||||||
|
commit: data_get($payload, 'after', 'HEAD'),
|
||||||
force_rebuild: false,
|
force_rebuild: false,
|
||||||
is_webhook: true,
|
is_webhook: true,
|
||||||
);
|
);
|
||||||
@ -173,6 +183,7 @@ public function manual(Request $request)
|
|||||||
application: $application,
|
application: $application,
|
||||||
pull_request_id: $pull_request_id,
|
pull_request_id: $pull_request_id,
|
||||||
deployment_uuid: $deployment_uuid,
|
deployment_uuid: $deployment_uuid,
|
||||||
|
commit: data_get($payload, 'object_attributes.last_commit.id', 'HEAD'),
|
||||||
force_rebuild: false,
|
force_rebuild: false,
|
||||||
is_webhook: true,
|
is_webhook: true,
|
||||||
git_type: 'gitlab'
|
git_type: 'gitlab'
|
||||||
|
@ -107,6 +107,8 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
|
|||||||
private ?string $fullRepoUrl = null;
|
private ?string $fullRepoUrl = null;
|
||||||
private ?string $branch = null;
|
private ?string $branch = null;
|
||||||
|
|
||||||
|
private ?string $coolify_variables = null;
|
||||||
|
|
||||||
public $tries = 1;
|
public $tries = 1;
|
||||||
public function __construct(int $application_deployment_queue_id)
|
public function __construct(int $application_deployment_queue_id)
|
||||||
{
|
{
|
||||||
@ -406,7 +408,7 @@ private function deploy_docker_compose_buildpack()
|
|||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
$this->execute_remote_command(
|
$this->execute_remote_command(
|
||||||
[executeInDocker($this->deployment_uuid, "SOURCE_COMMIT={$this->commit} docker compose --project-directory {$this->workdir} -f {$this->workdir}{$this->docker_compose_location} build"), "hidden" => true],
|
[executeInDocker($this->deployment_uuid, "{$this->coolify_variables} docker compose --project-directory {$this->workdir} -f {$this->workdir}{$this->docker_compose_location} build"), "hidden" => true],
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -436,9 +438,9 @@ private function deploy_docker_compose_buildpack()
|
|||||||
} else {
|
} else {
|
||||||
$this->write_deployment_configurations();
|
$this->write_deployment_configurations();
|
||||||
$server_workdir = $this->application->workdir();
|
$server_workdir = $this->application->workdir();
|
||||||
ray("SOURCE_COMMIT={$this->commit} docker compose --project-directory {$server_workdir} -f {$server_workdir}{$this->docker_compose_location} up -d");
|
ray("{$this->coolify_variables} docker compose --project-directory {$server_workdir} -f {$server_workdir}{$this->docker_compose_location} up -d");
|
||||||
$this->execute_remote_command(
|
$this->execute_remote_command(
|
||||||
["SOURCE_COMMIT={$this->commit} docker compose --project-directory {$server_workdir} -f {$server_workdir}{$this->docker_compose_location} up -d", "hidden" => true],
|
["{$this->coolify_variables} docker compose --project-directory {$server_workdir} -f {$server_workdir}{$this->docker_compose_location} up -d", "hidden" => true],
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
@ -449,7 +451,7 @@ private function deploy_docker_compose_buildpack()
|
|||||||
$this->write_deployment_configurations();
|
$this->write_deployment_configurations();
|
||||||
} else {
|
} else {
|
||||||
$this->execute_remote_command(
|
$this->execute_remote_command(
|
||||||
[executeInDocker($this->deployment_uuid, "SOURCE_COMMIT={$this->commit} docker compose --project-directory {$this->workdir} -f {$this->workdir}{$this->docker_compose_location} up -d"), "hidden" => true],
|
[executeInDocker($this->deployment_uuid, "{$this->coolify_variables} docker compose --project-directory {$this->workdir} -f {$this->workdir}{$this->docker_compose_location} up -d"), "hidden" => true],
|
||||||
);
|
);
|
||||||
$this->write_deployment_configurations();
|
$this->write_deployment_configurations();
|
||||||
}
|
}
|
||||||
@ -743,6 +745,16 @@ private function save_environment_variables()
|
|||||||
$envs->push("SOURCE_COMMIT=unknown");
|
$envs->push("SOURCE_COMMIT=unknown");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if ($this->application->environment_variables_preview->where('key', 'COOLIFY_FQDN')->isEmpty()) {
|
||||||
|
$envs->push("COOLIFY_FQDN={$this->preview->fqdn}");
|
||||||
|
}
|
||||||
|
if ($this->application->environment_variables_preview->where('key', 'COOLIFY_URL')->isEmpty()) {
|
||||||
|
$url = str($this->preview->fqdn)->replace('http://', '')->replace('https://', '');
|
||||||
|
$envs->push("COOLIFY_URL={$url}");
|
||||||
|
}
|
||||||
|
if ($this->application->environment_variables_preview->where('key', 'COOLIFY_BRANCH')->isEmpty()) {
|
||||||
|
$envs->push("COOLIFY_BRANCH={$this->application->git_branch}");
|
||||||
|
}
|
||||||
$envs = $envs->sort(function ($a, $b) {
|
$envs = $envs->sort(function ($a, $b) {
|
||||||
return strpos($a, '$') === false ? -1 : 1;
|
return strpos($a, '$') === false ? -1 : 1;
|
||||||
});
|
});
|
||||||
@ -777,6 +789,16 @@ private function save_environment_variables()
|
|||||||
$envs->push("SOURCE_COMMIT=unknown");
|
$envs->push("SOURCE_COMMIT=unknown");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if ($this->application->environment_variables->where('key', 'COOLIFY_FQDN')->isEmpty()) {
|
||||||
|
$envs->push("COOLIFY_FQDN={$this->application->fqdn}");
|
||||||
|
}
|
||||||
|
if ($this->application->environment_variables->where('key', 'COOLIFY_URL')->isEmpty()) {
|
||||||
|
$url = str($this->application->fqdn)->replace('http://', '')->replace('https://', '');
|
||||||
|
$envs->push("COOLIFY_URL={$url}");
|
||||||
|
}
|
||||||
|
if ($this->application->environment_variables_preview->where('key', 'COOLIFY_BRANCH')->isEmpty()) {
|
||||||
|
$envs->push("COOLIFY_BRANCH={$this->application->git_branch}");
|
||||||
|
}
|
||||||
$envs = $envs->sort(function ($a, $b) {
|
$envs = $envs->sort(function ($a, $b) {
|
||||||
return strpos($a, '$') === false ? -1 : 1;
|
return strpos($a, '$') === false ? -1 : 1;
|
||||||
});
|
});
|
||||||
@ -1080,6 +1102,23 @@ private function set_base_dir()
|
|||||||
{
|
{
|
||||||
$this->application_deployment_queue->addLogEntry("Setting base directory to {$this->workdir}.");
|
$this->application_deployment_queue->addLogEntry("Setting base directory to {$this->workdir}.");
|
||||||
}
|
}
|
||||||
|
private function set_coolify_variables()
|
||||||
|
{
|
||||||
|
$this->coolify_variables = "SOURCE_COMMIT={$this->commit} ";
|
||||||
|
if ($this->pull_request_id === 0) {
|
||||||
|
$fqdn = $this->application->fqdn;
|
||||||
|
} else {
|
||||||
|
$fqdn = $this->preview->fqdn;
|
||||||
|
}
|
||||||
|
if (isset($fqdn)) {
|
||||||
|
$this->coolify_variables .= "COOLIFY_FQDN={$fqdn} ";
|
||||||
|
$url = str($fqdn)->replace('http://', '')->replace('https://', '');
|
||||||
|
$this->coolify_variables .= "COOLIFY_URL={$url} ";
|
||||||
|
}
|
||||||
|
if (isset($this->application->git_branch)) {
|
||||||
|
$this->coolify_variables .= "COOLIFY_BRANCH={$this->application->git_branch} ";
|
||||||
|
}
|
||||||
|
}
|
||||||
private function check_git_if_build_needed()
|
private function check_git_if_build_needed()
|
||||||
{
|
{
|
||||||
$this->generate_git_import_commands();
|
$this->generate_git_import_commands();
|
||||||
@ -1113,7 +1152,10 @@ private function check_git_if_build_needed()
|
|||||||
}
|
}
|
||||||
if ($this->saved_outputs->get('git_commit_sha') && !$this->rollback) {
|
if ($this->saved_outputs->get('git_commit_sha') && !$this->rollback) {
|
||||||
$this->commit = $this->saved_outputs->get('git_commit_sha')->before("\t");
|
$this->commit = $this->saved_outputs->get('git_commit_sha')->before("\t");
|
||||||
|
$this->application_deployment_queue->commit = $this->commit;
|
||||||
|
$this->application_deployment_queue->save();
|
||||||
}
|
}
|
||||||
|
$this->set_coolify_variables();
|
||||||
}
|
}
|
||||||
private function clone_repository()
|
private function clone_repository()
|
||||||
{
|
{
|
||||||
@ -1129,6 +1171,18 @@ private function clone_repository()
|
|||||||
]
|
]
|
||||||
);
|
);
|
||||||
$this->create_workdir();
|
$this->create_workdir();
|
||||||
|
$this->execute_remote_command(
|
||||||
|
[
|
||||||
|
executeInDocker($this->deployment_uuid, "cd {$this->workdir} && git log -1 {$this->commit} --pretty=%B"),
|
||||||
|
"hidden" => true,
|
||||||
|
"save" => "commit_message"
|
||||||
|
]
|
||||||
|
);
|
||||||
|
if ($this->saved_outputs->get('commit_message')) {
|
||||||
|
$this->application_deployment_queue->commit_message = $this->saved_outputs->get('commit_message');
|
||||||
|
ApplicationDeploymentQueue::whereCommit($this->commit)->whereApplicationId($this->application->id)->update(['commit_message' => $this->saved_outputs->get('commit_message')]);
|
||||||
|
$this->application_deployment_queue->save();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private function generate_git_import_commands()
|
private function generate_git_import_commands()
|
||||||
@ -1277,9 +1331,11 @@ private function generate_compose_file()
|
|||||||
if ($this->pull_request_id !== 0) {
|
if ($this->pull_request_id !== 0) {
|
||||||
$labels = collect(generateLabelsApplication($this->application, $this->preview));
|
$labels = collect(generateLabelsApplication($this->application, $this->preview));
|
||||||
}
|
}
|
||||||
|
if ($this->application->settings->is_container_label_escape_enabled) {
|
||||||
$labels = $labels->map(function ($value, $key) {
|
$labels = $labels->map(function ($value, $key) {
|
||||||
return escapeDollarSign($value);
|
return escapeDollarSign($value);
|
||||||
});
|
});
|
||||||
|
}
|
||||||
$labels = $labels->merge(defaultLabels($this->application->id, $this->application->uuid, $this->pull_request_id))->toArray();
|
$labels = $labels->merge(defaultLabels($this->application->id, $this->application->uuid, $this->pull_request_id))->toArray();
|
||||||
|
|
||||||
// Check for custom HEALTHCHECK
|
// Check for custom HEALTHCHECK
|
||||||
@ -1767,11 +1823,11 @@ private function build_by_compose_file()
|
|||||||
$this->application_deployment_queue->addLogEntry("Pulling latest images from the registry.");
|
$this->application_deployment_queue->addLogEntry("Pulling latest images from the registry.");
|
||||||
$this->execute_remote_command(
|
$this->execute_remote_command(
|
||||||
[executeInDocker($this->deployment_uuid, "docker compose --project-directory {$this->workdir} pull"), "hidden" => true],
|
[executeInDocker($this->deployment_uuid, "docker compose --project-directory {$this->workdir} pull"), "hidden" => true],
|
||||||
[executeInDocker($this->deployment_uuid, "SOURCE_COMMIT={$this->commit} docker compose --project-directory {$this->workdir} build"), "hidden" => true],
|
[executeInDocker($this->deployment_uuid, "{$this->coolify_variables} docker compose --project-directory {$this->workdir} build"), "hidden" => true],
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
$this->execute_remote_command(
|
$this->execute_remote_command(
|
||||||
[executeInDocker($this->deployment_uuid, "SOURCE_COMMIT={$this->commit} docker compose --project-directory {$this->workdir} -f {$this->workdir}{$this->docker_compose_location} build"), "hidden" => true],
|
[executeInDocker($this->deployment_uuid, "{$this->coolify_variables} docker compose --project-directory {$this->workdir} -f {$this->workdir}{$this->docker_compose_location} build"), "hidden" => true],
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
$this->application_deployment_queue->addLogEntry("New images built.");
|
$this->application_deployment_queue->addLogEntry("New images built.");
|
||||||
@ -1783,16 +1839,16 @@ private function start_by_compose_file()
|
|||||||
$this->application_deployment_queue->addLogEntry("Pulling latest images from the registry.");
|
$this->application_deployment_queue->addLogEntry("Pulling latest images from the registry.");
|
||||||
$this->execute_remote_command(
|
$this->execute_remote_command(
|
||||||
[executeInDocker($this->deployment_uuid, "docker compose --project-directory {$this->workdir} pull"), "hidden" => true],
|
[executeInDocker($this->deployment_uuid, "docker compose --project-directory {$this->workdir} pull"), "hidden" => true],
|
||||||
[executeInDocker($this->deployment_uuid, "SOURCE_COMMIT={$this->commit} docker compose --project-directory {$this->workdir} up --build -d"), "hidden" => true],
|
[executeInDocker($this->deployment_uuid, "{$this->coolify_variables} docker compose --project-directory {$this->workdir} up --build -d"), "hidden" => true],
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
if ($this->use_build_server) {
|
if ($this->use_build_server) {
|
||||||
$this->execute_remote_command(
|
$this->execute_remote_command(
|
||||||
["SOURCE_COMMIT={$this->commit} docker compose --project-directory {$this->configuration_dir} -f {$this->configuration_dir}{$this->docker_compose_location} up --build -d", "hidden" => true],
|
["{$this->coolify_variables} docker compose --project-directory {$this->configuration_dir} -f {$this->configuration_dir}{$this->docker_compose_location} up --build -d", "hidden" => true],
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
$this->execute_remote_command(
|
$this->execute_remote_command(
|
||||||
[executeInDocker($this->deployment_uuid, "SOURCE_COMMIT={$this->commit} docker compose --project-directory {$this->workdir} -f {$this->workdir}{$this->docker_compose_location} up --build -d"), "hidden" => true],
|
[executeInDocker($this->deployment_uuid, "{$this->coolify_variables} docker compose --project-directory {$this->workdir} -f {$this->workdir}{$this->docker_compose_location} up --build -d"), "hidden" => true],
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -37,336 +37,5 @@ public function uniqueId(): int
|
|||||||
public function handle()
|
public function handle()
|
||||||
{
|
{
|
||||||
GetContainersStatus::run($this->server);
|
GetContainersStatus::run($this->server);
|
||||||
return;
|
|
||||||
// if (!$this->server->isFunctional()) {
|
|
||||||
// return 'Server is not ready.';
|
|
||||||
// };
|
|
||||||
// $applications = $this->server->applications();
|
|
||||||
// $skip_these_applications = collect([]);
|
|
||||||
// foreach ($applications as $application) {
|
|
||||||
// if ($application->additional_servers->count() > 0) {
|
|
||||||
// $skip_these_applications->push($application);
|
|
||||||
// ComplexStatusCheck::run($application);
|
|
||||||
// $applications = $applications->filter(function ($value, $key) use ($application) {
|
|
||||||
// return $value->id !== $application->id;
|
|
||||||
// });
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// $applications = $applications->filter(function ($value, $key) use ($skip_these_applications) {
|
|
||||||
// return !$skip_these_applications->pluck('id')->contains($value->id);
|
|
||||||
// });
|
|
||||||
// try {
|
|
||||||
// if ($this->server->isSwarm()) {
|
|
||||||
// $containers = instant_remote_process(["docker service inspect $(docker service ls -q) --format '{{json .}}'"], $this->server, false);
|
|
||||||
// $containerReplicates = instant_remote_process(["docker service ls --format '{{json .}}'"], $this->server, false);
|
|
||||||
// } else {
|
|
||||||
// // Precheck for containers
|
|
||||||
// $containers = instant_remote_process(["docker container ls -q"], $this->server, false);
|
|
||||||
// if (!$containers) {
|
|
||||||
// return;
|
|
||||||
// }
|
|
||||||
// $containers = instant_remote_process(["docker container inspect $(docker container ls -q) --format '{{json .}}'"], $this->server, false);
|
|
||||||
// $containerReplicates = null;
|
|
||||||
// }
|
|
||||||
// if (is_null($containers)) {
|
|
||||||
// return;
|
|
||||||
// }
|
|
||||||
|
|
||||||
// $containers = format_docker_command_output_to_json($containers);
|
|
||||||
// if ($containerReplicates) {
|
|
||||||
// $containerReplicates = format_docker_command_output_to_json($containerReplicates);
|
|
||||||
// foreach ($containerReplicates as $containerReplica) {
|
|
||||||
// $name = data_get($containerReplica, 'Name');
|
|
||||||
// $containers = $containers->map(function ($container) use ($name, $containerReplica) {
|
|
||||||
// if (data_get($container, 'Spec.Name') === $name) {
|
|
||||||
// $replicas = data_get($containerReplica, 'Replicas');
|
|
||||||
// $running = str($replicas)->explode('/')[0];
|
|
||||||
// $total = str($replicas)->explode('/')[1];
|
|
||||||
// if ($running === $total) {
|
|
||||||
// data_set($container, 'State.Status', 'running');
|
|
||||||
// data_set($container, 'State.Health.Status', 'healthy');
|
|
||||||
// } else {
|
|
||||||
// data_set($container, 'State.Status', 'starting');
|
|
||||||
// data_set($container, 'State.Health.Status', 'unhealthy');
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// return $container;
|
|
||||||
// });
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// $databases = $this->server->databases();
|
|
||||||
// $services = $this->server->services()->get();
|
|
||||||
// $previews = $this->server->previews();
|
|
||||||
// $foundApplications = [];
|
|
||||||
// $foundApplicationPreviews = [];
|
|
||||||
// $foundDatabases = [];
|
|
||||||
// $foundServices = [];
|
|
||||||
|
|
||||||
// foreach ($containers as $container) {
|
|
||||||
// if ($this->server->isSwarm()) {
|
|
||||||
// $labels = data_get($container, 'Spec.Labels');
|
|
||||||
// $uuid = data_get($labels, 'coolify.name');
|
|
||||||
// } else {
|
|
||||||
// $labels = data_get($container, 'Config.Labels');
|
|
||||||
// }
|
|
||||||
// $containerStatus = data_get($container, 'State.Status');
|
|
||||||
// $containerHealth = data_get($container, 'State.Health.Status', 'unhealthy');
|
|
||||||
// $containerStatus = "$containerStatus ($containerHealth)";
|
|
||||||
// $labels = Arr::undot(format_docker_labels_to_json($labels));
|
|
||||||
// $applicationId = data_get($labels, 'coolify.applicationId');
|
|
||||||
// if ($applicationId) {
|
|
||||||
// $pullRequestId = data_get($labels, 'coolify.pullRequestId');
|
|
||||||
// if ($pullRequestId) {
|
|
||||||
// if (str($applicationId)->contains('-')) {
|
|
||||||
// $applicationId = str($applicationId)->before('-');
|
|
||||||
// }
|
|
||||||
// $preview = ApplicationPreview::where('application_id', $applicationId)->where('pull_request_id', $pullRequestId)->first();
|
|
||||||
// if ($preview) {
|
|
||||||
// $foundApplicationPreviews[] = $preview->id;
|
|
||||||
// $statusFromDb = $preview->status;
|
|
||||||
// if ($statusFromDb !== $containerStatus) {
|
|
||||||
// $preview->update(['status' => $containerStatus]);
|
|
||||||
// }
|
|
||||||
// } else {
|
|
||||||
// //Notify user that this container should not be there.
|
|
||||||
// }
|
|
||||||
// } else {
|
|
||||||
// $application = $applications->where('id', $applicationId)->first();
|
|
||||||
// if ($application) {
|
|
||||||
// $foundApplications[] = $application->id;
|
|
||||||
// $statusFromDb = $application->status;
|
|
||||||
// if ($statusFromDb !== $containerStatus) {
|
|
||||||
// $application->update(['status' => $containerStatus]);
|
|
||||||
// }
|
|
||||||
// } else {
|
|
||||||
// //Notify user that this container should not be there.
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// } else {
|
|
||||||
// $uuid = data_get($labels, 'com.docker.compose.service');
|
|
||||||
// $type = data_get($labels, 'coolify.type');
|
|
||||||
|
|
||||||
// if ($uuid) {
|
|
||||||
// if ($type === 'service') {
|
|
||||||
// $database_id = data_get($labels, 'coolify.service.subId');
|
|
||||||
// if ($database_id) {
|
|
||||||
// $service_db = ServiceDatabase::where('id', $database_id)->first();
|
|
||||||
// if ($service_db) {
|
|
||||||
// $uuid = $service_db->service->uuid;
|
|
||||||
// $isPublic = data_get($service_db, 'is_public');
|
|
||||||
// if ($isPublic) {
|
|
||||||
// $foundTcpProxy = $containers->filter(function ($value, $key) use ($uuid) {
|
|
||||||
// if ($this->server->isSwarm()) {
|
|
||||||
// return data_get($value, 'Spec.Name') === "coolify-proxy_$uuid";
|
|
||||||
// } else {
|
|
||||||
// return data_get($value, 'Name') === "/$uuid-proxy";
|
|
||||||
// }
|
|
||||||
// })->first();
|
|
||||||
// if (!$foundTcpProxy) {
|
|
||||||
// StartDatabaseProxy::run($service_db);
|
|
||||||
// // $this->server->team?->notify(new ContainerRestarted("TCP Proxy for {$service_db->service->name}", $this->server));
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// } else {
|
|
||||||
// $database = $databases->where('uuid', $uuid)->first();
|
|
||||||
// if ($database) {
|
|
||||||
// $isPublic = data_get($database, 'is_public');
|
|
||||||
// $foundDatabases[] = $database->id;
|
|
||||||
// $statusFromDb = $database->status;
|
|
||||||
// if ($statusFromDb !== $containerStatus) {
|
|
||||||
// $database->update(['status' => $containerStatus]);
|
|
||||||
// }
|
|
||||||
// if ($isPublic) {
|
|
||||||
// $foundTcpProxy = $containers->filter(function ($value, $key) use ($uuid) {
|
|
||||||
// if ($this->server->isSwarm()) {
|
|
||||||
// return data_get($value, 'Spec.Name') === "coolify-proxy_$uuid";
|
|
||||||
// } else {
|
|
||||||
// return data_get($value, 'Name') === "/$uuid-proxy";
|
|
||||||
// }
|
|
||||||
// })->first();
|
|
||||||
// if (!$foundTcpProxy) {
|
|
||||||
// StartDatabaseProxy::run($database);
|
|
||||||
// $this->server->team?->notify(new ContainerRestarted("TCP Proxy for {$database->name}", $this->server));
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// } else {
|
|
||||||
// // Notify user that this container should not be there.
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// if (data_get($container, 'Name') === '/coolify-db') {
|
|
||||||
// $foundDatabases[] = 0;
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// $serviceLabelId = data_get($labels, 'coolify.serviceId');
|
|
||||||
// if ($serviceLabelId) {
|
|
||||||
// $subType = data_get($labels, 'coolify.service.subType');
|
|
||||||
// $subId = data_get($labels, 'coolify.service.subId');
|
|
||||||
// $service = $services->where('id', $serviceLabelId)->first();
|
|
||||||
// if (!$service) {
|
|
||||||
// continue;
|
|
||||||
// }
|
|
||||||
// if ($subType === 'application') {
|
|
||||||
// $service = $service->applications()->where('id', $subId)->first();
|
|
||||||
// } else {
|
|
||||||
// $service = $service->databases()->where('id', $subId)->first();
|
|
||||||
// }
|
|
||||||
// if ($service) {
|
|
||||||
// $foundServices[] = "$service->id-$service->name";
|
|
||||||
// $statusFromDb = $service->status;
|
|
||||||
// if ($statusFromDb !== $containerStatus) {
|
|
||||||
// // ray('Updating status: ' . $containerStatus);
|
|
||||||
// $service->update(['status' => $containerStatus]);
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// $exitedServices = collect([]);
|
|
||||||
// foreach ($services as $service) {
|
|
||||||
// $apps = $service->applications()->get();
|
|
||||||
// $dbs = $service->databases()->get();
|
|
||||||
// foreach ($apps as $app) {
|
|
||||||
// if (in_array("$app->id-$app->name", $foundServices)) {
|
|
||||||
// continue;
|
|
||||||
// } else {
|
|
||||||
// $exitedServices->push($app);
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// foreach ($dbs as $db) {
|
|
||||||
// if (in_array("$db->id-$db->name", $foundServices)) {
|
|
||||||
// continue;
|
|
||||||
// } else {
|
|
||||||
// $exitedServices->push($db);
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// $exitedServices = $exitedServices->unique('id');
|
|
||||||
// foreach ($exitedServices as $exitedService) {
|
|
||||||
// if (str($exitedService->status)->startsWith('exited')) {
|
|
||||||
// continue;
|
|
||||||
// }
|
|
||||||
// $name = data_get($exitedService, 'name');
|
|
||||||
// $fqdn = data_get($exitedService, 'fqdn');
|
|
||||||
// $containerName = $name ? "$name, available at $fqdn" : $fqdn;
|
|
||||||
// $projectUuid = data_get($service, 'environment.project.uuid');
|
|
||||||
// $serviceUuid = data_get($service, 'uuid');
|
|
||||||
// $environmentName = data_get($service, 'environment.name');
|
|
||||||
|
|
||||||
// if ($projectUuid && $serviceUuid && $environmentName) {
|
|
||||||
// $url = base_url() . '/project/' . $projectUuid . "/" . $environmentName . "/service/" . $serviceUuid;
|
|
||||||
// } else {
|
|
||||||
// $url = null;
|
|
||||||
// }
|
|
||||||
// $this->server->team?->notify(new ContainerStopped($containerName, $this->server, $url));
|
|
||||||
// $exitedService->update(['status' => 'exited']);
|
|
||||||
// }
|
|
||||||
|
|
||||||
// $notRunningApplications = $applications->pluck('id')->diff($foundApplications);
|
|
||||||
// foreach ($notRunningApplications as $applicationId) {
|
|
||||||
// $application = $applications->where('id', $applicationId)->first();
|
|
||||||
// if (str($application->status)->startsWith('exited')) {
|
|
||||||
// continue;
|
|
||||||
// }
|
|
||||||
// $application->update(['status' => 'exited']);
|
|
||||||
|
|
||||||
// $name = data_get($application, 'name');
|
|
||||||
// $fqdn = data_get($application, 'fqdn');
|
|
||||||
|
|
||||||
// $containerName = $name ? "$name ($fqdn)" : $fqdn;
|
|
||||||
|
|
||||||
// $projectUuid = data_get($application, 'environment.project.uuid');
|
|
||||||
// $applicationUuid = data_get($application, 'uuid');
|
|
||||||
// $environment = data_get($application, 'environment.name');
|
|
||||||
|
|
||||||
// if ($projectUuid && $applicationUuid && $environment) {
|
|
||||||
// $url = base_url() . '/project/' . $projectUuid . "/" . $environment . "/application/" . $applicationUuid;
|
|
||||||
// } else {
|
|
||||||
// $url = null;
|
|
||||||
// }
|
|
||||||
|
|
||||||
// $this->server->team?->notify(new ContainerStopped($containerName, $this->server, $url));
|
|
||||||
// }
|
|
||||||
// $notRunningApplicationPreviews = $previews->pluck('id')->diff($foundApplicationPreviews);
|
|
||||||
// foreach ($notRunningApplicationPreviews as $previewId) {
|
|
||||||
// $preview = $previews->where('id', $previewId)->first();
|
|
||||||
// if (str($preview->status)->startsWith('exited')) {
|
|
||||||
// continue;
|
|
||||||
// }
|
|
||||||
// $preview->update(['status' => 'exited']);
|
|
||||||
|
|
||||||
// $name = data_get($preview, 'name');
|
|
||||||
// $fqdn = data_get($preview, 'fqdn');
|
|
||||||
|
|
||||||
// $containerName = $name ? "$name ($fqdn)" : $fqdn;
|
|
||||||
|
|
||||||
// $projectUuid = data_get($preview, 'application.environment.project.uuid');
|
|
||||||
// $environmentName = data_get($preview, 'application.environment.name');
|
|
||||||
// $applicationUuid = data_get($preview, 'application.uuid');
|
|
||||||
|
|
||||||
// if ($projectUuid && $applicationUuid && $environmentName) {
|
|
||||||
// $url = base_url() . '/project/' . $projectUuid . "/" . $environmentName . "/application/" . $applicationUuid;
|
|
||||||
// } else {
|
|
||||||
// $url = null;
|
|
||||||
// }
|
|
||||||
|
|
||||||
// $this->server->team?->notify(new ContainerStopped($containerName, $this->server, $url));
|
|
||||||
// }
|
|
||||||
// $notRunningDatabases = $databases->pluck('id')->diff($foundDatabases);
|
|
||||||
// foreach ($notRunningDatabases as $database) {
|
|
||||||
// $database = $databases->where('id', $database)->first();
|
|
||||||
// if (str($database->status)->startsWith('exited')) {
|
|
||||||
// continue;
|
|
||||||
// }
|
|
||||||
// $database->update(['status' => 'exited']);
|
|
||||||
|
|
||||||
// $name = data_get($database, 'name');
|
|
||||||
// $fqdn = data_get($database, 'fqdn');
|
|
||||||
|
|
||||||
// $containerName = $name;
|
|
||||||
|
|
||||||
// $projectUuid = data_get($database, 'environment.project.uuid');
|
|
||||||
// $environmentName = data_get($database, 'environment.name');
|
|
||||||
// $databaseUuid = data_get($database, 'uuid');
|
|
||||||
|
|
||||||
// if ($projectUuid && $databaseUuid && $environmentName) {
|
|
||||||
// $url = base_url() . '/project/' . $projectUuid . "/" . $environmentName . "/database/" . $databaseUuid;
|
|
||||||
// } else {
|
|
||||||
// $url = null;
|
|
||||||
// }
|
|
||||||
// $this->server->team?->notify(new ContainerStopped($containerName, $this->server, $url));
|
|
||||||
// }
|
|
||||||
|
|
||||||
// // Check if proxy is running
|
|
||||||
// $this->server->proxyType();
|
|
||||||
// $foundProxyContainer = $containers->filter(function ($value, $key) {
|
|
||||||
// if ($this->server->isSwarm()) {
|
|
||||||
// return data_get($value, 'Spec.Name') === 'coolify-proxy_traefik';
|
|
||||||
// } else {
|
|
||||||
// return data_get($value, 'Name') === '/coolify-proxy';
|
|
||||||
// }
|
|
||||||
// })->first();
|
|
||||||
// if (!$foundProxyContainer) {
|
|
||||||
// try {
|
|
||||||
// $shouldStart = CheckProxy::run($this->server);
|
|
||||||
// if ($shouldStart) {
|
|
||||||
// StartProxy::run($this->server, false);
|
|
||||||
// $this->server->team?->notify(new ContainerRestarted('coolify-proxy', $this->server));
|
|
||||||
// }
|
|
||||||
// } catch (\Throwable $e) {
|
|
||||||
// ray($e);
|
|
||||||
// }
|
|
||||||
// } else {
|
|
||||||
// $this->server->proxy->status = data_get($foundProxyContainer, 'State.Status');
|
|
||||||
// $this->server->save();
|
|
||||||
// $connectProxyToDockerNetworks = connectProxyToNetworks($this->server);
|
|
||||||
// instant_remote_process($connectProxyToDockerNetworks, $this->server, false);
|
|
||||||
// }
|
|
||||||
// } catch (\Throwable $e) {
|
|
||||||
// send_internal_notification("ContainerStatusJob failed on ({$this->server->id}) with: " . $e->getMessage());
|
|
||||||
// ray($e->getMessage());
|
|
||||||
// return handleError($e);
|
|
||||||
// }
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -77,8 +77,12 @@ public function handle(): void
|
|||||||
$this->containers[] = data_get($application, 'name') . '-' . data_get($this->resource, 'uuid');
|
$this->containers[] = data_get($application, 'name') . '-' . data_get($this->resource, 'uuid');
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
$this->resource->databases()->get()->each(function ($database) {
|
||||||
|
if (str(data_get($database, 'status'))->contains('running')) {
|
||||||
|
$this->containers[] = data_get($database, 'name') . '-' . data_get($this->resource, 'uuid');
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
if (count($this->containers) == 0) {
|
if (count($this->containers) == 0) {
|
||||||
throw new \Exception('ScheduledTaskJob failed: No containers running.');
|
throw new \Exception('ScheduledTaskJob failed: No containers running.');
|
||||||
}
|
}
|
||||||
@ -89,7 +93,7 @@ public function handle(): void
|
|||||||
|
|
||||||
foreach ($this->containers as $containerName) {
|
foreach ($this->containers as $containerName) {
|
||||||
if (count($this->containers) == 1 || str_starts_with($containerName, $this->task->container . '-' . $this->resource->uuid)) {
|
if (count($this->containers) == 1 || str_starts_with($containerName, $this->task->container . '-' . $this->resource->uuid)) {
|
||||||
$cmd = 'sh -c "' . str_replace('"', '\"', $this->task->command) . '"';
|
$cmd = "sh -c '" . str_replace("'", "'\''", $this->task->command) . "'";
|
||||||
$exec = "docker exec {$containerName} {$cmd}";
|
$exec = "docker exec {$containerName} {$cmd}";
|
||||||
$this->task_output = instant_remote_process([$exec], $this->server, true);
|
$this->task_output = instant_remote_process([$exec], $this->server, true);
|
||||||
$this->task_log->update([
|
$this->task_log->update([
|
||||||
|
@ -40,7 +40,7 @@ public function handle()
|
|||||||
try {
|
try {
|
||||||
$servers = $this->team->servers;
|
$servers = $this->team->servers;
|
||||||
$servers_count = $servers->count();
|
$servers_count = $servers->count();
|
||||||
$limit = $this->team->limits['serverLimit'];
|
$limit = data_get($this->team->limits, 'serverLimit', 2);
|
||||||
$number_of_servers_to_disable = $servers_count - $limit;
|
$number_of_servers_to_disable = $servers_count - $limit;
|
||||||
ray('ServerLimitCheckJob', $this->team->uuid, $servers_count, $limit, $number_of_servers_to_disable);
|
ray('ServerLimitCheckJob', $this->team->uuid, $servers_count, $limit, $number_of_servers_to_disable);
|
||||||
if ($number_of_servers_to_disable > 0) {
|
if ($number_of_servers_to_disable > 0) {
|
||||||
|
@ -22,6 +22,7 @@ class General extends Component
|
|||||||
public ?string $git_commit_sha = null;
|
public ?string $git_commit_sha = null;
|
||||||
public string $build_pack;
|
public string $build_pack;
|
||||||
public ?string $ports_exposes = null;
|
public ?string $ports_exposes = null;
|
||||||
|
public bool $is_container_label_escape_enabled = true;
|
||||||
|
|
||||||
public $customLabels;
|
public $customLabels;
|
||||||
public bool $labelsChanged = false;
|
public bool $labelsChanged = false;
|
||||||
@ -74,6 +75,7 @@ class General extends Component
|
|||||||
'application.post_deployment_command_container' => 'nullable',
|
'application.post_deployment_command_container' => 'nullable',
|
||||||
'application.settings.is_static' => 'boolean|required',
|
'application.settings.is_static' => 'boolean|required',
|
||||||
'application.settings.is_build_server_enabled' => 'boolean|required',
|
'application.settings.is_build_server_enabled' => 'boolean|required',
|
||||||
|
'application.settings.is_container_label_escape_enabled' => 'boolean|required',
|
||||||
'application.watch_paths' => 'nullable',
|
'application.watch_paths' => 'nullable',
|
||||||
];
|
];
|
||||||
protected $validationAttributes = [
|
protected $validationAttributes = [
|
||||||
@ -109,6 +111,7 @@ class General extends Component
|
|||||||
'application.docker_compose_custom_build_command' => 'Docker compose custom build command',
|
'application.docker_compose_custom_build_command' => 'Docker compose custom build command',
|
||||||
'application.settings.is_static' => 'Is static',
|
'application.settings.is_static' => 'Is static',
|
||||||
'application.settings.is_build_server_enabled' => 'Is build server enabled',
|
'application.settings.is_build_server_enabled' => 'Is build server enabled',
|
||||||
|
'application.settings.is_container_label_escape_enabled' => 'Is container label escape enabled',
|
||||||
'application.watch_paths' => 'Watch paths',
|
'application.watch_paths' => 'Watch paths',
|
||||||
];
|
];
|
||||||
public function mount()
|
public function mount()
|
||||||
@ -124,6 +127,7 @@ public function mount()
|
|||||||
}
|
}
|
||||||
$this->parsedServiceDomains = $this->application->docker_compose_domains ? json_decode($this->application->docker_compose_domains, true) : [];
|
$this->parsedServiceDomains = $this->application->docker_compose_domains ? json_decode($this->application->docker_compose_domains, true) : [];
|
||||||
$this->ports_exposes = $this->application->ports_exposes;
|
$this->ports_exposes = $this->application->ports_exposes;
|
||||||
|
$this->is_container_label_escape_enabled = $this->application->settings->is_container_label_escape_enabled;
|
||||||
$this->customLabels = $this->application->parseContainerLabels();
|
$this->customLabels = $this->application->parseContainerLabels();
|
||||||
if (!$this->customLabels && $this->application->destination->server->proxyType() !== 'NONE') {
|
if (!$this->customLabels && $this->application->destination->server->proxyType() !== 'NONE') {
|
||||||
$this->customLabels = str(implode("|", generateLabelsApplication($this->application)))->replace("|", "\n");
|
$this->customLabels = str(implode("|", generateLabelsApplication($this->application)))->replace("|", "\n");
|
||||||
@ -145,7 +149,7 @@ public function instantSave()
|
|||||||
$this->application->settings->save();
|
$this->application->settings->save();
|
||||||
$this->dispatch('success', 'Settings saved.');
|
$this->dispatch('success', 'Settings saved.');
|
||||||
$this->application->refresh();
|
$this->application->refresh();
|
||||||
if ($this->ports_exposes !== $this->application->ports_exposes) {
|
if ($this->ports_exposes !== $this->application->ports_exposes || $this->is_container_label_escape_enabled !== $this->application->settings->is_container_label_escape_enabled) {
|
||||||
$this->resetDefaultLabels(false);
|
$this->resetDefaultLabels(false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -204,6 +208,9 @@ public function generateDomain(string $serviceName)
|
|||||||
$this->application->docker_compose_domains = json_encode($this->parsedServiceDomains);
|
$this->application->docker_compose_domains = json_encode($this->parsedServiceDomains);
|
||||||
$this->application->save();
|
$this->application->save();
|
||||||
$this->dispatch('success', 'Domain generated.');
|
$this->dispatch('success', 'Domain generated.');
|
||||||
|
if ($this->application->build_pack === 'dockercompose') {
|
||||||
|
$this->loadComposeFile();
|
||||||
|
}
|
||||||
return $domain;
|
return $domain;
|
||||||
}
|
}
|
||||||
public function updatedApplicationBaseDirectory()
|
public function updatedApplicationBaseDirectory()
|
||||||
@ -257,9 +264,12 @@ public function resetDefaultLabels()
|
|||||||
{
|
{
|
||||||
$this->customLabels = str(implode("|", generateLabelsApplication($this->application)))->replace("|", "\n");
|
$this->customLabels = str(implode("|", generateLabelsApplication($this->application)))->replace("|", "\n");
|
||||||
$this->ports_exposes = $this->application->ports_exposes;
|
$this->ports_exposes = $this->application->ports_exposes;
|
||||||
|
$this->is_container_label_escape_enabled = $this->application->settings->is_container_label_escape_enabled;
|
||||||
$this->application->custom_labels = base64_encode($this->customLabels);
|
$this->application->custom_labels = base64_encode($this->customLabels);
|
||||||
$this->application->save();
|
$this->application->save();
|
||||||
|
if ($this->application->build_pack === 'dockercompose') {
|
||||||
|
$this->loadComposeFile();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public function checkFqdns($showToaster = true)
|
public function checkFqdns($showToaster = true)
|
||||||
@ -304,7 +314,7 @@ public function submit($showToaster = true)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
$this->validate();
|
$this->validate();
|
||||||
if ($this->ports_exposes !== $this->application->ports_exposes) {
|
if ($this->ports_exposes !== $this->application->ports_exposes || $this->is_container_label_escape_enabled !== $this->application->settings->is_container_label_escape_enabled) {
|
||||||
$this->resetDefaultLabels();
|
$this->resetDefaultLabels();
|
||||||
}
|
}
|
||||||
if (data_get($this->application, 'build_pack') === 'dockerimage') {
|
if (data_get($this->application, 'build_pack') === 'dockerimage') {
|
||||||
|
@ -3,7 +3,6 @@
|
|||||||
namespace App\Livewire\Project\Service;
|
namespace App\Livewire\Project\Service;
|
||||||
|
|
||||||
use App\Actions\Docker\GetContainersStatus;
|
use App\Actions\Docker\GetContainersStatus;
|
||||||
use App\Jobs\ContainerStatusJob;
|
|
||||||
use App\Models\Service;
|
use App\Models\Service;
|
||||||
use Livewire\Component;
|
use Livewire\Component;
|
||||||
|
|
||||||
|
@ -12,6 +12,7 @@ class EditCompose extends Component
|
|||||||
protected $rules = [
|
protected $rules = [
|
||||||
'service.docker_compose_raw' => 'required',
|
'service.docker_compose_raw' => 'required',
|
||||||
'service.docker_compose' => 'required',
|
'service.docker_compose' => 'required',
|
||||||
|
'service.is_container_label_escape_enabled' => 'required',
|
||||||
];
|
];
|
||||||
public function mount()
|
public function mount()
|
||||||
{
|
{
|
||||||
@ -23,6 +24,14 @@ public function saveEditedCompose()
|
|||||||
$this->dispatch('info', "Saving new docker compose...");
|
$this->dispatch('info', "Saving new docker compose...");
|
||||||
$this->dispatch('saveCompose', $this->service->docker_compose_raw);
|
$this->dispatch('saveCompose', $this->service->docker_compose_raw);
|
||||||
}
|
}
|
||||||
|
public function instantSave()
|
||||||
|
{
|
||||||
|
$this->validate([
|
||||||
|
'service.is_container_label_escape_enabled' => 'required',
|
||||||
|
]);
|
||||||
|
$this->service->save(['is_container_label_escape_enabled' => $this->service->is_container_label_escape_enabled]);
|
||||||
|
$this->dispatch('success', "Service updated successfully");
|
||||||
|
}
|
||||||
public function render()
|
public function render()
|
||||||
{
|
{
|
||||||
return view('livewire.project.service.edit-compose');
|
return view('livewire.project.service.edit-compose');
|
||||||
|
@ -2,11 +2,14 @@
|
|||||||
|
|
||||||
namespace App\Livewire\Project\Shared\ScheduledTask;
|
namespace App\Livewire\Project\Shared\ScheduledTask;
|
||||||
|
|
||||||
|
use Illuminate\Support\Collection;
|
||||||
use Livewire\Component;
|
use Livewire\Component;
|
||||||
|
|
||||||
class Add extends Component
|
class Add extends Component
|
||||||
{
|
{
|
||||||
public $parameters;
|
public $parameters;
|
||||||
|
public string $type;
|
||||||
|
public Collection $containerNames;
|
||||||
public string $name;
|
public string $name;
|
||||||
public string $command;
|
public string $command;
|
||||||
public string $frequency;
|
public string $frequency;
|
||||||
@ -29,6 +32,9 @@ class Add extends Component
|
|||||||
public function mount()
|
public function mount()
|
||||||
{
|
{
|
||||||
$this->parameters = get_route_parameters();
|
$this->parameters = get_route_parameters();
|
||||||
|
if ($this->containerNames->count() > 0) {
|
||||||
|
$this->container = $this->containerNames->first();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public function submit()
|
public function submit()
|
||||||
@ -40,6 +46,11 @@ public function submit()
|
|||||||
$this->dispatch('error', 'Invalid Cron / Human expression.');
|
$this->dispatch('error', 'Invalid Cron / Human expression.');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
if (empty($this->container) || $this->container == 'null') {
|
||||||
|
if ($this->type == 'service') {
|
||||||
|
$this->container = $this->subServiceName;
|
||||||
|
}
|
||||||
|
}
|
||||||
$this->dispatch('saveScheduledTask', [
|
$this->dispatch('saveScheduledTask', [
|
||||||
'name' => $this->name,
|
'name' => $this->name,
|
||||||
'command' => $this->command,
|
'command' => $this->command,
|
||||||
|
@ -3,14 +3,13 @@
|
|||||||
namespace App\Livewire\Project\Shared\ScheduledTask;
|
namespace App\Livewire\Project\Shared\ScheduledTask;
|
||||||
|
|
||||||
use App\Models\ScheduledTask;
|
use App\Models\ScheduledTask;
|
||||||
|
use Illuminate\Support\Collection;
|
||||||
use Livewire\Component;
|
use Livewire\Component;
|
||||||
use Visus\Cuid2\Cuid2;
|
|
||||||
use Illuminate\Support\Str;
|
|
||||||
|
|
||||||
class All extends Component
|
class All extends Component
|
||||||
{
|
{
|
||||||
public $resource;
|
public $resource;
|
||||||
public string|null $modalId = null;
|
public Collection $containerNames;
|
||||||
public ?string $variables = null;
|
public ?string $variables = null;
|
||||||
public array $parameters;
|
public array $parameters;
|
||||||
protected $listeners = ['refreshTasks', 'saveScheduledTask' => 'submit'];
|
protected $listeners = ['refreshTasks', 'saveScheduledTask' => 'submit'];
|
||||||
@ -18,7 +17,18 @@ class All extends Component
|
|||||||
public function mount()
|
public function mount()
|
||||||
{
|
{
|
||||||
$this->parameters = get_route_parameters();
|
$this->parameters = get_route_parameters();
|
||||||
$this->modalId = new Cuid2(7);
|
if ($this->resource->type() == 'service') {
|
||||||
|
$this->containerNames = $this->resource->applications()->pluck('name');
|
||||||
|
$this->containerNames = $this->containerNames->merge($this->resource->databases()->pluck('name'));
|
||||||
|
} elseif ($this->resource->type() == 'application') {
|
||||||
|
if ($this->resource->build_pack === 'dockercompose') {
|
||||||
|
$parsed = $this->resource->parseCompose();
|
||||||
|
$containers = collect($parsed['services'])->keys();
|
||||||
|
$this->containerNames = $containers;
|
||||||
|
} else {
|
||||||
|
$this->containerNames = collect([]);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
public function refreshTasks()
|
public function refreshTasks()
|
||||||
{
|
{
|
||||||
|
@ -17,6 +17,7 @@ class Show extends Component
|
|||||||
public string $type;
|
public string $type;
|
||||||
|
|
||||||
protected $rules = [
|
protected $rules = [
|
||||||
|
'task.enabled' => 'required|boolean',
|
||||||
'task.name' => 'required|string',
|
'task.name' => 'required|string',
|
||||||
'task.command' => 'required|string',
|
'task.command' => 'required|string',
|
||||||
'task.frequency' => 'required|string',
|
'task.frequency' => 'required|string',
|
||||||
@ -45,9 +46,18 @@ public function mount()
|
|||||||
$this->task = ModelsScheduledTask::where('uuid', request()->route('task_uuid'))->first();
|
$this->task = ModelsScheduledTask::where('uuid', request()->route('task_uuid'))->first();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function instantSave()
|
||||||
|
{
|
||||||
|
$this->validateOnly('task.enabled');
|
||||||
|
$this->task->save(['enabled' => $this->task->enabled]);
|
||||||
|
$this->dispatch('success', 'Scheduled task updated.');
|
||||||
|
$this->dispatch('refreshTasks');
|
||||||
|
}
|
||||||
public function submit()
|
public function submit()
|
||||||
{
|
{
|
||||||
$this->validate();
|
$this->validate();
|
||||||
|
$this->task->name = str($this->task->name)->trim()->value();
|
||||||
|
$this->task->container = str($this->task->container)->trim()->value();
|
||||||
$this->task->save();
|
$this->task->save();
|
||||||
$this->dispatch('success', 'Scheduled task updated.');
|
$this->dispatch('success', 'Scheduled task updated.');
|
||||||
$this->dispatch('refreshTasks');
|
$this->dispatch('refreshTasks');
|
||||||
@ -60,11 +70,9 @@ public function delete()
|
|||||||
|
|
||||||
if ($this->type == 'application') {
|
if ($this->type == 'application') {
|
||||||
return redirect()->route('project.application.configuration', $this->parameters);
|
return redirect()->route('project.application.configuration', $this->parameters);
|
||||||
}
|
} else {
|
||||||
else {
|
|
||||||
return redirect()->route('project.service.configuration', $this->parameters);
|
return redirect()->route('project.service.configuration', $this->parameters);
|
||||||
}
|
}
|
||||||
|
|
||||||
} catch (\Exception $e) {
|
} catch (\Exception $e) {
|
||||||
return handleError($e);
|
return handleError($e);
|
||||||
}
|
}
|
||||||
|
@ -146,9 +146,13 @@ public function gitBranchLocation(): Attribute
|
|||||||
if (!is_null($this->source?->html_url) && !is_null($this->git_repository) && !is_null($this->git_branch)) {
|
if (!is_null($this->source?->html_url) && !is_null($this->git_repository) && !is_null($this->git_branch)) {
|
||||||
return "{$this->source->html_url}/{$this->git_repository}/tree/{$this->git_branch}";
|
return "{$this->source->html_url}/{$this->git_repository}/tree/{$this->git_branch}";
|
||||||
}
|
}
|
||||||
|
// Convert the SSH URL to HTTPS URL
|
||||||
|
if (strpos($this->git_repository, 'git@') === 0) {
|
||||||
|
$git_repository = str_replace(['git@', ':', '.git'], ['', '/', ''], $this->git_repository);
|
||||||
|
return "https://{$git_repository}/tree/{$this->git_branch}";
|
||||||
|
}
|
||||||
return $this->git_repository;
|
return $this->git_repository;
|
||||||
}
|
}
|
||||||
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -159,6 +163,11 @@ public function gitWebhook(): Attribute
|
|||||||
if (!is_null($this->source?->html_url) && !is_null($this->git_repository) && !is_null($this->git_branch)) {
|
if (!is_null($this->source?->html_url) && !is_null($this->git_repository) && !is_null($this->git_branch)) {
|
||||||
return "{$this->source->html_url}/{$this->git_repository}/settings/hooks";
|
return "{$this->source->html_url}/{$this->git_repository}/settings/hooks";
|
||||||
}
|
}
|
||||||
|
// Convert the SSH URL to HTTPS URL
|
||||||
|
if (strpos($this->git_repository, 'git@') === 0) {
|
||||||
|
$git_repository = str_replace(['git@', ':', '.git'], ['', '/', ''], $this->git_repository);
|
||||||
|
return "https://{$git_repository}/settings/hooks";
|
||||||
|
}
|
||||||
return $this->git_repository;
|
return $this->git_repository;
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
@ -171,10 +180,26 @@ public function gitCommits(): Attribute
|
|||||||
if (!is_null($this->source?->html_url) && !is_null($this->git_repository) && !is_null($this->git_branch)) {
|
if (!is_null($this->source?->html_url) && !is_null($this->git_repository) && !is_null($this->git_branch)) {
|
||||||
return "{$this->source->html_url}/{$this->git_repository}/commits/{$this->git_branch}";
|
return "{$this->source->html_url}/{$this->git_repository}/commits/{$this->git_branch}";
|
||||||
}
|
}
|
||||||
|
// Convert the SSH URL to HTTPS URL
|
||||||
|
if (strpos($this->git_repository, 'git@') === 0) {
|
||||||
|
$git_repository = str_replace(['git@', ':', '.git'], ['', '/', ''], $this->git_repository);
|
||||||
|
return "https://{$git_repository}/commits/{$this->git_branch}";
|
||||||
|
}
|
||||||
return $this->git_repository;
|
return $this->git_repository;
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
public function gitCommitLink($link): string
|
||||||
|
{
|
||||||
|
if (!is_null($this->source?->html_url) && !is_null($this->git_repository) && !is_null($this->git_branch)) {
|
||||||
|
return "{$this->source->html_url}/{$this->git_repository}/commit/{$link}";
|
||||||
|
}
|
||||||
|
if (strpos($this->git_repository, 'git@') === 0) {
|
||||||
|
$git_repository = str_replace(['git@', ':', '.git'], ['', '/', ''], $this->git_repository);
|
||||||
|
return "https://{$git_repository}/commit/{$link}";
|
||||||
|
}
|
||||||
|
return $this->git_repository;
|
||||||
|
}
|
||||||
public function dockerfileLocation(): Attribute
|
public function dockerfileLocation(): Attribute
|
||||||
{
|
{
|
||||||
return Attribute::make(
|
return Attribute::make(
|
||||||
|
@ -9,7 +9,8 @@ class ApplicationDeploymentQueue extends Model
|
|||||||
{
|
{
|
||||||
protected $guarded = [];
|
protected $guarded = [];
|
||||||
|
|
||||||
public function setStatus(string $status) {
|
public function setStatus(string $status)
|
||||||
|
{
|
||||||
$this->update([
|
$this->update([
|
||||||
'status' => $status,
|
'status' => $status,
|
||||||
]);
|
]);
|
||||||
@ -21,7 +22,13 @@ public function getOutput($name)
|
|||||||
}
|
}
|
||||||
return collect(json_decode($this->logs))->where('name', $name)->first()?->output ?? null;
|
return collect(json_decode($this->logs))->where('name', $name)->first()?->output ?? null;
|
||||||
}
|
}
|
||||||
|
public function commitMessage()
|
||||||
|
{
|
||||||
|
if (empty($this->commit_message) || is_null($this->commit_message)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return str($this->commit_message)->trim()->limit(50)->value();
|
||||||
|
}
|
||||||
public function addLogEntry(string $message, string $type = 'stdout', bool $hidden = false)
|
public function addLogEntry(string $message, string $type = 'stdout', bool $hidden = false)
|
||||||
{
|
{
|
||||||
if ($type === 'error') {
|
if ($type === 'error') {
|
||||||
|
@ -450,6 +450,7 @@ public function extraFields()
|
|||||||
$data = collect([]);
|
$data = collect([]);
|
||||||
$admin_user = $this->environment_variables()->where('key', 'SERVICE_USER_ADMIN')->first();
|
$admin_user = $this->environment_variables()->where('key', 'SERVICE_USER_ADMIN')->first();
|
||||||
$admin_password = $this->environment_variables()->where('key', 'SERVICE_PASSWORD_ADMIN')->first();
|
$admin_password = $this->environment_variables()->where('key', 'SERVICE_PASSWORD_ADMIN')->first();
|
||||||
|
if ($admin_user) {
|
||||||
$data = $data->merge([
|
$data = $data->merge([
|
||||||
'User' => [
|
'User' => [
|
||||||
'key' => 'SERVICE_USER_ADMIN',
|
'key' => 'SERVICE_USER_ADMIN',
|
||||||
@ -458,6 +459,7 @@ public function extraFields()
|
|||||||
'rules' => 'required',
|
'rules' => 'required',
|
||||||
],
|
],
|
||||||
]);
|
]);
|
||||||
|
}
|
||||||
if ($admin_password) {
|
if ($admin_password) {
|
||||||
$data = $data->merge([
|
$data = $data->merge([
|
||||||
'Password' => [
|
'Password' => [
|
||||||
@ -677,6 +679,17 @@ public function server()
|
|||||||
{
|
{
|
||||||
return $this->belongsTo(Server::class);
|
return $this->belongsTo(Server::class);
|
||||||
}
|
}
|
||||||
|
public function byUuid(string $uuid) {
|
||||||
|
$app = $this->applications()->whereUuid($uuid)->first();
|
||||||
|
if ($app) {
|
||||||
|
return $app;
|
||||||
|
}
|
||||||
|
$db = $this->databases()->whereUuid($uuid)->first();
|
||||||
|
if ($db) {
|
||||||
|
return $db;
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
public function byName(string $name)
|
public function byName(string $name)
|
||||||
{
|
{
|
||||||
$app = $this->applications()->whereName($name)->first();
|
$app = $this->applications()->whereName($name)->first();
|
||||||
|
@ -183,6 +183,7 @@ public function role()
|
|||||||
if (data_get($this, 'pivot')) {
|
if (data_get($this, 'pivot')) {
|
||||||
return $this->pivot->role;
|
return $this->pivot->role;
|
||||||
}
|
}
|
||||||
return auth()->user()->teams->where('id', currentTeam()->id)->first()->pivot->role;
|
$user = auth()->user()->teams->where('id', currentTeam()->id)->first();
|
||||||
|
return data_get($user, 'pivot.role');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -6,7 +6,6 @@
|
|||||||
use App\Models\ApplicationDeploymentQueue;
|
use App\Models\ApplicationDeploymentQueue;
|
||||||
use App\Models\Server;
|
use App\Models\Server;
|
||||||
use App\Models\StandaloneDocker;
|
use App\Models\StandaloneDocker;
|
||||||
use Illuminate\Support\Collection;
|
|
||||||
use Spatie\Url\Url;
|
use Spatie\Url\Url;
|
||||||
|
|
||||||
function queue_application_deployment(Application $application, string $deployment_uuid, int | null $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 | null $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)
|
||||||
|
@ -1174,6 +1174,13 @@ function parseDockerComposeFile(Service|Application $resource, bool $isNew = fal
|
|||||||
]
|
]
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
if ($serviceLabels->count() > 0) {
|
||||||
|
if ($resource->is_container_label_escape_enabled) {
|
||||||
|
$serviceLabels = $serviceLabels->map(function ($value, $key) {
|
||||||
|
return escapeDollarSign($value);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
data_set($service, 'labels', $serviceLabels->toArray());
|
data_set($service, 'labels', $serviceLabels->toArray());
|
||||||
data_forget($service, 'is_database');
|
data_forget($service, 'is_database');
|
||||||
if (!data_get($service, 'restart')) {
|
if (!data_get($service, 'restart')) {
|
||||||
@ -1259,6 +1266,7 @@ function parseDockerComposeFile(Service|Application $resource, bool $isNew = fal
|
|||||||
$servicePorts = collect(data_get($service, 'ports', []));
|
$servicePorts = collect(data_get($service, 'ports', []));
|
||||||
$serviceNetworks = collect(data_get($service, 'networks', []));
|
$serviceNetworks = collect(data_get($service, 'networks', []));
|
||||||
$serviceVariables = collect(data_get($service, 'environment', []));
|
$serviceVariables = collect(data_get($service, 'environment', []));
|
||||||
|
$serviceDependencies = collect(data_get($service, 'depends_on', []));
|
||||||
$serviceLabels = collect(data_get($service, 'labels', []));
|
$serviceLabels = collect(data_get($service, 'labels', []));
|
||||||
$serviceBuildVariables = collect(data_get($service, 'build.args', []));
|
$serviceBuildVariables = collect(data_get($service, 'build.args', []));
|
||||||
$serviceVariables = $serviceVariables->merge($serviceBuildVariables);
|
$serviceVariables = $serviceVariables->merge($serviceBuildVariables);
|
||||||
@ -1275,11 +1283,7 @@ function parseDockerComposeFile(Service|Application $resource, bool $isNew = fal
|
|||||||
$serviceLabels->push("$removedLabelName=$removedLabel");
|
$serviceLabels->push("$removedLabelName=$removedLabel");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if ($serviceLabels->count() > 0) {
|
|
||||||
$serviceLabels = $serviceLabels->map(function ($value, $key) {
|
|
||||||
return escapeDollarSign($value);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
$baseName = generateApplicationContainerName($resource, $pull_request_id);
|
$baseName = generateApplicationContainerName($resource, $pull_request_id);
|
||||||
$containerName = "$serviceName-$baseName";
|
$containerName = "$serviceName-$baseName";
|
||||||
if (count($serviceVolumes) > 0) {
|
if (count($serviceVolumes) > 0) {
|
||||||
@ -1370,6 +1374,13 @@ function parseDockerComposeFile(Service|Application $resource, bool $isNew = fal
|
|||||||
data_set($service, 'volumes', $serviceVolumes->toArray());
|
data_set($service, 'volumes', $serviceVolumes->toArray());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if ($pull_request_id !== 0 && count($serviceDependencies) > 0) {
|
||||||
|
$serviceDependencies = $serviceDependencies->map(function ($dependency) use ($pull_request_id) {
|
||||||
|
return $dependency . "-pr-$pull_request_id";
|
||||||
|
});
|
||||||
|
data_set($service, 'depends_on', $serviceDependencies->toArray());
|
||||||
|
}
|
||||||
|
|
||||||
// Decide if the service is a database
|
// Decide if the service is a database
|
||||||
$isDatabase = isDatabaseImage(data_get_str($service, 'image'));
|
$isDatabase = isDatabaseImage(data_get_str($service, 'image'));
|
||||||
data_set($service, 'is_database', $isDatabase);
|
data_set($service, 'is_database', $isDatabase);
|
||||||
@ -1652,6 +1663,13 @@ function parseDockerComposeFile(Service|Application $resource, bool $isNew = fal
|
|||||||
]
|
]
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
if ($serviceLabels->count() > 0) {
|
||||||
|
if ($resource->settings->is_container_label_escape_enabled) {
|
||||||
|
$serviceLabels = $serviceLabels->map(function ($value, $key) {
|
||||||
|
return escapeDollarSign($value);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
data_set($service, 'labels', $serviceLabels->toArray());
|
data_set($service, 'labels', $serviceLabels->toArray());
|
||||||
data_forget($service, 'is_database');
|
data_forget($service, 'is_database');
|
||||||
if (!data_get($service, 'restart')) {
|
if (!data_get($service, 'restart')) {
|
||||||
|
@ -7,7 +7,7 @@
|
|||||||
|
|
||||||
// The release version of your application
|
// The release version of your application
|
||||||
// Example with dynamic git hash: trim(exec('git --git-dir ' . base_path('.git') . ' log --pretty="%h" -n1 HEAD'))
|
// Example with dynamic git hash: trim(exec('git --git-dir ' . base_path('.git') . ' log --pretty="%h" -n1 HEAD'))
|
||||||
'release' => '4.0.0-beta.277',
|
'release' => '4.0.0-beta.278',
|
||||||
// When left empty or `null` the Laravel environment will be used
|
// When left empty or `null` the Laravel environment will be used
|
||||||
'environment' => config('app.env'),
|
'environment' => config('app.env'),
|
||||||
|
|
||||||
|
@ -1,3 +1,3 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
return '4.0.0-beta.277';
|
return '4.0.0-beta.278';
|
||||||
|
@ -0,0 +1,28 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
use Illuminate\Database\Migrations\Migration;
|
||||||
|
use Illuminate\Database\Schema\Blueprint;
|
||||||
|
use Illuminate\Support\Facades\Schema;
|
||||||
|
|
||||||
|
return new class extends Migration
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Run the migrations.
|
||||||
|
*/
|
||||||
|
public function up(): void
|
||||||
|
{
|
||||||
|
Schema::table('subscriptions', function (Blueprint $table) {
|
||||||
|
$table->longText('stripe_comment')->change();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reverse the migrations.
|
||||||
|
*/
|
||||||
|
public function down(): void
|
||||||
|
{
|
||||||
|
Schema::table('subscriptions', function (Blueprint $table) {
|
||||||
|
$table->string('stripe_comment')->change();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
@ -0,0 +1,28 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
use Illuminate\Database\Migrations\Migration;
|
||||||
|
use Illuminate\Database\Schema\Blueprint;
|
||||||
|
use Illuminate\Support\Facades\Schema;
|
||||||
|
|
||||||
|
return new class extends Migration
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Run the migrations.
|
||||||
|
*/
|
||||||
|
public function up(): void
|
||||||
|
{
|
||||||
|
Schema::table('application_deployment_queues', function (Blueprint $table) {
|
||||||
|
$table->string('commit_message', 50)->nullable();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reverse the migrations.
|
||||||
|
*/
|
||||||
|
public function down(): void
|
||||||
|
{
|
||||||
|
Schema::table('application_deployment_queues', function (Blueprint $table) {
|
||||||
|
$table->dropColumn('commit_message');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
@ -0,0 +1,34 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
use Illuminate\Database\Migrations\Migration;
|
||||||
|
use Illuminate\Database\Schema\Blueprint;
|
||||||
|
use Illuminate\Support\Facades\Schema;
|
||||||
|
|
||||||
|
return new class extends Migration
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Run the migrations.
|
||||||
|
*/
|
||||||
|
public function up(): void
|
||||||
|
{
|
||||||
|
Schema::table('application_settings', function (Blueprint $table) {
|
||||||
|
$table->boolean('is_container_label_escape_enabled')->default(true);
|
||||||
|
});
|
||||||
|
Schema::table('services', function (Blueprint $table) {
|
||||||
|
$table->boolean('is_container_label_escape_enabled')->default(true);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reverse the migrations.
|
||||||
|
*/
|
||||||
|
public function down(): void
|
||||||
|
{
|
||||||
|
Schema::table('application_settings', function (Blueprint $table) {
|
||||||
|
$table->dropColumn('is_container_label_escape_enabled');
|
||||||
|
});
|
||||||
|
Schema::table('services', function (Blueprint $table) {
|
||||||
|
$table->dropColumn('is_container_label_escape_enabled');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
@ -2,15 +2,15 @@ FROM alpine:3.17
|
|||||||
|
|
||||||
ARG TARGETPLATFORM
|
ARG TARGETPLATFORM
|
||||||
# https://download.docker.com/linux/static/stable/
|
# https://download.docker.com/linux/static/stable/
|
||||||
ARG DOCKER_VERSION=24.0.9
|
ARG DOCKER_VERSION=26.1.2
|
||||||
# https://github.com/docker/compose/releases
|
# https://github.com/docker/compose/releases
|
||||||
ARG DOCKER_COMPOSE_VERSION=2.25.0
|
ARG DOCKER_COMPOSE_VERSION=2.27.0
|
||||||
# https://github.com/docker/buildx/releases
|
# https://github.com/docker/buildx/releases
|
||||||
ARG DOCKER_BUILDX_VERSION=0.13.1
|
ARG DOCKER_BUILDX_VERSION=0.14.0
|
||||||
# https://github.com/buildpacks/pack/releases
|
# https://github.com/buildpacks/pack/releases
|
||||||
ARG PACK_VERSION=0.33.2
|
ARG PACK_VERSION=0.33.2
|
||||||
# https://github.com/railwayapp/nixpacks/releases
|
# https://github.com/railwayapp/nixpacks/releases
|
||||||
ARG NIXPACKS_VERSION=1.21.2
|
ARG NIXPACKS_VERSION=1.21.3
|
||||||
|
|
||||||
USER root
|
USER root
|
||||||
WORKDIR /artifacts
|
WORKDIR /artifacts
|
||||||
|
@ -2,7 +2,7 @@ FROM serversideup/php:8.2-fpm-nginx-v2.2.1
|
|||||||
|
|
||||||
ARG TARGETPLATFORM
|
ARG TARGETPLATFORM
|
||||||
# https://github.com/cloudflare/cloudflared/releases
|
# https://github.com/cloudflare/cloudflared/releases
|
||||||
ARG CLOUDFLARED_VERSION=2024.2.1
|
ARG CLOUDFLARED_VERSION=2024.4.1
|
||||||
|
|
||||||
ARG POSTGRES_VERSION=15
|
ARG POSTGRES_VERSION=15
|
||||||
RUN apt-get update
|
RUN apt-get update
|
||||||
|
@ -15,7 +15,7 @@ FROM serversideup/php:8.2-fpm-nginx-v2.2.1
|
|||||||
|
|
||||||
ARG TARGETPLATFORM
|
ARG TARGETPLATFORM
|
||||||
# https://github.com/cloudflare/cloudflared/releases
|
# https://github.com/cloudflare/cloudflared/releases
|
||||||
ARG CLOUDFLARED_VERSION=2024.2.1
|
ARG CLOUDFLARED_VERSION=2024.4.1
|
||||||
ARG POSTGRES_VERSION=15
|
ARG POSTGRES_VERSION=15
|
||||||
|
|
||||||
WORKDIR /var/www/html
|
WORKDIR /var/www/html
|
||||||
|
@ -2,11 +2,11 @@ FROM debian:12-slim
|
|||||||
|
|
||||||
ARG TARGETPLATFORM
|
ARG TARGETPLATFORM
|
||||||
# https://download.docker.com/linux/static/stable/
|
# https://download.docker.com/linux/static/stable/
|
||||||
ARG DOCKER_VERSION=24.0.5
|
ARG DOCKER_VERSION=26.1.2
|
||||||
# https://github.com/docker/compose/releases
|
# https://github.com/docker/compose/releases
|
||||||
ARG DOCKER_COMPOSE_VERSION=2.21.0
|
ARG DOCKER_COMPOSE_VERSION=2.27.0
|
||||||
# https://github.com/docker/buildx/releases
|
# https://github.com/docker/buildx/releases
|
||||||
ARG DOCKER_BUILDX_VERSION=0.11.2
|
ARG DOCKER_BUILDX_VERSION=0.14.0
|
||||||
|
|
||||||
USER root
|
USER root
|
||||||
WORKDIR /root
|
WORKDIR /root
|
||||||
|
1
public/svgs/twenty.svg
Normal file
1
public/svgs/twenty.svg
Normal file
@ -0,0 +1 @@
|
|||||||
|
<svg fill="none" height="136" viewBox="0 0 136 136" width="136" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"><clipPath id="a"><rect height="136" rx="16" width="136"/></clipPath><g clip-path="url(#a)"><path d="m136 .00002289h-136l.00014448 135.99997711h135.99985552zm-108.73 50.64007711c0-7.43 6.03-13.46 13.46-13.46h25.91c.38 0 .73.23.89.58s.09.76-.17 1.05l-5.68 6.17c-.99 1.07-2.38 1.69-3.84 1.69h-17.04c-2.23 0-4.04 1.81-4.04 4.04v10.18c0 1.31-1.06 2.37-2.37 2.37h-4.74c-1.31 0-2.37-1.06-2.37-2.37v-10.25zm80.61 34.72c0 7.43-6.03 13.4599-13.46 13.4599h-11.01c-7.43 0-13.46-6.0299-13.46-13.4599v-19.27c0-1.31.49-2.57 1.38-3.54l6.42-6.97c.27-.29.69-.39 1.07-.25.37.15.62.4999.62.8999v29.0701c0 2.23 1.81 4.04 4.04 4.04h10.88c2.23 0 4.04-1.81 4.04-4.04v-34.59c0-2.23-1.81-4.04-4.04-4.04h-12.65c-1.45 0-2.83.61-3.82 1.67l-37.73 41h22.67c1.31 0 2.37 1.06 2.37 2.37v4.74c0 1.31-1.06 2.3699-2.37 2.3699h-30.55c-2.77 0-5.02-2.2499-5.02-5.0199v-2.5101c0-1.26.47-2.4699 1.33-3.3999l42.3-45.95c2.8-3.04 6.73-4.76 10.86-4.76h12.66c7.43 0 13.46 6.03 13.46 13.46v34.72z" fill="#000"/><g fill="#fff"><path d="m27.27 50.6401c0-7.43 6.03-13.46 13.46-13.46h25.91c.38 0 .73.23.89.58s.09.76-.17 1.05l-5.68 6.17c-.99 1.07-2.38 1.69-3.84 1.69h-17.04c-2.23 0-4.04 1.81-4.04 4.04v10.18c0 1.31-1.06 2.37-2.37 2.37h-4.74c-1.31 0-2.37-1.06-2.37-2.37v-10.25z"/><path d="m107.88 85.3601c0 7.43-6.03 13.4599-13.46 13.4599h-11.01c-7.43 0-13.46-6.0299-13.46-13.4599v-19.27c0-1.31.49-2.57 1.38-3.54l6.42-6.97c.27-.29.69-.39 1.07-.25.37.15.62.4999.62.8999v29.0701c0 2.23 1.81 4.04 4.04 4.04h10.88c2.23 0 4.04-1.81 4.04-4.04v-34.59c0-2.23-1.81-4.04-4.04-4.04h-12.65c-1.45 0-2.83.61-3.82 1.67l-37.73 41h22.67c1.31 0 2.37 1.06 2.37 2.37v4.74c0 1.31-1.06 2.3699-2.37 2.3699h-30.55c-2.77 0-5.02-2.2499-5.02-5.0199v-2.5101c0-1.26.47-2.4699 1.33-3.3999l42.3-45.95c2.8-3.04 6.73-4.76 10.86-4.76h12.66c7.43 0 13.46 6.03 13.46 13.46v34.72z"/></g></g></svg>
|
After Width: | Height: | Size: 1.9 KiB |
@ -11,22 +11,22 @@
|
|||||||
<div class="grid gap-2 lg:grid-cols-1">
|
<div class="grid gap-2 lg:grid-cols-1">
|
||||||
@forelse ($destinations as $destination)
|
@forelse ($destinations as $destination)
|
||||||
@if ($destination->getMorphClass() === 'App\Models\StandaloneDocker')
|
@if ($destination->getMorphClass() === 'App\Models\StandaloneDocker')
|
||||||
<div class="box group">
|
<a class="box group"
|
||||||
<a class="flex flex-col mx-6"
|
|
||||||
href="{{ route('destination.show', ['destination_uuid' => data_get($destination, 'uuid')]) }}">
|
href="{{ route('destination.show', ['destination_uuid' => data_get($destination, 'uuid')]) }}">
|
||||||
|
<div class="flex flex-col mx-6">
|
||||||
<div class="box-title">{{ $destination->name }}</div>
|
<div class="box-title">{{ $destination->name }}</div>
|
||||||
<div class="box-description">server: {{ $destination->server->name }}</div>
|
<div class="box-description">server: {{ $destination->server->name }}</div>
|
||||||
</a>
|
|
||||||
</div>
|
</div>
|
||||||
|
</a>
|
||||||
@endif
|
@endif
|
||||||
@if ($destination->getMorphClass() === 'App\Models\SwarmDocker')
|
@if ($destination->getMorphClass() === 'App\Models\SwarmDocker')
|
||||||
<div class="box group">
|
<a class="box group"
|
||||||
<a class="flex flex-col mx-6"
|
|
||||||
href="{{ route('destination.show', ['destination_uuid' => data_get($destination, 'uuid')]) }}">
|
href="{{ route('destination.show', ['destination_uuid' => data_get($destination, 'uuid')]) }}">
|
||||||
|
<div class="flex flex-col mx-6">
|
||||||
<div class="box-title">{{ $destination->name }}</div>
|
<div class="box-title">{{ $destination->name }}</div>
|
||||||
<div class="box-description">server: {{ $destination->server->name }}</div>
|
<div class="box-description">server: {{ $destination->server->name }}</div>
|
||||||
</a>
|
|
||||||
</div>
|
</div>
|
||||||
|
</a>
|
||||||
@endif
|
@endif
|
||||||
@empty
|
@empty
|
||||||
<div>
|
<div>
|
||||||
|
@ -57,8 +57,7 @@ class="flex flex-col items-center justify-center p-10 mx-2 mt-10 bg-white border
|
|||||||
Localhost is not reachable with the following public key.
|
Localhost is not reachable with the following public key.
|
||||||
<br /> <br />
|
<br /> <br />
|
||||||
Please make sure you have the correct public key in your ~/.ssh/authorized_keys file for
|
Please make sure you have the correct public key in your ~/.ssh/authorized_keys file for
|
||||||
user
|
user or skip the boarding process and add a new private key manually to Coolify and to the
|
||||||
'root' or skip the boarding process and add a new private key manually to Coolify and to the
|
|
||||||
server.
|
server.
|
||||||
<br />
|
<br />
|
||||||
Check this <a target="_blank" class="underline"
|
Check this <a target="_blank" class="underline"
|
||||||
@ -146,9 +145,12 @@ class="flex flex-col items-center justify-center p-10 mx-2 mt-10 bg-white border
|
|||||||
This server is not reachable with the following public key.
|
This server is not reachable with the following public key.
|
||||||
<br /> <br />
|
<br /> <br />
|
||||||
Please make sure you have the correct public key in your ~/.ssh/authorized_keys file for
|
Please make sure you have the correct public key in your ~/.ssh/authorized_keys file for
|
||||||
user
|
user or skip the boarding process and add a new private key manually to Coolify and to the
|
||||||
'root' or skip the boarding process and add a new private key manually to Coolify and to the
|
|
||||||
server.
|
server.
|
||||||
|
<br />
|
||||||
|
Check this <a target="_blank" class="underline"
|
||||||
|
href="https://coolify.io/docs/knowledge-base/server/openssh">documentation</a> for further
|
||||||
|
help.
|
||||||
<x-forms.input readonly id="serverPublicKey"></x-forms.input>
|
<x-forms.input readonly id="serverPublicKey"></x-forms.input>
|
||||||
<x-forms.button class="w-64 box-boarding" wire:target="validateServer"
|
<x-forms.button class="w-64 box-boarding" wire:target="validateServer"
|
||||||
wire:click="validateServer">Check
|
wire:click="validateServer">Check
|
||||||
@ -229,10 +231,6 @@ class="font-bold underline" target="_blank"
|
|||||||
<x-forms.button type="submit">Continue</x-forms.button>
|
<x-forms.button type="submit">Continue</x-forms.button>
|
||||||
</form>
|
</form>
|
||||||
</x-slot:actions>
|
</x-slot:actions>
|
||||||
<x-slot:explanation>
|
|
||||||
<p>Username should be <x-highlighted text="root" /> for now. We are working on to use
|
|
||||||
non-root users.</p>
|
|
||||||
</x-slot:explanation>
|
|
||||||
</x-boarding-step>
|
</x-boarding-step>
|
||||||
@elseif ($currentState === 'validate-server')
|
@elseif ($currentState === 'validate-server')
|
||||||
<x-boarding-step title="Validate & Configure Server">
|
<x-boarding-step title="Validate & Configure Server">
|
||||||
|
@ -15,7 +15,7 @@
|
|||||||
checkPusherInterval = setInterval(() => {
|
checkPusherInterval = setInterval(() => {
|
||||||
if (window.Echo && window.Echo.connector.pusher.connection.state !== 'connected') {
|
if (window.Echo && window.Echo.connector.pusher.connection.state !== 'connected') {
|
||||||
checkNumber++;
|
checkNumber++;
|
||||||
if (checkNumber > 4) {
|
if (checkNumber > 5) {
|
||||||
this.popups.realtime = true;
|
this.popups.realtime = true;
|
||||||
console.error(
|
console.error(
|
||||||
'Coolify could not connect to its real-time service. This will cause unusual problems on the UI if not fixed! Please check the related documentation (https://coolify.io/docs/knowledge-base/cloudflare/tunnels) or get help on Discord (https://coollabs.io/discord).)'
|
'Coolify could not connect to its real-time service. This will cause unusual problems on the UI if not fixed! Please check the related documentation (https://coolify.io/docs/knowledge-base/cloudflare/tunnels) or get help on Discord (https://coollabs.io/discord).)'
|
||||||
@ -23,31 +23,36 @@
|
|||||||
clearInterval(checkPusherInterval);
|
clearInterval(checkPusherInterval);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}, 1000);
|
}, 2000);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}">
|
}">
|
||||||
@auth
|
@auth
|
||||||
<span x-show="popups.realtime === true">
|
<span x-show="popups.realtime === true">
|
||||||
|
@if (!isCloud())
|
||||||
<x-popup>
|
<x-popup>
|
||||||
<x-slot:title>
|
<x-slot:title>
|
||||||
<span class="font-bold text-left text-red-500">WARNING: </span>Realtime Error?!
|
<span class="font-bold text-left text-red-500">WARNING: </span>Realtime Error?!
|
||||||
</x-slot:title>
|
</x-slot:title>
|
||||||
<x-slot:description>
|
<x-slot:description>
|
||||||
<span>Coolify could not connect to its real-time service.<br>This will cause unusual problems on the UI
|
<span>Coolify could not connect to its real-time service.<br>This will cause unusual problems on the
|
||||||
|
UI
|
||||||
if
|
if
|
||||||
not fixed! <br><br>
|
not fixed! <br><br>
|
||||||
Please ensure that you have opened the
|
Please ensure that you have opened the
|
||||||
<a class="underline" href='https://coolify.io/docs/knowledge-base/server/firewall' target='_blank'>required ports</a>,
|
<a class="underline" href='https://coolify.io/docs/knowledge-base/server/firewall'
|
||||||
|
target='_blank'>required ports</a>,
|
||||||
check the
|
check the
|
||||||
related <a class="underline" href='https://coolify.io/docs/knowledge-base/cloudflare/tunnels'
|
related <a class="underline" href='https://coolify.io/docs/knowledge-base/cloudflare/tunnels'
|
||||||
target='_blank'>documentation</a> or get
|
target='_blank'>documentation</a> or get
|
||||||
help on <a class="underline" href='https://coollabs.io/discord' target='_blank'>Discord</a>. </span>
|
help on <a class="underline" href='https://coollabs.io/discord' target='_blank'>Discord</a>.
|
||||||
|
</span>
|
||||||
</x-slot:description>
|
</x-slot:description>
|
||||||
<x-slot:button-text @click="disableRealtime()">
|
<x-slot:button-text @click="disableRealtime()">
|
||||||
Acknowledge & Disable This Popup
|
Acknowledge & Disable This Popup
|
||||||
</x-slot:button-text>
|
</x-slot:button-text>
|
||||||
</x-popup>
|
</x-popup>
|
||||||
|
@endif
|
||||||
</span>
|
</span>
|
||||||
@endauth
|
@endauth
|
||||||
<span x-show="popups.sponsorship">
|
<span x-show="popups.sponsorship">
|
||||||
|
@ -27,18 +27,15 @@ class="w-6 h-6" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
|
|||||||
</form>
|
</form>
|
||||||
@endif
|
@endif
|
||||||
@forelse ($deployments as $deployment)
|
@forelse ($deployments as $deployment)
|
||||||
<a @class([
|
<div @class([
|
||||||
'dark:bg-coolgray-100 p-2 border-l border-dashed transition-colors hover:no-underline box-without-bg-without-border bg-white flex-col',
|
'dark:bg-coolgray-100 p-2 border-l border-dashed transition-colors hover:no-underline box-without-bg-without-border bg-white flex-col cursor-pointer dark:hover:text-neutral-400 dark:hover:bg-coolgray-200',
|
||||||
'dark:hover:bg-coolgray-200' =>
|
'border-warning' =>
|
||||||
data_get($deployment, 'status') === 'queued',
|
|
||||||
'border-warning hover:bg-warning hover:text-black' =>
|
|
||||||
data_get($deployment, 'status') === 'in_progress' ||
|
data_get($deployment, 'status') === 'in_progress' ||
|
||||||
data_get($deployment, 'status') === 'cancelled-by-user',
|
data_get($deployment, 'status') === 'cancelled-by-user',
|
||||||
'border-error dark:hover:bg-error hover:bg-neutral-200' =>
|
'border-error' => data_get($deployment, 'status') === 'failed',
|
||||||
data_get($deployment, 'status') === 'failed',
|
'border-success' => data_get($deployment, 'status') === 'finished',
|
||||||
'border-success dark:hover:bg-success hover:bg-neutral-200' =>
|
])
|
||||||
data_get($deployment, 'status') === 'finished',
|
x-on:click.stop="goto('{{ $current_url . '/' . data_get($deployment, 'deployment_uuid') }}')">
|
||||||
]) href="{{ $current_url . '/' . data_get($deployment, 'deployment_uuid') }}">
|
|
||||||
<div class="flex flex-col justify-start">
|
<div class="flex flex-col justify-start">
|
||||||
<div class="flex gap-1">
|
<div class="flex gap-1">
|
||||||
{{ $deployment->created_at }} UTC
|
{{ $deployment->created_at }} UTC
|
||||||
@ -64,11 +61,27 @@ class="w-6 h-6" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
|
|||||||
@endif
|
@endif
|
||||||
</div>
|
</div>
|
||||||
@else
|
@else
|
||||||
<div class="flex gap-1">
|
<div class="flex items-center gap-1">
|
||||||
|
@if (data_get($deployment, 'rollback') === true)
|
||||||
|
Rollback
|
||||||
|
@else
|
||||||
Manual
|
Manual
|
||||||
|
@endif
|
||||||
|
@if (data_get($deployment, 'commit'))
|
||||||
|
<div class="dark:hover:text-white"
|
||||||
|
x-on:click.stop="goto('{{ $application->gitCommitLink(data_get($deployment, 'commit')) }}')">
|
||||||
|
<div class="text-xs underline">
|
||||||
|
@if ($deployment->commitMessage())
|
||||||
|
({{data_get_str($deployment, 'commit')->limit(7)}} - {{ $deployment->commitMessage() }})
|
||||||
|
@else
|
||||||
|
{{ data_get_str($deployment, 'commit')->limit(7) }}
|
||||||
|
@endif
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@endif
|
@endif
|
||||||
@if (data_get($deployment, 'server_name'))
|
</div>
|
||||||
|
@endif
|
||||||
|
@if (data_get($deployment, 'server_name') && $application->additional_servers->count() > 0)
|
||||||
<div class="flex gap-1">
|
<div class="flex gap-1">
|
||||||
Server: {{ data_get($deployment, 'server_name') }}
|
Server: {{ data_get($deployment, 'server_name') }}
|
||||||
</div>
|
</div>
|
||||||
@ -85,15 +98,19 @@ class="w-6 h-6" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
|
|||||||
<span class="font-bold" x-text="measure_finished_time()">0s</span>
|
<span class="font-bold" x-text="measure_finished_time()">0s</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</a>
|
</div>
|
||||||
@empty
|
@empty
|
||||||
<div class="">No deployments found</div>
|
<div class="">No deployments found</div>
|
||||||
@endforelse
|
@endforelse
|
||||||
|
|
||||||
@if ($deployments_count > 0)
|
@if ($deployments_count > 0)
|
||||||
<script src="https://cdn.jsdelivr.net/npm/dayjs@1/dayjs.min.js"></script>
|
<script src="https://cdn.jsdelivr.net/npm/dayjs@1/dayjs.min.js"></script>
|
||||||
<script src="https://cdn.jsdelivr.net/npm/dayjs@1/plugin/utc.js"></script>
|
<script src="https://cdn.jsdelivr.net/npm/dayjs@1/plugin/utc.js"></script>
|
||||||
<script src="https://cdn.jsdelivr.net/npm/dayjs@1/plugin/relativeTime.js"></script>
|
<script src="https://cdn.jsdelivr.net/npm/dayjs@1/plugin/relativeTime.js"></script>
|
||||||
<script>
|
<script>
|
||||||
|
function goto(url) {
|
||||||
|
window.location.href = url;
|
||||||
|
};
|
||||||
document.addEventListener('alpine:init', () => {
|
document.addEventListener('alpine:init', () => {
|
||||||
let timers = {};
|
let timers = {};
|
||||||
|
|
||||||
|
@ -219,6 +219,11 @@ class="underline" href="https://coolify.io/docs/knowledge-base/docker/registry"
|
|||||||
<x-forms.textarea rows="10" readonly id="application.docker_compose"
|
<x-forms.textarea rows="10" readonly id="application.docker_compose"
|
||||||
label="Docker Compose Content" helper="You need to modify the docker compose file." />
|
label="Docker Compose Content" helper="You need to modify the docker compose file." />
|
||||||
@endif
|
@endif
|
||||||
|
<div class="w-72">
|
||||||
|
<x-forms.checkbox label="Escape special characters in labels?"
|
||||||
|
helper="By default, $ (and other chars) is escaped. So if you write $ in the labels, it will be saved as $$.<br><br>If you want to use env variables inside the labels, turn this off."
|
||||||
|
id="application.settings.is_container_label_escape_enabled" instantSave></x-forms.checkbox>
|
||||||
|
</div>
|
||||||
{{-- <x-forms.textarea rows="10" readonly id="application.docker_compose_pr"
|
{{-- <x-forms.textarea rows="10" readonly id="application.docker_compose_pr"
|
||||||
label="Docker PR Compose Content" helper="You need to modify the docker compose file." /> --}}
|
label="Docker PR Compose Content" helper="You need to modify the docker compose file." /> --}}
|
||||||
@endif
|
@endif
|
||||||
@ -242,8 +247,15 @@ class="underline" href="https://coolify.io/docs/knowledge-base/docker/registry"
|
|||||||
@endif
|
@endif
|
||||||
</div>
|
</div>
|
||||||
<x-forms.textarea label="Container Labels" rows="15" id="customLabels"></x-forms.textarea>
|
<x-forms.textarea label="Container Labels" rows="15" id="customLabels"></x-forms.textarea>
|
||||||
<x-modal-confirmation buttonFullWidth action="resetDefaultLabels" buttonTitle="Reset to Coolify Generated Labels">
|
<div class="w-72">
|
||||||
Are you sure you want to reset the labels to Coolify generated labels? <br>It could break the proxy configuration after you restart the container.
|
<x-forms.checkbox label="Escape special characters in labels?"
|
||||||
|
helper="By default, $ (and other chars) is escaped. So if you write $ in the labels, it will be saved as $$.<br><br>If you want to use env variables inside the labels, turn this off."
|
||||||
|
id="application.settings.is_container_label_escape_enabled" instantSave></x-forms.checkbox>
|
||||||
|
</div>
|
||||||
|
<x-modal-confirmation buttonFullWidth action="resetDefaultLabels"
|
||||||
|
buttonTitle="Reset to Coolify Generated Labels">
|
||||||
|
Are you sure you want to reset the labels to Coolify generated labels? <br>It could break the proxy
|
||||||
|
configuration after you restart the container.
|
||||||
</x-modal-confirmation>
|
</x-modal-confirmation>
|
||||||
|
|
||||||
@endif
|
@endif
|
||||||
|
@ -19,10 +19,12 @@
|
|||||||
<h2 class="pt-4 pb-4">Select a Github App</h2>
|
<h2 class="pt-4 pb-4">Select a Github App</h2>
|
||||||
<div class="flex flex-col gap-2">
|
<div class="flex flex-col gap-2">
|
||||||
@if ($current_step === 'github_apps')
|
@if ($current_step === 'github_apps')
|
||||||
<div class="flex flex-row justify-center gap-2 text-left">
|
<div class="flex flex-col justify-center gap-2 text-left">
|
||||||
@foreach ($github_apps as $ghapp)
|
@foreach ($github_apps as $ghapp)
|
||||||
|
<div class="flex">
|
||||||
<div class="w-full gap-2 py-4 bg-white cursor-pointer group hover:bg-coollabs dark:bg-coolgray-200 box"
|
<div class="w-full gap-2 py-4 bg-white cursor-pointer group hover:bg-coollabs dark:bg-coolgray-200 box"
|
||||||
wire:click.prevent="loadRepositories({{ $ghapp->id }})" wire:key="{{ $ghapp->id }}">
|
wire:click.prevent="loadRepositories({{ $ghapp->id }})"
|
||||||
|
wire:key="{{ $ghapp->id }}">
|
||||||
<div class="flex mr-4">
|
<div class="flex mr-4">
|
||||||
<div class="flex flex-col mx-6">
|
<div class="flex flex-col mx-6">
|
||||||
<div class="box-title">
|
<div class="box-title">
|
||||||
@ -36,6 +38,7 @@
|
|||||||
<div class="flex flex-col items-center justify-center">
|
<div class="flex flex-col items-center justify-center">
|
||||||
<x-loading wire:loading wire:target="loadRepositories({{ $ghapp->id }})" />
|
<x-loading wire:loading wire:target="loadRepositories({{ $ghapp->id }})" />
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
@endforeach
|
@endforeach
|
||||||
</div>
|
</div>
|
||||||
@endif
|
@endif
|
||||||
|
@ -16,6 +16,10 @@
|
|||||||
@click.prevent="activeTab = 'storages';
|
@click.prevent="activeTab = 'storages';
|
||||||
window.location.hash = 'storages'"
|
window.location.hash = 'storages'"
|
||||||
href="#">Storages</a>
|
href="#">Storages</a>
|
||||||
|
<a class="menu-item" :class="activeTab === 'scheduled-tasks' && 'menu-item-active'"
|
||||||
|
@click.prevent="activeTab = 'scheduled-tasks'; window.location.hash = 'scheduled-tasks'"
|
||||||
|
href="#">Scheduled Tasks
|
||||||
|
</a>
|
||||||
<a class="menu-item sm:min-w-fit" :class="activeTab === 'execute-command' && 'menu-item-active'"
|
<a class="menu-item sm:min-w-fit" :class="activeTab === 'execute-command' && 'menu-item-active'"
|
||||||
@click.prevent="activeTab = 'execute-command';
|
@click.prevent="activeTab = 'execute-command';
|
||||||
window.location.hash = 'execute-command'"
|
window.location.hash = 'execute-command'"
|
||||||
@ -103,10 +107,13 @@ class="w-4 h-4 dark:text-warning text-coollabs"
|
|||||||
href="{{ route('project.service.index', [...$parameters, 'stack_service_uuid' => $application->uuid]) }}">
|
href="{{ route('project.service.index', [...$parameters, 'stack_service_uuid' => $application->uuid]) }}">
|
||||||
Settings
|
Settings
|
||||||
</a>
|
</a>
|
||||||
|
@if (str($application->status)->contains('running'))
|
||||||
<x-modal-confirmation action="restartApplication({{ $application->id }})"
|
<x-modal-confirmation action="restartApplication({{ $application->id }})"
|
||||||
isErrorButton buttonTitle="Restart">
|
isErrorButton buttonTitle="Restart">
|
||||||
This application will be unavailable during the restart. <br>Please think again.
|
This application will be unavailable during the restart. <br>Please think
|
||||||
|
again.
|
||||||
</x-modal-confirmation>
|
</x-modal-confirmation>
|
||||||
|
@endif
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -169,6 +176,9 @@ class="w-4 h-4 dark:text-warning text-coollabs"
|
|||||||
<livewire:project.service.storage wire:key="database-{{ $database->id }}" :resource="$database" />
|
<livewire:project.service.storage wire:key="database-{{ $database->id }}" :resource="$database" />
|
||||||
@endforeach
|
@endforeach
|
||||||
</div>
|
</div>
|
||||||
|
<div x-cloak x-show="activeTab === 'scheduled-tasks'">
|
||||||
|
<livewire:project.shared.scheduled-task.all :resource="$service" />
|
||||||
|
</div>
|
||||||
<div x-cloak x-show="activeTab === 'webhooks'">
|
<div x-cloak x-show="activeTab === 'webhooks'">
|
||||||
<livewire:project.shared.webhooks :resource="$service" />
|
<livewire:project.shared.webhooks :resource="$service" />
|
||||||
</div>
|
</div>
|
||||||
|
@ -3,6 +3,11 @@
|
|||||||
prevent
|
prevent
|
||||||
name collision. <br>To see the actual volume names, check the Deployable Compose file, or go to Storage
|
name collision. <br>To see the actual volume names, check the Deployable Compose file, or go to Storage
|
||||||
menu.</div>
|
menu.</div>
|
||||||
|
<div class="pb-2 w-72">
|
||||||
|
<x-forms.checkbox label="Escape special characters in labels?"
|
||||||
|
helper="By default, $ (and other chars) is escaped. So if you write $ in the labels, it will be saved as $$.<br><br>If you want to use env variables inside the labels, turn this off."
|
||||||
|
id="service.is_container_label_escape_enabled" instantSave></x-forms.checkbox>
|
||||||
|
</div>
|
||||||
<div x-cloak x-show="raw" class="font-mono">
|
<div x-cloak x-show="raw" class="font-mono">
|
||||||
<x-forms.textarea allowTab rows="20" id="service.docker_compose_raw">
|
<x-forms.textarea allowTab rows="20" id="service.docker_compose_raw">
|
||||||
</x-forms.textarea>
|
</x-forms.textarea>
|
||||||
|
@ -10,14 +10,6 @@ class="{{ request()->routeIs('project.service.configuration') ? 'menu-item-activ
|
|||||||
<a class="menu-item" :class="activeTab === 'general' && 'menu-item-active'"
|
<a class="menu-item" :class="activeTab === 'general' && 'menu-item-active'"
|
||||||
@click.prevent="activeTab = 'general'; window.location.hash = 'general'; if(window.location.search) window.location.search = ''"
|
@click.prevent="activeTab = 'general'; window.location.hash = 'general'; if(window.location.search) window.location.search = ''"
|
||||||
href="#">General</a>
|
href="#">General</a>
|
||||||
<a class="menu-item" :class="activeTab === 'storages' && 'menu-item-active'"
|
|
||||||
@click.prevent="activeTab = 'storages'; window.location.hash = 'storages'; if(window.location.search) window.location.search = ''"
|
|
||||||
href="#">Storages
|
|
||||||
</a>
|
|
||||||
<a class="menu-item" :class="activeTab === 'scheduled-tasks' && 'menu-item-active'"
|
|
||||||
@click.prevent="activeTab = 'scheduled-tasks'; window.location.hash = 'scheduled-tasks'"
|
|
||||||
href="#">Scheduled Tasks
|
|
||||||
</a>
|
|
||||||
@if (str($serviceDatabase?->databaseType())->contains('mysql') ||
|
@if (str($serviceDatabase?->databaseType())->contains('mysql') ||
|
||||||
str($serviceDatabase?->databaseType())->contains('postgres') ||
|
str($serviceDatabase?->databaseType())->contains('postgres') ||
|
||||||
str($serviceDatabase?->databaseType())->contains('mariadb'))
|
str($serviceDatabase?->databaseType())->contains('mariadb'))
|
||||||
@ -30,28 +22,12 @@ class="{{ request()->routeIs('project.service.configuration') ? 'menu-item-activ
|
|||||||
<div x-cloak x-show="activeTab === 'general'" class="h-full">
|
<div x-cloak x-show="activeTab === 'general'" class="h-full">
|
||||||
<livewire:project.service.service-application-view :application="$serviceApplication" />
|
<livewire:project.service.service-application-view :application="$serviceApplication" />
|
||||||
</div>
|
</div>
|
||||||
<div x-cloak x-show="activeTab === 'storages'">
|
|
||||||
<div class="flex items-center gap-2">
|
|
||||||
<h2>Storages</h2>
|
|
||||||
</div>
|
|
||||||
<div class="pb-4">Persistent storage to preserve data between deployments.</div>
|
|
||||||
<span class="dark:text-warning">Please modify storage layout in your Docker Compose file.</span>
|
|
||||||
<livewire:project.service.storage wire:key="application-{{ $serviceApplication->id }}"
|
|
||||||
:resource="$serviceApplication" />
|
|
||||||
</div>
|
|
||||||
@endisset
|
@endisset
|
||||||
@isset($serviceDatabase)
|
@isset($serviceDatabase)
|
||||||
<div x-cloak x-show="activeTab === 'general'" class="h-full">
|
<div x-cloak x-show="activeTab === 'general'" class="h-full">
|
||||||
<livewire:project.service.database :database="$serviceDatabase" />
|
<livewire:project.service.database :database="$serviceDatabase" />
|
||||||
</div>
|
</div>
|
||||||
<div x-cloak x-show="activeTab === 'storages'">
|
|
||||||
<div class="flex items-center gap-2">
|
|
||||||
<h2>Storages</h2>
|
|
||||||
</div>
|
|
||||||
<div class="pb-4">Persistent storage to preserve data between deployments.</div>
|
|
||||||
<span class="dark:text-warning">Please modify storage layout in your Docker Compose file.</span>
|
|
||||||
<livewire:project.service.storage wire:key="application-{{ $serviceDatabase->id }}" :resource="$serviceDatabase" />
|
|
||||||
</div>
|
|
||||||
<div x-cloak x-show="activeTab === 'backups'">
|
<div x-cloak x-show="activeTab === 'backups'">
|
||||||
<div class="flex gap-2 ">
|
<div class="flex gap-2 ">
|
||||||
<h2 class="pb-4">Scheduled Backups</h2>
|
<h2 class="pb-4">Scheduled Backups</h2>
|
||||||
@ -62,9 +38,6 @@ class="{{ request()->routeIs('project.service.configuration') ? 'menu-item-activ
|
|||||||
<livewire:project.database.scheduled-backups :database="$serviceDatabase" />
|
<livewire:project.database.scheduled-backups :database="$serviceDatabase" />
|
||||||
</div>
|
</div>
|
||||||
@endisset
|
@endisset
|
||||||
<div x-cloak x-show="activeTab === 'scheduled-tasks'">
|
|
||||||
<livewire:project.shared.scheduled-task.all :resource="$service" />
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -2,9 +2,26 @@
|
|||||||
<x-forms.input autofocus placeholder="Run cron" id="name" label="Name" />
|
<x-forms.input autofocus placeholder="Run cron" id="name" label="Name" />
|
||||||
<x-forms.input placeholder="php artisan schedule:run" id="command" label="Command" />
|
<x-forms.input placeholder="php artisan schedule:run" id="command" label="Command" />
|
||||||
<x-forms.input placeholder="0 0 * * * or daily" id="frequency" label="Frequency" />
|
<x-forms.input placeholder="0 0 * * * or daily" id="frequency" label="Frequency" />
|
||||||
|
@if ($type === 'application')
|
||||||
|
@if ($containerNames->count() > 1)
|
||||||
|
<x-forms.select id="container" label="Container name">
|
||||||
|
@foreach ($containerNames as $containerName)
|
||||||
|
<option value="{{ $containerName }}">{{ $containerName }}</option>
|
||||||
|
@endforeach
|
||||||
|
</x-forms.select>
|
||||||
|
@else
|
||||||
<x-forms.input placeholder="php" id="container"
|
<x-forms.input placeholder="php" id="container"
|
||||||
helper="You can leave it empty if your resource only have one container." label="Container name" />
|
helper="You can leave it empty if your resource only have one container." label="Container name" />
|
||||||
<x-forms.button @click="slideOverOpen=false" type="submit">
|
@endif
|
||||||
|
@elseif ($type === 'service')
|
||||||
|
<x-forms.select id="container" label="Container name">
|
||||||
|
@foreach ($containerNames as $containerName)
|
||||||
|
<option value="{{ $containerName }}">{{ $containerName }}</option>
|
||||||
|
@endforeach
|
||||||
|
</x-forms.select>
|
||||||
|
@endif
|
||||||
|
|
||||||
|
<x-forms.button @click="modalOpen=false" type="submit">
|
||||||
Save
|
Save
|
||||||
</x-forms.button>
|
</x-forms.button>
|
||||||
</form>
|
</form>
|
||||||
|
@ -1,23 +1,48 @@
|
|||||||
<div>
|
<div>
|
||||||
<div class="flex gap-2">
|
<div class="flex gap-2">
|
||||||
<h2>Scheduled Tasks</h2>
|
<h2>Scheduled Tasks</h2>
|
||||||
<x-modal-input buttonTitle="+ Add" title="New Scheduled Task">
|
<x-modal-input buttonTitle="+ Add" title="New Scheduled Task" :closeOutside=false>
|
||||||
<livewire:project.shared.scheduled-task.add />
|
@if ($resource->type() == 'application')
|
||||||
|
<livewire:project.shared.scheduled-task.add :type="$resource->type()" :containerNames="$containerNames"/>
|
||||||
|
@elseif ($resource->type() == 'service')
|
||||||
|
<livewire:project.shared.scheduled-task.add :type="$resource->type()" :containerNames="$containerNames"/>
|
||||||
|
@endif
|
||||||
</x-modal-input>
|
</x-modal-input>
|
||||||
</div>
|
</div>
|
||||||
<div class="flex flex-wrap gap-2 pt-4">
|
<div class="flex flex-wrap gap-2 pt-4">
|
||||||
@forelse($resource->scheduled_tasks as $task)
|
@forelse($resource->scheduled_tasks as $task)
|
||||||
<a class="flex flex-col box"
|
@if ($resource->type() == 'application')
|
||||||
@if ($resource->type() == 'application') href="{{ route('project.application.scheduled-tasks', [...$parameters, 'task_uuid' => $task->uuid]) }}">
|
<a class="box"
|
||||||
@elseif ($resource->type() == 'service')
|
href="{{ route('project.application.scheduled-tasks', [...$parameters, 'task_uuid' => $task->uuid]) }}">
|
||||||
href="{{ route('project.service.scheduled-tasks', [...$parameters, 'task_uuid' => $task->uuid]) }}"> @endif
|
<span class="flex flex-col">
|
||||||
<div><span class="font-bold dark:text-warning">{{ $task->name }}<span>
|
<span class="text-lg font-bold">{{ $task->name }}
|
||||||
</div>
|
@if ($task->container)
|
||||||
<div>Frequency: {{ $task->frequency }}</div>
|
<span class="text-xs font-normal">({{ $task->container }})</span>
|
||||||
<div>Last run: {{ data_get($task->latest_log, 'status', 'No runs yet') }}</div>
|
@endif
|
||||||
|
</span>
|
||||||
|
|
||||||
|
<span>Frequency: {{ $task->frequency }}</span>
|
||||||
|
<span>Last run: {{ data_get($task->latest_log, 'status', 'No runs yet') }}
|
||||||
|
</span>
|
||||||
|
</span>
|
||||||
</a>
|
</a>
|
||||||
@empty
|
@elseif ($resource->type() == 'service')
|
||||||
|
<a class="box"
|
||||||
|
href="{{ route('project.service.scheduled-tasks', [...$parameters, 'task_uuid' => $task->uuid]) }}">
|
||||||
|
<span class="flex flex-col">
|
||||||
|
<span class="text-lg font-bold">{{ $task->name }}
|
||||||
|
@if ($task->container)
|
||||||
|
<span class="text-xs font-normal">({{ $task->container }})</span>
|
||||||
|
@endif
|
||||||
|
</span>
|
||||||
|
<span>Frequency: {{ $task->frequency }}</span>
|
||||||
|
<span>Last run: {{ data_get($task->latest_log, 'status', 'No runs yet') }}
|
||||||
|
</span>
|
||||||
|
</span>
|
||||||
|
</a>
|
||||||
|
@endif
|
||||||
|
@empty
|
||||||
<div>No scheduled tasks configured.</div>
|
<div>No scheduled tasks configured.</div>
|
||||||
@endforelse
|
@endforelse
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -12,7 +12,7 @@
|
|||||||
</div>
|
</div>
|
||||||
@endif
|
@endif
|
||||||
<a wire:click="selectTask({{ data_get($execution, 'id') }})" @class([
|
<a wire:click="selectTask({{ data_get($execution, 'id') }})" @class([
|
||||||
'flex flex-col border-l border-dashed transition-colors box-without-bg bg-coolgray-100 hover:bg-coolgray-100',
|
'flex flex-col border-l transition-colors box-without-bg bg-coolgray-100 hover:bg-coolgray-200 cursor-pointer',
|
||||||
'bg-coolgray-200 dark:text-white hover:bg-coolgray-200' =>
|
'bg-coolgray-200 dark:text-white hover:bg-coolgray-200' =>
|
||||||
data_get($execution, 'id') == $selectedKey,
|
data_get($execution, 'id') == $selectedKey,
|
||||||
'border-green-500' => data_get($execution, 'status') === 'success',
|
'border-green-500' => data_get($execution, 'status') === 'success',
|
||||||
|
@ -1,13 +1,13 @@
|
|||||||
<div>
|
<div>
|
||||||
<h1>Scheduled Task</h1>
|
|
||||||
@if ($type === 'application')
|
@if ($type === 'application')
|
||||||
|
<h1>Scheduled Task</h1>
|
||||||
<livewire:project.application.heading :application="$resource" />
|
<livewire:project.application.heading :application="$resource" />
|
||||||
@elseif ($type === 'service')
|
@elseif ($type === 'service')
|
||||||
<livewire:project.service.navbar :service="$resource" :parameters="$parameters" />
|
<livewire:project.service.navbar :service="$resource" :parameters="$parameters" />
|
||||||
@endif
|
@endif
|
||||||
|
|
||||||
<form wire:submit="submit" class="w-full">
|
<form wire:submit="submit" class="w-full">
|
||||||
<div class="flex flex-col gap-2 pb-4">
|
<div class="flex flex-col gap-2 pb-2">
|
||||||
<div class="flex items-end gap-2 pt-4">
|
<div class="flex items-end gap-2 pt-4">
|
||||||
<h2>Scheduled Task</h2>
|
<h2>Scheduled Task</h2>
|
||||||
<x-forms.button type="submit">
|
<x-forms.button type="submit">
|
||||||
@ -17,13 +17,23 @@
|
|||||||
You will delete scheduled task <span class="font-bold dark:text-warning">{{ $task->name }}</span>.
|
You will delete scheduled task <span class="font-bold dark:text-warning">{{ $task->name }}</span>.
|
||||||
</x-modal-confirmation>
|
</x-modal-confirmation>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="w-48">
|
||||||
|
<x-forms.checkbox instantSave id="task.enabled" label="Enabled" />
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="flex w-full gap-2">
|
<div class="flex w-full gap-2">
|
||||||
<x-forms.input placeholder="Name" id="task.name" label="Name" required />
|
<x-forms.input placeholder="Name" id="task.name" label="Name" required />
|
||||||
<x-forms.input placeholder="php artisan schedule:run" id="task.command" label="Command" required />
|
<x-forms.input placeholder="php artisan schedule:run" id="task.command" label="Command" required />
|
||||||
<x-forms.input placeholder="0 0 * * * or daily" id="task.frequency" label="Frequency" required />
|
<x-forms.input placeholder="0 0 * * * or daily" id="task.frequency" label="Frequency" required />
|
||||||
<x-forms.input placeholder="php" helper="You can leave it empty if your resource only have one container."
|
@if ($type === 'application')
|
||||||
id="task.container" label="Container name" />
|
<x-forms.input placeholder="php"
|
||||||
|
helper="You can leave it empty if your resource only have one container." id="task.container"
|
||||||
|
label="Container name" />
|
||||||
|
@elseif ($type === 'service')
|
||||||
|
<x-forms.input placeholder="php"
|
||||||
|
helper="You can leave it empty if your resource only have one service in your stack. Otherwise use the stack name, without the random generated id. So if you have a mysql service in your stack, use mysql."
|
||||||
|
id="task.container" label="Service name" />
|
||||||
|
@endif
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
|
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
<div class="flex gap-2">
|
<div class="flex gap-2">
|
||||||
<h3 class="dark:text-white">File: {{ str_replace('|', '.', $fileName) }}</h3>
|
<h3 class="dark:text-white">File: {{ str_replace('|', '.', $fileName) }}</h3>
|
||||||
<div class="flex gap-2">
|
<div class="flex gap-2">
|
||||||
<x-modal-input buttonTitle="+ Add" title="Edit Configuration">
|
<x-modal-input buttonTitle="Edit" title="Edit Configuration">
|
||||||
<livewire:server.proxy.new-dynamic-configuration :server_id="$server_id" :fileName="$fileName" :value="$value"
|
<livewire:server.proxy.new-dynamic-configuration :server_id="$server_id" :fileName="$fileName" :value="$value"
|
||||||
:newFile="$newFile" wire:key="{{ $fileName }}" />
|
:newFile="$newFile" wire:key="{{ $fileName }}" />
|
||||||
</x-modal-input>
|
</x-modal-input>
|
||||||
|
@ -1,12 +1,11 @@
|
|||||||
<div>
|
<div class="w-full">
|
||||||
<div class="subtitle">S3 Storage used to save backups / files.</div>
|
|
||||||
<form class="flex flex-col gap-2" wire:submit='submit'>
|
<form class="flex flex-col gap-2" wire:submit='submit'>
|
||||||
<div class="flex gap-2">
|
<div class="flex gap-2">
|
||||||
<x-forms.input required label="Name" id="name" />
|
<x-forms.input required label="Name" id="name" />
|
||||||
<x-forms.input label="Description" id="description" />
|
<x-forms.input label="Description" id="description" />
|
||||||
</div>
|
</div>
|
||||||
<div class="flex gap-2">
|
|
||||||
<x-forms.input required type="url" label="Endpoint" id="endpoint" />
|
<x-forms.input required type="url" label="Endpoint" id="endpoint" />
|
||||||
|
<div class="flex gap-2">
|
||||||
<x-forms.input required label="Bucket" id="bucket" />
|
<x-forms.input required label="Bucket" id="bucket" />
|
||||||
<x-forms.input required label="Region" id="region" />
|
<x-forms.input required label="Region" id="region" />
|
||||||
</div>
|
</div>
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
<div>
|
<div>
|
||||||
<div class="flex items-start gap-2">
|
<div class="flex items-start gap-2">
|
||||||
<h1>S3 Storages</h1>
|
<h1>S3 Storages</h1>
|
||||||
<x-modal-input buttonTitle="+ Add" title="New S3 Storage">
|
<x-modal-input buttonTitle="+ Add" title="New S3 Storage" :closeOutside="false">
|
||||||
<livewire:storage.create />
|
<livewire:storage.create />
|
||||||
</x-modal-input>
|
</x-modal-input>
|
||||||
</div>
|
</div>
|
||||||
|
@ -62,7 +62,7 @@ class="flex-none w-8 h-8 mr-3 text-warning"
|
|||||||
></path></svg
|
></path></svg
|
||||||
>
|
>
|
||||||
|
|
||||||
<div class="flex flex-col text-sm text-white">
|
<div class="flex flex-col text-sm dark:text-white">
|
||||||
<div>
|
<div>
|
||||||
You need to bring your own servers from any cloud provider (such as <a
|
You need to bring your own servers from any cloud provider (such as <a
|
||||||
class="underline"
|
class="underline"
|
||||||
|
@ -7,7 +7,7 @@ set -e # Exit immediately if a command exits with a non-zero status
|
|||||||
set -o pipefail # Cause a pipeline to return the status of the last command that exited with a non-zero status
|
set -o pipefail # Cause a pipeline to return the status of the last command that exited with a non-zero status
|
||||||
|
|
||||||
VERSION="1.3.1"
|
VERSION="1.3.1"
|
||||||
DOCKER_VERSION="24.0"
|
DOCKER_VERSION="26.0"
|
||||||
|
|
||||||
CDN="https://cdn.coollabs.io/coolify"
|
CDN="https://cdn.coollabs.io/coolify"
|
||||||
OS_TYPE=$(grep -w "ID" /etc/os-release | cut -d "=" -f 2 | tr -d '"')
|
OS_TYPE=$(grep -w "ID" /etc/os-release | cut -d "=" -f 2 | tr -d '"')
|
||||||
|
71
templates/compose/twenty.yaml
Normal file
71
templates/compose/twenty.yaml
Normal file
@ -0,0 +1,71 @@
|
|||||||
|
# documentation: https://docs.twenty.com
|
||||||
|
# slogan: Twenty is a CRM designed to fit your unique business needs.
|
||||||
|
# tags: crm, self-hosted, dashboard
|
||||||
|
# logo: svgs/twenty.svg
|
||||||
|
# port: 3000
|
||||||
|
|
||||||
|
services:
|
||||||
|
twenty:
|
||||||
|
image: 'twentycrm/twenty:latest'
|
||||||
|
environment:
|
||||||
|
- SERVICE_FQDN_TRIGGER_3000
|
||||||
|
- SERVER_URL=$SERVICE_FQDN_TWENTY
|
||||||
|
- FRONT_BASE_URL=$SERVICE_FQDN_TWENTY
|
||||||
|
- ENABLE_DB_MIGRATIONS=true
|
||||||
|
- SIGN_IN_PREFILLED=false
|
||||||
|
|
||||||
|
- STORAGE_TYPE=${STORAGE_TYPE:-local}
|
||||||
|
- STORAGE_S3_REGION=$STORAGE_S3_REGION
|
||||||
|
- STORAGE_S3_NAME=$STORAGE_S3_NAME
|
||||||
|
- STORAGE_S3_ENDPOINT=$STORAGE_S3_ENDPOINT
|
||||||
|
|
||||||
|
- ACCESS_TOKEN_SECRET=$SERVICE_BASE64_32_ACCESS
|
||||||
|
- LOGIN_TOKEN_SECRET=$SERVICE_BASE64_32_LOGIN
|
||||||
|
- REFRESH_TOKEN_SECRET=$SERVICE_BASE64_32_REFRESH
|
||||||
|
- FILE_TOKEN_SECRET=$SERVICE_BASE64_32_FILE
|
||||||
|
- POSTGRES_ADMIN_PASSWORD=$SERVICE_PASSWORD_POSTGRES
|
||||||
|
- PG_DATABASE_URL=postgres://postgres:$SERVICE_PASSWORD_POSTGRES@postgres:5432/default
|
||||||
|
|
||||||
|
- EMAIL_FROM_ADDRESS=$EMAIL_FROM_ADDRESS
|
||||||
|
- EMAIL_FROM_NAME=$EMAIL_FROM_NAME
|
||||||
|
- EMAIL_SYSTEM_ADDRESS=$EMAIL_SYSTEM_ADDRESS
|
||||||
|
- EMAIL_DRIVER=${EMAIL_DRIVER:-logger}
|
||||||
|
- EMAIL_SMTP_HOST=$EMAIL_SMTP_HOST
|
||||||
|
- EMAIL_SMTP_PORT=$EMAIL_SMTP_PORT
|
||||||
|
- EMAIL_SMTP_USER=$EMAIL_SMTP_USER
|
||||||
|
- EMAIL_SMTP_PASSWORD=$EMAIL_SMTP_PASSWORD
|
||||||
|
|
||||||
|
- TELEMETRY_ENABLED=${TELEMETRY_ENABLED:-false}
|
||||||
|
- CACHE_STORAGE_TYPE=${CACHE_STORAGE_TYPE:-redis}
|
||||||
|
- REDIS_HOST=redis
|
||||||
|
- REDIS_PORT=6379
|
||||||
|
depends_on:
|
||||||
|
postgres:
|
||||||
|
condition: service_healthy
|
||||||
|
healthcheck:
|
||||||
|
test: ["CMD", "curl", "-f", "http://localhost:3000/healthz"]
|
||||||
|
interval: 2s
|
||||||
|
timeout: 10s
|
||||||
|
retries: 15
|
||||||
|
postgres:
|
||||||
|
image: "twentycrm/twenty-postgres:latest"
|
||||||
|
environment:
|
||||||
|
- POSTGRES_USER=postgres
|
||||||
|
- POSTGRES_PASSWORD=$SERVICE_PASSWORD_POSTGRES
|
||||||
|
- POSTGRES_DB=default
|
||||||
|
volumes:
|
||||||
|
- pg-data:/bitnami/postgresql
|
||||||
|
healthcheck:
|
||||||
|
test: ["CMD-SHELL", "pg_isready -U $${POSTGRES_USER} -d $${POSTGRES_DB}"]
|
||||||
|
interval: 5s
|
||||||
|
timeout: 20s
|
||||||
|
retries: 10
|
||||||
|
redis:
|
||||||
|
image: "redis:latest"
|
||||||
|
volumes:
|
||||||
|
- "redis-data:/data"
|
||||||
|
healthcheck:
|
||||||
|
test: ["CMD", "redis-cli", "ping"]
|
||||||
|
interval: 5s
|
||||||
|
timeout: 20s
|
||||||
|
retries: 10
|
@ -948,6 +948,19 @@
|
|||||||
"minversion": "0.0.0",
|
"minversion": "0.0.0",
|
||||||
"port": "3000"
|
"port": "3000"
|
||||||
},
|
},
|
||||||
|
"twenty": {
|
||||||
|
"documentation": "https:\/\/docs.twenty.com",
|
||||||
|
"slogan": "Twenty is a CRM designed to fit your unique business needs.",
|
||||||
|
"compose": "c2VydmljZXM6CiAgdHdlbnR5OgogICAgaW1hZ2U6ICd0d2VudHljcm0vdHdlbnR5OmxhdGVzdCcKICAgIGVudmlyb25tZW50OgogICAgICAtIFNFUlZJQ0VfRlFETl9UUklHR0VSXzMwMDAKICAgICAgLSBTRVJWRVJfVVJMPSRTRVJWSUNFX0ZRRE5fVFdFTlRZCiAgICAgIC0gRlJPTlRfQkFTRV9VUkw9JFNFUlZJQ0VfRlFETl9UV0VOVFkKICAgICAgLSBFTkFCTEVfREJfTUlHUkFUSU9OUz10cnVlCiAgICAgIC0gU0lHTl9JTl9QUkVGSUxMRUQ9ZmFsc2UKICAgICAgLSAnU1RPUkFHRV9UWVBFPSR7U1RPUkFHRV9UWVBFOi1sb2NhbH0nCiAgICAgIC0gU1RPUkFHRV9TM19SRUdJT049JFNUT1JBR0VfUzNfUkVHSU9OCiAgICAgIC0gU1RPUkFHRV9TM19OQU1FPSRTVE9SQUdFX1MzX05BTUUKICAgICAgLSBTVE9SQUdFX1MzX0VORFBPSU5UPSRTVE9SQUdFX1MzX0VORFBPSU5UCiAgICAgIC0gQUNDRVNTX1RPS0VOX1NFQ1JFVD0kU0VSVklDRV9CQVNFNjRfMzJfQUNDRVNTCiAgICAgIC0gTE9HSU5fVE9LRU5fU0VDUkVUPSRTRVJWSUNFX0JBU0U2NF8zMl9MT0dJTgogICAgICAtIFJFRlJFU0hfVE9LRU5fU0VDUkVUPSRTRVJWSUNFX0JBU0U2NF8zMl9SRUZSRVNICiAgICAgIC0gRklMRV9UT0tFTl9TRUNSRVQ9JFNFUlZJQ0VfQkFTRTY0XzMyX0ZJTEUKICAgICAgLSBQT1NUR1JFU19BRE1JTl9QQVNTV09SRD0kU0VSVklDRV9QQVNTV09SRF9QT1NUR1JFUwogICAgICAtICdQR19EQVRBQkFTRV9VUkw9cG9zdGdyZXM6Ly9wb3N0Z3JlczokU0VSVklDRV9QQVNTV09SRF9QT1NUR1JFU0Bwb3N0Z3Jlczo1NDMyL2RlZmF1bHQnCiAgICAgIC0gRU1BSUxfRlJPTV9BRERSRVNTPSRFTUFJTF9GUk9NX0FERFJFU1MKICAgICAgLSBFTUFJTF9GUk9NX05BTUU9JEVNQUlMX0ZST01fTkFNRQogICAgICAtIEVNQUlMX1NZU1RFTV9BRERSRVNTPSRFTUFJTF9TWVNURU1fQUREUkVTUwogICAgICAtICdFTUFJTF9EUklWRVI9JHtFTUFJTF9EUklWRVI6LWxvZ2dlcn0nCiAgICAgIC0gRU1BSUxfU01UUF9IT1NUPSRFTUFJTF9TTVRQX0hPU1QKICAgICAgLSBFTUFJTF9TTVRQX1BPUlQ9JEVNQUlMX1NNVFBfUE9SVAogICAgICAtIEVNQUlMX1NNVFBfVVNFUj0kRU1BSUxfU01UUF9VU0VSCiAgICAgIC0gRU1BSUxfU01UUF9QQVNTV09SRD0kRU1BSUxfU01UUF9QQVNTV09SRAogICAgICAtICdURUxFTUVUUllfRU5BQkxFRD0ke1RFTEVNRVRSWV9FTkFCTEVEOi1mYWxzZX0nCiAgICAgIC0gJ0NBQ0hFX1NUT1JBR0VfVFlQRT0ke0NBQ0hFX1NUT1JBR0VfVFlQRTotcmVkaXN9JwogICAgICAtIFJFRElTX0hPU1Q9cmVkaXMKICAgICAgLSBSRURJU19QT1JUPTYzNzkKICAgIGRlcGVuZHNfb246CiAgICAgIHBvc3RncmVzOgogICAgICAgIGNvbmRpdGlvbjogc2VydmljZV9oZWFsdGh5CiAgICBoZWFsdGhjaGVjazoKICAgICAgdGVzdDoKICAgICAgICAtIENNRAogICAgICAgIC0gY3VybAogICAgICAgIC0gJy1mJwogICAgICAgIC0gJ2h0dHA6Ly9sb2NhbGhvc3Q6MzAwMC9oZWFsdGh6JwogICAgICBpbnRlcnZhbDogMnMKICAgICAgdGltZW91dDogMTBzCiAgICAgIHJldHJpZXM6IDE1CiAgcG9zdGdyZXM6CiAgICBpbWFnZTogJ3R3ZW50eWNybS90d2VudHktcG9zdGdyZXM6bGF0ZXN0JwogICAgZW52aXJvbm1lbnQ6CiAgICAgIC0gUE9TVEdSRVNfVVNFUj1wb3N0Z3JlcwogICAgICAtIFBPU1RHUkVTX1BBU1NXT1JEPSRTRVJWSUNFX1BBU1NXT1JEX1BPU1RHUkVTCiAgICAgIC0gUE9TVEdSRVNfREI9ZGVmYXVsdAogICAgdm9sdW1lczoKICAgICAgLSAncGctZGF0YTovYml0bmFtaS9wb3N0Z3Jlc3FsJwogICAgaGVhbHRoY2hlY2s6CiAgICAgIHRlc3Q6CiAgICAgICAgLSBDTUQtU0hFTEwKICAgICAgICAtICdwZ19pc3JlYWR5IC1VICQke1BPU1RHUkVTX1VTRVJ9IC1kICQke1BPU1RHUkVTX0RCfScKICAgICAgaW50ZXJ2YWw6IDVzCiAgICAgIHRpbWVvdXQ6IDIwcwogICAgICByZXRyaWVzOiAxMAogIHJlZGlzOgogICAgaW1hZ2U6ICdyZWRpczpsYXRlc3QnCiAgICB2b2x1bWVzOgogICAgICAtICdyZWRpcy1kYXRhOi9kYXRhJwogICAgaGVhbHRoY2hlY2s6CiAgICAgIHRlc3Q6CiAgICAgICAgLSBDTUQKICAgICAgICAtIHJlZGlzLWNsaQogICAgICAgIC0gcGluZwogICAgICBpbnRlcnZhbDogNXMKICAgICAgdGltZW91dDogMjBzCiAgICAgIHJldHJpZXM6IDEwCg==",
|
||||||
|
"tags": [
|
||||||
|
"crm",
|
||||||
|
"self-hosted",
|
||||||
|
"dashboard"
|
||||||
|
],
|
||||||
|
"logo": "svgs\/twenty.svg",
|
||||||
|
"minversion": "0.0.0",
|
||||||
|
"port": "3000"
|
||||||
|
},
|
||||||
"umami": {
|
"umami": {
|
||||||
"documentation": "https:\/\/umami.is",
|
"documentation": "https:\/\/umami.is",
|
||||||
"slogan": "Umami is web analytics platform which provides insights into visitor behavior without compromising user privacy.",
|
"slogan": "Umami is web analytics platform which provides insights into visitor behavior without compromising user privacy.",
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"coolify": {
|
"coolify": {
|
||||||
"v4": {
|
"v4": {
|
||||||
"version": "4.0.0-beta.277"
|
"version": "4.0.0-beta.278"
|
||||||
},
|
},
|
||||||
"sentinel": {
|
"sentinel": {
|
||||||
"version": "0.0.4"
|
"version": "0.0.4"
|
||||||
|
Loading…
Reference in New Issue
Block a user