mirror of
https://github.com/ershisan99/coolify.git
synced 2026-01-02 12:33:52 +00:00
Compare commits
47 Commits
v4.0.0-bet
...
v4.0.0-bet
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
0f99ee787c | ||
|
|
95777e978e | ||
|
|
fb0b9dbfed | ||
|
|
9617000daa | ||
|
|
1818404172 | ||
|
|
d9a966fd98 | ||
|
|
763ce5fc14 | ||
|
|
df021760a7 | ||
|
|
fb2598f2e4 | ||
|
|
7af07b2718 | ||
|
|
23a94c9378 | ||
|
|
ed34fc9645 | ||
|
|
cafd9e0ab2 | ||
|
|
e882477e21 | ||
|
|
db0e3cfcc4 | ||
|
|
b3c4429028 | ||
|
|
87ab4bd71e | ||
|
|
61e1fdede9 | ||
|
|
b189919f97 | ||
|
|
8f5b084931 | ||
|
|
eb96a5ae7b | ||
|
|
f0fb9dbb94 | ||
|
|
cb2d4b4a0a | ||
|
|
3aace2d4f9 | ||
|
|
8c2ed75653 | ||
|
|
8c8aafbc65 | ||
|
|
a2c39fd07e | ||
|
|
9698a051d9 | ||
|
|
51423394ba | ||
|
|
8e5e36dd5b | ||
|
|
c1dd05dcd8 | ||
|
|
dd1ce6ee6c | ||
|
|
fe4c6d396c | ||
|
|
8db54ec069 | ||
|
|
3abc720926 | ||
|
|
64cc0b63f1 | ||
|
|
c78068466b | ||
|
|
88e407756d | ||
|
|
1538116e6e | ||
|
|
aba47d58a4 | ||
|
|
e7f184dd82 | ||
|
|
2ad8d7812b | ||
|
|
8212bb99a1 | ||
|
|
5fc382d09d | ||
|
|
78a80c46da | ||
|
|
c365d132af | ||
|
|
4dc3db3845 |
@@ -9,7 +9,7 @@ use Illuminate\Support\Facades\Mail;
|
|||||||
|
|
||||||
set_transanctional_email_settings();
|
set_transanctional_email_settings();
|
||||||
|
|
||||||
$users = User::whereEmail('andras.bacsai@gmail.com');
|
$users = User::whereEmail('test@example.com');
|
||||||
foreach ($users as $user) {
|
foreach ($users as $user) {
|
||||||
Mail::send([], [], function ($message) use ($user) {
|
Mail::send([], [], function ($message) use ($user) {
|
||||||
$message
|
$message
|
||||||
|
|||||||
@@ -3,7 +3,6 @@
|
|||||||
namespace App\Actions\Application;
|
namespace App\Actions\Application;
|
||||||
|
|
||||||
use App\Models\Application;
|
use App\Models\Application;
|
||||||
use App\Notifications\Application\StatusChanged;
|
|
||||||
use Lorisleiva\Actions\Concerns\AsAction;
|
use Lorisleiva\Actions\Concerns\AsAction;
|
||||||
|
|
||||||
class StopApplication
|
class StopApplication
|
||||||
@@ -12,7 +11,7 @@ class StopApplication
|
|||||||
public function handle(Application $application)
|
public function handle(Application $application)
|
||||||
{
|
{
|
||||||
$server = $application->destination->server;
|
$server = $application->destination->server;
|
||||||
$containers = getCurrentApplicationContainerStatus($server, $application->id);
|
$containers = getCurrentApplicationContainerStatus($server, $application->id, 0);
|
||||||
if ($containers->count() > 0) {
|
if ($containers->count() > 0) {
|
||||||
foreach ($containers as $container) {
|
foreach ($containers as $container) {
|
||||||
$containerName = data_get($container, 'Names');
|
$containerName = data_get($container, 'Names');
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
namespace App\Actions\Database;
|
namespace App\Actions\Database;
|
||||||
|
|
||||||
|
use App\Models\ServiceDatabase;
|
||||||
use App\Models\StandaloneMariadb;
|
use App\Models\StandaloneMariadb;
|
||||||
use App\Models\StandaloneMongodb;
|
use App\Models\StandaloneMongodb;
|
||||||
use App\Models\StandaloneMysql;
|
use App\Models\StandaloneMysql;
|
||||||
@@ -14,21 +15,53 @@ class StartDatabaseProxy
|
|||||||
{
|
{
|
||||||
use AsAction;
|
use AsAction;
|
||||||
|
|
||||||
public function handle(StandaloneRedis|StandalonePostgresql|StandaloneMongodb|StandaloneMysql|StandaloneMariadb $database)
|
public function handle(StandaloneRedis|StandalonePostgresql|StandaloneMongodb|StandaloneMysql|StandaloneMariadb|ServiceDatabase $database)
|
||||||
{
|
{
|
||||||
$internalPort = null;
|
$internalPort = null;
|
||||||
if ($database->getMorphClass() === 'App\Models\StandaloneRedis') {
|
$type = $database->getMorphClass();
|
||||||
|
$network = data_get($database, 'destination.network');
|
||||||
|
$server = data_get($database, 'destination.server');
|
||||||
|
$containerName = data_get($database, 'uuid');
|
||||||
|
$proxyContainerName = "{$database->uuid}-proxy";
|
||||||
|
if ($database->getMorphClass() === 'App\Models\ServiceDatabase') {
|
||||||
|
$databaseType = $database->databaseType();
|
||||||
|
$network = data_get($database, 'service.destination.network');
|
||||||
|
$server = data_get($database, 'service.destination.server');
|
||||||
|
$proxyContainerName = "{$database->service->uuid}-proxy";
|
||||||
|
switch ($databaseType) {
|
||||||
|
case 'standalone-mariadb':
|
||||||
|
$type = 'App\Models\StandaloneMariadb';
|
||||||
|
$containerName = "mariadb-{$database->service->uuid}";
|
||||||
|
break;
|
||||||
|
case 'standalone-mongodb':
|
||||||
|
$type = 'App\Models\StandaloneMongodb';
|
||||||
|
$containerName = "mongodb-{$database->service->uuid}";
|
||||||
|
break;
|
||||||
|
case 'standalone-mysql':
|
||||||
|
$type = 'App\Models\StandaloneMysql';
|
||||||
|
$containerName = "mysql-{$database->service->uuid}";
|
||||||
|
break;
|
||||||
|
case 'standalone-postgresql':
|
||||||
|
$type = 'App\Models\StandalonePostgresql';
|
||||||
|
$containerName = "postgresql-{$database->service->uuid}";
|
||||||
|
break;
|
||||||
|
case 'standalone-redis':
|
||||||
|
$type = 'App\Models\StandaloneRedis';
|
||||||
|
$containerName = "redis-{$database->service->uuid}";
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if ($type === 'App\Models\StandaloneRedis') {
|
||||||
$internalPort = 6379;
|
$internalPort = 6379;
|
||||||
} else if ($database->getMorphClass() === 'App\Models\StandalonePostgresql') {
|
} else if ($type === 'App\Models\StandalonePostgresql') {
|
||||||
$internalPort = 5432;
|
$internalPort = 5432;
|
||||||
} else if ($database->getMorphClass() === 'App\Models\StandaloneMongodb') {
|
} else if ($type === 'App\Models\StandaloneMongodb') {
|
||||||
$internalPort = 27017;
|
$internalPort = 27017;
|
||||||
} else if ($database->getMorphClass() === 'App\Models\StandaloneMysql') {
|
} else if ($type === 'App\Models\StandaloneMysql') {
|
||||||
$internalPort = 3306;
|
$internalPort = 3306;
|
||||||
} else if ($database->getMorphClass() === 'App\Models\StandaloneMariadb') {
|
} else if ($type === 'App\Models\StandaloneMariadb') {
|
||||||
$internalPort = 3306;
|
$internalPort = 3306;
|
||||||
}
|
}
|
||||||
$containerName = "{$database->uuid}-proxy";
|
|
||||||
$configuration_dir = database_proxy_dir($database->uuid);
|
$configuration_dir = database_proxy_dir($database->uuid);
|
||||||
$nginxconf = <<<EOF
|
$nginxconf = <<<EOF
|
||||||
user nginx;
|
user nginx;
|
||||||
@@ -42,7 +75,7 @@ class StartDatabaseProxy
|
|||||||
stream {
|
stream {
|
||||||
server {
|
server {
|
||||||
listen $database->public_port;
|
listen $database->public_port;
|
||||||
proxy_pass $database->uuid:$internalPort;
|
proxy_pass $containerName:$internalPort;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
EOF;
|
EOF;
|
||||||
@@ -54,19 +87,19 @@ class StartDatabaseProxy
|
|||||||
$docker_compose = [
|
$docker_compose = [
|
||||||
'version' => '3.8',
|
'version' => '3.8',
|
||||||
'services' => [
|
'services' => [
|
||||||
$containerName => [
|
$proxyContainerName => [
|
||||||
'build' => [
|
'build' => [
|
||||||
'context' => $configuration_dir,
|
'context' => $configuration_dir,
|
||||||
'dockerfile' => 'Dockerfile',
|
'dockerfile' => 'Dockerfile',
|
||||||
],
|
],
|
||||||
'image' => "nginx:stable-alpine",
|
'image' => "nginx:stable-alpine",
|
||||||
'container_name' => $containerName,
|
'container_name' => $proxyContainerName,
|
||||||
'restart' => RESTART_MODE,
|
'restart' => RESTART_MODE,
|
||||||
'ports' => [
|
'ports' => [
|
||||||
"$database->public_port:$database->public_port",
|
"$database->public_port:$database->public_port",
|
||||||
],
|
],
|
||||||
'networks' => [
|
'networks' => [
|
||||||
$database->destination->network,
|
$network,
|
||||||
],
|
],
|
||||||
'healthcheck' => [
|
'healthcheck' => [
|
||||||
'test' => [
|
'test' => [
|
||||||
@@ -81,9 +114,9 @@ class StartDatabaseProxy
|
|||||||
]
|
]
|
||||||
],
|
],
|
||||||
'networks' => [
|
'networks' => [
|
||||||
$database->destination->network => [
|
$network => [
|
||||||
'external' => true,
|
'external' => true,
|
||||||
'name' => $database->destination->network,
|
'name' => $network,
|
||||||
'attachable' => true,
|
'attachable' => true,
|
||||||
]
|
]
|
||||||
]
|
]
|
||||||
@@ -97,6 +130,6 @@ class StartDatabaseProxy
|
|||||||
"echo '{$nginxconf_base64}' | base64 -d > $configuration_dir/nginx.conf",
|
"echo '{$nginxconf_base64}' | base64 -d > $configuration_dir/nginx.conf",
|
||||||
"echo '{$dockercompose_base64}' | base64 -d > $configuration_dir/docker-compose.yaml",
|
"echo '{$dockercompose_base64}' | base64 -d > $configuration_dir/docker-compose.yaml",
|
||||||
"docker compose --project-directory {$configuration_dir} up --build -d",
|
"docker compose --project-directory {$configuration_dir} up --build -d",
|
||||||
], $database->destination->server);
|
], $server);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -56,7 +56,7 @@ class StartMariadb
|
|||||||
'memswap_limit' => $this->database->limits_memory_swap,
|
'memswap_limit' => $this->database->limits_memory_swap,
|
||||||
'mem_swappiness' => $this->database->limits_memory_swappiness,
|
'mem_swappiness' => $this->database->limits_memory_swappiness,
|
||||||
'mem_reservation' => $this->database->limits_memory_reservation,
|
'mem_reservation' => $this->database->limits_memory_reservation,
|
||||||
'cpus' => $this->database->limits_cpus,
|
'cpus' => (int) $this->database->limits_cpus,
|
||||||
'cpuset' => $this->database->limits_cpuset,
|
'cpuset' => $this->database->limits_cpuset,
|
||||||
'cpu_shares' => $this->database->limits_cpu_shares,
|
'cpu_shares' => $this->database->limits_cpu_shares,
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -63,7 +63,7 @@ class StartMongodb
|
|||||||
'memswap_limit' => $this->database->limits_memory_swap,
|
'memswap_limit' => $this->database->limits_memory_swap,
|
||||||
'mem_swappiness' => $this->database->limits_memory_swappiness,
|
'mem_swappiness' => $this->database->limits_memory_swappiness,
|
||||||
'mem_reservation' => $this->database->limits_memory_reservation,
|
'mem_reservation' => $this->database->limits_memory_reservation,
|
||||||
'cpus' => $this->database->limits_cpus,
|
'cpus' => (int) $this->database->limits_cpus,
|
||||||
'cpuset' => $this->database->limits_cpuset,
|
'cpuset' => $this->database->limits_cpuset,
|
||||||
'cpu_shares' => $this->database->limits_cpu_shares,
|
'cpu_shares' => $this->database->limits_cpu_shares,
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -56,7 +56,7 @@ class StartMysql
|
|||||||
'memswap_limit' => $this->database->limits_memory_swap,
|
'memswap_limit' => $this->database->limits_memory_swap,
|
||||||
'mem_swappiness' => $this->database->limits_memory_swappiness,
|
'mem_swappiness' => $this->database->limits_memory_swappiness,
|
||||||
'mem_reservation' => $this->database->limits_memory_reservation,
|
'mem_reservation' => $this->database->limits_memory_reservation,
|
||||||
'cpus' => $this->database->limits_cpus,
|
'cpus' => (int) $this->database->limits_cpus,
|
||||||
'cpuset' => $this->database->limits_cpuset,
|
'cpuset' => $this->database->limits_cpuset,
|
||||||
'cpu_shares' => $this->database->limits_cpu_shares,
|
'cpu_shares' => $this->database->limits_cpu_shares,
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -32,6 +32,8 @@ class StartPostgresql
|
|||||||
$volume_names = $this->generate_local_persistent_volumes_only_volume_names();
|
$volume_names = $this->generate_local_persistent_volumes_only_volume_names();
|
||||||
$environment_variables = $this->generate_environment_variables();
|
$environment_variables = $this->generate_environment_variables();
|
||||||
$this->generate_init_scripts();
|
$this->generate_init_scripts();
|
||||||
|
$this->add_custom_conf();
|
||||||
|
|
||||||
$docker_compose = [
|
$docker_compose = [
|
||||||
'version' => '3.8',
|
'version' => '3.8',
|
||||||
'services' => [
|
'services' => [
|
||||||
@@ -64,7 +66,7 @@ class StartPostgresql
|
|||||||
'memswap_limit' => $this->database->limits_memory_swap,
|
'memswap_limit' => $this->database->limits_memory_swap,
|
||||||
'mem_swappiness' => $this->database->limits_memory_swappiness,
|
'mem_swappiness' => $this->database->limits_memory_swappiness,
|
||||||
'mem_reservation' => $this->database->limits_memory_reservation,
|
'mem_reservation' => $this->database->limits_memory_reservation,
|
||||||
'cpus' => $this->database->limits_cpus,
|
'cpus' => (int) $this->database->limits_cpus,
|
||||||
'cpuset' => $this->database->limits_cpuset,
|
'cpuset' => $this->database->limits_cpuset,
|
||||||
'cpu_shares' => $this->database->limits_cpu_shares,
|
'cpu_shares' => $this->database->limits_cpu_shares,
|
||||||
]
|
]
|
||||||
@@ -96,6 +98,19 @@ class StartPostgresql
|
|||||||
];
|
];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if (!is_null($this->database->postgres_conf)) {
|
||||||
|
$docker_compose['services'][$container_name]['volumes'][] = [
|
||||||
|
'type' => 'bind',
|
||||||
|
'source' => $this->configuration_dir . '/custom-postgres.conf',
|
||||||
|
'target' => '/etc/postgresql/postgresql.conf',
|
||||||
|
'read_only' => true,
|
||||||
|
];
|
||||||
|
$docker_compose['services'][$container_name]['command'] = [
|
||||||
|
'postgres',
|
||||||
|
'-c',
|
||||||
|
'config_file=/etc/postgresql/postgresql.conf',
|
||||||
|
];
|
||||||
|
}
|
||||||
$docker_compose = Yaml::dump($docker_compose, 10);
|
$docker_compose = Yaml::dump($docker_compose, 10);
|
||||||
$docker_compose_base64 = base64_encode($docker_compose);
|
$docker_compose_base64 = base64_encode($docker_compose);
|
||||||
$this->commands[] = "echo '{$docker_compose_base64}' | base64 -d > $this->configuration_dir/docker-compose.yml";
|
$this->commands[] = "echo '{$docker_compose_base64}' | base64 -d > $this->configuration_dir/docker-compose.yml";
|
||||||
@@ -171,4 +186,14 @@ class StartPostgresql
|
|||||||
$this->init_scripts[] = "$this->configuration_dir/docker-entrypoint-initdb.d/{$filename}";
|
$this->init_scripts[] = "$this->configuration_dir/docker-entrypoint-initdb.d/{$filename}";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
private function add_custom_conf()
|
||||||
|
{
|
||||||
|
if (is_null($this->database->postgres_conf)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
$filename = 'custom-postgres.conf';
|
||||||
|
$content = $this->database->postgres_conf;
|
||||||
|
$content_base64 = base64_encode($content);
|
||||||
|
$this->commands[] = "echo '{$content_base64}' | base64 -d > $this->configuration_dir/{$filename}";
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -65,7 +65,7 @@ class StartRedis
|
|||||||
'memswap_limit' => $this->database->limits_memory_swap,
|
'memswap_limit' => $this->database->limits_memory_swap,
|
||||||
'mem_swappiness' => $this->database->limits_memory_swappiness,
|
'mem_swappiness' => $this->database->limits_memory_swappiness,
|
||||||
'mem_reservation' => $this->database->limits_memory_reservation,
|
'mem_reservation' => $this->database->limits_memory_reservation,
|
||||||
'cpus' => $this->database->limits_cpus,
|
'cpus' => (int) $this->database->limits_cpus,
|
||||||
'cpuset' => $this->database->limits_cpuset,
|
'cpuset' => $this->database->limits_cpuset,
|
||||||
'cpu_shares' => $this->database->limits_cpu_shares,
|
'cpu_shares' => $this->database->limits_cpu_shares,
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
namespace App\Actions\Database;
|
namespace App\Actions\Database;
|
||||||
|
|
||||||
|
use App\Models\ServiceDatabase;
|
||||||
use App\Models\StandaloneMariadb;
|
use App\Models\StandaloneMariadb;
|
||||||
use App\Models\StandaloneMongodb;
|
use App\Models\StandaloneMongodb;
|
||||||
use App\Models\StandaloneMysql;
|
use App\Models\StandaloneMysql;
|
||||||
@@ -13,9 +14,13 @@ class StopDatabaseProxy
|
|||||||
{
|
{
|
||||||
use AsAction;
|
use AsAction;
|
||||||
|
|
||||||
public function handle(StandaloneRedis|StandalonePostgresql|StandaloneMongodb|StandaloneMysql|StandaloneMariadb $database)
|
public function handle(StandaloneRedis|StandalonePostgresql|StandaloneMongodb|StandaloneMysql|StandaloneMariadb|ServiceDatabase $database)
|
||||||
{
|
{
|
||||||
instant_remote_process(["docker rm -f {$database->uuid}-proxy"], $database->destination->server);
|
$server = data_get($database, 'destination.server');
|
||||||
|
if ($database->getMorphClass() === 'App\Models\ServiceDatabase') {
|
||||||
|
$server = data_get($database, 'service.server');
|
||||||
|
}
|
||||||
|
instant_remote_process(["docker rm -f {$database->uuid}-proxy"], $server);
|
||||||
$database->is_public = false;
|
$database->is_public = false;
|
||||||
$database->save();
|
$database->save();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -105,6 +105,7 @@ class CloneProject extends Component
|
|||||||
$newDatabase = $database->replicate()->fill([
|
$newDatabase = $database->replicate()->fill([
|
||||||
'uuid' => $uuid,
|
'uuid' => $uuid,
|
||||||
'status' => 'exited',
|
'status' => 'exited',
|
||||||
|
'started_at' => null,
|
||||||
'environment_id' => $newEnvironment->id,
|
'environment_id' => $newEnvironment->id,
|
||||||
'destination_id' => $this->selectedServer,
|
'destination_id' => $this->selectedServer,
|
||||||
]);
|
]);
|
||||||
|
|||||||
@@ -13,7 +13,8 @@ class General extends Component
|
|||||||
protected $listeners = ['refresh'];
|
protected $listeners = ['refresh'];
|
||||||
|
|
||||||
public StandaloneMariadb $database;
|
public StandaloneMariadb $database;
|
||||||
public string $db_url;
|
public ?string $db_url = null;
|
||||||
|
public ?string $db_url_public = null;
|
||||||
|
|
||||||
protected $rules = [
|
protected $rules = [
|
||||||
'database.name' => 'required',
|
'database.name' => 'required',
|
||||||
@@ -41,9 +42,20 @@ class General extends Component
|
|||||||
'database.is_public' => 'Is Public',
|
'database.is_public' => 'Is Public',
|
||||||
'database.public_port' => 'Public Port',
|
'database.public_port' => 'Public Port',
|
||||||
];
|
];
|
||||||
|
|
||||||
|
public function mount()
|
||||||
|
{
|
||||||
|
$this->db_url = $this->database->getDbUrl(true);
|
||||||
|
if ($this->database->is_public) {
|
||||||
|
$this->db_url_public = $this->database->getDbUrl();
|
||||||
|
}
|
||||||
|
}
|
||||||
public function submit()
|
public function submit()
|
||||||
{
|
{
|
||||||
try {
|
try {
|
||||||
|
if (str($this->database->public_port)->isEmpty()) {
|
||||||
|
$this->database->public_port = null;
|
||||||
|
}
|
||||||
$this->validate();
|
$this->validate();
|
||||||
$this->database->save();
|
$this->database->save();
|
||||||
$this->emit('success', 'Database updated successfully.');
|
$this->emit('success', 'Database updated successfully.');
|
||||||
@@ -66,12 +78,13 @@ class General extends Component
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
StartDatabaseProxy::run($this->database);
|
StartDatabaseProxy::run($this->database);
|
||||||
|
$this->db_url_public = $this->database->getDbUrl();
|
||||||
$this->emit('success', 'Database is now publicly accessible.');
|
$this->emit('success', 'Database is now publicly accessible.');
|
||||||
} else {
|
} else {
|
||||||
StopDatabaseProxy::run($this->database);
|
StopDatabaseProxy::run($this->database);
|
||||||
|
$this->db_url_public = null;
|
||||||
$this->emit('success', 'Database is no longer publicly accessible.');
|
$this->emit('success', 'Database is no longer publicly accessible.');
|
||||||
}
|
}
|
||||||
$this->db_url = $this->database->getDbUrl();
|
|
||||||
$this->database->save();
|
$this->database->save();
|
||||||
} catch (\Throwable $e) {
|
} catch (\Throwable $e) {
|
||||||
$this->database->is_public = !$this->database->is_public;
|
$this->database->is_public = !$this->database->is_public;
|
||||||
@@ -83,11 +96,6 @@ class General extends Component
|
|||||||
$this->database->refresh();
|
$this->database->refresh();
|
||||||
}
|
}
|
||||||
|
|
||||||
public function mount()
|
|
||||||
{
|
|
||||||
$this->db_url = $this->database->getDbUrl();
|
|
||||||
}
|
|
||||||
|
|
||||||
public function render()
|
public function render()
|
||||||
{
|
{
|
||||||
return view('livewire.project.database.mariadb.general');
|
return view('livewire.project.database.mariadb.general');
|
||||||
|
|||||||
@@ -13,7 +13,8 @@ class General extends Component
|
|||||||
protected $listeners = ['refresh'];
|
protected $listeners = ['refresh'];
|
||||||
|
|
||||||
public StandaloneMongodb $database;
|
public StandaloneMongodb $database;
|
||||||
public string $db_url;
|
public ?string $db_url = null;
|
||||||
|
public ?string $db_url_public = null;
|
||||||
|
|
||||||
protected $rules = [
|
protected $rules = [
|
||||||
'database.name' => 'required',
|
'database.name' => 'required',
|
||||||
@@ -39,13 +40,25 @@ class General extends Component
|
|||||||
'database.is_public' => 'Is Public',
|
'database.is_public' => 'Is Public',
|
||||||
'database.public_port' => 'Public Port',
|
'database.public_port' => 'Public Port',
|
||||||
];
|
];
|
||||||
|
|
||||||
|
public function mount()
|
||||||
|
{
|
||||||
|
$this->db_url = $this->database->getDbUrl(true);
|
||||||
|
if ($this->database->is_public) {
|
||||||
|
$this->db_url_public = $this->database->getDbUrl();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public function submit()
|
public function submit()
|
||||||
{
|
{
|
||||||
try {
|
try {
|
||||||
$this->validate();
|
if (str($this->database->public_port)->isEmpty()) {
|
||||||
if ($this->database->mongo_conf === "") {
|
$this->database->public_port = null;
|
||||||
|
}
|
||||||
|
if (str($this->database->mongo_conf)->isEmpty()) {
|
||||||
$this->database->mongo_conf = null;
|
$this->database->mongo_conf = null;
|
||||||
}
|
}
|
||||||
|
$this->validate();
|
||||||
$this->database->save();
|
$this->database->save();
|
||||||
$this->emit('success', 'Database updated successfully.');
|
$this->emit('success', 'Database updated successfully.');
|
||||||
} catch (Exception $e) {
|
} catch (Exception $e) {
|
||||||
@@ -67,12 +80,13 @@ class General extends Component
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
StartDatabaseProxy::run($this->database);
|
StartDatabaseProxy::run($this->database);
|
||||||
|
$this->db_url_public = $this->database->getDbUrl();
|
||||||
$this->emit('success', 'Database is now publicly accessible.');
|
$this->emit('success', 'Database is now publicly accessible.');
|
||||||
} else {
|
} else {
|
||||||
StopDatabaseProxy::run($this->database);
|
StopDatabaseProxy::run($this->database);
|
||||||
|
$this->db_url_public = null;
|
||||||
$this->emit('success', 'Database is no longer publicly accessible.');
|
$this->emit('success', 'Database is no longer publicly accessible.');
|
||||||
}
|
}
|
||||||
$this->db_url = $this->database->getDbUrl();
|
|
||||||
$this->database->save();
|
$this->database->save();
|
||||||
} catch (\Throwable $e) {
|
} catch (\Throwable $e) {
|
||||||
$this->database->is_public = !$this->database->is_public;
|
$this->database->is_public = !$this->database->is_public;
|
||||||
@@ -84,11 +98,6 @@ class General extends Component
|
|||||||
$this->database->refresh();
|
$this->database->refresh();
|
||||||
}
|
}
|
||||||
|
|
||||||
public function mount()
|
|
||||||
{
|
|
||||||
$this->db_url = $this->database->getDbUrl();
|
|
||||||
}
|
|
||||||
|
|
||||||
public function render()
|
public function render()
|
||||||
{
|
{
|
||||||
return view('livewire.project.database.mongodb.general');
|
return view('livewire.project.database.mongodb.general');
|
||||||
|
|||||||
@@ -13,7 +13,8 @@ class General extends Component
|
|||||||
protected $listeners = ['refresh'];
|
protected $listeners = ['refresh'];
|
||||||
|
|
||||||
public StandaloneMysql $database;
|
public StandaloneMysql $database;
|
||||||
public string $db_url;
|
public ?string $db_url = null;
|
||||||
|
public ?string $db_url_public = null;
|
||||||
|
|
||||||
protected $rules = [
|
protected $rules = [
|
||||||
'database.name' => 'required',
|
'database.name' => 'required',
|
||||||
@@ -41,9 +42,20 @@ class General extends Component
|
|||||||
'database.is_public' => 'Is Public',
|
'database.is_public' => 'Is Public',
|
||||||
'database.public_port' => 'Public Port',
|
'database.public_port' => 'Public Port',
|
||||||
];
|
];
|
||||||
|
|
||||||
|
public function mount()
|
||||||
|
{
|
||||||
|
$this->db_url = $this->database->getDbUrl(true);
|
||||||
|
if ($this->database->is_public) {
|
||||||
|
$this->db_url_public = $this->database->getDbUrl();
|
||||||
|
}
|
||||||
|
}
|
||||||
public function submit()
|
public function submit()
|
||||||
{
|
{
|
||||||
try {
|
try {
|
||||||
|
if (str($this->database->public_port)->isEmpty()) {
|
||||||
|
$this->database->public_port = null;
|
||||||
|
}
|
||||||
$this->validate();
|
$this->validate();
|
||||||
$this->database->save();
|
$this->database->save();
|
||||||
$this->emit('success', 'Database updated successfully.');
|
$this->emit('success', 'Database updated successfully.');
|
||||||
@@ -66,12 +78,13 @@ class General extends Component
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
StartDatabaseProxy::run($this->database);
|
StartDatabaseProxy::run($this->database);
|
||||||
|
$this->db_url_public = $this->database->getDbUrl();
|
||||||
$this->emit('success', 'Database is now publicly accessible.');
|
$this->emit('success', 'Database is now publicly accessible.');
|
||||||
} else {
|
} else {
|
||||||
StopDatabaseProxy::run($this->database);
|
StopDatabaseProxy::run($this->database);
|
||||||
|
$this->db_url_public = null;
|
||||||
$this->emit('success', 'Database is no longer publicly accessible.');
|
$this->emit('success', 'Database is no longer publicly accessible.');
|
||||||
}
|
}
|
||||||
$this->db_url = $this->database->getDbUrl();
|
|
||||||
$this->database->save();
|
$this->database->save();
|
||||||
} catch (\Throwable $e) {
|
} catch (\Throwable $e) {
|
||||||
$this->database->is_public = !$this->database->is_public;
|
$this->database->is_public = !$this->database->is_public;
|
||||||
@@ -83,11 +96,6 @@ class General extends Component
|
|||||||
$this->database->refresh();
|
$this->database->refresh();
|
||||||
}
|
}
|
||||||
|
|
||||||
public function mount()
|
|
||||||
{
|
|
||||||
$this->db_url = $this->database->getDbUrl();
|
|
||||||
}
|
|
||||||
|
|
||||||
public function render()
|
public function render()
|
||||||
{
|
{
|
||||||
return view('livewire.project.database.mysql.general');
|
return view('livewire.project.database.mysql.general');
|
||||||
|
|||||||
@@ -15,7 +15,8 @@ class General extends Component
|
|||||||
public StandalonePostgresql $database;
|
public StandalonePostgresql $database;
|
||||||
public string $new_filename;
|
public string $new_filename;
|
||||||
public string $new_content;
|
public string $new_content;
|
||||||
public string $db_url;
|
public ?string $db_url = null;
|
||||||
|
public ?string $db_url_public = null;
|
||||||
|
|
||||||
protected $listeners = ['refresh', 'save_init_script', 'delete_init_script'];
|
protected $listeners = ['refresh', 'save_init_script', 'delete_init_script'];
|
||||||
|
|
||||||
@@ -27,6 +28,7 @@ class General extends Component
|
|||||||
'database.postgres_db' => 'required',
|
'database.postgres_db' => 'required',
|
||||||
'database.postgres_initdb_args' => 'nullable',
|
'database.postgres_initdb_args' => 'nullable',
|
||||||
'database.postgres_host_auth_method' => 'nullable',
|
'database.postgres_host_auth_method' => 'nullable',
|
||||||
|
'database.postgres_conf' => 'nullable',
|
||||||
'database.init_scripts' => 'nullable',
|
'database.init_scripts' => 'nullable',
|
||||||
'database.image' => 'required',
|
'database.image' => 'required',
|
||||||
'database.ports_mappings' => 'nullable',
|
'database.ports_mappings' => 'nullable',
|
||||||
@@ -41,6 +43,7 @@ class General extends Component
|
|||||||
'database.postgres_db' => 'Postgres DB',
|
'database.postgres_db' => 'Postgres DB',
|
||||||
'database.postgres_initdb_args' => 'Postgres Initdb Args',
|
'database.postgres_initdb_args' => 'Postgres Initdb Args',
|
||||||
'database.postgres_host_auth_method' => 'Postgres Host Auth Method',
|
'database.postgres_host_auth_method' => 'Postgres Host Auth Method',
|
||||||
|
'database.postgres_conf' => 'Postgres Configuration',
|
||||||
'database.init_scripts' => 'Init Scripts',
|
'database.init_scripts' => 'Init Scripts',
|
||||||
'database.image' => 'Image',
|
'database.image' => 'Image',
|
||||||
'database.ports_mappings' => 'Port Mapping',
|
'database.ports_mappings' => 'Port Mapping',
|
||||||
@@ -49,7 +52,10 @@ class General extends Component
|
|||||||
];
|
];
|
||||||
public function mount()
|
public function mount()
|
||||||
{
|
{
|
||||||
$this->db_url = $this->database->getDbUrl();
|
$this->db_url = $this->database->getDbUrl(true);
|
||||||
|
if ($this->database->is_public) {
|
||||||
|
$this->db_url_public = $this->database->getDbUrl();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
public function instantSave()
|
public function instantSave()
|
||||||
{
|
{
|
||||||
@@ -66,12 +72,13 @@ class General extends Component
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
StartDatabaseProxy::run($this->database);
|
StartDatabaseProxy::run($this->database);
|
||||||
|
$this->db_url_public = $this->database->getDbUrl();
|
||||||
$this->emit('success', 'Database is now publicly accessible.');
|
$this->emit('success', 'Database is now publicly accessible.');
|
||||||
} else {
|
} else {
|
||||||
StopDatabaseProxy::run($this->database);
|
StopDatabaseProxy::run($this->database);
|
||||||
|
$this->db_url_public = null;
|
||||||
$this->emit('success', 'Database is no longer publicly accessible.');
|
$this->emit('success', 'Database is no longer publicly accessible.');
|
||||||
}
|
}
|
||||||
$this->db_url = $this->database->getDbUrl();
|
|
||||||
$this->database->save();
|
$this->database->save();
|
||||||
} catch (\Throwable $e) {
|
} catch (\Throwable $e) {
|
||||||
$this->database->is_public = !$this->database->is_public;
|
$this->database->is_public = !$this->database->is_public;
|
||||||
@@ -91,7 +98,6 @@ class General extends Component
|
|||||||
$collection = collect($this->database->init_scripts);
|
$collection = collect($this->database->init_scripts);
|
||||||
$found = $collection->firstWhere('filename', $script['filename']);
|
$found = $collection->firstWhere('filename', $script['filename']);
|
||||||
if ($found) {
|
if ($found) {
|
||||||
ray($collection->filter(fn ($s) => $s['filename'] !== $script['filename'])->toArray());
|
|
||||||
$this->database->init_scripts = $collection->filter(fn ($s) => $s['filename'] !== $script['filename'])->toArray();
|
$this->database->init_scripts = $collection->filter(fn ($s) => $s['filename'] !== $script['filename'])->toArray();
|
||||||
$this->database->save();
|
$this->database->save();
|
||||||
$this->refresh();
|
$this->refresh();
|
||||||
@@ -135,6 +141,9 @@ class General extends Component
|
|||||||
public function submit()
|
public function submit()
|
||||||
{
|
{
|
||||||
try {
|
try {
|
||||||
|
if (str($this->database->public_port)->isEmpty()) {
|
||||||
|
$this->database->public_port = null;
|
||||||
|
}
|
||||||
$this->validate();
|
$this->validate();
|
||||||
$this->database->save();
|
$this->database->save();
|
||||||
$this->emit('success', 'Database updated successfully.');
|
$this->emit('success', 'Database updated successfully.');
|
||||||
|
|||||||
@@ -13,7 +13,8 @@ class General extends Component
|
|||||||
protected $listeners = ['refresh'];
|
protected $listeners = ['refresh'];
|
||||||
|
|
||||||
public StandaloneRedis $database;
|
public StandaloneRedis $database;
|
||||||
public string $db_url;
|
public ?string $db_url = null;
|
||||||
|
public ?string $db_url_public = null;
|
||||||
|
|
||||||
protected $rules = [
|
protected $rules = [
|
||||||
'database.name' => 'required',
|
'database.name' => 'required',
|
||||||
@@ -35,6 +36,13 @@ class General extends Component
|
|||||||
'database.is_public' => 'Is Public',
|
'database.is_public' => 'Is Public',
|
||||||
'database.public_port' => 'Public Port',
|
'database.public_port' => 'Public Port',
|
||||||
];
|
];
|
||||||
|
public function mount()
|
||||||
|
{
|
||||||
|
$this->db_url = $this->database->getDbUrl(true);
|
||||||
|
if ($this->database->is_public) {
|
||||||
|
$this->db_url_public = $this->database->getDbUrl();
|
||||||
|
}
|
||||||
|
}
|
||||||
public function submit()
|
public function submit()
|
||||||
{
|
{
|
||||||
try {
|
try {
|
||||||
@@ -63,12 +71,13 @@ class General extends Component
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
StartDatabaseProxy::run($this->database);
|
StartDatabaseProxy::run($this->database);
|
||||||
|
$this->db_url_public = $this->database->getDbUrl();
|
||||||
$this->emit('success', 'Database is now publicly accessible.');
|
$this->emit('success', 'Database is now publicly accessible.');
|
||||||
} else {
|
} else {
|
||||||
StopDatabaseProxy::run($this->database);
|
StopDatabaseProxy::run($this->database);
|
||||||
|
$this->db_url_public = null;
|
||||||
$this->emit('success', 'Database is no longer publicly accessible.');
|
$this->emit('success', 'Database is no longer publicly accessible.');
|
||||||
}
|
}
|
||||||
$this->db_url = $this->database->getDbUrl();
|
|
||||||
$this->database->save();
|
$this->database->save();
|
||||||
} catch (\Throwable $e) {
|
} catch (\Throwable $e) {
|
||||||
$this->database->is_public = !$this->database->is_public;
|
$this->database->is_public = !$this->database->is_public;
|
||||||
@@ -80,10 +89,6 @@ class General extends Component
|
|||||||
$this->database->refresh();
|
$this->database->refresh();
|
||||||
}
|
}
|
||||||
|
|
||||||
public function mount()
|
|
||||||
{
|
|
||||||
$this->db_url = $this->database->getDbUrl();
|
|
||||||
}
|
|
||||||
public function render()
|
public function render()
|
||||||
{
|
{
|
||||||
return view('livewire.project.database.redis.general');
|
return view('livewire.project.database.redis.general');
|
||||||
|
|||||||
@@ -2,28 +2,56 @@
|
|||||||
|
|
||||||
namespace App\Http\Livewire\Project\Service;
|
namespace App\Http\Livewire\Project\Service;
|
||||||
|
|
||||||
|
use App\Actions\Database\StartDatabaseProxy;
|
||||||
|
use App\Actions\Database\StopDatabaseProxy;
|
||||||
use App\Models\ServiceDatabase;
|
use App\Models\ServiceDatabase;
|
||||||
use Livewire\Component;
|
use Livewire\Component;
|
||||||
|
|
||||||
class Database extends Component
|
class Database extends Component
|
||||||
{
|
{
|
||||||
public ServiceDatabase $database;
|
public ServiceDatabase $database;
|
||||||
|
public ?string $db_url_public = null;
|
||||||
public $fileStorages;
|
public $fileStorages;
|
||||||
|
|
||||||
protected $listeners = ["refreshFileStorages"];
|
protected $listeners = ["refreshFileStorages"];
|
||||||
protected $rules = [
|
protected $rules = [
|
||||||
'database.human_name' => 'nullable',
|
'database.human_name' => 'nullable',
|
||||||
'database.description' => 'nullable',
|
'database.description' => 'nullable',
|
||||||
'database.image' => 'required',
|
'database.image' => 'required',
|
||||||
'database.exclude_from_status' => 'required|boolean',
|
'database.exclude_from_status' => 'required|boolean',
|
||||||
|
'database.public_port' => 'nullable|integer',
|
||||||
|
'database.is_public' => 'required|boolean',
|
||||||
];
|
];
|
||||||
public function render()
|
public function render()
|
||||||
{
|
{
|
||||||
return view('livewire.project.service.database');
|
return view('livewire.project.service.database');
|
||||||
}
|
}
|
||||||
public function mount() {
|
public function mount() {
|
||||||
|
if ($this->database->is_public) {
|
||||||
|
$this->db_url_public = $this->database->getServiceDatabaseUrl();
|
||||||
|
}
|
||||||
$this->refreshFileStorages();
|
$this->refreshFileStorages();
|
||||||
}
|
}
|
||||||
public function instantSave() {
|
public function instantSave() {
|
||||||
|
if ($this->database->is_public && !$this->database->public_port) {
|
||||||
|
$this->emit('error', 'Public port is required.');
|
||||||
|
$this->database->is_public = false;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if ($this->database->is_public) {
|
||||||
|
if (!str($this->database->status)->startsWith('running')) {
|
||||||
|
$this->emit('error', 'Database must be started to be publicly accessible.');
|
||||||
|
$this->database->is_public = false;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
StartDatabaseProxy::run($this->database);
|
||||||
|
$this->db_url_public = $this->database->getServiceDatabaseUrl();
|
||||||
|
$this->emit('success', 'Database is now publicly accessible.');
|
||||||
|
} else {
|
||||||
|
StopDatabaseProxy::run($this->database);
|
||||||
|
$this->db_url_public = null;
|
||||||
|
$this->emit('success', 'Database is no longer publicly accessible.');
|
||||||
|
}
|
||||||
$this->submit();
|
$this->submit();
|
||||||
}
|
}
|
||||||
public function refreshFileStorages()
|
public function refreshFileStorages()
|
||||||
|
|||||||
@@ -55,12 +55,17 @@ class All extends Component
|
|||||||
{
|
{
|
||||||
if ($isPreview) {
|
if ($isPreview) {
|
||||||
$variables = parseEnvFormatToArray($this->variablesPreview);
|
$variables = parseEnvFormatToArray($this->variablesPreview);
|
||||||
|
$this->resource->environment_variables_preview()->whereNotIn('key', array_keys($variables))->delete();
|
||||||
} else {
|
} else {
|
||||||
$variables = parseEnvFormatToArray($this->variables);
|
$variables = parseEnvFormatToArray($this->variables);
|
||||||
|
$this->resource->environment_variables()->whereNotIn('key', array_keys($variables))->delete();
|
||||||
}
|
}
|
||||||
foreach ($variables as $key => $variable) {
|
foreach ($variables as $key => $variable) {
|
||||||
$found = $this->resource->environment_variables()->where('key', $key)->first();
|
if ($isPreview) {
|
||||||
$foundPreview = $this->resource->environment_variables_preview()->where('key', $key)->first();
|
$found = $this->resource->environment_variables_preview()->where('key', $key)->first();
|
||||||
|
} else {
|
||||||
|
$found = $this->resource->environment_variables()->where('key', $key)->first();
|
||||||
|
}
|
||||||
if ($found) {
|
if ($found) {
|
||||||
if ($found->is_shown_once) {
|
if ($found->is_shown_once) {
|
||||||
continue;
|
continue;
|
||||||
@@ -68,14 +73,6 @@ class All extends Component
|
|||||||
$found->value = $variable;
|
$found->value = $variable;
|
||||||
$found->save();
|
$found->save();
|
||||||
continue;
|
continue;
|
||||||
}
|
|
||||||
if ($foundPreview) {
|
|
||||||
if ($foundPreview->is_shown_once) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
$foundPreview->value = $variable;
|
|
||||||
$foundPreview->save();
|
|
||||||
continue;
|
|
||||||
} else {
|
} else {
|
||||||
$environment = new EnvironmentVariable();
|
$environment = new EnvironmentVariable();
|
||||||
$environment->key = $key;
|
$environment->key = $key;
|
||||||
|
|||||||
@@ -31,7 +31,7 @@ class Logs extends Component
|
|||||||
$this->resource = Application::where('uuid', $this->parameters['application_uuid'])->firstOrFail();
|
$this->resource = Application::where('uuid', $this->parameters['application_uuid'])->firstOrFail();
|
||||||
$this->status = $this->resource->status;
|
$this->status = $this->resource->status;
|
||||||
$this->server = $this->resource->destination->server;
|
$this->server = $this->resource->destination->server;
|
||||||
$containers = getCurrentApplicationContainerStatus($this->server, $this->resource->id);
|
$containers = getCurrentApplicationContainerStatus($this->server, $this->resource->id, 0);
|
||||||
if ($containers->count() > 0) {
|
if ($containers->count() > 0) {
|
||||||
$this->container = data_get($containers[0], 'Names');
|
$this->container = data_get($containers[0], 'Names');
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -52,7 +52,7 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
|
|||||||
private GithubApp|GitlabApp|string $source = 'other';
|
private GithubApp|GitlabApp|string $source = 'other';
|
||||||
private StandaloneDocker|SwarmDocker $destination;
|
private StandaloneDocker|SwarmDocker $destination;
|
||||||
private Server $server;
|
private Server $server;
|
||||||
private ApplicationPreview|null $preview = null;
|
private ?ApplicationPreview $preview = null;
|
||||||
|
|
||||||
private string $container_name;
|
private string $container_name;
|
||||||
private ?string $currently_running_container_name = null;
|
private ?string $currently_running_container_name = null;
|
||||||
@@ -78,6 +78,7 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
|
|||||||
private string $dockerConfigFileExists = 'NOK';
|
private string $dockerConfigFileExists = 'NOK';
|
||||||
|
|
||||||
private int $customPort = 22;
|
private int $customPort = 22;
|
||||||
|
private ?string $customRepository = null;
|
||||||
|
|
||||||
private ?string $fullRepoUrl = null;
|
private ?string $fullRepoUrl = null;
|
||||||
private ?string $branch = null;
|
private ?string $branch = null;
|
||||||
@@ -138,21 +139,6 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
|
|||||||
|
|
||||||
public function handle(): void
|
public function handle(): void
|
||||||
{
|
{
|
||||||
// ray()->measure();
|
|
||||||
$containers = getCurrentApplicationContainerStatus($this->server, $this->application->id, $this->pull_request_id);
|
|
||||||
if ($containers->count() === 1) {
|
|
||||||
$this->currently_running_container_name = data_get($containers[0], 'Names');
|
|
||||||
} else {
|
|
||||||
$foundContainer = $containers->filter(function ($container) {
|
|
||||||
return !str(data_get($container, 'Names'))->startsWith("{$this->application->uuid}-pr-");
|
|
||||||
})->first();
|
|
||||||
if ($foundContainer) {
|
|
||||||
$this->currently_running_container_name = data_get($foundContainer, 'Names');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if ($this->pull_request_id !== 0 && $this->pull_request_id !== null) {
|
|
||||||
$this->currently_running_container_name = $this->container_name;
|
|
||||||
}
|
|
||||||
$this->application_deployment_queue->update([
|
$this->application_deployment_queue->update([
|
||||||
'status' => ApplicationDeploymentStatus::IN_PROGRESS->value,
|
'status' => ApplicationDeploymentStatus::IN_PROGRESS->value,
|
||||||
]);
|
]);
|
||||||
@@ -193,7 +179,9 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
|
|||||||
$this->customPort = $matches[0];
|
$this->customPort = $matches[0];
|
||||||
$gitHost = str($this->application->git_repository)->before(':');
|
$gitHost = str($this->application->git_repository)->before(':');
|
||||||
$gitRepo = str($this->application->git_repository)->after('/');
|
$gitRepo = str($this->application->git_repository)->after('/');
|
||||||
$this->application->git_repository = "$gitHost:$gitRepo";
|
$this->customRepository = "$gitHost:$gitRepo";
|
||||||
|
} else {
|
||||||
|
$this->customRepository = $this->application->git_repository;
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
if ($this->restart_only) {
|
if ($this->restart_only) {
|
||||||
@@ -204,6 +192,8 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
|
|||||||
$this->deploy_dockerimage_buildpack();
|
$this->deploy_dockerimage_buildpack();
|
||||||
} else if ($this->application->build_pack === 'dockerfile') {
|
} else if ($this->application->build_pack === 'dockerfile') {
|
||||||
$this->deploy_dockerfile_buildpack();
|
$this->deploy_dockerfile_buildpack();
|
||||||
|
} else if ($this->application->build_pack === 'static') {
|
||||||
|
$this->deploy_static_buildpack();
|
||||||
} else {
|
} else {
|
||||||
if ($this->pull_request_id !== 0) {
|
if ($this->pull_request_id !== 0) {
|
||||||
$this->deploy_pull_request();
|
$this->deploy_pull_request();
|
||||||
@@ -239,6 +229,14 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
|
|||||||
[
|
[
|
||||||
"docker rm -f {$this->deployment_uuid} >/dev/null 2>&1",
|
"docker rm -f {$this->deployment_uuid} >/dev/null 2>&1",
|
||||||
"hidden" => true,
|
"hidden" => true,
|
||||||
|
"ignore_errors" => true,
|
||||||
|
]
|
||||||
|
);
|
||||||
|
$this->execute_remote_command(
|
||||||
|
[
|
||||||
|
"docker image prune -f >/dev/null 2>&1",
|
||||||
|
"hidden" => true,
|
||||||
|
"ignore_errors" => true,
|
||||||
]
|
]
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -258,7 +256,7 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
|
|||||||
// executeInDocker($this->deployment_uuid, "echo '$dockercompose_base64' | base64 -d > $this->workdir/docker-compose.yaml")
|
// executeInDocker($this->deployment_uuid, "echo '$dockercompose_base64' | base64 -d > $this->workdir/docker-compose.yaml")
|
||||||
// ],
|
// ],
|
||||||
// );
|
// );
|
||||||
// $this->build_image_name = Str::lower("{$this->application->git_repository}:build");
|
// $this->build_image_name = Str::lower("{$this->customRepository}:build");
|
||||||
// $this->production_image_name = Str::lower("{$this->application->uuid}:latest");
|
// $this->production_image_name = Str::lower("{$this->application->uuid}:latest");
|
||||||
// $this->save_environment_variables();
|
// $this->save_environment_variables();
|
||||||
// $containers = getCurrentApplicationContainerStatus($this->application->destination->server, $this->application->id);
|
// $containers = getCurrentApplicationContainerStatus($this->application->destination->server, $this->application->id);
|
||||||
@@ -283,7 +281,7 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
|
|||||||
private function generate_image_names()
|
private function generate_image_names()
|
||||||
{
|
{
|
||||||
if ($this->application->dockerfile) {
|
if ($this->application->dockerfile) {
|
||||||
$this->build_image_name = Str::lower("{$this->application->git_repository}:build");
|
$this->build_image_name = Str::lower("{$this->application->uuid}:build");
|
||||||
$this->production_image_name = Str::lower("{$this->application->uuid}:latest");
|
$this->production_image_name = Str::lower("{$this->application->uuid}:latest");
|
||||||
} else if ($this->application->build_pack === 'dockerimage') {
|
} else if ($this->application->build_pack === 'dockerimage') {
|
||||||
$this->production_image_name = Str::lower("{$this->dockerImage}:{$this->dockerImageTag}");
|
$this->production_image_name = Str::lower("{$this->dockerImage}:{$this->dockerImageTag}");
|
||||||
@@ -295,7 +293,7 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
|
|||||||
if (strlen($tag) > 128) {
|
if (strlen($tag) > 128) {
|
||||||
$tag = $tag->substr(0, 128);
|
$tag = $tag->substr(0, 128);
|
||||||
}
|
}
|
||||||
$this->build_image_name = Str::lower("{$this->application->git_repository}:{$tag}-build");
|
$this->build_image_name = Str::lower("{$this->application->uuid}:{$tag}-build");
|
||||||
$this->production_image_name = Str::lower("{$this->application->uuid}:{$tag}");
|
$this->production_image_name = Str::lower("{$this->application->uuid}:{$tag}");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -303,7 +301,7 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
|
|||||||
{
|
{
|
||||||
$this->execute_remote_command(
|
$this->execute_remote_command(
|
||||||
[
|
[
|
||||||
"echo 'Starting deployment of {$this->application->git_repository}:{$this->application->git_branch}.'"
|
"echo 'Starting deployment of {$this->customRepository}:{$this->application->git_branch}.'"
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
$this->prepare_builder_image();
|
$this->prepare_builder_image();
|
||||||
@@ -380,7 +378,7 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
|
|||||||
}
|
}
|
||||||
$this->execute_remote_command(
|
$this->execute_remote_command(
|
||||||
[
|
[
|
||||||
"echo 'Starting deployment of {$this->application->git_repository}:{$this->application->git_branch}.'"
|
"echo 'Starting deployment of {$this->customRepository}:{$this->application->git_branch}.'"
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
$this->prepare_builder_image();
|
$this->prepare_builder_image();
|
||||||
@@ -399,7 +397,7 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
|
|||||||
{
|
{
|
||||||
$this->execute_remote_command(
|
$this->execute_remote_command(
|
||||||
[
|
[
|
||||||
"echo 'Starting deployment of {$this->application->git_repository}:{$this->application->git_branch}.'"
|
"echo 'Starting deployment of {$this->customRepository}:{$this->application->git_branch}.'"
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
$this->prepare_builder_image();
|
$this->prepare_builder_image();
|
||||||
@@ -433,6 +431,23 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
|
|||||||
$this->build_image();
|
$this->build_image();
|
||||||
$this->rolling_update();
|
$this->rolling_update();
|
||||||
}
|
}
|
||||||
|
private function deploy_static_buildpack()
|
||||||
|
{
|
||||||
|
$this->execute_remote_command(
|
||||||
|
[
|
||||||
|
"echo 'Starting deployment of {$this->customRepository}:{$this->application->git_branch}.'"
|
||||||
|
],
|
||||||
|
);
|
||||||
|
$this->prepare_builder_image();
|
||||||
|
$this->check_git_if_build_needed();
|
||||||
|
$this->set_base_dir();
|
||||||
|
$this->generate_image_names();
|
||||||
|
$this->clone_repository();
|
||||||
|
$this->cleanup_git();
|
||||||
|
$this->build_image();
|
||||||
|
$this->generate_compose_file();
|
||||||
|
$this->rolling_update();
|
||||||
|
}
|
||||||
|
|
||||||
private function rolling_update()
|
private function rolling_update()
|
||||||
{
|
{
|
||||||
@@ -499,9 +514,10 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
|
|||||||
}
|
}
|
||||||
private function deploy_pull_request()
|
private function deploy_pull_request()
|
||||||
{
|
{
|
||||||
|
$this->newVersionIsHealthy = true;
|
||||||
$this->generate_image_names();
|
$this->generate_image_names();
|
||||||
$this->execute_remote_command([
|
$this->execute_remote_command([
|
||||||
"echo 'Starting pull request (#{$this->pull_request_id}) deployment of {$this->application->git_repository}:{$this->application->git_branch}.'",
|
"echo 'Starting pull request (#{$this->pull_request_id}) deployment of {$this->customRepository}:{$this->application->git_branch}.'",
|
||||||
]);
|
]);
|
||||||
$this->prepare_builder_image();
|
$this->prepare_builder_image();
|
||||||
$this->clone_repository();
|
$this->clone_repository();
|
||||||
@@ -515,12 +531,7 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
|
|||||||
// $this->generate_build_env_variables();
|
// $this->generate_build_env_variables();
|
||||||
// $this->add_build_env_variables_to_dockerfile();
|
// $this->add_build_env_variables_to_dockerfile();
|
||||||
$this->build_image();
|
$this->build_image();
|
||||||
if ($this->currently_running_container_name) {
|
$this->stop_running_container();
|
||||||
$this->execute_remote_command(
|
|
||||||
["echo -n 'Removing old version of your application.'"],
|
|
||||||
[executeInDocker($this->deployment_uuid, "docker rm -f $this->currently_running_container_name >/dev/null 2>&1"), "hidden" => true, "ignore_errors" => true],
|
|
||||||
);
|
|
||||||
}
|
|
||||||
$this->execute_remote_command(
|
$this->execute_remote_command(
|
||||||
["echo -n 'Starting preview deployment.'"],
|
["echo -n 'Starting preview deployment.'"],
|
||||||
[executeInDocker($this->deployment_uuid, "docker compose --project-directory {$this->workdir} up -d"), "hidden" => true],
|
[executeInDocker($this->deployment_uuid, "docker compose --project-directory {$this->workdir} up -d"), "hidden" => true],
|
||||||
@@ -599,7 +610,7 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
|
|||||||
$importCommands = $this->generate_git_import_commands();
|
$importCommands = $this->generate_git_import_commands();
|
||||||
$this->execute_remote_command(
|
$this->execute_remote_command(
|
||||||
[
|
[
|
||||||
"echo -n 'Importing {$this->application->git_repository}:{$this->application->git_branch} (commit sha {$this->application->git_commit_sha}) to {$this->basedir}. '"
|
"echo -n 'Importing {$this->customRepository}:{$this->application->git_branch} (commit sha {$this->application->git_commit_sha}) to {$this->basedir}. '"
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
$importCommands, "hidden" => true
|
$importCommands, "hidden" => true
|
||||||
@@ -624,15 +635,15 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
|
|||||||
|
|
||||||
if ($this->source->getMorphClass() == 'App\Models\GithubApp') {
|
if ($this->source->getMorphClass() == 'App\Models\GithubApp') {
|
||||||
if ($this->source->is_public) {
|
if ($this->source->is_public) {
|
||||||
$this->fullRepoUrl = "{$this->source->html_url}/{$this->application->git_repository}";
|
$this->fullRepoUrl = "{$this->source->html_url}/{$this->customRepository}";
|
||||||
$git_clone_command = "{$git_clone_command} {$this->source->html_url}/{$this->application->git_repository} {$this->basedir}";
|
$git_clone_command = "{$git_clone_command} {$this->source->html_url}/{$this->customRepository} {$this->basedir}";
|
||||||
$git_clone_command = $this->set_git_import_settings($git_clone_command);
|
$git_clone_command = $this->set_git_import_settings($git_clone_command);
|
||||||
|
|
||||||
$commands->push(executeInDocker($this->deployment_uuid, $git_clone_command));
|
$commands->push(executeInDocker($this->deployment_uuid, $git_clone_command));
|
||||||
} else {
|
} else {
|
||||||
$github_access_token = generate_github_installation_token($this->source);
|
$github_access_token = generate_github_installation_token($this->source);
|
||||||
$commands->push(executeInDocker($this->deployment_uuid, "git clone -q -b {$this->application->git_branch} $source_html_url_scheme://x-access-token:$github_access_token@$source_html_url_host/{$this->application->git_repository}.git {$this->basedir}"));
|
$commands->push(executeInDocker($this->deployment_uuid, "git clone -q -b {$this->application->git_branch} $source_html_url_scheme://x-access-token:$github_access_token@$source_html_url_host/{$this->customRepository}.git {$this->basedir}"));
|
||||||
$this->fullRepoUrl = "$source_html_url_scheme://x-access-token:$github_access_token@$source_html_url_host/{$this->application->git_repository}.git";
|
$this->fullRepoUrl = "$source_html_url_scheme://x-access-token:$github_access_token@$source_html_url_host/{$this->customRepository}.git";
|
||||||
}
|
}
|
||||||
if ($this->pull_request_id !== 0) {
|
if ($this->pull_request_id !== 0) {
|
||||||
$this->branch = "pull/{$this->pull_request_id}/head:$pr_branch_name";
|
$this->branch = "pull/{$this->pull_request_id}/head:$pr_branch_name";
|
||||||
@@ -642,13 +653,13 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
if ($this->application->deploymentType() === 'deploy_key') {
|
if ($this->application->deploymentType() === 'deploy_key') {
|
||||||
$this->fullRepoUrl = $this->application->git_repository;
|
$this->fullRepoUrl = $this->customRepository;
|
||||||
$private_key = data_get($this->application, 'private_key.private_key');
|
$private_key = data_get($this->application, 'private_key.private_key');
|
||||||
if (is_null($private_key)) {
|
if (is_null($private_key)) {
|
||||||
throw new Exception('Private key not found. Please add a private key to the application and try again.');
|
throw new Exception('Private key not found. Please add a private key to the application and try again.');
|
||||||
}
|
}
|
||||||
$private_key = base64_encode($private_key);
|
$private_key = base64_encode($private_key);
|
||||||
$git_clone_command = "GIT_SSH_COMMAND=\"ssh -o ConnectTimeout=30 -p {$this->customPort} -o Port={$this->customPort} -o LogLevel=ERROR -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -i /root/.ssh/id_rsa\" {$git_clone_command} {$this->application->git_repository} {$this->basedir}";
|
$git_clone_command = "GIT_SSH_COMMAND=\"ssh -o ConnectTimeout=30 -p {$this->customPort} -o Port={$this->customPort} -o LogLevel=ERROR -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -i /root/.ssh/id_rsa\" {$git_clone_command} {$this->customRepository} {$this->basedir}";
|
||||||
$git_clone_command = $this->set_git_import_settings($git_clone_command);
|
$git_clone_command = $this->set_git_import_settings($git_clone_command);
|
||||||
$commands = collect([
|
$commands = collect([
|
||||||
executeInDocker($this->deployment_uuid, "mkdir -p /root/.ssh"),
|
executeInDocker($this->deployment_uuid, "mkdir -p /root/.ssh"),
|
||||||
@@ -659,8 +670,8 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
|
|||||||
return $commands->implode(' && ');
|
return $commands->implode(' && ');
|
||||||
}
|
}
|
||||||
if ($this->application->deploymentType() === 'other') {
|
if ($this->application->deploymentType() === 'other') {
|
||||||
$this->fullRepoUrl = $this->application->git_repository;
|
$this->fullRepoUrl = $this->customRepository;
|
||||||
$git_clone_command = "{$git_clone_command} {$this->application->git_repository} {$this->basedir}";
|
$git_clone_command = "{$git_clone_command} {$this->customRepository} {$this->basedir}";
|
||||||
$git_clone_command = $this->set_git_import_settings($git_clone_command);
|
$git_clone_command = $this->set_git_import_settings($git_clone_command);
|
||||||
$commands->push(executeInDocker($this->deployment_uuid, $git_clone_command));
|
$commands->push(executeInDocker($this->deployment_uuid, $git_clone_command));
|
||||||
return $commands->implode(' && ');
|
return $commands->implode(' && ');
|
||||||
@@ -803,7 +814,7 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
|
|||||||
'memswap_limit' => $this->application->limits_memory_swap,
|
'memswap_limit' => $this->application->limits_memory_swap,
|
||||||
'mem_swappiness' => $this->application->limits_memory_swappiness,
|
'mem_swappiness' => $this->application->limits_memory_swappiness,
|
||||||
'mem_reservation' => $this->application->limits_memory_reservation,
|
'mem_reservation' => $this->application->limits_memory_reservation,
|
||||||
'cpus' => $this->application->limits_cpus,
|
'cpus' => (int) $this->application->limits_cpus,
|
||||||
'cpuset' => $this->application->limits_cpuset,
|
'cpuset' => $this->application->limits_cpuset,
|
||||||
'cpu_shares' => $this->application->limits_cpu_shares,
|
'cpu_shares' => $this->application->limits_cpu_shares,
|
||||||
]
|
]
|
||||||
@@ -882,11 +893,17 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
|
|||||||
foreach ($this->application->runtime_environment_variables as $env) {
|
foreach ($this->application->runtime_environment_variables as $env) {
|
||||||
$environment_variables->push("$env->key=$env->value");
|
$environment_variables->push("$env->key=$env->value");
|
||||||
}
|
}
|
||||||
|
foreach ($this->application->nixpacks_environment_variables as $env) {
|
||||||
|
$environment_variables->push("$env->key=$env->value");
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
// ray($this->application->runtime_environment_variables_preview)->green();
|
// ray($this->application->runtime_environment_variables_preview)->green();
|
||||||
foreach ($this->application->runtime_environment_variables_preview as $env) {
|
foreach ($this->application->runtime_environment_variables_preview as $env) {
|
||||||
$environment_variables->push("$env->key=$env->value");
|
$environment_variables->push("$env->key=$env->value");
|
||||||
}
|
}
|
||||||
|
foreach ($this->application->nixpacks_environment_variables_preview as $env) {
|
||||||
|
$environment_variables->push("$env->key=$env->value");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
// Add PORT if not exists, use the first port as default
|
// Add PORT if not exists, use the first port as default
|
||||||
if ($environment_variables->filter(fn ($env) => Str::of($env)->contains('PORT'))->isEmpty()) {
|
if ($environment_variables->filter(fn ($env) => Str::of($env)->contains('PORT'))->isEmpty()) {
|
||||||
@@ -920,22 +937,26 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
|
|||||||
|
|
||||||
private function build_image()
|
private function build_image()
|
||||||
{
|
{
|
||||||
$this->execute_remote_command([
|
if ($this->application->build_pack === 'static') {
|
||||||
"echo -n 'Building docker image for your application. To check the current progress, click on Show Debug Logs.'",
|
|
||||||
]);
|
|
||||||
|
|
||||||
if ($this->application->settings->is_static) {
|
|
||||||
$this->execute_remote_command([
|
$this->execute_remote_command([
|
||||||
executeInDocker($this->deployment_uuid, "docker build $this->buildTarget $this->addHosts --network host -f {$this->workdir}/{$this->dockerfile_location} {$this->build_args} --progress plain -t $this->build_image_name {$this->workdir}"), "hidden" => true
|
"echo -n 'Static deployment. Copying static assets to the image.'",
|
||||||
]);
|
]);
|
||||||
|
} else {
|
||||||
|
$this->execute_remote_command([
|
||||||
|
"echo -n 'Building docker image for your application. To check the current progress, click on Show Debug Logs.'",
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
$dockerfile = base64_encode("FROM {$this->application->static_image}
|
if ($this->application->settings->is_static || $this->application->build_pack === 'static') {
|
||||||
|
if ($this->application->build_pack === 'static') {
|
||||||
|
$dockerfile = base64_encode("FROM {$this->application->static_image}
|
||||||
WORKDIR /usr/share/nginx/html/
|
WORKDIR /usr/share/nginx/html/
|
||||||
LABEL coolify.deploymentId={$this->deployment_uuid}
|
LABEL coolify.deploymentId={$this->deployment_uuid}
|
||||||
COPY --from=$this->build_image_name /app/{$this->application->publish_directory} .
|
COPY . .
|
||||||
|
RUN rm -f /usr/share/nginx/html/nginx.conf
|
||||||
|
RUN rm -f /usr/share/nginx/html/Dockerfile
|
||||||
COPY ./nginx.conf /etc/nginx/conf.d/default.conf");
|
COPY ./nginx.conf /etc/nginx/conf.d/default.conf");
|
||||||
|
$nginx_config = base64_encode("server {
|
||||||
$nginx_config = base64_encode("server {
|
|
||||||
listen 80;
|
listen 80;
|
||||||
listen [::]:80;
|
listen [::]:80;
|
||||||
server_name localhost;
|
server_name localhost;
|
||||||
@@ -951,15 +972,43 @@ COPY ./nginx.conf /etc/nginx/conf.d/default.conf");
|
|||||||
root /usr/share/nginx/html;
|
root /usr/share/nginx/html;
|
||||||
}
|
}
|
||||||
}");
|
}");
|
||||||
|
} else {
|
||||||
|
$this->execute_remote_command([
|
||||||
|
executeInDocker($this->deployment_uuid, "docker build $this->buildTarget $this->addHosts --network host -f {$this->workdir}/{$this->dockerfile_location} {$this->build_args} --progress plain -t $this->build_image_name {$this->workdir}"), "hidden" => true
|
||||||
|
]);
|
||||||
|
|
||||||
|
$dockerfile = base64_encode("FROM {$this->application->static_image}
|
||||||
|
WORKDIR /usr/share/nginx/html/
|
||||||
|
LABEL coolify.deploymentId={$this->deployment_uuid}
|
||||||
|
COPY --from=$this->build_image_name /app/{$this->application->publish_directory} .
|
||||||
|
COPY ./nginx.conf /etc/nginx/conf.d/default.conf");
|
||||||
|
|
||||||
|
$nginx_config = base64_encode("server {
|
||||||
|
listen 80;
|
||||||
|
listen [::]:80;
|
||||||
|
server_name localhost;
|
||||||
|
|
||||||
|
location / {
|
||||||
|
root /usr/share/nginx/html;
|
||||||
|
index index.html;
|
||||||
|
try_files \$uri \$uri.html \$uri/index.html \$uri/ /index.html =404;
|
||||||
|
}
|
||||||
|
|
||||||
|
error_page 500 502 503 504 /50x.html;
|
||||||
|
location = /50x.html {
|
||||||
|
root /usr/share/nginx/html;
|
||||||
|
}
|
||||||
|
}");
|
||||||
|
}
|
||||||
$this->execute_remote_command(
|
$this->execute_remote_command(
|
||||||
[
|
[
|
||||||
executeInDocker($this->deployment_uuid, "echo '{$dockerfile}' | base64 -d > {$this->workdir}/Dockerfile-prod")
|
executeInDocker($this->deployment_uuid, "echo '{$dockerfile}' | base64 -d > {$this->workdir}/Dockerfile")
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
executeInDocker($this->deployment_uuid, "echo '{$nginx_config}' | base64 -d > {$this->workdir}/nginx.conf")
|
executeInDocker($this->deployment_uuid, "echo '{$nginx_config}' | base64 -d > {$this->workdir}/nginx.conf")
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
executeInDocker($this->deployment_uuid, "docker build $this->addHosts --network host -f {$this->workdir}/Dockerfile-prod {$this->build_args} --progress plain -t $this->production_image_name {$this->workdir}"), "hidden" => true
|
executeInDocker($this->deployment_uuid, "docker build $this->addHosts --network host -f {$this->workdir}/Dockerfile {$this->build_args} --progress plain -t $this->production_image_name {$this->workdir}"), "hidden" => true
|
||||||
]
|
]
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
@@ -971,18 +1020,30 @@ COPY ./nginx.conf /etc/nginx/conf.d/default.conf");
|
|||||||
|
|
||||||
private function stop_running_container(bool $force = false)
|
private function stop_running_container(bool $force = false)
|
||||||
{
|
{
|
||||||
if ($this->currently_running_container_name) {
|
$this->execute_remote_command(["echo -n 'Removing old version of your application.'"]);
|
||||||
if ($this->newVersionIsHealthy || $force) {
|
|
||||||
$this->execute_remote_command(
|
if ($this->newVersionIsHealthy || $force) {
|
||||||
["echo -n 'Removing old version of your application.'"],
|
$containers = getCurrentApplicationContainerStatus($this->server, $this->application->id, $this->pull_request_id);
|
||||||
[executeInDocker($this->deployment_uuid, "docker rm -f $this->currently_running_container_name >/dev/null 2>&1"), "hidden" => true, "ignore_errors" => true],
|
if ($this->pull_request_id !== 0) {
|
||||||
);
|
$containers = $containers->filter(function ($container) {
|
||||||
|
return data_get($container, 'Names') === $this->container_name;
|
||||||
|
});
|
||||||
} else {
|
} else {
|
||||||
$this->execute_remote_command(
|
$containers = $containers->filter(function ($container) {
|
||||||
["echo -n 'New version is not healthy, rolling back to the old version.'"],
|
return data_get($container, 'Names') !== $this->container_name;
|
||||||
[executeInDocker($this->deployment_uuid, "docker rm -f $this->container_name >/dev/null 2>&1"), "hidden" => true, "ignore_errors" => true],
|
});
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
$containers->each(function ($container) {
|
||||||
|
$containerName = data_get($container, 'Names');
|
||||||
|
$this->execute_remote_command(
|
||||||
|
[executeInDocker($this->deployment_uuid, "docker rm -f $containerName >/dev/null 2>&1"), "hidden" => true, "ignore_errors" => true],
|
||||||
|
);
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
$this->execute_remote_command(
|
||||||
|
["echo -n 'New version is not healthy, rolling back to the old version.'"],
|
||||||
|
[executeInDocker($this->deployment_uuid, "docker rm -f $this->container_name >/dev/null 2>&1"), "hidden" => true, "ignore_errors" => true],
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1048,8 +1109,11 @@ COPY ./nginx.conf /etc/nginx/conf.d/default.conf");
|
|||||||
{
|
{
|
||||||
$this->execute_remote_command(
|
$this->execute_remote_command(
|
||||||
["echo 'Oops something is not okay, are you okay? 😢'"],
|
["echo 'Oops something is not okay, are you okay? 😢'"],
|
||||||
["echo '{$exception->getMessage()}'"]
|
["echo '{$exception->getMessage()}'"],
|
||||||
|
["echo -n 'Deployment failed. Removing the new version of your application.'"],
|
||||||
|
[executeInDocker($this->deployment_uuid, "docker rm -f $this->container_name >/dev/null 2>&1"), "hidden" => true, "ignore_errors" => true]
|
||||||
);
|
);
|
||||||
|
|
||||||
$this->next(ApplicationDeploymentStatus::FAILED->value);
|
$this->next(ApplicationDeploymentStatus::FAILED->value);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -36,6 +36,7 @@ class DatabaseBackupJob implements ShouldQueue, ShouldBeEncrypted
|
|||||||
public StandalonePostgresql|StandaloneMongodb|StandaloneMysql|StandaloneMariadb|ServiceDatabase $database;
|
public StandalonePostgresql|StandaloneMongodb|StandaloneMysql|StandaloneMariadb|ServiceDatabase $database;
|
||||||
|
|
||||||
public ?string $container_name = null;
|
public ?string $container_name = null;
|
||||||
|
public ?string $directory_name = null;
|
||||||
public ?ScheduledDatabaseBackupExecution $backup_log = null;
|
public ?ScheduledDatabaseBackupExecution $backup_log = null;
|
||||||
public string $backup_status = 'failed';
|
public string $backup_status = 'failed';
|
||||||
public ?string $backup_location = null;
|
public ?string $backup_location = null;
|
||||||
@@ -88,27 +89,98 @@ class DatabaseBackupJob implements ShouldQueue, ShouldBeEncrypted
|
|||||||
if (data_get($this->backup, 'database_type') === 'App\Models\ServiceDatabase') {
|
if (data_get($this->backup, 'database_type') === 'App\Models\ServiceDatabase') {
|
||||||
$databaseType = $this->database->databaseType();
|
$databaseType = $this->database->databaseType();
|
||||||
$serviceUuid = $this->database->service->uuid;
|
$serviceUuid = $this->database->service->uuid;
|
||||||
|
$serviceName = str($this->database->service->name)->slug();
|
||||||
if ($databaseType === 'standalone-postgresql') {
|
if ($databaseType === 'standalone-postgresql') {
|
||||||
$this->container_name = "postgresql-$serviceUuid";
|
$this->container_name = "{$this->database->name}-$serviceUuid";
|
||||||
|
$this->directory_name = $serviceName . '-' . $this->container_name;
|
||||||
$commands[] = "docker exec $this->container_name env | grep POSTGRES_";
|
$commands[] = "docker exec $this->container_name env | grep POSTGRES_";
|
||||||
$envs = instant_remote_process($commands, $this->server);
|
$envs = instant_remote_process($commands, $this->server);
|
||||||
$databasesToBackup = Str::of($envs)->after('POSTGRES_DB=')->before("\n")->value();
|
$envs = str($envs)->explode("\n");
|
||||||
$this->database->postgres_user = Str::of($envs)->after('POSTGRES_USER=')->before("\n")->value();
|
|
||||||
|
$user = $envs->filter(function ($env) {
|
||||||
|
return str($env)->startsWith('POSTGRES_USER=');
|
||||||
|
})->first();
|
||||||
|
if ($user) {
|
||||||
|
$this->database->postgres_user = str($user)->after('POSTGRES_USER=')->value();
|
||||||
|
} else {
|
||||||
|
$this->database->postgres_user = 'postgres';
|
||||||
|
}
|
||||||
|
|
||||||
|
$db = $envs->filter(function ($env) {
|
||||||
|
return str($env)->startsWith('POSTGRES_DB=');
|
||||||
|
})->first();
|
||||||
|
|
||||||
|
if ($db) {
|
||||||
|
$databasesToBackup = str($db)->after('POSTGRES_DB=')->value();
|
||||||
|
} else {
|
||||||
|
$databasesToBackup = $this->database->postgres_user;
|
||||||
|
}
|
||||||
} else if ($databaseType === 'standalone-mysql') {
|
} else if ($databaseType === 'standalone-mysql') {
|
||||||
$this->container_name = "mysql-$serviceUuid";
|
$this->container_name = "{$this->database->name}-$serviceUuid";
|
||||||
|
$this->directory_name = $serviceName . '-' . $this->container_name;
|
||||||
$commands[] = "docker exec $this->container_name env | grep MYSQL_";
|
$commands[] = "docker exec $this->container_name env | grep MYSQL_";
|
||||||
$envs = instant_remote_process($commands, $this->server);
|
$envs = instant_remote_process($commands, $this->server);
|
||||||
$databasesToBackup = Str::of($envs)->after('MYSQL_DATABASE=')->before("\n")->value();
|
$envs = str($envs)->explode("\n");
|
||||||
$this->database->mysql_root_password = Str::of($envs)->after('MYSQL_ROOT_PASSWORD=')->before("\n")->value();
|
|
||||||
|
$rootPassword = $envs->filter(function ($env) {
|
||||||
|
return str($env)->startsWith('MYSQL_ROOT_PASSWORD=');
|
||||||
|
})->first();
|
||||||
|
if ($rootPassword) {
|
||||||
|
$this->database->mysql_root_password = str($rootPassword)->after('MYSQL_ROOT_PASSWORD=')->value();
|
||||||
|
}
|
||||||
|
|
||||||
|
$db = $envs->filter(function ($env) {
|
||||||
|
return str($env)->startsWith('MYSQL_DATABASE=');
|
||||||
|
})->first();
|
||||||
|
|
||||||
|
if ($db) {
|
||||||
|
$databasesToBackup = str($db)->after('MYSQL_DATABASE=')->value();
|
||||||
|
} else {
|
||||||
|
throw new \Exception('MYSQL_DATABASE not found');
|
||||||
|
}
|
||||||
} else if ($databaseType === 'standalone-mariadb') {
|
} else if ($databaseType === 'standalone-mariadb') {
|
||||||
$this->container_name = "mariadb-$serviceUuid";
|
$this->container_name = "{$this->database->name}-$serviceUuid";
|
||||||
|
$this->directory_name = $serviceName . '-' . $this->container_name;
|
||||||
$commands[] = "docker exec $this->container_name env | grep MARIADB_";
|
$commands[] = "docker exec $this->container_name env | grep MARIADB_";
|
||||||
$envs = instant_remote_process($commands, $this->server);
|
$envs = instant_remote_process($commands, $this->server);
|
||||||
$databasesToBackup = Str::of($envs)->after('MARIADB_DATABASE=')->before("\n")->value();
|
$envs = str($envs)->explode("\n");
|
||||||
$this->database->mysql_root_password = Str::of($envs)->after('MARIADB_ROOT_PASSWORD=')->before("\n")->value();
|
|
||||||
|
$rootPassword = $envs->filter(function ($env) {
|
||||||
|
return str($env)->startsWith('MARIADB_ROOT_PASSWORD=');
|
||||||
|
})->first();
|
||||||
|
if ($rootPassword) {
|
||||||
|
$this->database->mysql_root_password = str($rootPassword)->after('MARIADB_ROOT_PASSWORD=')->value();
|
||||||
|
} else {
|
||||||
|
$rootPassword = $envs->filter(function ($env) {
|
||||||
|
return str($env)->startsWith('MYSQL_ROOT_PASSWORD=');
|
||||||
|
})->first();
|
||||||
|
if ($rootPassword) {
|
||||||
|
$this->database->mysql_root_password = str($rootPassword)->after('MYSQL_ROOT_PASSWORD=')->value();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$db = $envs->filter(function ($env) {
|
||||||
|
return str($env)->startsWith('MARIADB_DATABASE=');
|
||||||
|
})->first();
|
||||||
|
|
||||||
|
if ($db) {
|
||||||
|
$databasesToBackup = str($db)->after('MARIADB_DATABASE=')->value();
|
||||||
|
} else {
|
||||||
|
$db = $envs->filter(function ($env) {
|
||||||
|
return str($env)->startsWith('MYSQL_DATABASE=');
|
||||||
|
})->first();
|
||||||
|
|
||||||
|
if ($db) {
|
||||||
|
$databasesToBackup = str($db)->after('MYSQL_DATABASE=')->value();
|
||||||
|
} else {
|
||||||
|
throw new \Exception('MARIADB_DATABASE or MYSQL_DATABASE not found');
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
$databaseName = str($this->database->name)->slug()->value();
|
||||||
$this->container_name = $this->database->uuid;
|
$this->container_name = $this->database->uuid;
|
||||||
|
$this->directory_name = $databaseName . '-' . $this->container_name;
|
||||||
$databaseType = $this->database->type();
|
$databaseType = $this->database->type();
|
||||||
$databasesToBackup = data_get($this->backup, 'databases_to_backup');
|
$databasesToBackup = data_get($this->backup, 'databases_to_backup');
|
||||||
}
|
}
|
||||||
@@ -147,11 +219,11 @@ class DatabaseBackupJob implements ShouldQueue, ShouldBeEncrypted
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
$this->backup_dir = backup_dir() . "/databases/" . Str::of($this->team->name)->slug() . '-' . $this->team->id . '/' . $this->container_name;
|
$this->backup_dir = backup_dir() . "/databases/" . Str::of($this->team->name)->slug() . '-' . $this->team->id . '/' . $this->directory_name;
|
||||||
|
|
||||||
if ($this->database->name === 'coolify-db') {
|
if ($this->database->name === 'coolify-db') {
|
||||||
$databasesToBackup = ['coolify'];
|
$databasesToBackup = ['coolify'];
|
||||||
$this->container_name = "coolify-db";
|
$this->directory_name = $this->container_name = "coolify-db";
|
||||||
$ip = Str::slug($this->server->ip);
|
$ip = Str::slug($this->server->ip);
|
||||||
$this->backup_dir = backup_dir() . "/coolify" . "/coolify-db-$ip";
|
$this->backup_dir = backup_dir() . "/coolify" . "/coolify-db-$ip";
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,6 +3,7 @@
|
|||||||
namespace App\Jobs;
|
namespace App\Jobs;
|
||||||
|
|
||||||
use App\Models\Server;
|
use App\Models\Server;
|
||||||
|
use Exception;
|
||||||
use Illuminate\Bus\Queueable;
|
use Illuminate\Bus\Queueable;
|
||||||
use Illuminate\Contracts\Queue\ShouldBeEncrypted;
|
use Illuminate\Contracts\Queue\ShouldBeEncrypted;
|
||||||
use Illuminate\Contracts\Queue\ShouldQueue;
|
use Illuminate\Contracts\Queue\ShouldQueue;
|
||||||
@@ -10,12 +11,13 @@ use Illuminate\Foundation\Bus\Dispatchable;
|
|||||||
use Illuminate\Queue\InteractsWithQueue;
|
use Illuminate\Queue\InteractsWithQueue;
|
||||||
use Illuminate\Queue\Middleware\WithoutOverlapping;
|
use Illuminate\Queue\Middleware\WithoutOverlapping;
|
||||||
use Illuminate\Queue\SerializesModels;
|
use Illuminate\Queue\SerializesModels;
|
||||||
|
use Illuminate\Support\Facades\Log;
|
||||||
|
|
||||||
class DockerCleanupJob implements ShouldQueue, ShouldBeEncrypted
|
class DockerCleanupJob implements ShouldQueue, ShouldBeEncrypted
|
||||||
{
|
{
|
||||||
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
|
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
|
||||||
|
|
||||||
public $timeout = 1000;
|
public $timeout = 300;
|
||||||
public ?string $dockerRootFilesystem = null;
|
public ?string $dockerRootFilesystem = null;
|
||||||
public ?int $usageBefore = null;
|
public ?int $usageBefore = null;
|
||||||
|
|
||||||
@@ -33,14 +35,15 @@ class DockerCleanupJob implements ShouldQueue, ShouldBeEncrypted
|
|||||||
}
|
}
|
||||||
public function handle(): void
|
public function handle(): void
|
||||||
{
|
{
|
||||||
$queuedCount = 0;
|
$isInprogress = false;
|
||||||
$this->server->applications()->each(function ($application) use ($queuedCount) {
|
$this->server->applications()->each(function ($application) use (&$isInprogress) {
|
||||||
$count = data_get($application->deployments(), 'count', 0);
|
if ($application->isDeploymentInprogress()) {
|
||||||
$queuedCount += $count;
|
$isInprogress = true;
|
||||||
|
return;
|
||||||
|
}
|
||||||
});
|
});
|
||||||
if ($queuedCount > 0) {
|
if ($isInprogress) {
|
||||||
ray('DockerCleanupJob: ApplicationDeploymentQueue is not empty, skipping')->color('orange');
|
throw new Exception('DockerCleanupJob: ApplicationDeploymentQueue is not empty, skipping...');
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
if (!$this->server->isFunctional()) {
|
if (!$this->server->isFunctional()) {
|
||||||
@@ -49,23 +52,25 @@ class DockerCleanupJob implements ShouldQueue, ShouldBeEncrypted
|
|||||||
$this->dockerRootFilesystem = "/";
|
$this->dockerRootFilesystem = "/";
|
||||||
$this->usageBefore = $this->getFilesystemUsage();
|
$this->usageBefore = $this->getFilesystemUsage();
|
||||||
if ($this->usageBefore >= $this->server->settings->cleanup_after_percentage) {
|
if ($this->usageBefore >= $this->server->settings->cleanup_after_percentage) {
|
||||||
ray('Cleaning up ' . $this->server->name)->color('orange');
|
ray('Cleaning up ' . $this->server->name);
|
||||||
instant_remote_process(['docker image prune -af'], $this->server);
|
instant_remote_process(['docker image prune -af'], $this->server);
|
||||||
instant_remote_process(['docker container prune -f --filter "label=coolify.managed=true"'], $this->server);
|
instant_remote_process(['docker container prune -f --filter "label=coolify.managed=true"'], $this->server);
|
||||||
instant_remote_process(['docker builder prune -af'], $this->server);
|
instant_remote_process(['docker builder prune -af'], $this->server);
|
||||||
$usageAfter = $this->getFilesystemUsage();
|
$usageAfter = $this->getFilesystemUsage();
|
||||||
if ($usageAfter < $this->usageBefore) {
|
if ($usageAfter < $this->usageBefore) {
|
||||||
ray('Saved ' . ($this->usageBefore - $usageAfter) . '% disk space on ' . $this->server->name)->color('orange');
|
ray('Saved ' . ($this->usageBefore - $usageAfter) . '% disk space on ' . $this->server->name);
|
||||||
send_internal_notification('DockerCleanupJob done: Saved ' . ($this->usageBefore - $usageAfter) . '% disk space on ' . $this->server->name);
|
send_internal_notification('DockerCleanupJob done: Saved ' . ($this->usageBefore - $usageAfter) . '% disk space on ' . $this->server->name);
|
||||||
|
Log::info('DockerCleanupJob done: Saved ' . ($this->usageBefore - $usageAfter) . '% disk space on ' . $this->server->name);
|
||||||
} else {
|
} else {
|
||||||
ray('DockerCleanupJob failed to save disk space on ' . $this->server->name)->color('orange');
|
Log::info('DockerCleanupJob failed to save disk space on ' . $this->server->name);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
ray('No need to clean up ' . $this->server->name)->color('orange');
|
ray('No need to clean up ' . $this->server->name);
|
||||||
|
Log::info('No need to clean up ' . $this->server->name);
|
||||||
}
|
}
|
||||||
} catch (\Throwable $e) {
|
} catch (\Throwable $e) {
|
||||||
send_internal_notification('DockerCleanupJob failed with: ' . $e->getMessage());
|
send_internal_notification('DockerCleanupJob failed with: ' . $e->getMessage());
|
||||||
ray($e->getMessage())->color('orange');
|
ray($e->getMessage());
|
||||||
throw $e;
|
throw $e;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -213,6 +213,14 @@ class Application extends BaseModel
|
|||||||
return $this->morphTo();
|
return $this->morphTo();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function isDeploymentInprogress() {
|
||||||
|
$deployments = ApplicationDeploymentQueue::where('application_id', $this->id)->where('status', 'in_progress')->count();
|
||||||
|
if ($deployments > 0) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
public function deployments(int $skip = 0, int $take = 10)
|
public function deployments(int $skip = 0, int $take = 10)
|
||||||
{
|
{
|
||||||
$deployments = ApplicationDeploymentQueue::where('application_id', $this->id)->orderBy('created_at', 'desc');
|
$deployments = ApplicationDeploymentQueue::where('application_id', $this->id)->orderBy('created_at', 'desc');
|
||||||
|
|||||||
@@ -28,6 +28,16 @@ class ServiceDatabase extends BaseModel
|
|||||||
}
|
}
|
||||||
return "standalone-$image";
|
return "standalone-$image";
|
||||||
}
|
}
|
||||||
|
public function getServiceDatabaseUrl() {
|
||||||
|
// $type = $this->databaseType();
|
||||||
|
$port = $this->public_port;
|
||||||
|
$realIp = $this->service->server->ip;
|
||||||
|
if ($realIp === 'host.docker.internal' || isDev()) {
|
||||||
|
$realIp = base_ip();
|
||||||
|
}
|
||||||
|
$url = "{$realIp}:{$port}";
|
||||||
|
return $url;
|
||||||
|
}
|
||||||
public function service()
|
public function service()
|
||||||
{
|
{
|
||||||
return $this->belongsTo(Service::class);
|
return $this->belongsTo(Service::class);
|
||||||
|
|||||||
@@ -58,8 +58,9 @@ class StandaloneRedis extends BaseModel
|
|||||||
{
|
{
|
||||||
return 'standalone-redis';
|
return 'standalone-redis';
|
||||||
}
|
}
|
||||||
public function getDbUrl(): string {
|
public function getDbUrl(bool $useInternal = false): string
|
||||||
if ($this->is_public) {
|
{
|
||||||
|
if ($this->is_public && !$useInternal) {
|
||||||
return "redis://:{$this->redis_password}@{$this->destination->server->getIp}:{$this->public_port}/0";
|
return "redis://:{$this->redis_password}@{$this->destination->server->getIp}:{$this->public_port}/0";
|
||||||
} else {
|
} else {
|
||||||
return "redis://:{$this->redis_password}@{$this->uuid}:6379/0";
|
return "redis://:{$this->redis_password}@{$this->uuid}:6379/0";
|
||||||
|
|||||||
@@ -84,11 +84,14 @@ class DeploymentFailed extends Notification implements ShouldQueue
|
|||||||
} else {
|
} else {
|
||||||
$message = 'Coolify: Deployment failed of **' . $this->application_name . '** (' . $this->fqdn . '): ';
|
$message = 'Coolify: Deployment failed of **' . $this->application_name . '** (' . $this->fqdn . '): ';
|
||||||
}
|
}
|
||||||
|
$buttons[] = [
|
||||||
|
"text" => "Deployment logs",
|
||||||
|
"url" => $this->deployment_url
|
||||||
|
];
|
||||||
return [
|
return [
|
||||||
"message" => $message,
|
"message" => $message,
|
||||||
"buttons" => [
|
"buttons" => [
|
||||||
"text" => "View Deployment Logs",
|
...$buttons
|
||||||
"url" => $this->deployment_url
|
|
||||||
],
|
],
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ class Input extends Component
|
|||||||
public bool $readonly = false,
|
public bool $readonly = false,
|
||||||
public string|null $helper = null,
|
public string|null $helper = null,
|
||||||
public bool $allowToPeak = true,
|
public bool $allowToPeak = true,
|
||||||
public string $defaultClass = "input input-sm bg-coolgray-200 rounded text-white w-full disabled:bg-coolgray-200/50 disabled:border-none placeholder:text-coolgray-500 read-only:text-neutral-500 read-only:bg-coolgray-200/50"
|
public string $defaultClass = "input input-sm bg-coolgray-100 rounded text-white w-full disabled:bg-coolgray-200/50 disabled:border-none placeholder:text-coolgray-500 read-only:text-neutral-500 read-only:bg-coolgray-200/50"
|
||||||
) {
|
) {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ class Select extends Component
|
|||||||
public string|null $label = null,
|
public string|null $label = null,
|
||||||
public string|null $helper = null,
|
public string|null $helper = null,
|
||||||
public bool $required = false,
|
public bool $required = false,
|
||||||
public string $defaultClass = "select select-sm w-full rounded text-white text-sm bg-coolgray-200 font-normal disabled:bg-coolgray-200/50 disabled:border-none"
|
public string $defaultClass = "select select-sm w-full rounded text-white text-sm bg-coolgray-100 font-normal disabled:bg-coolgray-200/50 disabled:border-none"
|
||||||
) {
|
) {
|
||||||
//
|
//
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -25,7 +25,7 @@ class Textarea extends Component
|
|||||||
public bool $readonly = false,
|
public bool $readonly = false,
|
||||||
public string|null $helper = null,
|
public string|null $helper = null,
|
||||||
public bool $realtimeValidation = false,
|
public bool $realtimeValidation = false,
|
||||||
public string $defaultClass = "textarea leading-normal bg-coolgray-200 rounded text-white scrollbar disabled:bg-coolgray-200/50 disabled:border-none placeholder:text-coolgray-500 read-only:text-neutral-500 read-only:bg-coolgray-200/50"
|
public string $defaultClass = "textarea leading-normal bg-coolgray-100 rounded text-white scrollbar disabled:bg-coolgray-200/50 disabled:border-none placeholder:text-coolgray-500 read-only:text-neutral-500 read-only:bg-coolgray-200/50"
|
||||||
) {
|
) {
|
||||||
//
|
//
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,15 +10,24 @@ use Visus\Cuid2\Cuid2;
|
|||||||
|
|
||||||
function getCurrentApplicationContainerStatus(Server $server, int $id, ?int $pullRequestId = null): Collection
|
function getCurrentApplicationContainerStatus(Server $server, int $id, ?int $pullRequestId = null): Collection
|
||||||
{
|
{
|
||||||
if ($pullRequestId) {
|
ray($id, $pullRequestId);
|
||||||
$containers = instant_remote_process(["docker ps -a --filter='label=coolify.applicationId={$id}' --filter='label=coolify.pullRequestId={$pullRequestId}' --format '{{json .}}' "], $server);
|
$containers = collect([]);
|
||||||
} else {
|
$containers = instant_remote_process(["docker ps -a --filter='label=coolify.applicationId={$id}' --format '{{json .}}' "], $server);
|
||||||
$containers = instant_remote_process(["docker ps -a --filter='label=coolify.applicationId={$id}' --format '{{json .}}'"], $server);
|
$containers = format_docker_command_output_to_json($containers);
|
||||||
}
|
$containers = $containers->map(function ($container) use ($pullRequestId) {
|
||||||
if (!$containers) {
|
$labels = data_get($container, 'Labels');
|
||||||
return collect([]);
|
if (!str($labels)->contains("coolify.pullRequestId=")) {
|
||||||
}
|
data_set($container, 'Labels', $labels . ",coolify.pullRequestId={$pullRequestId}");
|
||||||
return format_docker_command_output_to_json($containers);
|
return $container;
|
||||||
|
}
|
||||||
|
if (str($labels)->contains("coolify.pullRequestId=$pullRequestId")) {
|
||||||
|
return $container;
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
});
|
||||||
|
$containers = $containers->filter();
|
||||||
|
ray($containers);
|
||||||
|
return $containers;
|
||||||
}
|
}
|
||||||
|
|
||||||
function format_docker_command_output_to_json($rawOutput): Collection
|
function format_docker_command_output_to_json($rawOutput): Collection
|
||||||
@@ -128,9 +137,7 @@ function defaultLabels($id, $name, $pull_request_id = 0, string $type = 'applica
|
|||||||
$labels->push("coolify." . $type . "Id=" . $id);
|
$labels->push("coolify." . $type . "Id=" . $id);
|
||||||
$labels->push("coolify.type=$type");
|
$labels->push("coolify.type=$type");
|
||||||
$labels->push('coolify.name=' . $name);
|
$labels->push('coolify.name=' . $name);
|
||||||
if ($pull_request_id !== 0) {
|
$labels->push('coolify.pullRequestId=' . $pull_request_id);
|
||||||
$labels->push('coolify.pullRequestId=' . $pull_request_id);
|
|
||||||
}
|
|
||||||
if ($type === 'service') {
|
if ($type === 'service') {
|
||||||
$labels->push('coolify.service.subId=' . $subId);
|
$labels->push('coolify.service.subId=' . $subId);
|
||||||
$labels->push('coolify.service.subType=' . $subType);
|
$labels->push('coolify.service.subType=' . $subType);
|
||||||
|
|||||||
743
composer.lock
generated
743
composer.lock
generated
File diff suppressed because it is too large
Load Diff
@@ -7,7 +7,7 @@ return [
|
|||||||
|
|
||||||
// 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.112',
|
'release' => '4.0.0-beta.121',
|
||||||
// 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'),
|
||||||
|
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ return [
|
|||||||
'stripe_price_id_pro_yearly' => env('STRIPE_PRICE_ID_PRO_YEARLY', null),
|
'stripe_price_id_pro_yearly' => env('STRIPE_PRICE_ID_PRO_YEARLY', null),
|
||||||
'stripe_price_id_ultimate_monthly' => env('STRIPE_PRICE_ID_ULTIMATE_MONTHLY', null),
|
'stripe_price_id_ultimate_monthly' => env('STRIPE_PRICE_ID_ULTIMATE_MONTHLY', null),
|
||||||
'stripe_price_id_ultimate_yearly' => env('STRIPE_PRICE_ID_ULTIMATE_YEARLY', null),
|
'stripe_price_id_ultimate_yearly' => env('STRIPE_PRICE_ID_ULTIMATE_YEARLY', null),
|
||||||
|
'stripe_excluded_plans' => env('STRIPE_EXCLUDED_PLANS', null),
|
||||||
|
|
||||||
|
|
||||||
// Paddle
|
// Paddle
|
||||||
|
|||||||
@@ -1,3 +1,3 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
return '4.0.0-beta.112';
|
return '4.0.0-beta.121';
|
||||||
|
|||||||
@@ -0,0 +1,30 @@
|
|||||||
|
<?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('standalone_postgresqls', function (Blueprint $table) {
|
||||||
|
$table->longText('postgres_conf')->nullable();
|
||||||
|
$table->string('image')->default('postgres:16-alpine')->change();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reverse the migrations.
|
||||||
|
*/
|
||||||
|
public function down(): void
|
||||||
|
{
|
||||||
|
Schema::table('standalone_postgresqls', function (Blueprint $table) {
|
||||||
|
$table->dropColumn('postgres_conf');
|
||||||
|
$table->string('image')->default('postgres:15-alpine')->change();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
@@ -0,0 +1,30 @@
|
|||||||
|
<?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('service_databases', function (Blueprint $table) {
|
||||||
|
$table->integer('public_port')->nullable();
|
||||||
|
$table->boolean('is_public')->default(false);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reverse the migrations.
|
||||||
|
*/
|
||||||
|
public function down(): void
|
||||||
|
{
|
||||||
|
Schema::table('service_databases', function (Blueprint $table) {
|
||||||
|
$table->dropColumn('public_port');
|
||||||
|
$table->dropColumn('is_public');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
@@ -53,7 +53,7 @@ a {
|
|||||||
@apply text-white;
|
@apply text-white;
|
||||||
}
|
}
|
||||||
.box {
|
.box {
|
||||||
@apply flex p-2 transition-colors cursor-pointer min-h-16 bg-coolgray-200 hover:bg-coollabs-100 hover:text-white hover:no-underline min-w-[24rem];
|
@apply flex p-2 transition-colors cursor-pointer min-h-16 bg-coolgray-100 hover:bg-coollabs-100 hover:text-white hover:no-underline min-w-[24rem];
|
||||||
}
|
}
|
||||||
.box-without-bg {
|
.box-without-bg {
|
||||||
@apply flex p-2 transition-colors min-h-16 hover:text-white hover:no-underline min-w-[24rem];
|
@apply flex p-2 transition-colors min-h-16 hover:text-white hover:no-underline min-w-[24rem];
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
<template>
|
<template>
|
||||||
<Transition name="fade">
|
<Transition name="fade">
|
||||||
<div>
|
<div>
|
||||||
<div class="flex items-center p-1 px-2 overflow-hidden transition-all transform rounded cursor-pointer bg-coolgray-200"
|
<div class="flex items-center p-1 px-2 overflow-hidden transition-all transform rounded cursor-pointer bg-coolgray-100"
|
||||||
@click="showCommandPalette = true">
|
@click="showCommandPalette = true">
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" class="w-6 h-6 icon" viewBox="0 0 24 24" stroke-width="2"
|
<svg xmlns="http://www.w3.org/2000/svg" class="w-6 h-6 icon" viewBox="0 0 24 24" stroke-width="2"
|
||||||
stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round">
|
stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round">
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
@auth
|
@auth
|
||||||
<nav class="fixed h-full overflow-hidden overflow-y-auto pt-14 scrollbar">
|
<nav class="fixed h-full overflow-hidden overflow-y-auto pt-14 scrollbar">
|
||||||
<a href="/" class="fixed top-0 z-50 mx-3 mt-3 cursor-pointer bg-coolgray-100"><img class="transition rounded w-11 h-11" src="{{ asset('coolify-transparent.png') }}"></a>
|
<a href="/" class="fixed top-0 z-50 mx-3 mt-3 bg-transparent cursor-pointer"><img
|
||||||
|
class="transition rounded w-11 h-11" src="{{ asset('coolify-transparent.png') }}"></a>
|
||||||
<ul class="flex flex-col h-full gap-4 menu flex-nowrap">
|
<ul class="flex flex-col h-full gap-4 menu flex-nowrap">
|
||||||
<li title="Dashboard">
|
<li title="Dashboard">
|
||||||
<a class="hover:bg-transparent" @if (!request()->is('/')) href="/" @endif>
|
<a class="hover:bg-transparent" @if (!request()->is('/')) href="/" @endif>
|
||||||
@@ -11,6 +12,18 @@
|
|||||||
</svg>
|
</svg>
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
|
<li title="Help us!">
|
||||||
|
<a class="hover:bg-transparent"href="https://coolify.io/sponsorships" target="_blank">
|
||||||
|
<svg class="icon hover:text-pink-500" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<g fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round"
|
||||||
|
stroke-width="2">
|
||||||
|
<path d="M19.5 12.572L12 20l-7.5-7.428A5 5 0 1 1 12 6.006a5 5 0 1 1 7.5 6.572" />
|
||||||
|
<path
|
||||||
|
d="M12 6L8.707 9.293a1 1 0 0 0 0 1.414l.543.543c.69.69 1.81.69 2.5 0l1-1a3.182 3.182 0 0 1 4.5 0l2.25 2.25m-7 3l2 2M15 13l2 2" />
|
||||||
|
</g>
|
||||||
|
</svg>
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
<li title="Send us feedback or get help!" class="fixed top-0 right-0 p-2 px-4 pt-4 mt-auto text-xs">
|
<li title="Send us feedback or get help!" class="fixed top-0 right-0 p-2 px-4 pt-4 mt-auto text-xs">
|
||||||
<div class="justify-center" wire:click="help" onclick="help.showModal()">
|
<div class="justify-center" wire:click="help" onclick="help.showModal()">
|
||||||
<svg class="w-5 h-5" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
|
<svg class="w-5 h-5" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
@auth
|
@auth
|
||||||
<nav class="fixed h-full overflow-hidden overflow-y-auto pt-28 scrollbar">
|
<nav class="fixed h-full overflow-hidden overflow-y-auto pt-28 scrollbar">
|
||||||
<a href="/" class="fixed top-0 z-50 mx-3 mt-3 cursor-pointer bg-coolgray-100"><img class="transition rounded w-11 h-11" src="{{ asset('coolify-transparent.png') }}"></a>
|
<a href="/" class="fixed top-0 z-50 mx-3 mt-3 bg-transparent cursor-pointer"><img
|
||||||
|
class="transition rounded w-11 h-11" src="{{ asset('coolify-transparent.png') }}"></a>
|
||||||
<ul class="flex flex-col h-full gap-4 menu flex-nowrap">
|
<ul class="flex flex-col h-full gap-4 menu flex-nowrap">
|
||||||
<li title="Dashboard">
|
<li title="Dashboard">
|
||||||
<a class="hover:bg-transparent" @if (!request()->is('/')) href="/" @endif>
|
<a class="hover:bg-transparent" @if (!request()->is('/')) href="/" @endif>
|
||||||
@@ -87,6 +88,18 @@
|
|||||||
@if (isInstanceAdmin() && !isCloud())
|
@if (isInstanceAdmin() && !isCloud())
|
||||||
<livewire:upgrade />
|
<livewire:upgrade />
|
||||||
@endif
|
@endif
|
||||||
|
<li title="Help us!">
|
||||||
|
<a class="hover:bg-transparent"href="https://coolify.io/sponsorships" target="_blank">
|
||||||
|
<svg class="icon hover:text-pink-500" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<g fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round"
|
||||||
|
stroke-width="2">
|
||||||
|
<path d="M19.5 12.572L12 20l-7.5-7.428A5 5 0 1 1 12 6.006a5 5 0 1 1 7.5 6.572" />
|
||||||
|
<path
|
||||||
|
d="M12 6L8.707 9.293a1 1 0 0 0 0 1.414l.543.543c.69.69 1.81.69 2.5 0l1-1a3.182 3.182 0 0 1 4.5 0l2.25 2.25m-7 3l2 2M15 13l2 2" />
|
||||||
|
</g>
|
||||||
|
</svg>
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
<li title="Profile">
|
<li title="Profile">
|
||||||
<a class="hover:bg-transparent" @if (!request()->is('profile')) href="/profile" @endif>
|
<a class="hover:bg-transparent" @if (!request()->is('profile')) href="/profile" @endif>
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" class="icon" viewBox="0 0 24 24" stroke-width="1.5"
|
<svg xmlns="http://www.w3.org/2000/svg" class="icon" viewBox="0 0 24 24" stroke-width="1.5"
|
||||||
|
|||||||
@@ -25,7 +25,6 @@
|
|||||||
@endif
|
@endif
|
||||||
</head>
|
</head>
|
||||||
@section('body')
|
@section('body')
|
||||||
|
|
||||||
<body>
|
<body>
|
||||||
@livewireScripts
|
@livewireScripts
|
||||||
<dialog id="help" class="modal">
|
<dialog id="help" class="modal">
|
||||||
|
|||||||
@@ -14,7 +14,7 @@
|
|||||||
@endif
|
@endif
|
||||||
<div id="screen" :class="fullscreen ? 'fullscreen' : ''">
|
<div id="screen" :class="fullscreen ? 'fullscreen' : ''">
|
||||||
<div @if ($isKeepAliveOn) wire:poll.2000ms="polling" @endif
|
<div @if ($isKeepAliveOn) wire:poll.2000ms="polling" @endif
|
||||||
class="relative flex flex-col-reverse w-full p-2 px-4 mt-4 overflow-y-auto scrollbar border-coolgray-400"
|
class="relative flex flex-col-reverse w-full p-2 px-4 mt-4 overflow-y-auto text-white bg-coolgray-100 scrollbar border-coolgray-300"
|
||||||
:class="fullscreen ? '' : 'max-h-[40rem] border border-dotted rounded'">
|
:class="fullscreen ? '' : 'max-h-[40rem] border border-dotted rounded'">
|
||||||
<button title="Minimize" x-show="fullscreen" class="fixed top-4 right-4" x-on:click="makeFullscreen"><svg
|
<button title="Minimize" x-show="fullscreen" class="fixed top-4 right-4" x-on:click="makeFullscreen"><svg
|
||||||
class="icon" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
|
class="icon" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
|
||||||
@@ -48,7 +48,7 @@
|
|||||||
@foreach (decode_remote_command_output($application_deployment_queue) as $line)
|
@foreach (decode_remote_command_output($application_deployment_queue) as $line)
|
||||||
<div @class([
|
<div @class([
|
||||||
'font-mono whitespace-pre-line',
|
'font-mono whitespace-pre-line',
|
||||||
'text-neutral-400' => $line['type'] == 'stdout',
|
'text-white' => $line['type'] == 'stdout',
|
||||||
'text-error' => $line['type'] == 'stderr',
|
'text-error' => $line['type'] == 'stderr',
|
||||||
'text-warning' => $line['hidden'],
|
'text-warning' => $line['hidden'],
|
||||||
])>[{{ $line['timestamp'] }}] @if ($line['hidden'])
|
])>[{{ $line['timestamp'] }}] @if ($line['hidden'])
|
||||||
|
|||||||
@@ -12,14 +12,14 @@
|
|||||||
</form>
|
</form>
|
||||||
@forelse ($deployments as $deployment)
|
@forelse ($deployments as $deployment)
|
||||||
<a @class([
|
<a @class([
|
||||||
'bg-coolgray-200 p-2 border-l border-dashed transition-colors hover:no-underline',
|
'bg-coolgray-100 p-2 border-l border-dashed transition-colors hover:no-underline',
|
||||||
'cursor-not-allowed hover:bg-coolgray-200' =>
|
'hover:bg-coolgray-200' =>
|
||||||
data_get($deployment, 'status') === 'queued' ||
|
data_get($deployment, 'status') === 'queued',
|
||||||
data_get($deployment, 'status') === 'cancelled by system',
|
|
||||||
'border-warning hover:bg-warning hover:text-black' =>
|
'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',
|
||||||
'border-error hover:bg-error' =>
|
'border-error hover:bg-error' =>
|
||||||
data_get($deployment, 'status') === 'error',
|
data_get($deployment, 'status') === 'failed',
|
||||||
'border-success hover:bg-success' =>
|
'border-success hover:bg-success' =>
|
||||||
data_get($deployment, 'status') === 'finished',
|
data_get($deployment, 'status') === 'finished',
|
||||||
]) href="{{ $current_url . '/' . data_get($deployment, 'deployment_uuid') }}"
|
]) href="{{ $current_url . '/' . data_get($deployment, 'deployment_uuid') }}"
|
||||||
|
|||||||
@@ -27,10 +27,11 @@
|
|||||||
<div class="flex gap-2">
|
<div class="flex gap-2">
|
||||||
<x-forms.select wire:model="application.build_pack" label="Build Pack" required>
|
<x-forms.select wire:model="application.build_pack" label="Build Pack" required>
|
||||||
<option value="nixpacks">Nixpacks</option>
|
<option value="nixpacks">Nixpacks</option>
|
||||||
|
<option value="static">Static</option>
|
||||||
<option value="dockerfile">Dockerfile</option>
|
<option value="dockerfile">Dockerfile</option>
|
||||||
<option value="dockerimage">Docker Image</option>
|
<option value="dockerimage">Docker Image</option>
|
||||||
</x-forms.select>
|
</x-forms.select>
|
||||||
@if ($application->settings->is_static)
|
@if ($application->settings->is_static || $application->build_pack === 'static')
|
||||||
<x-forms.select id="application.static_image" label="Static Image" required>
|
<x-forms.select id="application.static_image" label="Static Image" required>
|
||||||
<option value="nginx:alpine">nginx:alpine</option>
|
<option value="nginx:alpine">nginx:alpine</option>
|
||||||
<option disabled value="apache:alpine">apache:alpine</option>
|
<option disabled value="apache:alpine">apache:alpine</option>
|
||||||
@@ -50,9 +51,9 @@
|
|||||||
<h3>Build</h3>
|
<h3>Build</h3>
|
||||||
@if ($application->could_set_build_commands())
|
@if ($application->could_set_build_commands())
|
||||||
@if ($application->build_pack === 'nixpacks')
|
@if ($application->build_pack === 'nixpacks')
|
||||||
<div>Nixpacks will detect your package manager/configurations: <a class="underline"
|
<div>Nixpacks will detect the required configuration automatically.
|
||||||
href="https://nixpacks.com/docs/providers">Nixpacks documentation</a></div>
|
<a class="underline" href="https://coolify.io/docs/frameworks/">Framework Specific Docs</a>
|
||||||
<div class="text-warning">You probably do not need to modify the commands below.</div>
|
</div>
|
||||||
<div class="flex flex-col gap-2 xl:flex-row">
|
<div class="flex flex-col gap-2 xl:flex-row">
|
||||||
<x-forms.input placeholder="If you modify this, you probably need to have a nixpacks.toml"
|
<x-forms.input placeholder="If you modify this, you probably need to have a nixpacks.toml"
|
||||||
id="application.install_command" label="Install Command" />
|
id="application.install_command" label="Install Command" />
|
||||||
@@ -72,7 +73,8 @@
|
|||||||
<x-forms.input placeholder="/Dockerfile" id="application.dockerfile_location"
|
<x-forms.input placeholder="/Dockerfile" id="application.dockerfile_location"
|
||||||
label="Dockerfile Location"
|
label="Dockerfile Location"
|
||||||
helper="It is calculated together with the Base Directory: {{ Str::start($application->base_directory . $application->dockerfile_location, '/') }}" />
|
helper="It is calculated together with the Base Directory: {{ Str::start($application->base_directory . $application->dockerfile_location, '/') }}" />
|
||||||
<x-forms.input id="application.dockerfile_target_build" label="Docker Build Stage Target" helper="Useful if you have multi-staged dockerfile." />
|
<x-forms.input id="application.dockerfile_target_build" label="Docker Build Stage Target"
|
||||||
|
helper="Useful if you have multi-staged dockerfile." />
|
||||||
@endif
|
@endif
|
||||||
@if ($application->could_set_build_commands())
|
@if ($application->could_set_build_commands())
|
||||||
@if ($application->settings->is_static)
|
@if ($application->settings->is_static)
|
||||||
@@ -96,7 +98,7 @@
|
|||||||
@endif
|
@endif
|
||||||
<h3>Network</h3>
|
<h3>Network</h3>
|
||||||
<div class="flex flex-col gap-2 xl:flex-row">
|
<div class="flex flex-col gap-2 xl:flex-row">
|
||||||
@if ($application->settings->is_static)
|
@if ($application->settings->is_static || $application->build_pack === 'static')
|
||||||
<x-forms.input id="application.ports_exposes" label="Ports Exposes" readonly />
|
<x-forms.input id="application.ports_exposes" label="Ports Exposes" readonly />
|
||||||
@else
|
@else
|
||||||
<x-forms.input placeholder="3000,3001" id="application.ports_exposes" label="Ports Exposes" required
|
<x-forms.input placeholder="3000,3001" id="application.ports_exposes" label="Ports Exposes" required
|
||||||
|
|||||||
@@ -49,9 +49,14 @@
|
|||||||
label="Public Port" />
|
label="Public Port" />
|
||||||
<x-forms.checkbox instantSave id="database.is_public" label="Accessible over the internet" />
|
<x-forms.checkbox instantSave id="database.is_public" label="Accessible over the internet" />
|
||||||
</div>
|
</div>
|
||||||
<x-forms.input label="MariaDB URL"
|
<x-forms.input label="MariaDB URL (internal)"
|
||||||
helper="If you change the user/password/port, this could be different. This is with the default values."
|
helper="If you change the user/password/port, this could be different. This is with the default values."
|
||||||
type="password" readonly wire:model="db_url" />
|
type="password" readonly wire:model="db_url" />
|
||||||
|
@if ($db_url_public)
|
||||||
|
<x-forms.input label="MariaDB URL (public)"
|
||||||
|
helper="If you change the user/password/port, this could be different. This is with the default values."
|
||||||
|
type="password" readonly wire:model="db_url_public" />
|
||||||
|
@endif
|
||||||
</div>
|
</div>
|
||||||
<x-forms.textarea label="Custom MariaDB Configuration" rows="10" id="database.mariadb_conf" />
|
<x-forms.textarea label="Custom MariaDB Configuration" rows="10" id="database.mariadb_conf" />
|
||||||
</form>
|
</form>
|
||||||
|
|||||||
@@ -43,9 +43,14 @@
|
|||||||
label="Public Port" />
|
label="Public Port" />
|
||||||
<x-forms.checkbox instantSave id="database.is_public" label="Accessible over the internet" />
|
<x-forms.checkbox instantSave id="database.is_public" label="Accessible over the internet" />
|
||||||
</div>
|
</div>
|
||||||
<x-forms.input label="Mongo URL"
|
<x-forms.input label="Mongo URL (internal)"
|
||||||
helper="If you change the user/password/port, this could be different. This is with the default values."
|
helper="If you change the user/password/port, this could be different. This is with the default values."
|
||||||
type="password" readonly wire:model="db_url" />
|
type="password" readonly wire:model="db_url" />
|
||||||
|
@if ($db_url_public)
|
||||||
|
<x-forms.input label="Mongo URL (public)"
|
||||||
|
helper="If you change the user/password/port, this could be different. This is with the default values."
|
||||||
|
type="password" readonly wire:model="db_url_public" />
|
||||||
|
@endif
|
||||||
</div>
|
</div>
|
||||||
<x-forms.textarea label="Custom MongoDB Configuration" rows="10" id="database.mongo_conf" />
|
<x-forms.textarea label="Custom MongoDB Configuration" rows="10" id="database.mongo_conf" />
|
||||||
</form>
|
</form>
|
||||||
|
|||||||
@@ -49,9 +49,14 @@
|
|||||||
label="Public Port" />
|
label="Public Port" />
|
||||||
<x-forms.checkbox instantSave id="database.is_public" label="Accessible over the internet" />
|
<x-forms.checkbox instantSave id="database.is_public" label="Accessible over the internet" />
|
||||||
</div>
|
</div>
|
||||||
<x-forms.input label="MySQL URL"
|
<x-forms.input label="MySQL URL (internal)"
|
||||||
helper="If you change the user/password/port, this could be different. This is with the default values."
|
helper="If you change the user/password/port, this could be different. This is with the default values."
|
||||||
type="password" readonly wire:model="db_url" />
|
type="password" readonly wire:model="db_url" />
|
||||||
|
@if ($db_url_public)
|
||||||
|
<x-forms.input label="MySQL URL (public)"
|
||||||
|
helper="If you change the user/password/port, this could be different. This is with the default values."
|
||||||
|
type="password" readonly wire:model="db_url_public" />
|
||||||
|
@endif
|
||||||
</div>
|
</div>
|
||||||
<x-forms.textarea label="Custom Mysql Configuration" rows="10" id="database.mysql_conf" />
|
<x-forms.textarea label="Custom Mysql Configuration" rows="10" id="database.mysql_conf" />
|
||||||
</form>
|
</form>
|
||||||
|
|||||||
@@ -31,14 +31,15 @@
|
|||||||
<div class="flex gap-2">
|
<div class="flex gap-2">
|
||||||
<x-forms.input label="Initial Username" id="database.postgres_username" placeholder="If empty: postgres"
|
<x-forms.input label="Initial Username" id="database.postgres_username" placeholder="If empty: postgres"
|
||||||
readonly helper="You can only change this in the database." />
|
readonly helper="You can only change this in the database." />
|
||||||
<x-forms.input label="Initial Password" id="database.postgres_password" type="password" required readonly
|
<x-forms.input label="Initial Password" id="database.postgres_password" type="password" required
|
||||||
helper="You can only change this in the database." />
|
readonly helper="You can only change this in the database." />
|
||||||
<x-forms.input label="Initial Database" id="database.postgres_db"
|
<x-forms.input label="Initial Database" id="database.postgres_db"
|
||||||
placeholder="If empty, it will be the same as Username." readonly
|
placeholder="If empty, it will be the same as Username." readonly
|
||||||
helper="You can only change this in the database." />
|
helper="You can only change this in the database." />
|
||||||
</div>
|
</div>
|
||||||
@else
|
@else
|
||||||
<div class="pt-8 text-warning">Please verify these values. You can only modify them before the initial start. After that, you need to modify it in the database.
|
<div class="pt-8 text-warning">Please verify these values. You can only modify them before the initial
|
||||||
|
start. After that, you need to modify it in the database.
|
||||||
</div>
|
</div>
|
||||||
<div class="flex gap-2 pb-8">
|
<div class="flex gap-2 pb-8">
|
||||||
<x-forms.input label="Username" id="database.postgres_user" placeholder="If empty: postgres" />
|
<x-forms.input label="Username" id="database.postgres_user" placeholder="If empty: postgres" />
|
||||||
@@ -62,8 +63,16 @@
|
|||||||
label="Public Port" />
|
label="Public Port" />
|
||||||
<x-forms.checkbox instantSave id="database.is_public" label="Accessible over the internet" />
|
<x-forms.checkbox instantSave id="database.is_public" label="Accessible over the internet" />
|
||||||
</div>
|
</div>
|
||||||
<x-forms.input label="Postgres URL" helper="If you change the user/password/port, this could be different. This is with the default values." type="password" readonly wire:model="db_url" />
|
<x-forms.input label="Postgres URL (internal)"
|
||||||
|
helper="If you change the user/password/port, this could be different. This is with the default values."
|
||||||
|
type="password" readonly wire:model="db_url" />
|
||||||
|
@if ($db_url_public)
|
||||||
|
<x-forms.input label="Postgres URL (public)"
|
||||||
|
helper="If you change the user/password/port, this could be different. This is with the default values."
|
||||||
|
type="password" readonly wire:model="db_url_public" />
|
||||||
|
@endif
|
||||||
</div>
|
</div>
|
||||||
|
<x-forms.textarea label="Custom PostgreSQL Configuration" rows="10" id="database.postgres_conf" />
|
||||||
</form>
|
</form>
|
||||||
<div class="pb-16">
|
<div class="pb-16">
|
||||||
<div class="flex gap-2 pt-4 pb-2">
|
<div class="flex gap-2 pt-4 pb-2">
|
||||||
|
|||||||
@@ -21,8 +21,17 @@
|
|||||||
label="Public Port" />
|
label="Public Port" />
|
||||||
<x-forms.checkbox instantSave id="database.is_public" label="Accessible over the internet" />
|
<x-forms.checkbox instantSave id="database.is_public" label="Accessible over the internet" />
|
||||||
</div>
|
</div>
|
||||||
<x-forms.input label="Redis URL" helper="If you change the user/password/port, this could be different. This is with the default values." type="password" readonly wire:model="db_url" />
|
<x-forms.input label="Redis URL (internal)"
|
||||||
|
helper="If you change the user/password/port, this could be different. This is with the default values."
|
||||||
|
type="password" readonly wire:model="db_url" />
|
||||||
|
@if ($db_url_public)
|
||||||
|
<x-forms.input label="Redis URL (public)"
|
||||||
|
helper="If you change the user/password/port, this could be different. This is with the default values."
|
||||||
|
type="password" readonly wire:model="db_url_public" />
|
||||||
|
@endif
|
||||||
</div>
|
</div>
|
||||||
<x-forms.textarea helper="<a target='_blank' class='text-white underline' href='https://raw.githubusercontent.com/redis/redis/7.2/redis.conf'>Redis Default Configuration</a>" label="Custom Redis Configuration" rows="10" id="database.redis_conf" />
|
<x-forms.textarea
|
||||||
|
helper="<a target='_blank' class='text-white underline' href='https://raw.githubusercontent.com/redis/redis/7.2/redis.conf'>Redis Default Configuration</a>"
|
||||||
|
label="Custom Redis Configuration" rows="10" id="database.redis_conf" />
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -12,10 +12,19 @@
|
|||||||
<div class="flex gap-2">
|
<div class="flex gap-2">
|
||||||
<x-forms.input label="Name" id="database.human_name" placeholder="Name"></x-forms.input>
|
<x-forms.input label="Name" id="database.human_name" placeholder="Name"></x-forms.input>
|
||||||
<x-forms.input label="Description" id="database.description"></x-forms.input>
|
<x-forms.input label="Description" id="database.description"></x-forms.input>
|
||||||
|
<x-forms.input required
|
||||||
|
helper="You can change the image you would like to deploy.<br><br><span class='text-warning'>WARNING. You could corrupt your data. Only do it if you know what you are doing.</span>"
|
||||||
|
label="Image Tag" id="database.image"></x-forms.input>
|
||||||
</div>
|
</div>
|
||||||
<div class="flex gap-2">
|
<div class="flex items-end gap-2">
|
||||||
<x-forms.input required helper="You can change the image you would like to deploy.<br><br><span class='text-warning'>WARNING. You could corrupt your data. Only do it if you know what you are doing.</span>" label="Image Tag"
|
@if ($db_url_public)
|
||||||
id="database.image"></x-forms.input>
|
<x-forms.input label="Database URL (public)"
|
||||||
|
helper="Your credentials are available in your environment variables." type="password" readonly
|
||||||
|
wire:model="db_url_public" />
|
||||||
|
@endif
|
||||||
|
<x-forms.input placeholder="5432" disabled="{{ $database->is_public }}" id="database.public_port"
|
||||||
|
label="Public Port" />
|
||||||
|
<x-forms.checkbox instantSave id="database.is_public" label="Accessible over the internet" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<h3 class="pt-2">Advanced</h3>
|
<h3 class="pt-2">Advanced</h3>
|
||||||
|
|||||||
@@ -13,8 +13,13 @@
|
|||||||
@click.prevent="activeTab = 'storages'; window.location.hash = 'storages'; if(window.location.search) window.location.search = ''"
|
@click.prevent="activeTab = 'storages'; window.location.hash = 'storages'; if(window.location.search) window.location.search = ''"
|
||||||
href="#">Storages
|
href="#">Storages
|
||||||
</a>
|
</a>
|
||||||
<a :class="activeTab === 'backups' && 'text-white'"
|
@if (
|
||||||
@click.prevent="activeTab = 'backups'; window.location.hash = 'backups'" href="#">Backups</a>
|
$serviceDatabase?->databaseType() === 'standalone-mysql' ||
|
||||||
|
$serviceDatabase?->databaseType() === 'standalone-postgresql' ||
|
||||||
|
$serviceDatabase?->databaseType() === 'standalone-mariadb')
|
||||||
|
<a :class="activeTab === 'backups' && 'text-white'"
|
||||||
|
@click.prevent="activeTab = 'backups'; window.location.hash = 'backups'" href="#">Backups</a>
|
||||||
|
@endif
|
||||||
@if (data_get($parameters, 'service_name'))
|
@if (data_get($parameters, 'service_name'))
|
||||||
<a class="{{ request()->routeIs('project.service.logs') ? 'text-white' : '' }}"
|
<a class="{{ request()->routeIs('project.service.logs') ? 'text-white' : '' }}"
|
||||||
href="{{ route('project.service.logs', $parameters) }}">
|
href="{{ route('project.service.logs', $parameters) }}">
|
||||||
|
|||||||
@@ -2,7 +2,9 @@
|
|||||||
@if (
|
@if (
|
||||||
$resource->getMorphClass() == 'App\Models\Application' ||
|
$resource->getMorphClass() == 'App\Models\Application' ||
|
||||||
$resource->getMorphClass() == 'App\Models\StandalonePostgresql' ||
|
$resource->getMorphClass() == 'App\Models\StandalonePostgresql' ||
|
||||||
$resource->getMorphClass() == 'App\Models\StandaloneRedis')
|
$resource->getMorphClass() == 'App\Models\StandaloneRedis' ||
|
||||||
|
$resource->getMorphClass() == 'App\Models\StandaloneMariadb' ||
|
||||||
|
$resource->getMorphClass() == 'App\Models\StandaloneMongodb')
|
||||||
<div class="flex items-center gap-2">
|
<div class="flex items-center gap-2">
|
||||||
<h2>Storages</h2>
|
<h2>Storages</h2>
|
||||||
<x-helper
|
<x-helper
|
||||||
|
|||||||
@@ -14,7 +14,7 @@
|
|||||||
<x-forms.button type="submit">Refresh</x-forms.button>
|
<x-forms.button type="submit">Refresh</x-forms.button>
|
||||||
</form>
|
</form>
|
||||||
<div id="screen" x-data="{ fullscreen: false, alwaysScroll: false, intervalId: null }" :class="fullscreen ? 'fullscreen' : 'container w-full pt-4 mx-auto'">
|
<div id="screen" x-data="{ fullscreen: false, alwaysScroll: false, intervalId: null }" :class="fullscreen ? 'fullscreen' : 'container w-full pt-4 mx-auto'">
|
||||||
<div class="relative flex flex-col-reverse w-full p-4 pt-6 overflow-y-auto text-white scrollbar border-coolgray-300"
|
<div class="relative flex flex-col-reverse w-full p-4 pt-6 overflow-y-auto text-white bg-coolgray-100 scrollbar border-coolgray-300"
|
||||||
:class="fullscreen ? '' : 'max-h-[40rem] border border-solid rounded'">
|
:class="fullscreen ? '' : 'max-h-[40rem] border border-solid rounded'">
|
||||||
<button title="Minimize" x-show="fullscreen" class="fixed top-4 right-4" x-on:click="makeFullscreen"><svg
|
<button title="Minimize" x-show="fullscreen" class="fixed top-4 right-4" x-on:click="makeFullscreen"><svg
|
||||||
class="icon" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
|
class="icon" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
|||||||
@@ -5,10 +5,12 @@
|
|||||||
<div class="flex flex-col gap-4 min-w-fit">
|
<div class="flex flex-col gap-4 min-w-fit">
|
||||||
<a :class="activeTab === 'general' && 'text-white'"
|
<a :class="activeTab === 'general' && 'text-white'"
|
||||||
@click.prevent="activeTab = 'general'; window.location.hash = 'general'" href="#">General</a>
|
@click.prevent="activeTab = 'general'; window.location.hash = 'general'" href="#">General</a>
|
||||||
<a :class="activeTab === 'environment-variables' && 'text-white'"
|
@if ($application->build_pack !== 'static')
|
||||||
@click.prevent="activeTab = 'environment-variables'; window.location.hash = 'environment-variables'"
|
<a :class="activeTab === 'environment-variables' && 'text-white'"
|
||||||
href="#">Environment
|
@click.prevent="activeTab = 'environment-variables'; window.location.hash = 'environment-variables'"
|
||||||
Variables</a>
|
href="#">Environment
|
||||||
|
Variables</a>
|
||||||
|
@endif
|
||||||
@if ($application->git_based())
|
@if ($application->git_based())
|
||||||
<a :class="activeTab === 'source' && 'text-white'"
|
<a :class="activeTab === 'source' && 'text-white'"
|
||||||
@click.prevent="activeTab = 'source'; window.location.hash = 'source'" href="#">Source</a>
|
@click.prevent="activeTab = 'source'; window.location.hash = 'source'" href="#">Source</a>
|
||||||
@@ -16,21 +18,25 @@
|
|||||||
<a :class="activeTab === 'server' && 'text-white'"
|
<a :class="activeTab === 'server' && 'text-white'"
|
||||||
@click.prevent="activeTab = 'server'; window.location.hash = 'server'" href="#">Server
|
@click.prevent="activeTab = 'server'; window.location.hash = 'server'" href="#">Server
|
||||||
</a>
|
</a>
|
||||||
<a :class="activeTab === 'storages' && 'text-white'"
|
@if ($application->build_pack !== 'static')
|
||||||
@click.prevent="activeTab = 'storages'; window.location.hash = 'storages'" href="#">Storages
|
<a :class="activeTab === 'storages' && 'text-white'"
|
||||||
</a>
|
@click.prevent="activeTab = 'storages'; window.location.hash = 'storages'" href="#">Storages
|
||||||
|
</a>
|
||||||
|
@endif
|
||||||
<a :class="activeTab === 'webhooks' && 'text-white'"
|
<a :class="activeTab === 'webhooks' && 'text-white'"
|
||||||
@click.prevent="activeTab = 'webhooks'; window.location.hash = 'webhooks'" href="#">Webhooks
|
@click.prevent="activeTab = 'webhooks'; window.location.hash = 'webhooks'" href="#">Webhooks
|
||||||
</a>
|
</a>
|
||||||
@if ($application->git_based())
|
@if ($application->git_based() && $application->build_pack !== 'static')
|
||||||
<a :class="activeTab === 'previews' && 'text-white'"
|
<a :class="activeTab === 'previews' && 'text-white'"
|
||||||
@click.prevent="activeTab = 'previews'; window.location.hash = 'previews'" href="#">Preview
|
@click.prevent="activeTab = 'previews'; window.location.hash = 'previews'" href="#">Preview
|
||||||
Deployments
|
Deployments
|
||||||
</a>
|
</a>
|
||||||
@endif
|
@endif
|
||||||
<a :class="activeTab === 'health' && 'text-white'"
|
@if ($application->build_pack !== 'static')
|
||||||
@click.prevent="activeTab = 'health'; window.location.hash = 'health'" href="#">Health Checks
|
<a :class="activeTab === 'health' && 'text-white'"
|
||||||
</a>
|
@click.prevent="activeTab = 'health'; window.location.hash = 'health'" href="#">Health Checks
|
||||||
|
</a>
|
||||||
|
@endif
|
||||||
<a :class="activeTab === 'rollback' && 'text-white'"
|
<a :class="activeTab === 'rollback' && 'text-white'"
|
||||||
@click.prevent="activeTab = 'rollback'; window.location.hash = 'rollback'" href="#">Rollback
|
@click.prevent="activeTab = 'rollback'; window.location.hash = 'rollback'" href="#">Rollback
|
||||||
</a>
|
</a>
|
||||||
|
|||||||
@@ -237,7 +237,7 @@ Route::post('/payments/stripe/events', function () {
|
|||||||
try {
|
try {
|
||||||
$webhookSecret = config('subscription.stripe_webhook_secret');
|
$webhookSecret = config('subscription.stripe_webhook_secret');
|
||||||
$signature = request()->header('Stripe-Signature');
|
$signature = request()->header('Stripe-Signature');
|
||||||
|
$excludedPlans = config('subscription.stripe_excluded_plans');
|
||||||
$event = \Stripe\Webhook::constructEvent(
|
$event = \Stripe\Webhook::constructEvent(
|
||||||
request()->getContent(),
|
request()->getContent(),
|
||||||
$signature,
|
$signature,
|
||||||
@@ -253,6 +253,10 @@ Route::post('/payments/stripe/events', function () {
|
|||||||
switch ($type) {
|
switch ($type) {
|
||||||
case 'checkout.session.completed':
|
case 'checkout.session.completed':
|
||||||
$clientReferenceId = data_get($data, 'client_reference_id');
|
$clientReferenceId = data_get($data, 'client_reference_id');
|
||||||
|
if (is_null($clientReferenceId)) {
|
||||||
|
send_internal_notification('Checkout session completed without client reference id.');
|
||||||
|
break;
|
||||||
|
}
|
||||||
$userId = Str::before($clientReferenceId, ':');
|
$userId = Str::before($clientReferenceId, ':');
|
||||||
$teamId = Str::after($clientReferenceId, ':');
|
$teamId = Str::after($clientReferenceId, ':');
|
||||||
$subscriptionId = data_get($data, 'subscription');
|
$subscriptionId = data_get($data, 'subscription');
|
||||||
@@ -282,12 +286,17 @@ Route::post('/payments/stripe/events', function () {
|
|||||||
break;
|
break;
|
||||||
case 'invoice.paid':
|
case 'invoice.paid':
|
||||||
$customerId = data_get($data, 'customer');
|
$customerId = data_get($data, 'customer');
|
||||||
|
$planId = data_get($data, 'lines.data.0.plan.id');
|
||||||
|
if (Str::contains($excludedPlans, $planId)) {
|
||||||
|
send_internal_notification('Subscription excluded.');
|
||||||
|
break;
|
||||||
|
}
|
||||||
$subscription = Subscription::where('stripe_customer_id', $customerId)->first();
|
$subscription = Subscription::where('stripe_customer_id', $customerId)->first();
|
||||||
if (!$subscription) {
|
if (!$subscription) {
|
||||||
Sleep::for(5)->seconds();
|
Sleep::for(5)->seconds();
|
||||||
$subscription = Subscription::where('stripe_customer_id', $customerId)->firstOrFail();
|
$subscription = Subscription::where('stripe_customer_id', $customerId)->firstOrFail();
|
||||||
}
|
}
|
||||||
$planId = data_get($data, 'lines.data.0.plan.id');
|
|
||||||
$subscription->update([
|
$subscription->update([
|
||||||
'stripe_plan_id' => $planId,
|
'stripe_plan_id' => $planId,
|
||||||
'stripe_invoice_paid' => true,
|
'stripe_invoice_paid' => true,
|
||||||
@@ -303,11 +312,15 @@ Route::post('/payments/stripe/events', function () {
|
|||||||
break;
|
break;
|
||||||
case 'customer.subscription.updated':
|
case 'customer.subscription.updated':
|
||||||
$customerId = data_get($data, 'customer');
|
$customerId = data_get($data, 'customer');
|
||||||
$subscription = Subscription::where('stripe_customer_id', $customerId)->firstOrFail();
|
|
||||||
$trialEndedAlready = data_get($subscription, 'stripe_trial_already_ended');
|
|
||||||
$status = data_get($data, 'status');
|
$status = data_get($data, 'status');
|
||||||
$subscriptionId = data_get($data, 'items.data.0.subscription');
|
$subscriptionId = data_get($data, 'items.data.0.subscription');
|
||||||
$planId = data_get($data, 'items.data.0.plan.id');
|
$planId = data_get($data, 'items.data.0.plan.id');
|
||||||
|
if (Str::contains($excludedPlans, $planId)) {
|
||||||
|
send_internal_notification('Subscription excluded.');
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
$subscription = Subscription::where('stripe_customer_id', $customerId)->firstOrFail();
|
||||||
|
$trialEndedAlready = data_get($subscription, 'stripe_trial_already_ended');
|
||||||
$cancelAtPeriodEnd = data_get($data, 'cancel_at_period_end');
|
$cancelAtPeriodEnd = data_get($data, 'cancel_at_period_end');
|
||||||
$alreadyCancelAtPeriodEnd = data_get($subscription, 'stripe_cancel_at_period_end');
|
$alreadyCancelAtPeriodEnd = data_get($subscription, 'stripe_cancel_at_period_end');
|
||||||
$feedback = data_get($data, 'cancellation_details.feedback');
|
$feedback = data_get($data, 'cancellation_details.feedback');
|
||||||
|
|||||||
@@ -39,12 +39,12 @@ module.exports = {
|
|||||||
themes: [
|
themes: [
|
||||||
{
|
{
|
||||||
coollabs: {
|
coollabs: {
|
||||||
primary: "#323232",
|
primary: "#202020",
|
||||||
"primary-focus": "#242424",
|
"primary-focus": "#242424",
|
||||||
secondary: "#6B16ED",
|
secondary: "#6B16ED",
|
||||||
accent: "#4338ca",
|
accent: "#4338ca",
|
||||||
neutral: "#1B1D1D",
|
neutral: "#1B1D1D",
|
||||||
"base-100": "#181818",
|
"base-100": "#101010",
|
||||||
info: "#2563EB",
|
info: "#2563EB",
|
||||||
success: "#16A34A",
|
success: "#16A34A",
|
||||||
warning: "#FCD34D",
|
warning: "#FCD34D",
|
||||||
|
|||||||
48
templates/compose/directus-with-postgresql.yaml
Normal file
48
templates/compose/directus-with-postgresql.yaml
Normal file
@@ -0,0 +1,48 @@
|
|||||||
|
# documentation: https://docs.directus.io/self-hosted/quickstart.html
|
||||||
|
# slogan: Directus is an open-source tool that wraps custom SQL databases with a dynamic API, and provides an intuitive admin app for managing its content.
|
||||||
|
# tags: directus, cms, database, sql
|
||||||
|
|
||||||
|
services:
|
||||||
|
directus:
|
||||||
|
image: directus/directus:10.7
|
||||||
|
volumes:
|
||||||
|
- directus-uploads:/directus/uploads
|
||||||
|
- directus-extensions:/directus/extensions
|
||||||
|
environment:
|
||||||
|
- SERVICE_FQDN_DIRECTUS
|
||||||
|
- KEY=$SERVICE_BASE64_64_KEY
|
||||||
|
- SECRET=$SERVICE_BASE64_64_SECRET
|
||||||
|
- ADMIN_EMAIL=${ADMIN_EMAIL:-admin@example.com}
|
||||||
|
- ADMIN_PASSWORD=$SERVICE_PASSWORD_ADMIN
|
||||||
|
- DB_CLIENT=postgres
|
||||||
|
- DB_HOST=postgresql
|
||||||
|
- DB_PORT=5432
|
||||||
|
- DB_DATABASE=${POSTGRESQL_DATABASE:-directus}
|
||||||
|
- DB_USER=$SERVICE_USER_POSTGRESQL
|
||||||
|
- DB_PASSWORD=$SERVICE_PASSWORD_POSTGRESQL
|
||||||
|
- REDIS_HOST=redis
|
||||||
|
- REDIS_PORT=6379
|
||||||
|
- WEBSOCKETS_ENABLED=true
|
||||||
|
postgresql:
|
||||||
|
image: postgres:15-alpine
|
||||||
|
volumes:
|
||||||
|
- directus-postgresql-data:/var/lib/postgresql/data
|
||||||
|
environment:
|
||||||
|
- POSTGRES_USER=${SERVICE_USER_POSTGRESQL}
|
||||||
|
- POSTGRES_PASSWORD=${SERVICE_PASSWORD_POSTGRESQL}
|
||||||
|
- POSTGRES_DB=${POSTGRESQL_DATABASE:-directus}
|
||||||
|
healthcheck:
|
||||||
|
test: ["CMD-SHELL", "pg_isready -U $${POSTGRES_USER} -d $${POSTGRES_DB}"]
|
||||||
|
interval: 5s
|
||||||
|
timeout: 20s
|
||||||
|
retries: 10
|
||||||
|
redis:
|
||||||
|
image: redis:6-alpine
|
||||||
|
command: redis-server --appendonly yes
|
||||||
|
volumes:
|
||||||
|
- directus-redis-data:/data
|
||||||
|
healthcheck:
|
||||||
|
test: ["CMD", "redis-cli", "ping"]
|
||||||
|
interval: 5s
|
||||||
|
timeout: 20s
|
||||||
|
retries: 10
|
||||||
19
templates/compose/directus.yaml
Normal file
19
templates/compose/directus.yaml
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
# documentation: https://docs.directus.io/self-hosted/quickstart.html
|
||||||
|
# slogan: Directus is an open-source tool that wraps custom SQL databases with a dynamic API, and provides an intuitive admin app for managing its content.
|
||||||
|
# tags: directus, cms, database, sql
|
||||||
|
|
||||||
|
services:
|
||||||
|
directus:
|
||||||
|
image: directus/directus:10.7
|
||||||
|
volumes:
|
||||||
|
- directus-database:/directus/database
|
||||||
|
- directus-uploads:/directus/uploads
|
||||||
|
environment:
|
||||||
|
- SERVICE_FQDN_DIRECTUS
|
||||||
|
- KEY=$SERVICE_BASE64_64_KEY
|
||||||
|
- SECRET=$SERVICE_BASE64_64_SECRET
|
||||||
|
- ADMIN_EMAIL=${ADMIN_EMAIL:-admin@example.com}
|
||||||
|
- ADMIN_PASSWORD=$SERVICE_PASSWORD_ADMIN
|
||||||
|
- DB_CLIENT=sqlite3
|
||||||
|
- DB_FILENAME=/directus/database/data.db
|
||||||
|
- WEBSOCKETS_ENABLED=true
|
||||||
@@ -5,7 +5,7 @@
|
|||||||
services:
|
services:
|
||||||
|
|
||||||
redis:
|
redis:
|
||||||
image: redis:6.2.4
|
image: redis:6-alpine
|
||||||
command: redis-server --appendonly yes
|
command: redis-server --appendonly yes
|
||||||
healthcheck:
|
healthcheck:
|
||||||
test: [ "CMD", "redis-cli", "ping" ]
|
test: [ "CMD", "redis-cli", "ping" ]
|
||||||
|
|||||||
@@ -65,6 +65,28 @@
|
|||||||
"bookmarks"
|
"bookmarks"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
"directus-with-postgresql": {
|
||||||
|
"documentation": "https:\/\/docs.directus.io\/self-hosted\/quickstart.html",
|
||||||
|
"slogan": "Directus is an open-source tool that wraps custom SQL databases with a dynamic API, and provides an intuitive admin app for managing its content.",
|
||||||
|
"compose": "c2VydmljZXM6CiAgZGlyZWN0dXM6CiAgICBpbWFnZTogJ2RpcmVjdHVzL2RpcmVjdHVzOjEwLjcnCiAgICB2b2x1bWVzOgogICAgICAtICdkaXJlY3R1cy11cGxvYWRzOi9kaXJlY3R1cy91cGxvYWRzJwogICAgICAtICdkaXJlY3R1cy1leHRlbnNpb25zOi9kaXJlY3R1cy9leHRlbnNpb25zJwogICAgZW52aXJvbm1lbnQ6CiAgICAgIC0gU0VSVklDRV9GUUROX0RJUkVDVFVTCiAgICAgIC0gS0VZPSRTRVJWSUNFX0JBU0U2NF82NF9LRVkKICAgICAgLSBTRUNSRVQ9JFNFUlZJQ0VfQkFTRTY0XzY0X1NFQ1JFVAogICAgICAtICdBRE1JTl9FTUFJTD0ke0FETUlOX0VNQUlMOi1hZG1pbkBleGFtcGxlLmNvbX0nCiAgICAgIC0gQURNSU5fUEFTU1dPUkQ9JFNFUlZJQ0VfUEFTU1dPUkRfQURNSU4KICAgICAgLSBEQl9DTElFTlQ9cG9zdGdyZXMKICAgICAgLSBEQl9IT1NUPXBvc3RncmVzcWwKICAgICAgLSBEQl9QT1JUPTU0MzIKICAgICAgLSAnREJfREFUQUJBU0U9JHtQT1NUR1JFU1FMX0RBVEFCQVNFOi1kaXJlY3R1c30nCiAgICAgIC0gREJfVVNFUj0kU0VSVklDRV9VU0VSX1BPU1RHUkVTUUwKICAgICAgLSBEQl9QQVNTV09SRD0kU0VSVklDRV9QQVNTV09SRF9QT1NUR1JFU1FMCiAgICAgIC0gUkVESVNfSE9TVD1yZWRpcwogICAgICAtIFJFRElTX1BPUlQ9NjM3OQogICAgICAtIFdFQlNPQ0tFVFNfRU5BQkxFRD10cnVlCiAgcG9zdGdyZXNxbDoKICAgIGltYWdlOiAncG9zdGdyZXM6MTUtYWxwaW5lJwogICAgdm9sdW1lczoKICAgICAgLSAnZGlyZWN0dXMtcG9zdGdyZXNxbC1kYXRhOi92YXIvbGliL3Bvc3RncmVzcWwvZGF0YScKICAgIGVudmlyb25tZW50OgogICAgICAtICdQT1NUR1JFU19VU0VSPSR7U0VSVklDRV9VU0VSX1BPU1RHUkVTUUx9JwogICAgICAtICdQT1NUR1JFU19QQVNTV09SRD0ke1NFUlZJQ0VfUEFTU1dPUkRfUE9TVEdSRVNRTH0nCiAgICAgIC0gJ1BPU1RHUkVTX0RCPSR7UE9TVEdSRVNRTF9EQVRBQkFTRTotZGlyZWN0dXN9JwogICAgaGVhbHRoY2hlY2s6CiAgICAgIHRlc3Q6CiAgICAgICAgLSBDTUQtU0hFTEwKICAgICAgICAtICdwZ19pc3JlYWR5IC1VICQke1BPU1RHUkVTX1VTRVJ9IC1kICQke1BPU1RHUkVTX0RCfScKICAgICAgaW50ZXJ2YWw6IDVzCiAgICAgIHRpbWVvdXQ6IDIwcwogICAgICByZXRyaWVzOiAxMAogIHJlZGlzOgogICAgaW1hZ2U6ICdyZWRpczo2LWFscGluZScKICAgIGNvbW1hbmQ6ICdyZWRpcy1zZXJ2ZXIgLS1hcHBlbmRvbmx5IHllcycKICAgIHZvbHVtZXM6CiAgICAgIC0gJ2RpcmVjdHVzLXJlZGlzLWRhdGE6L2RhdGEnCiAgICBoZWFsdGhjaGVjazoKICAgICAgdGVzdDoKICAgICAgICAtIENNRAogICAgICAgIC0gcmVkaXMtY2xpCiAgICAgICAgLSBwaW5nCiAgICAgIGludGVydmFsOiA1cwogICAgICB0aW1lb3V0OiAyMHMKICAgICAgcmV0cmllczogMTAK",
|
||||||
|
"tags": [
|
||||||
|
"directus",
|
||||||
|
"cms",
|
||||||
|
"database",
|
||||||
|
"sql"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"directus": {
|
||||||
|
"documentation": "https:\/\/docs.directus.io\/self-hosted\/quickstart.html",
|
||||||
|
"slogan": "Directus is an open-source tool that wraps custom SQL databases with a dynamic API, and provides an intuitive admin app for managing its content.",
|
||||||
|
"compose": "c2VydmljZXM6CiAgZGlyZWN0dXM6CiAgICBpbWFnZTogJ2RpcmVjdHVzL2RpcmVjdHVzOjEwLjcnCiAgICB2b2x1bWVzOgogICAgICAtICdkaXJlY3R1cy1kYXRhYmFzZTovZGlyZWN0dXMvZGF0YWJhc2UnCiAgICAgIC0gJ2RpcmVjdHVzLXVwbG9hZHM6L2RpcmVjdHVzL3VwbG9hZHMnCiAgICBlbnZpcm9ubWVudDoKICAgICAgLSBTRVJWSUNFX0ZRRE5fRElSRUNUVVMKICAgICAgLSBLRVk9JFNFUlZJQ0VfQkFTRTY0XzY0X0tFWQogICAgICAtIFNFQ1JFVD0kU0VSVklDRV9CQVNFNjRfNjRfU0VDUkVUCiAgICAgIC0gJ0FETUlOX0VNQUlMPSR7QURNSU5fRU1BSUw6LWFkbWluQGV4YW1wbGUuY29tfScKICAgICAgLSBBRE1JTl9QQVNTV09SRD0kU0VSVklDRV9QQVNTV09SRF9BRE1JTgogICAgICAtIERCX0NMSUVOVD1zcWxpdGUzCiAgICAgIC0gREJfRklMRU5BTUU9L2RpcmVjdHVzL2RhdGFiYXNlL2RhdGEuZGIKICAgICAgLSBXRUJTT0NLRVRTX0VOQUJMRUQ9dHJ1ZQo=",
|
||||||
|
"tags": [
|
||||||
|
"directus",
|
||||||
|
"cms",
|
||||||
|
"database",
|
||||||
|
"sql"
|
||||||
|
]
|
||||||
|
},
|
||||||
"dokuwiki": {
|
"dokuwiki": {
|
||||||
"documentation": "https:\/\/www.dokuwiki.org\/faq",
|
"documentation": "https:\/\/www.dokuwiki.org\/faq",
|
||||||
"slogan": "A lightweight and easy-to-use wiki platform for creating and managing documentation and knowledge bases with simplicity and flexibility.",
|
"slogan": "A lightweight and easy-to-use wiki platform for creating and managing documentation and knowledge bases with simplicity and flexibility.",
|
||||||
@@ -253,7 +275,7 @@
|
|||||||
"kuzzle": {
|
"kuzzle": {
|
||||||
"documentation": "https:\/\/docs.kuzzle.io\/",
|
"documentation": "https:\/\/docs.kuzzle.io\/",
|
||||||
"slogan": "Kuzzle is a generic backend offering the basic building blocks common to every application.",
|
"slogan": "Kuzzle is a generic backend offering the basic building blocks common to every application.",
|
||||||
"compose": "c2VydmljZXM6CiAgcmVkaXM6CiAgICBpbWFnZTogJ3JlZGlzOjYuMi40JwogICAgY29tbWFuZDogJ3JlZGlzLXNlcnZlciAtLWFwcGVuZG9ubHkgeWVzJwogICAgaGVhbHRoY2hlY2s6CiAgICAgIHRlc3Q6CiAgICAgICAgLSBDTUQKICAgICAgICAtIHJlZGlzLWNsaQogICAgICAgIC0gcGluZwogICAgICBpbnRlcnZhbDogMXMKICAgICAgdGltZW91dDogM3MKICAgICAgcmV0cmllczogMzAKICBlbGFzdGljc2VhcmNoOgogICAgaW1hZ2U6ICdrdXp6bGVpby9lbGFzdGljc2VhcmNoOjcnCiAgICBoZWFsdGhjaGVjazoKICAgICAgdGVzdDoKICAgICAgICAtIENNRAogICAgICAgIC0gY3VybAogICAgICAgIC0gJy1mJwogICAgICAgIC0gJ2h0dHA6Ly9sb2NhbGhvc3Q6OTIwMCcKICAgICAgaW50ZXJ2YWw6IDJzCiAgICAgIHRpbWVvdXQ6IDJzCiAgICAgIHJldHJpZXM6IDEwCiAgICB1bGltaXRzOgogICAgICBub2ZpbGU6IDY1NTM2CiAga3V6emxlOgogICAgaW1hZ2U6ICdrdXp6bGVpby9rdXp6bGU6bGF0ZXN0JwogICAgZW52aXJvbm1lbnQ6CiAgICAgIC0gU0VSVklDRV9GUUROX0tVWlpMRV83NTEyCiAgICAgIC0gJ2t1enpsZV9zZXJ2aWNlc19fc3RvcmFnZUVuZ2luZV9fY2xpZW50X19ub2RlPWh0dHA6Ly9lbGFzdGljc2VhcmNoOjkyMDAnCiAgICAgIC0ga3V6emxlX3NlcnZpY2VzX19zdG9yYWdlRW5naW5lX19jb21tb25NYXBwaW5nX19keW5hbWljPXRydWUKICAgICAgLSBrdXp6bGVfc2VydmljZXNfX2ludGVybmFsQ2FjaGVfX25vZGVfX2hvc3Q9cmVkaXMKICAgICAgLSBrdXp6bGVfc2VydmljZXNfX21lbW9yeVN0b3JhZ2VfX25vZGVfX2hvc3Q9cmVkaXMKICAgICAgLSBrdXp6bGVfc2VydmVyX19wcm90b2NvbHNfX21xdHRfX2VuYWJsZWQ9dHJ1ZQogICAgICAtIGt1enpsZV9zZXJ2ZXJfX3Byb3RvY29sc19fbXF0dF9fZGV2ZWxvcG1lbnRNb2RlPWZhbHNlCiAgICAgIC0ga3V6emxlX2xpbWl0c19fbG9naW5zUGVyU2Vjb25kPTUwCiAgICAgIC0gTk9ERV9FTlY9cHJvZHVjdGlvbgogICAgICAtICdERUJVRz0ke0RFQlVHOi1rdXp6bGU6Y2x1c3RlcjpzeW5jfScKICAgICAgLSAnREVCVUdfREVQVEg9JHtERUJVR19ERVBUSDotMH0nCiAgICAgIC0gJ0RFQlVHX01BWF9BUlJBWV9MRU5HVEg9JHtERUJVR19NQVhfQVJSQVk6LTEwMH0nCiAgICAgIC0gJ0RFQlVHX0VYUEFORD0ke0RFQlVHX0VYUEFORDotb2ZmfScKICAgICAgLSAnREVCVUdfU0hPV19ISURERU49eyRERUJVR19TSE9XX0hJRERFTjotb259JwogICAgICAtICdERUJVR19DT0xPUlM9JHtERUJVR19DT0xPUlM6LW9ufScKICAgIGNhcF9hZGQ6CiAgICAgIC0gU1lTX1BUUkFDRQogICAgdWxpbWl0czoKICAgICAgbm9maWxlOiA2NTUzNgogICAgc3lzY3RsczoKICAgICAgLSBuZXQuY29yZS5zb21heGNvbm49ODE5MgogICAgaGVhbHRoY2hlY2s6CiAgICAgIHRlc3Q6CiAgICAgICAgLSBDTUQKICAgICAgICAtIGN1cmwKICAgICAgICAtICctZicKICAgICAgICAtICdodHRwOi8vbG9jYWxob3N0Ojc1MTIvX2hlYWx0aGNoZWNrJwogICAgICB0aW1lb3V0OiAxcwogICAgICBpbnRlcnZhbDogMnMKICAgICAgcmV0cmllczogMzAKICAgIGRlcGVuZHNfb246CiAgICAgIHJlZGlzOgogICAgICAgIGNvbmRpdGlvbjogc2VydmljZV9oZWFsdGh5CiAgICAgIGVsYXN0aWNzZWFyY2g6CiAgICAgICAgY29uZGl0aW9uOiBzZXJ2aWNlX2hlYWx0aHkK",
|
"compose": "c2VydmljZXM6CiAgcmVkaXM6CiAgICBpbWFnZTogJ3JlZGlzOjYtYWxwaW5lJwogICAgY29tbWFuZDogJ3JlZGlzLXNlcnZlciAtLWFwcGVuZG9ubHkgeWVzJwogICAgaGVhbHRoY2hlY2s6CiAgICAgIHRlc3Q6CiAgICAgICAgLSBDTUQKICAgICAgICAtIHJlZGlzLWNsaQogICAgICAgIC0gcGluZwogICAgICBpbnRlcnZhbDogMXMKICAgICAgdGltZW91dDogM3MKICAgICAgcmV0cmllczogMzAKICBlbGFzdGljc2VhcmNoOgogICAgaW1hZ2U6ICdrdXp6bGVpby9lbGFzdGljc2VhcmNoOjcnCiAgICBoZWFsdGhjaGVjazoKICAgICAgdGVzdDoKICAgICAgICAtIENNRAogICAgICAgIC0gY3VybAogICAgICAgIC0gJy1mJwogICAgICAgIC0gJ2h0dHA6Ly9sb2NhbGhvc3Q6OTIwMCcKICAgICAgaW50ZXJ2YWw6IDJzCiAgICAgIHRpbWVvdXQ6IDJzCiAgICAgIHJldHJpZXM6IDEwCiAgICB1bGltaXRzOgogICAgICBub2ZpbGU6IDY1NTM2CiAga3V6emxlOgogICAgaW1hZ2U6ICdrdXp6bGVpby9rdXp6bGU6bGF0ZXN0JwogICAgZW52aXJvbm1lbnQ6CiAgICAgIC0gU0VSVklDRV9GUUROX0tVWlpMRV83NTEyCiAgICAgIC0gJ2t1enpsZV9zZXJ2aWNlc19fc3RvcmFnZUVuZ2luZV9fY2xpZW50X19ub2RlPWh0dHA6Ly9lbGFzdGljc2VhcmNoOjkyMDAnCiAgICAgIC0ga3V6emxlX3NlcnZpY2VzX19zdG9yYWdlRW5naW5lX19jb21tb25NYXBwaW5nX19keW5hbWljPXRydWUKICAgICAgLSBrdXp6bGVfc2VydmljZXNfX2ludGVybmFsQ2FjaGVfX25vZGVfX2hvc3Q9cmVkaXMKICAgICAgLSBrdXp6bGVfc2VydmljZXNfX21lbW9yeVN0b3JhZ2VfX25vZGVfX2hvc3Q9cmVkaXMKICAgICAgLSBrdXp6bGVfc2VydmVyX19wcm90b2NvbHNfX21xdHRfX2VuYWJsZWQ9dHJ1ZQogICAgICAtIGt1enpsZV9zZXJ2ZXJfX3Byb3RvY29sc19fbXF0dF9fZGV2ZWxvcG1lbnRNb2RlPWZhbHNlCiAgICAgIC0ga3V6emxlX2xpbWl0c19fbG9naW5zUGVyU2Vjb25kPTUwCiAgICAgIC0gTk9ERV9FTlY9cHJvZHVjdGlvbgogICAgICAtICdERUJVRz0ke0RFQlVHOi1rdXp6bGU6Y2x1c3RlcjpzeW5jfScKICAgICAgLSAnREVCVUdfREVQVEg9JHtERUJVR19ERVBUSDotMH0nCiAgICAgIC0gJ0RFQlVHX01BWF9BUlJBWV9MRU5HVEg9JHtERUJVR19NQVhfQVJSQVk6LTEwMH0nCiAgICAgIC0gJ0RFQlVHX0VYUEFORD0ke0RFQlVHX0VYUEFORDotb2ZmfScKICAgICAgLSAnREVCVUdfU0hPV19ISURERU49eyRERUJVR19TSE9XX0hJRERFTjotb259JwogICAgICAtICdERUJVR19DT0xPUlM9JHtERUJVR19DT0xPUlM6LW9ufScKICAgIGNhcF9hZGQ6CiAgICAgIC0gU1lTX1BUUkFDRQogICAgdWxpbWl0czoKICAgICAgbm9maWxlOiA2NTUzNgogICAgc3lzY3RsczoKICAgICAgLSBuZXQuY29yZS5zb21heGNvbm49ODE5MgogICAgaGVhbHRoY2hlY2s6CiAgICAgIHRlc3Q6CiAgICAgICAgLSBDTUQKICAgICAgICAtIGN1cmwKICAgICAgICAtICctZicKICAgICAgICAtICdodHRwOi8vbG9jYWxob3N0Ojc1MTIvX2hlYWx0aGNoZWNrJwogICAgICB0aW1lb3V0OiAxcwogICAgICBpbnRlcnZhbDogMnMKICAgICAgcmV0cmllczogMzAKICAgIGRlcGVuZHNfb246CiAgICAgIHJlZGlzOgogICAgICAgIGNvbmRpdGlvbjogc2VydmljZV9oZWFsdGh5CiAgICAgIGVsYXN0aWNzZWFyY2g6CiAgICAgICAgY29uZGl0aW9uOiBzZXJ2aWNlX2hlYWx0aHkK",
|
||||||
"tags": [
|
"tags": [
|
||||||
"backend",
|
"backend",
|
||||||
"api",
|
"api",
|
||||||
|
|||||||
@@ -4,7 +4,7 @@
|
|||||||
"version": "3.12.36"
|
"version": "3.12.36"
|
||||||
},
|
},
|
||||||
"v4": {
|
"v4": {
|
||||||
"version": "4.0.0-beta.112"
|
"version": "4.0.0-beta.121"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user