mirror of
https://github.com/ershisan99/coolify.git
synced 2025-12-18 12:33:06 +00:00
Compare commits
228 Commits
v4.0.0-bet
...
v4.0.0-bet
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
2978042162 | ||
|
|
4225ec7060 | ||
|
|
893339fc8e | ||
|
|
356e7b57d2 | ||
|
|
4ee1f1a507 | ||
|
|
7d64df60cd | ||
|
|
eb3a4ca157 | ||
|
|
a7b5157fa6 | ||
|
|
793e6d19eb | ||
|
|
674fa4d09c | ||
|
|
0089e86dd1 | ||
|
|
e1d802b507 | ||
|
|
9927b71af9 | ||
|
|
b1c0f105ab | ||
|
|
35cae1d4dc | ||
|
|
3dab3365e2 | ||
|
|
1bdc7c87ba | ||
|
|
cab8ad0ca0 | ||
|
|
43409f3ff0 | ||
|
|
a5dd4cab52 | ||
|
|
a815240f4e | ||
|
|
2a44e7c5bd | ||
|
|
28c7e439b1 | ||
|
|
4396c786b4 | ||
|
|
b67bb8595f | ||
|
|
bec47487dd | ||
|
|
b110d0c12b | ||
|
|
ae425475b4 | ||
|
|
dc6aee44b3 | ||
|
|
4ffea311e8 | ||
|
|
77a6a6e46a | ||
|
|
2278ba31e7 | ||
|
|
aaeec3d340 | ||
|
|
2cbe530b7e | ||
|
|
6ada6d145c | ||
|
|
0f55e83591 | ||
|
|
4017ea7b65 | ||
|
|
a85066c644 | ||
|
|
b08d38f339 | ||
|
|
d4f4632461 | ||
|
|
666aa041f4 | ||
|
|
1c565fd502 | ||
|
|
7de2b8cbd7 | ||
|
|
852e906736 | ||
|
|
5778466947 | ||
|
|
7006239b0d | ||
|
|
49d011574d | ||
|
|
046a358ae0 | ||
|
|
d23f5af957 | ||
|
|
20a3f4b200 | ||
|
|
73acda833e | ||
|
|
fa895db76e | ||
|
|
88f33be5b6 | ||
|
|
21612cccf7 | ||
|
|
39a7332343 | ||
|
|
21825876fb | ||
|
|
aaee887d3e | ||
|
|
cb44373eff | ||
|
|
4e6ea4f584 | ||
|
|
62a93d3e51 | ||
|
|
f60c281e80 | ||
|
|
43c40cdb09 | ||
|
|
c851262d81 | ||
|
|
91783ccc3e | ||
|
|
6ba3d5f86e | ||
|
|
a9a20755a9 | ||
|
|
d2693c1ac8 | ||
|
|
aaa6f434a9 | ||
|
|
314a3ac83f | ||
|
|
36e177479e | ||
|
|
cbeebed6c9 | ||
|
|
4b905dbfad | ||
|
|
6072e7efc7 | ||
|
|
19097c6692 | ||
|
|
d37f63c63c | ||
|
|
574bafd950 | ||
|
|
2b805f869a | ||
|
|
36c4be1d17 | ||
|
|
f2d82e16d6 | ||
|
|
c6658e1ac7 | ||
|
|
22a7d85e58 | ||
|
|
b3421b47b6 | ||
|
|
b5247f77ec | ||
|
|
e63e806572 | ||
|
|
62b84add36 | ||
|
|
858ae1266f | ||
|
|
3ae990aa40 | ||
|
|
deb4b16ae1 | ||
|
|
b37dc4c73e | ||
|
|
6b08100819 | ||
|
|
2c45e7146b | ||
|
|
7c4a722d72 | ||
|
|
f4bccefaba | ||
|
|
491bb93e95 | ||
|
|
f35700c9ee | ||
|
|
bd26aca3d9 | ||
|
|
71d24773b6 | ||
|
|
781bd29f40 | ||
|
|
fd4dd1edfa | ||
|
|
d2db26cb5e | ||
|
|
01977839f7 | ||
|
|
0116892b6b | ||
|
|
4d88873524 | ||
|
|
8e46c0186d | ||
|
|
1b0e589aab | ||
|
|
02ba149e26 | ||
|
|
2981aa876c | ||
|
|
82057e1f50 | ||
|
|
4ce36631e0 | ||
|
|
995324d6b3 | ||
|
|
c61ad9cd95 | ||
|
|
26f4bcc77e | ||
|
|
c2b2d06e47 | ||
|
|
cbcc7f6d88 | ||
|
|
d05e23264b | ||
|
|
db9faed184 | ||
|
|
e7feac848a | ||
|
|
33b965d9db | ||
|
|
6c33bd9c72 | ||
|
|
c72fd2fc9d | ||
|
|
906a3dc9b4 | ||
|
|
2d3a6a4528 | ||
|
|
01abc26316 | ||
|
|
2dfe43fc3c | ||
|
|
f71861300a | ||
|
|
52d7841334 | ||
|
|
9c821e2480 | ||
|
|
f8f0aa171c | ||
|
|
38d9999814 | ||
|
|
920305432b | ||
|
|
42fb8ab379 | ||
|
|
88ab385100 | ||
|
|
479a3540ec | ||
|
|
47f5a0de81 | ||
|
|
311c118834 | ||
|
|
0c40c0d795 | ||
|
|
25f0a8f0b7 | ||
|
|
f58a1a9ecf | ||
|
|
efa2ae5177 | ||
|
|
5e55c799ec | ||
|
|
46e61cb409 | ||
|
|
b24a489c77 | ||
|
|
65a618d019 | ||
|
|
4459c9f73d | ||
|
|
3c13f1ff61 | ||
|
|
c39d6dd407 | ||
|
|
1249b1ece9 | ||
|
|
da6f2da3d0 | ||
|
|
dbc235d84a | ||
|
|
b86924bc0e | ||
|
|
0fb8cf4241 | ||
|
|
30b7e831c0 | ||
|
|
f1b4ebcde2 | ||
|
|
e3c4ebb121 | ||
|
|
2dd17cfac5 | ||
|
|
93d04ef426 | ||
|
|
70bfd4dd8a | ||
|
|
ca917d9d21 | ||
|
|
be633f0560 | ||
|
|
613e980267 | ||
|
|
4fb37054df | ||
|
|
07508df8fd | ||
|
|
2a52fb5872 | ||
|
|
f45b3cab55 | ||
|
|
eb76d63117 | ||
|
|
0964c7a338 | ||
|
|
7af151d44e | ||
|
|
7028391e57 | ||
|
|
cbae0845e7 | ||
|
|
0e512962c6 | ||
|
|
ac694b855b | ||
|
|
490d45e788 | ||
|
|
b0863eb5ea | ||
|
|
ee199ed038 | ||
|
|
41268fa20b | ||
|
|
7474896368 | ||
|
|
54c4296a25 | ||
|
|
116f5afe3c | ||
|
|
c015c8f45d | ||
|
|
fe26b3d759 | ||
|
|
afee2d8ca8 | ||
|
|
e9158b7305 | ||
|
|
0f5690db85 | ||
|
|
3ebb35a5cd | ||
|
|
f557cd0933 | ||
|
|
063aa702b1 | ||
|
|
4f1070083a | ||
|
|
5e625f71c5 | ||
|
|
cc36a0ecd1 | ||
|
|
a849c25672 | ||
|
|
8b95b93c72 | ||
|
|
3d9557bc50 | ||
|
|
01f2f56be6 | ||
|
|
e658ed993a | ||
|
|
58fc897ea5 | ||
|
|
f8c5a35b56 | ||
|
|
2c92cc40e1 | ||
|
|
1266810c4d | ||
|
|
1de8657a56 | ||
|
|
408c24c700 | ||
|
|
3a1e5f7f0c | ||
|
|
8d85976ac0 | ||
|
|
fe7eaa594f | ||
|
|
5500a1edb3 | ||
|
|
eb27e34972 | ||
|
|
8335c299b8 | ||
|
|
feadc60b14 | ||
|
|
b59799dc2b | ||
|
|
f0b2f6eb00 | ||
|
|
c3fb126a0a | ||
|
|
f6708f6e47 | ||
|
|
d48b5a9079 | ||
|
|
99354f0d7d | ||
|
|
0fa8552aa3 | ||
|
|
4869c388b2 | ||
|
|
2adade0927 | ||
|
|
87b7337d9e | ||
|
|
351c9c1ad3 | ||
|
|
7759fe2cdf | ||
|
|
5d2651afc1 | ||
|
|
ce3b2de5e7 | ||
|
|
1782f59a96 | ||
|
|
2f87deb10b | ||
|
|
65253ca54e | ||
|
|
7cc4a21383 | ||
|
|
af464c2af7 | ||
|
|
e7e85456ea | ||
|
|
440baf6009 |
@@ -2,8 +2,6 @@
|
||||
)
|
||||
|
||||
[](https://console.algora.io/org/coollabsio/bounties/new)
|
||||
[](https://console.algora.io/org/coollabsio/bounties?status=open)
|
||||
[](https://console.algora.io/org/coollabsio/bounties?status=completed)
|
||||
|
||||
# About the Project
|
||||
|
||||
@@ -49,6 +47,7 @@ Special thanks to our biggest sponsors!
|
||||
<a href="https://coolify.ad.vin/?ref=coolify.io" target="_blank"><img src="./other/logos/advin.png" alt="advin logo" width="250"/></a>
|
||||
<a href="https://trieve.ai/?ref=coolify.io" target="_blank"><img src="./other/logos/trieve_bg.png" alt="trieve logo" width="180"/></a>
|
||||
<a href="https://blacksmith.sh/?ref=coolify.io" target="_blank"><img src="./other/logos/blacksmith.svg" alt="blacksmith logo" width="200"/></a>
|
||||
<a href="https://latitude.sh/?ref=coolify.io" target="_blank"><img src="./other/logos/latitude.svg" alt="latitude logo" width="200"/></a>
|
||||
|
||||
## Github Sponsors ($40+)
|
||||
<a href="https://serpapi.com/?ref=coolify.io"><img width="60px" alt="SerpAPI" src="https://github.com/serpapi.png"/></a>
|
||||
|
||||
16
app/Actions/Application/LoadComposeFile.php
Normal file
16
app/Actions/Application/LoadComposeFile.php
Normal file
@@ -0,0 +1,16 @@
|
||||
<?php
|
||||
|
||||
namespace App\Actions\Application;
|
||||
|
||||
use App\Models\Application;
|
||||
use Lorisleiva\Actions\Concerns\AsAction;
|
||||
|
||||
class LoadComposeFile
|
||||
{
|
||||
use AsAction;
|
||||
|
||||
public function handle(Application $application)
|
||||
{
|
||||
$application->loadComposeFile();
|
||||
}
|
||||
}
|
||||
@@ -38,6 +38,12 @@ class StopApplication
|
||||
}
|
||||
}
|
||||
}
|
||||
if ($application->build_pack === 'dockercompose') {
|
||||
// remove network
|
||||
$uuid = $application->uuid;
|
||||
instant_remote_process(["docker network disconnect {$uuid} coolify-proxy"], $server, false);
|
||||
instant_remote_process(["docker network rm {$uuid}"], $server, false);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
namespace App\Actions\CoolifyTask;
|
||||
|
||||
use App\Data\CoolifyTaskArgs;
|
||||
use App\Enums\ActivityTypes;
|
||||
use App\Jobs\CoolifyTask;
|
||||
use Spatie\Activitylog\Models\Activity;
|
||||
|
||||
@@ -40,8 +41,18 @@ class PrepareCoolifyTask
|
||||
|
||||
public function __invoke(): Activity
|
||||
{
|
||||
$job = new CoolifyTask($this->activity, ignore_errors: $this->remoteProcessArgs->ignore_errors, call_event_on_finish: $this->remoteProcessArgs->call_event_on_finish, call_event_data: $this->remoteProcessArgs->call_event_data);
|
||||
dispatch($job);
|
||||
$job = new CoolifyTask(
|
||||
activity: $this->activity,
|
||||
ignore_errors: $this->remoteProcessArgs->ignore_errors,
|
||||
call_event_on_finish: $this->remoteProcessArgs->call_event_on_finish,
|
||||
call_event_data: $this->remoteProcessArgs->call_event_data,
|
||||
);
|
||||
if ($this->remoteProcessArgs->type === ActivityTypes::COMMAND->value) {
|
||||
ray('Dispatching a high priority job');
|
||||
dispatch($job)->onQueue('high');
|
||||
} else {
|
||||
dispatch($job);
|
||||
}
|
||||
$this->activity->refresh();
|
||||
|
||||
return $this->activity;
|
||||
|
||||
@@ -39,7 +39,7 @@ class RunRemoteProcess
|
||||
public function __construct(Activity $activity, bool $hide_from_output = false, bool $ignore_errors = false, $call_event_on_finish = null, $call_event_data = null)
|
||||
{
|
||||
|
||||
if ($activity->getExtraProperty('type') !== ActivityTypes::INLINE->value) {
|
||||
if ($activity->getExtraProperty('type') !== ActivityTypes::INLINE->value && $activity->getExtraProperty('type') !== ActivityTypes::COMMAND->value) {
|
||||
throw new \RuntimeException('Incompatible Activity to run a remote command.');
|
||||
}
|
||||
|
||||
|
||||
29
app/Actions/Database/RestartDatabase.php
Normal file
29
app/Actions/Database/RestartDatabase.php
Normal file
@@ -0,0 +1,29 @@
|
||||
<?php
|
||||
|
||||
namespace App\Actions\Database;
|
||||
|
||||
use App\Models\StandaloneClickhouse;
|
||||
use App\Models\StandaloneDragonfly;
|
||||
use App\Models\StandaloneKeydb;
|
||||
use App\Models\StandaloneMariadb;
|
||||
use App\Models\StandaloneMongodb;
|
||||
use App\Models\StandaloneMysql;
|
||||
use App\Models\StandalonePostgresql;
|
||||
use App\Models\StandaloneRedis;
|
||||
use Lorisleiva\Actions\Concerns\AsAction;
|
||||
|
||||
class RestartDatabase
|
||||
{
|
||||
use AsAction;
|
||||
|
||||
public function handle(StandaloneRedis|StandalonePostgresql|StandaloneMongodb|StandaloneMysql|StandaloneMariadb|StandaloneKeydb|StandaloneDragonfly|StandaloneClickhouse $database)
|
||||
{
|
||||
$server = $database->destination->server;
|
||||
if (! $server->isFunctional()) {
|
||||
return 'Server is not functional';
|
||||
}
|
||||
StopDatabase::run($database);
|
||||
|
||||
return StartDatabase::run($database);
|
||||
}
|
||||
}
|
||||
@@ -3,7 +3,6 @@
|
||||
namespace App\Actions\Database;
|
||||
|
||||
use App\Models\StandaloneClickhouse;
|
||||
use Illuminate\Support\Str;
|
||||
use Lorisleiva\Actions\Concerns\AsAction;
|
||||
use Symfony\Component\Yaml\Yaml;
|
||||
|
||||
@@ -155,11 +154,11 @@ class StartClickhouse
|
||||
$environment_variables->push("$env->key=$env->real_value");
|
||||
}
|
||||
|
||||
if ($environment_variables->filter(fn ($env) => Str::of($env)->contains('CLICKHOUSE_ADMIN_USER'))->isEmpty()) {
|
||||
if ($environment_variables->filter(fn ($env) => str($env)->contains('CLICKHOUSE_ADMIN_USER'))->isEmpty()) {
|
||||
$environment_variables->push("CLICKHOUSE_ADMIN_USER={$this->database->clickhouse_admin_user}");
|
||||
}
|
||||
|
||||
if ($environment_variables->filter(fn ($env) => Str::of($env)->contains('CLICKHOUSE_ADMIN_PASSWORD'))->isEmpty()) {
|
||||
if ($environment_variables->filter(fn ($env) => str($env)->contains('CLICKHOUSE_ADMIN_PASSWORD'))->isEmpty()) {
|
||||
$environment_variables->push("CLICKHOUSE_ADMIN_PASSWORD={$this->database->clickhouse_admin_password}");
|
||||
}
|
||||
|
||||
|
||||
57
app/Actions/Database/StartDatabase.php
Normal file
57
app/Actions/Database/StartDatabase.php
Normal file
@@ -0,0 +1,57 @@
|
||||
<?php
|
||||
|
||||
namespace App\Actions\Database;
|
||||
|
||||
use App\Models\StandaloneClickhouse;
|
||||
use App\Models\StandaloneDragonfly;
|
||||
use App\Models\StandaloneKeydb;
|
||||
use App\Models\StandaloneMariadb;
|
||||
use App\Models\StandaloneMongodb;
|
||||
use App\Models\StandaloneMysql;
|
||||
use App\Models\StandalonePostgresql;
|
||||
use App\Models\StandaloneRedis;
|
||||
use Lorisleiva\Actions\Concerns\AsAction;
|
||||
|
||||
class StartDatabase
|
||||
{
|
||||
use AsAction;
|
||||
|
||||
public function handle(StandaloneRedis|StandalonePostgresql|StandaloneMongodb|StandaloneMysql|StandaloneMariadb|StandaloneKeydb|StandaloneDragonfly|StandaloneClickhouse $database)
|
||||
{
|
||||
$server = $database->destination->server;
|
||||
if (! $server->isFunctional()) {
|
||||
return 'Server is not functional';
|
||||
}
|
||||
switch ($database->getMorphClass()) {
|
||||
case 'App\Models\StandalonePostgresql':
|
||||
$activity = StartPostgresql::run($database);
|
||||
break;
|
||||
case 'App\Models\StandaloneRedis':
|
||||
$activity = StartRedis::run($database);
|
||||
break;
|
||||
case 'App\Models\StandaloneMongodb':
|
||||
$activity = StartMongodb::run($database);
|
||||
break;
|
||||
case 'App\Models\StandaloneMysql':
|
||||
$activity = StartMysql::run($database);
|
||||
break;
|
||||
case 'App\Models\StandaloneMariadb':
|
||||
$activity = StartMariadb::run($database);
|
||||
break;
|
||||
case 'App\Models\StandaloneKeydb':
|
||||
$activity = StartKeydb::run($database);
|
||||
break;
|
||||
case 'App\Models\StandaloneDragonfly':
|
||||
$activity = StartDragonfly::run($database);
|
||||
break;
|
||||
case 'App\Models\StandaloneClickhouse':
|
||||
$activity = StartClickhouse::run($database);
|
||||
break;
|
||||
}
|
||||
if ($database->is_public && $database->public_port) {
|
||||
StartDatabaseProxy::dispatch($database);
|
||||
}
|
||||
|
||||
return $activity;
|
||||
}
|
||||
}
|
||||
@@ -3,7 +3,6 @@
|
||||
namespace App\Actions\Database;
|
||||
|
||||
use App\Models\StandaloneDragonfly;
|
||||
use Illuminate\Support\Str;
|
||||
use Lorisleiva\Actions\Concerns\AsAction;
|
||||
use Symfony\Component\Yaml\Yaml;
|
||||
|
||||
@@ -155,7 +154,7 @@ class StartDragonfly
|
||||
$environment_variables->push("$env->key=$env->real_value");
|
||||
}
|
||||
|
||||
if ($environment_variables->filter(fn ($env) => Str::of($env)->contains('REDIS_PASSWORD'))->isEmpty()) {
|
||||
if ($environment_variables->filter(fn ($env) => str($env)->contains('REDIS_PASSWORD'))->isEmpty()) {
|
||||
$environment_variables->push("REDIS_PASSWORD={$this->database->dragonfly_password}");
|
||||
}
|
||||
|
||||
|
||||
@@ -4,7 +4,6 @@ namespace App\Actions\Database;
|
||||
|
||||
use App\Models\StandaloneKeydb;
|
||||
use Illuminate\Support\Facades\Storage;
|
||||
use Illuminate\Support\Str;
|
||||
use Lorisleiva\Actions\Concerns\AsAction;
|
||||
use Symfony\Component\Yaml\Yaml;
|
||||
|
||||
@@ -163,7 +162,7 @@ class StartKeydb
|
||||
$environment_variables->push("$env->key=$env->real_value");
|
||||
}
|
||||
|
||||
if ($environment_variables->filter(fn ($env) => Str::of($env)->contains('REDIS_PASSWORD'))->isEmpty()) {
|
||||
if ($environment_variables->filter(fn ($env) => str($env)->contains('REDIS_PASSWORD'))->isEmpty()) {
|
||||
$environment_variables->push("REDIS_PASSWORD={$this->database->keydb_password}");
|
||||
}
|
||||
|
||||
|
||||
@@ -3,7 +3,6 @@
|
||||
namespace App\Actions\Database;
|
||||
|
||||
use App\Models\StandaloneMariadb;
|
||||
use Illuminate\Support\Str;
|
||||
use Lorisleiva\Actions\Concerns\AsAction;
|
||||
use Symfony\Component\Yaml\Yaml;
|
||||
|
||||
@@ -157,18 +156,18 @@ class StartMariadb
|
||||
$environment_variables->push("$env->key=$env->real_value");
|
||||
}
|
||||
|
||||
if ($environment_variables->filter(fn ($env) => Str::of($env)->contains('MARIADB_ROOT_PASSWORD'))->isEmpty()) {
|
||||
if ($environment_variables->filter(fn ($env) => str($env)->contains('MARIADB_ROOT_PASSWORD'))->isEmpty()) {
|
||||
$environment_variables->push("MARIADB_ROOT_PASSWORD={$this->database->mariadb_root_password}");
|
||||
}
|
||||
|
||||
if ($environment_variables->filter(fn ($env) => Str::of($env)->contains('MARIADB_DATABASE'))->isEmpty()) {
|
||||
if ($environment_variables->filter(fn ($env) => str($env)->contains('MARIADB_DATABASE'))->isEmpty()) {
|
||||
$environment_variables->push("MARIADB_DATABASE={$this->database->mariadb_database}");
|
||||
}
|
||||
|
||||
if ($environment_variables->filter(fn ($env) => Str::of($env)->contains('MARIADB_USER'))->isEmpty()) {
|
||||
if ($environment_variables->filter(fn ($env) => str($env)->contains('MARIADB_USER'))->isEmpty()) {
|
||||
$environment_variables->push("MARIADB_USER={$this->database->mariadb_user}");
|
||||
}
|
||||
if ($environment_variables->filter(fn ($env) => Str::of($env)->contains('MARIADB_PASSWORD'))->isEmpty()) {
|
||||
if ($environment_variables->filter(fn ($env) => str($env)->contains('MARIADB_PASSWORD'))->isEmpty()) {
|
||||
$environment_variables->push("MARIADB_PASSWORD={$this->database->mariadb_password}");
|
||||
}
|
||||
|
||||
|
||||
@@ -3,7 +3,6 @@
|
||||
namespace App\Actions\Database;
|
||||
|
||||
use App\Models\StandaloneMongodb;
|
||||
use Illuminate\Support\Str;
|
||||
use Lorisleiva\Actions\Concerns\AsAction;
|
||||
use Symfony\Component\Yaml\Yaml;
|
||||
|
||||
@@ -174,15 +173,15 @@ class StartMongodb
|
||||
$environment_variables->push("$env->key=$env->real_value");
|
||||
}
|
||||
|
||||
if ($environment_variables->filter(fn ($env) => Str::of($env)->contains('MONGO_INITDB_ROOT_USERNAME'))->isEmpty()) {
|
||||
if ($environment_variables->filter(fn ($env) => str($env)->contains('MONGO_INITDB_ROOT_USERNAME'))->isEmpty()) {
|
||||
$environment_variables->push("MONGO_INITDB_ROOT_USERNAME={$this->database->mongo_initdb_root_username}");
|
||||
}
|
||||
|
||||
if ($environment_variables->filter(fn ($env) => Str::of($env)->contains('MONGO_INITDB_ROOT_PASSWORD'))->isEmpty()) {
|
||||
if ($environment_variables->filter(fn ($env) => str($env)->contains('MONGO_INITDB_ROOT_PASSWORD'))->isEmpty()) {
|
||||
$environment_variables->push("MONGO_INITDB_ROOT_PASSWORD={$this->database->mongo_initdb_root_password}");
|
||||
}
|
||||
|
||||
if ($environment_variables->filter(fn ($env) => Str::of($env)->contains('MONGO_INITDB_DATABASE'))->isEmpty()) {
|
||||
if ($environment_variables->filter(fn ($env) => str($env)->contains('MONGO_INITDB_DATABASE'))->isEmpty()) {
|
||||
$environment_variables->push("MONGO_INITDB_DATABASE={$this->database->mongo_initdb_database}");
|
||||
}
|
||||
|
||||
|
||||
@@ -3,7 +3,6 @@
|
||||
namespace App\Actions\Database;
|
||||
|
||||
use App\Models\StandaloneMysql;
|
||||
use Illuminate\Support\Str;
|
||||
use Lorisleiva\Actions\Concerns\AsAction;
|
||||
use Symfony\Component\Yaml\Yaml;
|
||||
|
||||
@@ -157,18 +156,18 @@ class StartMysql
|
||||
$environment_variables->push("$env->key=$env->real_value");
|
||||
}
|
||||
|
||||
if ($environment_variables->filter(fn ($env) => Str::of($env)->contains('MYSQL_ROOT_PASSWORD'))->isEmpty()) {
|
||||
if ($environment_variables->filter(fn ($env) => str($env)->contains('MYSQL_ROOT_PASSWORD'))->isEmpty()) {
|
||||
$environment_variables->push("MYSQL_ROOT_PASSWORD={$this->database->mysql_root_password}");
|
||||
}
|
||||
|
||||
if ($environment_variables->filter(fn ($env) => Str::of($env)->contains('MYSQL_DATABASE'))->isEmpty()) {
|
||||
if ($environment_variables->filter(fn ($env) => str($env)->contains('MYSQL_DATABASE'))->isEmpty()) {
|
||||
$environment_variables->push("MYSQL_DATABASE={$this->database->mysql_database}");
|
||||
}
|
||||
|
||||
if ($environment_variables->filter(fn ($env) => Str::of($env)->contains('MYSQL_USER'))->isEmpty()) {
|
||||
if ($environment_variables->filter(fn ($env) => str($env)->contains('MYSQL_USER'))->isEmpty()) {
|
||||
$environment_variables->push("MYSQL_USER={$this->database->mysql_user}");
|
||||
}
|
||||
if ($environment_variables->filter(fn ($env) => Str::of($env)->contains('MYSQL_PASSWORD'))->isEmpty()) {
|
||||
if ($environment_variables->filter(fn ($env) => str($env)->contains('MYSQL_PASSWORD'))->isEmpty()) {
|
||||
$environment_variables->push("MYSQL_PASSWORD={$this->database->mysql_password}");
|
||||
}
|
||||
|
||||
|
||||
@@ -3,7 +3,6 @@
|
||||
namespace App\Actions\Database;
|
||||
|
||||
use App\Models\StandalonePostgresql;
|
||||
use Illuminate\Support\Str;
|
||||
use Lorisleiva\Actions\Concerns\AsAction;
|
||||
use Symfony\Component\Yaml\Yaml;
|
||||
|
||||
@@ -179,18 +178,18 @@ class StartPostgresql
|
||||
$environment_variables->push("$env->key=$env->real_value");
|
||||
}
|
||||
|
||||
if ($environment_variables->filter(fn ($env) => Str::of($env)->contains('POSTGRES_USER'))->isEmpty()) {
|
||||
if ($environment_variables->filter(fn ($env) => str($env)->contains('POSTGRES_USER'))->isEmpty()) {
|
||||
$environment_variables->push("POSTGRES_USER={$this->database->postgres_user}");
|
||||
}
|
||||
if ($environment_variables->filter(fn ($env) => Str::of($env)->contains('PGUSER'))->isEmpty()) {
|
||||
if ($environment_variables->filter(fn ($env) => str($env)->contains('PGUSER'))->isEmpty()) {
|
||||
$environment_variables->push("PGUSER={$this->database->postgres_user}");
|
||||
}
|
||||
|
||||
if ($environment_variables->filter(fn ($env) => Str::of($env)->contains('POSTGRES_PASSWORD'))->isEmpty()) {
|
||||
if ($environment_variables->filter(fn ($env) => str($env)->contains('POSTGRES_PASSWORD'))->isEmpty()) {
|
||||
$environment_variables->push("POSTGRES_PASSWORD={$this->database->postgres_password}");
|
||||
}
|
||||
|
||||
if ($environment_variables->filter(fn ($env) => Str::of($env)->contains('POSTGRES_DB'))->isEmpty()) {
|
||||
if ($environment_variables->filter(fn ($env) => str($env)->contains('POSTGRES_DB'))->isEmpty()) {
|
||||
$environment_variables->push("POSTGRES_DB={$this->database->postgres_db}");
|
||||
}
|
||||
|
||||
|
||||
@@ -4,7 +4,6 @@ namespace App\Actions\Database;
|
||||
|
||||
use App\Models\StandaloneRedis;
|
||||
use Illuminate\Support\Facades\Storage;
|
||||
use Illuminate\Support\Str;
|
||||
use Lorisleiva\Actions\Concerns\AsAction;
|
||||
use Symfony\Component\Yaml\Yaml;
|
||||
|
||||
@@ -167,7 +166,7 @@ class StartRedis
|
||||
$environment_variables->push("$env->key=$env->real_value");
|
||||
}
|
||||
|
||||
if ($environment_variables->filter(fn ($env) => Str::of($env)->contains('REDIS_PASSWORD'))->isEmpty()) {
|
||||
if ($environment_variables->filter(fn ($env) => str($env)->contains('REDIS_PASSWORD'))->isEmpty()) {
|
||||
$environment_variables->push("REDIS_PASSWORD={$this->database->redis_password}");
|
||||
}
|
||||
|
||||
|
||||
@@ -29,7 +29,5 @@ class StopDatabase
|
||||
if ($database->is_public) {
|
||||
StopDatabaseProxy::run($database);
|
||||
}
|
||||
// TODO: make notification for services
|
||||
// $database->environment->project->team->notify(new StatusChanged($database));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -27,7 +27,6 @@ class StopDatabaseProxy
|
||||
$server = data_get($database, 'service.server');
|
||||
}
|
||||
instant_remote_process(["docker rm -f {$uuid}-proxy"], $server);
|
||||
$database->is_public = false;
|
||||
$database->save();
|
||||
DatabaseStatusChanged::dispatch();
|
||||
}
|
||||
|
||||
@@ -3,7 +3,6 @@
|
||||
namespace App\Actions\Proxy;
|
||||
|
||||
use App\Models\Server;
|
||||
use Illuminate\Support\Str;
|
||||
use Lorisleiva\Actions\Concerns\AsAction;
|
||||
|
||||
class CheckConfiguration
|
||||
@@ -24,7 +23,7 @@ class CheckConfiguration
|
||||
$proxy_configuration = instant_remote_process($payload, $server, false);
|
||||
|
||||
if ($reset || ! $proxy_configuration || is_null($proxy_configuration)) {
|
||||
$proxy_configuration = Str::of(generate_default_proxy_configuration($server))->trim()->value;
|
||||
$proxy_configuration = str(generate_default_proxy_configuration($server))->trim()->value;
|
||||
}
|
||||
if (! $proxy_configuration || is_null($proxy_configuration)) {
|
||||
throw new \Exception('Could not generate proxy configuration');
|
||||
|
||||
@@ -3,7 +3,6 @@
|
||||
namespace App\Actions\Proxy;
|
||||
|
||||
use App\Models\Server;
|
||||
use Illuminate\Support\Str;
|
||||
use Lorisleiva\Actions\Concerns\AsAction;
|
||||
|
||||
class SaveConfiguration
|
||||
@@ -18,7 +17,7 @@ class SaveConfiguration
|
||||
$proxy_path = $server->proxyPath();
|
||||
$docker_compose_yml_base64 = base64_encode($proxy_settings);
|
||||
|
||||
$server->proxy->last_saved_settings = Str::of($docker_compose_yml_base64)->pipe('md5')->value;
|
||||
$server->proxy->last_saved_settings = str($docker_compose_yml_base64)->pipe('md5')->value;
|
||||
$server->save();
|
||||
|
||||
return instant_remote_process([
|
||||
|
||||
@@ -4,7 +4,6 @@ namespace App\Actions\Proxy;
|
||||
|
||||
use App\Events\ProxyStarted;
|
||||
use App\Models\Server;
|
||||
use Illuminate\Support\Str;
|
||||
use Lorisleiva\Actions\Concerns\AsAction;
|
||||
use Spatie\Activitylog\Models\Activity;
|
||||
|
||||
@@ -12,11 +11,11 @@ class StartProxy
|
||||
{
|
||||
use AsAction;
|
||||
|
||||
public function handle(Server $server, bool $async = true): string|Activity
|
||||
public function handle(Server $server, bool $async = true, bool $force = false): string|Activity
|
||||
{
|
||||
try {
|
||||
$proxyType = $server->proxyType();
|
||||
if (is_null($proxyType) || $proxyType === 'NONE' || $server->proxy->force_stop || $server->isBuildServer()) {
|
||||
if ((is_null($proxyType) || $proxyType === 'NONE' || $server->proxy->force_stop || $server->isBuildServer()) && $force === false) {
|
||||
return 'OK';
|
||||
}
|
||||
$commands = collect([]);
|
||||
@@ -27,7 +26,7 @@ class StartProxy
|
||||
}
|
||||
SaveConfiguration::run($server, $configuration);
|
||||
$docker_compose_yml_base64 = base64_encode($configuration);
|
||||
$server->proxy->last_applied_settings = Str::of($docker_compose_yml_base64)->pipe('md5')->value;
|
||||
$server->proxy->last_applied_settings = str($docker_compose_yml_base64)->pipe('md5')->value;
|
||||
$server->save();
|
||||
if ($server->isSwarm()) {
|
||||
$commands = $commands->merge([
|
||||
|
||||
@@ -11,6 +11,8 @@ class CleanupDocker
|
||||
|
||||
public function handle(Server $server, bool $force = true)
|
||||
{
|
||||
|
||||
// cleanup docker images, containers, and builder caches
|
||||
if ($force) {
|
||||
instant_remote_process(['docker image prune -af'], $server, false);
|
||||
instant_remote_process(['docker container prune -f --filter "label=coolify.managed=true"'], $server, false);
|
||||
@@ -20,5 +22,15 @@ class CleanupDocker
|
||||
instant_remote_process(['docker container prune -f --filter "label=coolify.managed=true"'], $server, false);
|
||||
instant_remote_process(['docker builder prune -f'], $server, false);
|
||||
}
|
||||
// cleanup networks
|
||||
// $networks = collectDockerNetworksByServer($server);
|
||||
// $proxyNetworks = collectProxyDockerNetworksByServer($server);
|
||||
// $diff = $proxyNetworks->diff($networks);
|
||||
// if ($diff->count() > 0) {
|
||||
// $diff->map(function ($network) use ($server) {
|
||||
// instant_remote_process(["docker network disconnect $network coolify-proxy"], $server);
|
||||
// instant_remote_process(["docker network rm $network"], $server);
|
||||
// });
|
||||
// }
|
||||
}
|
||||
}
|
||||
|
||||
19
app/Actions/Server/RunCommand.php
Normal file
19
app/Actions/Server/RunCommand.php
Normal file
@@ -0,0 +1,19 @@
|
||||
<?php
|
||||
|
||||
namespace App\Actions\Server;
|
||||
|
||||
use App\Enums\ActivityTypes;
|
||||
use App\Models\Server;
|
||||
use Lorisleiva\Actions\Concerns\AsAction;
|
||||
|
||||
class RunCommand
|
||||
{
|
||||
use AsAction;
|
||||
|
||||
public function handle(Server $server, $command)
|
||||
{
|
||||
$activity = remote_process(command: [$command], server: $server, ignore_errors: true, type: ActivityTypes::COMMAND->value);
|
||||
|
||||
return $activity;
|
||||
}
|
||||
}
|
||||
@@ -25,7 +25,7 @@ class UpdateCoolify
|
||||
if (! $this->server) {
|
||||
return;
|
||||
}
|
||||
CleanupDocker::run($this->server, false);
|
||||
CleanupDocker::dispatch($this->server, false)->onQueue('high');
|
||||
$this->latestVersion = get_latest_version_of_coolify();
|
||||
$this->currentVersion = config('version');
|
||||
if (! $manual_update) {
|
||||
@@ -48,6 +48,7 @@ class UpdateCoolify
|
||||
private function update()
|
||||
{
|
||||
if (isDev()) {
|
||||
ray('Running in dev mode');
|
||||
remote_process([
|
||||
'sleep 10',
|
||||
], $this->server);
|
||||
|
||||
18
app/Actions/Service/RestartService.php
Normal file
18
app/Actions/Service/RestartService.php
Normal file
@@ -0,0 +1,18 @@
|
||||
<?php
|
||||
|
||||
namespace App\Actions\Service;
|
||||
|
||||
use App\Models\Service;
|
||||
use Lorisleiva\Actions\Concerns\AsAction;
|
||||
|
||||
class RestartService
|
||||
{
|
||||
use AsAction;
|
||||
|
||||
public function handle(Service $service)
|
||||
{
|
||||
StopService::run($service);
|
||||
|
||||
return StartService::run($service);
|
||||
}
|
||||
}
|
||||
@@ -20,7 +20,7 @@ class StartService
|
||||
$commands[] = "docker network inspect $service->uuid >/dev/null 2>&1 || docker network create --attachable $service->uuid";
|
||||
$commands[] = 'echo Starting service.';
|
||||
$commands[] = "echo 'Pulling images.'";
|
||||
$commands[] = 'docker compose pull';
|
||||
$commands[] = 'docker compose pull --policy always';
|
||||
$commands[] = "echo 'Starting containers.'";
|
||||
$commands[] = 'docker compose up -d --remove-orphans --force-recreate --build';
|
||||
$commands[] = "docker network connect $service->uuid coolify-proxy >/dev/null 2>&1 || true";
|
||||
|
||||
@@ -19,18 +19,16 @@ class StopService
|
||||
ray('Stopping service: '.$service->name);
|
||||
$applications = $service->applications()->get();
|
||||
foreach ($applications as $application) {
|
||||
instant_remote_process(["docker rm -f {$application->name}-{$service->uuid}"], $service->server);
|
||||
instant_remote_process(["docker rm -f {$application->name}-{$service->uuid}"], $service->server, false);
|
||||
$application->update(['status' => 'exited']);
|
||||
}
|
||||
$dbs = $service->databases()->get();
|
||||
foreach ($dbs as $db) {
|
||||
instant_remote_process(["docker rm -f {$db->name}-{$service->uuid}"], $service->server);
|
||||
instant_remote_process(["docker rm -f {$db->name}-{$service->uuid}"], $service->server, false);
|
||||
$db->update(['status' => 'exited']);
|
||||
}
|
||||
instant_remote_process(["docker network disconnect {$service->uuid} coolify-proxy 2>/dev/null"], $service->server, false);
|
||||
instant_remote_process(["docker network rm {$service->uuid} 2>/dev/null"], $service->server, false);
|
||||
// TODO: make notification for databases
|
||||
// $service->environment->project->team->notify(new StatusChanged($service));
|
||||
instant_remote_process(["docker network disconnect {$service->uuid} coolify-proxy"], $service->server);
|
||||
instant_remote_process(["docker network rm {$service->uuid}"], $service->server);
|
||||
} catch (\Exception $e) {
|
||||
echo $e->getMessage();
|
||||
ray($e->getMessage());
|
||||
|
||||
@@ -15,7 +15,7 @@ class PullImage
|
||||
|
||||
$commands[] = 'cd '.$resource->workdir();
|
||||
$commands[] = "echo 'Saved configuration files to {$resource->workdir()}.'";
|
||||
$commands[] = 'docker compose pull';
|
||||
$commands[] = 'docker compose pull --policy always';
|
||||
|
||||
$server = data_get($resource, 'server');
|
||||
|
||||
|
||||
101
app/Console/Commands/CloudCleanupSubscriptions.php
Normal file
101
app/Console/Commands/CloudCleanupSubscriptions.php
Normal file
@@ -0,0 +1,101 @@
|
||||
<?php
|
||||
|
||||
namespace App\Console\Commands;
|
||||
|
||||
use App\Models\Team;
|
||||
use Illuminate\Console\Command;
|
||||
|
||||
class CloudCleanupSubscriptions extends Command
|
||||
{
|
||||
protected $signature = 'cloud:cleanup-subs';
|
||||
|
||||
protected $description = 'Cleanup subcriptions teams';
|
||||
|
||||
public function handle()
|
||||
{
|
||||
try {
|
||||
if (! isCloud()) {
|
||||
$this->error('This command can only be run on cloud');
|
||||
|
||||
return;
|
||||
}
|
||||
ray()->clearAll();
|
||||
$this->info('Cleaning up subcriptions teams');
|
||||
$stripe = new \Stripe\StripeClient(config('subscription.stripe_api_key'));
|
||||
|
||||
$teams = Team::all()->filter(function ($team) {
|
||||
return $team->id !== 0;
|
||||
})->sortBy('id');
|
||||
foreach ($teams as $team) {
|
||||
if ($team) {
|
||||
$this->info("Checking team {$team->id}");
|
||||
}
|
||||
if (! data_get($team, 'subscription')) {
|
||||
$this->disableServers($team);
|
||||
|
||||
continue;
|
||||
}
|
||||
// If the team has no subscription id and the invoice is paid, we need to reset the invoice paid status
|
||||
if (! (data_get($team, 'subscription.stripe_subscription_id'))) {
|
||||
$this->info("Resetting invoice paid status for team {$team->id} {$team->name}");
|
||||
|
||||
$team->subscription->update([
|
||||
'stripe_invoice_paid' => false,
|
||||
'stripe_trial_already_ended' => false,
|
||||
'stripe_subscription_id' => null,
|
||||
]);
|
||||
$this->disableServers($team);
|
||||
|
||||
continue;
|
||||
} else {
|
||||
$subscription = $stripe->subscriptions->retrieve(data_get($team, 'subscription.stripe_subscription_id'), []);
|
||||
$status = data_get($subscription, 'status');
|
||||
if ($status === 'active' || $status === 'past_due') {
|
||||
$team->subscription->update([
|
||||
'stripe_invoice_paid' => true,
|
||||
'stripe_trial_already_ended' => false,
|
||||
]);
|
||||
|
||||
continue;
|
||||
}
|
||||
$this->info('Subscription status: '.$status);
|
||||
$this->info('Subscription id: '.data_get($team, 'subscription.stripe_subscription_id'));
|
||||
$confirm = $this->confirm('Do you want to cancel the subscription?', true);
|
||||
if (! $confirm) {
|
||||
$this->info("Skipping team {$team->id} {$team->name}");
|
||||
} else {
|
||||
$this->info("Cancelling subscription for team {$team->id} {$team->name}");
|
||||
$team->subscription->update([
|
||||
'stripe_invoice_paid' => false,
|
||||
'stripe_trial_already_ended' => false,
|
||||
'stripe_subscription_id' => null,
|
||||
]);
|
||||
$this->disableServers($team);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} catch (\Exception $e) {
|
||||
$this->error($e->getMessage());
|
||||
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
private function disableServers(Team $team)
|
||||
{
|
||||
foreach ($team->servers as $server) {
|
||||
if ($server->settings->is_usable === true || $server->settings->is_reachable === true || $server->ip !== '1.2.3.4') {
|
||||
$this->info("Disabling server {$server->id} {$server->name}");
|
||||
$server->settings()->update([
|
||||
'is_usable' => false,
|
||||
'is_reachable' => false,
|
||||
]);
|
||||
$server->update([
|
||||
'ip' => '1.2.3.4',
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -9,13 +9,41 @@ use Illuminate\Support\Facades\Process;
|
||||
|
||||
class Dev extends Command
|
||||
{
|
||||
protected $signature = 'dev:init';
|
||||
protected $signature = 'dev {--init} {--generate-openapi}';
|
||||
|
||||
protected $description = 'Init the app in dev mode';
|
||||
protected $description = 'Helper commands for development.';
|
||||
|
||||
public function handle()
|
||||
{
|
||||
if ($this->option('init')) {
|
||||
$this->init();
|
||||
|
||||
return;
|
||||
}
|
||||
if ($this->option('generate-openapi')) {
|
||||
$this->generateOpenApi();
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public function generateOpenApi()
|
||||
{
|
||||
// Generate OpenAPI documentation
|
||||
echo "Generating OpenAPI documentation.\n";
|
||||
$process = Process::run(['/var/www/html/vendor/bin/openapi', 'app', '-o', 'openapi.yaml']);
|
||||
$error = $process->errorOutput();
|
||||
$error = preg_replace('/^.*an object literal,.*$/m', '', $error);
|
||||
$error = preg_replace('/^\h*\v+/m', '', $error);
|
||||
echo $error;
|
||||
echo $process->output();
|
||||
}
|
||||
|
||||
public function init()
|
||||
{
|
||||
// Generate APP_KEY if not exists
|
||||
|
||||
if (empty(env('APP_KEY'))) {
|
||||
echo "Generating APP_KEY.\n";
|
||||
Artisan::call('key:generate');
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
namespace App\Console\Commands;
|
||||
|
||||
use App\Actions\Server\StopSentinel;
|
||||
use App\Enums\ApplicationDeploymentStatus;
|
||||
use App\Jobs\CleanupHelperContainersJob;
|
||||
use App\Models\ApplicationDeploymentQueue;
|
||||
@@ -23,6 +24,16 @@ class Init extends Command
|
||||
{
|
||||
$this->alive();
|
||||
get_public_ips();
|
||||
if (version_compare('4.0.0-beta.312', config('version'), '<=')) {
|
||||
$servers = Server::all();
|
||||
foreach ($servers as $server) {
|
||||
$server->settings->update(['is_metrics_enabled' => false]);
|
||||
if ($server->isFunctional()) {
|
||||
StopSentinel::dispatch($server);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$full_cleanup = $this->option('full-cleanup');
|
||||
$cleanup_deployments = $this->option('cleanup-deployments');
|
||||
|
||||
|
||||
@@ -82,7 +82,7 @@ class WaitlistInvite extends Command
|
||||
if (! $already_registered) {
|
||||
$this->password = Str::password();
|
||||
User::create([
|
||||
'name' => Str::of($this->next_patient->email)->before('@'),
|
||||
'name' => str($this->next_patient->email)->before('@'),
|
||||
'email' => $this->next_patient->email,
|
||||
'password' => Hash::make($this->password),
|
||||
'force_password_reset' => true,
|
||||
|
||||
@@ -5,4 +5,5 @@ namespace App\Enums;
|
||||
enum ActivityTypes: string
|
||||
{
|
||||
case INLINE = 'inline';
|
||||
case COMMAND = 'command';
|
||||
}
|
||||
|
||||
11
app/Enums/BuildPackTypes.php
Normal file
11
app/Enums/BuildPackTypes.php
Normal file
@@ -0,0 +1,11 @@
|
||||
<?php
|
||||
|
||||
namespace App\Enums;
|
||||
|
||||
enum BuildPackTypes: string
|
||||
{
|
||||
case NIXPACKS = 'nixpacks';
|
||||
case STATIC = 'static';
|
||||
case DOCKERFILE = 'dockerfile';
|
||||
case DOCKERCOMPOSE = 'dockercompose';
|
||||
}
|
||||
15
app/Enums/NewDatabaseTypes.php
Normal file
15
app/Enums/NewDatabaseTypes.php
Normal file
@@ -0,0 +1,15 @@
|
||||
<?php
|
||||
|
||||
namespace App\Enums;
|
||||
|
||||
enum NewDatabaseTypes: string
|
||||
{
|
||||
case POSTGRESQL = 'postgresql';
|
||||
case MYSQL = 'mysql';
|
||||
case MONGODB = 'mongodb';
|
||||
case REDIS = 'redis';
|
||||
case MARIADB = 'mariadb';
|
||||
case KEYDB = 'keydb';
|
||||
case DRAGONFLY = 'dragonfly';
|
||||
case CLICKHOUSE = 'clickhouse';
|
||||
}
|
||||
22
app/Enums/NewResourceTypes.php
Normal file
22
app/Enums/NewResourceTypes.php
Normal file
@@ -0,0 +1,22 @@
|
||||
<?php
|
||||
|
||||
namespace App\Enums;
|
||||
|
||||
enum NewResourceTypes: string
|
||||
{
|
||||
case PUBLIC = 'public';
|
||||
case PRIVATE_GH_APP = 'private-gh-app';
|
||||
case PRIVATE_DEPLOY_KEY = 'private-deploy-key';
|
||||
case DOCKERFILE = 'dockerfile';
|
||||
case DOCKERCOMPOSE = 'dockercompose';
|
||||
case DOCKER_IMAGE = 'docker-image';
|
||||
case SERVICE = 'service';
|
||||
case POSTGRESQL = 'postgresql';
|
||||
case MYSQL = 'mysql';
|
||||
case MONGODB = 'mongodb';
|
||||
case REDIS = 'redis';
|
||||
case MARIADB = 'mariadb';
|
||||
case KEYDB = 'keydb';
|
||||
case DRAGONFLY = 'dragonfly';
|
||||
case CLICKHOUSE = 'clickhouse';
|
||||
}
|
||||
10
app/Enums/RedirectTypes.php
Normal file
10
app/Enums/RedirectTypes.php
Normal file
@@ -0,0 +1,10 @@
|
||||
<?php
|
||||
|
||||
namespace App\Enums;
|
||||
|
||||
enum RedirectTypes: string
|
||||
{
|
||||
case BOTH = 'both';
|
||||
case WWW = 'www';
|
||||
case NON_WWW = 'non-www';
|
||||
}
|
||||
@@ -12,7 +12,7 @@ class DatabaseStatusChanged implements ShouldBroadcast
|
||||
{
|
||||
use Dispatchable, InteractsWithSockets, SerializesModels;
|
||||
|
||||
public $userId;
|
||||
public ?string $userId = null;
|
||||
|
||||
public function __construct($userId = null)
|
||||
{
|
||||
@@ -20,15 +20,19 @@ class DatabaseStatusChanged implements ShouldBroadcast
|
||||
$userId = auth()->user()->id ?? null;
|
||||
}
|
||||
if (is_null($userId)) {
|
||||
throw new \Exception('User id is null');
|
||||
return false;
|
||||
}
|
||||
$this->userId = $userId;
|
||||
}
|
||||
|
||||
public function broadcastOn(): array
|
||||
public function broadcastOn(): ?array
|
||||
{
|
||||
return [
|
||||
new PrivateChannel("user.{$this->userId}"),
|
||||
];
|
||||
if ($this->userId) {
|
||||
return [
|
||||
new PrivateChannel("user.{$this->userId}"),
|
||||
];
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,7 +12,7 @@ class ServiceStatusChanged implements ShouldBroadcast
|
||||
{
|
||||
use Dispatchable, InteractsWithSockets, SerializesModels;
|
||||
|
||||
public $userId;
|
||||
public ?string $userId = null;
|
||||
|
||||
public function __construct($userId = null)
|
||||
{
|
||||
@@ -20,15 +20,19 @@ class ServiceStatusChanged implements ShouldBroadcast
|
||||
$userId = auth()->user()->id ?? null;
|
||||
}
|
||||
if (is_null($userId)) {
|
||||
throw new \Exception('User id is null');
|
||||
return false;
|
||||
}
|
||||
$this->userId = $userId;
|
||||
}
|
||||
|
||||
public function broadcastOn(): array
|
||||
public function broadcastOn(): ?array
|
||||
{
|
||||
return [
|
||||
new PrivateChannel("user.{$this->userId}"),
|
||||
];
|
||||
if (! is_null($this->userId)) {
|
||||
return [
|
||||
new PrivateChannel("user.{$this->userId}"),
|
||||
];
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -50,7 +50,7 @@ class Handler extends ExceptionHandler
|
||||
return response()->json(['message' => $exception->getMessage()], 401);
|
||||
}
|
||||
|
||||
return redirect()->guest($exception->redirectTo() ?? route('login'));
|
||||
return redirect()->guest($exception->redirectTo($request) ?? route('login'));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -65,7 +65,7 @@ class Handler extends ExceptionHandler
|
||||
if ($e instanceof RuntimeException) {
|
||||
return;
|
||||
}
|
||||
$this->settings = InstanceSettings::get();
|
||||
$this->settings = \App\Models\InstanceSettings::get();
|
||||
if ($this->settings->do_not_track) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -1,183 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers\Api;
|
||||
|
||||
use App\Actions\Application\StopApplication;
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Models\Application;
|
||||
use App\Models\Project;
|
||||
use Illuminate\Http\Request;
|
||||
use Visus\Cuid2\Cuid2;
|
||||
|
||||
class Applications extends Controller
|
||||
{
|
||||
public function applications(Request $request)
|
||||
{
|
||||
$teamId = get_team_id_from_token();
|
||||
if (is_null($teamId)) {
|
||||
return invalid_token();
|
||||
}
|
||||
$projects = Project::where('team_id', $teamId)->get();
|
||||
$applications = collect();
|
||||
$applications->push($projects->pluck('applications')->flatten());
|
||||
$applications = $applications->flatten();
|
||||
|
||||
return response()->json($applications);
|
||||
}
|
||||
|
||||
public function application_by_uuid(Request $request)
|
||||
{
|
||||
$teamId = get_team_id_from_token();
|
||||
if (is_null($teamId)) {
|
||||
return invalid_token();
|
||||
}
|
||||
$uuid = $request->route('uuid');
|
||||
if (! $uuid) {
|
||||
return response()->json(['error' => 'UUID is required.'], 400);
|
||||
}
|
||||
$application = Application::where('uuid', $uuid)->first();
|
||||
if (! $application) {
|
||||
return response()->json(['error' => 'Application not found.'], 404);
|
||||
}
|
||||
|
||||
return response()->json($application);
|
||||
}
|
||||
|
||||
public function update_by_uuid(Request $request)
|
||||
{
|
||||
$teamId = get_team_id_from_token();
|
||||
if (is_null($teamId)) {
|
||||
return invalid_token();
|
||||
}
|
||||
|
||||
if ($request->collect()->count() == 0) {
|
||||
return response()->json([
|
||||
'message' => 'No data provided.',
|
||||
], 400);
|
||||
}
|
||||
$application = Application::where('uuid', $request->uuid)->first();
|
||||
|
||||
if (! $application) {
|
||||
return response()->json([
|
||||
'success' => false,
|
||||
'message' => 'Application not found',
|
||||
], 404);
|
||||
}
|
||||
ray($request->collect());
|
||||
|
||||
// if ($request->has('domains')) {
|
||||
// $existingDomains = explode(',', $application->fqdn);
|
||||
// $newDomains = $request->domains;
|
||||
// $filteredNewDomains = array_filter($newDomains, function ($domain) use ($existingDomains) {
|
||||
// return ! in_array($domain, $existingDomains);
|
||||
// });
|
||||
// $mergedDomains = array_unique(array_merge($existingDomains, $filteredNewDomains));
|
||||
// $application->fqdn = implode(',', $mergedDomains);
|
||||
// $application->custom_labels = base64_encode(implode("\n ", generateLabelsApplication($application)));
|
||||
// $application->save();
|
||||
// }
|
||||
|
||||
return response()->json([
|
||||
'message' => 'Application updated successfully.',
|
||||
'application' => serialize_api_response($application),
|
||||
]);
|
||||
}
|
||||
|
||||
public function action_deploy(Request $request)
|
||||
{
|
||||
$teamId = get_team_id_from_token();
|
||||
if (is_null($teamId)) {
|
||||
return invalid_token();
|
||||
}
|
||||
$force = $request->query->get('force') ?? false;
|
||||
$instant_deploy = $request->query->get('instant_deploy') ?? false;
|
||||
$uuid = $request->route('uuid');
|
||||
if (! $uuid) {
|
||||
return response()->json(['error' => 'UUID is required.'], 400);
|
||||
}
|
||||
$application = Application::where('uuid', $uuid)->first();
|
||||
if (! $application) {
|
||||
return response()->json(['error' => 'Application not found.'], 404);
|
||||
}
|
||||
|
||||
$deployment_uuid = new Cuid2(7);
|
||||
|
||||
queue_application_deployment(
|
||||
application: $application,
|
||||
deployment_uuid: $deployment_uuid,
|
||||
force_rebuild: $force,
|
||||
is_api: true,
|
||||
no_questions_asked: $instant_deploy
|
||||
);
|
||||
|
||||
return response()->json(
|
||||
[
|
||||
'message' => 'Deployment request queued.',
|
||||
'deployment_uuid' => $deployment_uuid->toString(),
|
||||
'deployment_api_url' => base_url().'/api/v1/deployment/'.$deployment_uuid->toString(),
|
||||
],
|
||||
200
|
||||
);
|
||||
}
|
||||
|
||||
public function action_stop(Request $request)
|
||||
{
|
||||
$teamId = get_team_id_from_token();
|
||||
if (is_null($teamId)) {
|
||||
return invalid_token();
|
||||
}
|
||||
$uuid = $request->route('uuid');
|
||||
$sync = $request->query->get('sync') ?? false;
|
||||
if (! $uuid) {
|
||||
return response()->json(['error' => 'UUID is required.'], 400);
|
||||
}
|
||||
$application = Application::where('uuid', $uuid)->first();
|
||||
if (! $application) {
|
||||
return response()->json(['error' => 'Application not found.'], 404);
|
||||
}
|
||||
if ($sync) {
|
||||
StopApplication::run($application);
|
||||
|
||||
return response()->json(['message' => 'Stopped the application.'], 200);
|
||||
} else {
|
||||
StopApplication::dispatch($application);
|
||||
|
||||
return response()->json(['message' => 'Stopping request queued.'], 200);
|
||||
}
|
||||
}
|
||||
|
||||
public function action_restart(Request $request)
|
||||
{
|
||||
$teamId = get_team_id_from_token();
|
||||
if (is_null($teamId)) {
|
||||
return invalid_token();
|
||||
}
|
||||
$uuid = $request->route('uuid');
|
||||
if (! $uuid) {
|
||||
return response()->json(['error' => 'UUID is required.'], 400);
|
||||
}
|
||||
$application = Application::where('uuid', $uuid)->first();
|
||||
if (! $application) {
|
||||
return response()->json(['error' => 'Application not found.'], 404);
|
||||
}
|
||||
|
||||
$deployment_uuid = new Cuid2(7);
|
||||
|
||||
queue_application_deployment(
|
||||
application: $application,
|
||||
deployment_uuid: $deployment_uuid,
|
||||
restart_only: true,
|
||||
is_api: true,
|
||||
);
|
||||
|
||||
return response()->json(
|
||||
[
|
||||
'message' => 'Restart request queued.',
|
||||
'deployment_uuid' => $deployment_uuid->toString(),
|
||||
'deployment_api_url' => base_url().'/api/v1/deployment/'.$deployment_uuid->toString(),
|
||||
],
|
||||
200
|
||||
);
|
||||
|
||||
}
|
||||
}
|
||||
2558
app/Http/Controllers/Api/ApplicationsController.php
Normal file
2558
app/Http/Controllers/Api/ApplicationsController.php
Normal file
File diff suppressed because it is too large
Load Diff
1818
app/Http/Controllers/Api/DatabasesController.php
Normal file
1818
app/Http/Controllers/Api/DatabasesController.php
Normal file
File diff suppressed because it is too large
Load Diff
@@ -1,234 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers\Api;
|
||||
|
||||
use App\Actions\Database\StartClickhouse;
|
||||
use App\Actions\Database\StartDragonfly;
|
||||
use App\Actions\Database\StartKeydb;
|
||||
use App\Actions\Database\StartMariadb;
|
||||
use App\Actions\Database\StartMongodb;
|
||||
use App\Actions\Database\StartMysql;
|
||||
use App\Actions\Database\StartPostgresql;
|
||||
use App\Actions\Database\StartRedis;
|
||||
use App\Actions\Service\StartService;
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Models\ApplicationDeploymentQueue;
|
||||
use App\Models\Server;
|
||||
use App\Models\Tag;
|
||||
use Illuminate\Http\Request;
|
||||
use Visus\Cuid2\Cuid2;
|
||||
|
||||
class Deploy extends Controller
|
||||
{
|
||||
public function deployments(Request $request)
|
||||
{
|
||||
$teamId = get_team_id_from_token();
|
||||
if (is_null($teamId)) {
|
||||
return invalid_token();
|
||||
}
|
||||
$servers = Server::whereTeamId($teamId)->get();
|
||||
$deployments_per_server = ApplicationDeploymentQueue::whereIn('status', ['in_progress', 'queued'])->whereIn('server_id', $servers->pluck('id'))->get([
|
||||
'id',
|
||||
'application_id',
|
||||
'application_name',
|
||||
'deployment_url',
|
||||
'pull_request_id',
|
||||
'server_name',
|
||||
'server_id',
|
||||
'status',
|
||||
])->sortBy('id')->toArray();
|
||||
|
||||
return response()->json(serialize_api_response($deployments_per_server), 200);
|
||||
}
|
||||
|
||||
public function deployment_by_uuid(Request $request)
|
||||
{
|
||||
$teamId = get_team_id_from_token();
|
||||
if (is_null($teamId)) {
|
||||
return invalid_token();
|
||||
}
|
||||
$uuid = $request->route('uuid');
|
||||
if (! $uuid) {
|
||||
return response()->json(['error' => 'UUID is required.'], 400);
|
||||
}
|
||||
$deployment = ApplicationDeploymentQueue::where('deployment_uuid', $uuid)->first()->makeHidden('logs');
|
||||
if (! $deployment) {
|
||||
return response()->json(['error' => 'Deployment not found.'], 404);
|
||||
}
|
||||
|
||||
return response()->json(serialize_api_response($deployment), 200);
|
||||
}
|
||||
|
||||
public function deploy(Request $request)
|
||||
{
|
||||
$teamId = get_team_id_from_token();
|
||||
$uuids = $request->query->get('uuid');
|
||||
$tags = $request->query->get('tag');
|
||||
$force = $request->query->get('force') ?? false;
|
||||
|
||||
if ($uuids && $tags) {
|
||||
return response()->json(['error' => 'You can only use uuid or tag, not both.', 'docs' => 'https://coolify.io/docs/api-reference/deploy-webhook'], 400);
|
||||
}
|
||||
if (is_null($teamId)) {
|
||||
return invalid_token();
|
||||
}
|
||||
if ($tags) {
|
||||
return $this->by_tags($tags, $teamId, $force);
|
||||
} elseif ($uuids) {
|
||||
return $this->by_uuids($uuids, $teamId, $force);
|
||||
}
|
||||
|
||||
return response()->json(['error' => 'You must provide uuid or tag.', 'docs' => 'https://coolify.io/docs/api-reference/deploy-webhook'], 400);
|
||||
}
|
||||
|
||||
private function by_uuids(string $uuid, int $teamId, bool $force = false)
|
||||
{
|
||||
$uuids = explode(',', $uuid);
|
||||
$uuids = collect(array_filter($uuids));
|
||||
|
||||
if (count($uuids) === 0) {
|
||||
return response()->json(['error' => 'No UUIDs provided.', 'docs' => 'https://coolify.io/docs/api-reference/deploy-webhook'], 400);
|
||||
}
|
||||
$deployments = collect();
|
||||
$payload = collect();
|
||||
foreach ($uuids as $uuid) {
|
||||
$resource = getResourceByUuid($uuid, $teamId);
|
||||
if ($resource) {
|
||||
['message' => $return_message, 'deployment_uuid' => $deployment_uuid] = $this->deploy_resource($resource, $force);
|
||||
if ($deployment_uuid) {
|
||||
$deployments->push(['message' => $return_message, 'resource_uuid' => $uuid, 'deployment_uuid' => $deployment_uuid->toString()]);
|
||||
} else {
|
||||
$deployments->push(['message' => $return_message, 'resource_uuid' => $uuid]);
|
||||
}
|
||||
}
|
||||
}
|
||||
if ($deployments->count() > 0) {
|
||||
$payload->put('deployments', $deployments->toArray());
|
||||
|
||||
return response()->json($payload->toArray(), 200);
|
||||
}
|
||||
|
||||
return response()->json(['error' => 'No resources found.', 'docs' => 'https://coolify.io/docs/api-reference/deploy-webhook'], 404);
|
||||
}
|
||||
|
||||
public function by_tags(string $tags, int $team_id, bool $force = false)
|
||||
{
|
||||
$tags = explode(',', $tags);
|
||||
$tags = collect(array_filter($tags));
|
||||
|
||||
if (count($tags) === 0) {
|
||||
return response()->json(['error' => 'No TAGs provided.', 'docs' => 'https://coolify.io/docs/api-reference/deploy-webhook'], 400);
|
||||
}
|
||||
$message = collect([]);
|
||||
$deployments = collect();
|
||||
$payload = collect();
|
||||
foreach ($tags as $tag) {
|
||||
$found_tag = Tag::where(['name' => $tag, 'team_id' => $team_id])->first();
|
||||
if (! $found_tag) {
|
||||
// $message->push("Tag {$tag} not found.");
|
||||
continue;
|
||||
}
|
||||
$applications = $found_tag->applications()->get();
|
||||
$services = $found_tag->services()->get();
|
||||
if ($applications->count() === 0 && $services->count() === 0) {
|
||||
$message->push("No resources found for tag {$tag}.");
|
||||
|
||||
continue;
|
||||
}
|
||||
foreach ($applications as $resource) {
|
||||
['message' => $return_message, 'deployment_uuid' => $deployment_uuid] = $this->deploy_resource($resource, $force);
|
||||
if ($deployment_uuid) {
|
||||
$deployments->push(['resource_uuid' => $resource->uuid, 'deployment_uuid' => $deployment_uuid->toString()]);
|
||||
}
|
||||
$message = $message->merge($return_message);
|
||||
}
|
||||
foreach ($services as $resource) {
|
||||
['message' => $return_message] = $this->deploy_resource($resource, $force);
|
||||
$message = $message->merge($return_message);
|
||||
}
|
||||
}
|
||||
ray($message);
|
||||
if ($message->count() > 0) {
|
||||
$payload->put('message', $message->toArray());
|
||||
if ($deployments->count() > 0) {
|
||||
$payload->put('details', $deployments->toArray());
|
||||
}
|
||||
|
||||
return response()->json($payload->toArray(), 200);
|
||||
}
|
||||
|
||||
return response()->json(['error' => 'No resources found with this tag.', 'docs' => 'https://coolify.io/docs/api-reference/deploy-webhook'], 404);
|
||||
}
|
||||
|
||||
public function deploy_resource($resource, bool $force = false): array
|
||||
{
|
||||
$message = null;
|
||||
$deployment_uuid = null;
|
||||
if (gettype($resource) !== 'object') {
|
||||
return ['message' => "Resource ($resource) not found.", 'deployment_uuid' => $deployment_uuid];
|
||||
}
|
||||
$type = $resource?->getMorphClass();
|
||||
if ($type === 'App\Models\Application') {
|
||||
$deployment_uuid = new Cuid2(7);
|
||||
queue_application_deployment(
|
||||
application: $resource,
|
||||
deployment_uuid: $deployment_uuid,
|
||||
force_rebuild: $force,
|
||||
);
|
||||
$message = "Application {$resource->name} deployment queued.";
|
||||
} elseif ($type === 'App\Models\StandalonePostgresql') {
|
||||
StartPostgresql::run($resource);
|
||||
$resource->update([
|
||||
'started_at' => now(),
|
||||
]);
|
||||
$message = "Database {$resource->name} started.";
|
||||
} elseif ($type === 'App\Models\StandaloneRedis') {
|
||||
StartRedis::run($resource);
|
||||
$resource->update([
|
||||
'started_at' => now(),
|
||||
]);
|
||||
$message = "Database {$resource->name} started.";
|
||||
} elseif ($type === 'App\Models\StandaloneKeydb') {
|
||||
StartKeydb::run($resource);
|
||||
$resource->update([
|
||||
'started_at' => now(),
|
||||
]);
|
||||
$message = "Database {$resource->name} started.";
|
||||
} elseif ($type === 'App\Models\StandaloneDragonfly') {
|
||||
StartDragonfly::run($resource);
|
||||
$resource->update([
|
||||
'started_at' => now(),
|
||||
]);
|
||||
$message = "Database {$resource->name} started.";
|
||||
} elseif ($type === 'App\Models\StandaloneClickhouse') {
|
||||
StartClickhouse::run($resource);
|
||||
$resource->update([
|
||||
'started_at' => now(),
|
||||
]);
|
||||
$message = "Database {$resource->name} started.";
|
||||
} elseif ($type === 'App\Models\StandaloneMongodb') {
|
||||
StartMongodb::run($resource);
|
||||
$resource->update([
|
||||
'started_at' => now(),
|
||||
]);
|
||||
$message = "Database {$resource->name} started.";
|
||||
} elseif ($type === 'App\Models\StandaloneMysql') {
|
||||
StartMysql::run($resource);
|
||||
$resource->update([
|
||||
'started_at' => now(),
|
||||
]);
|
||||
$message = "Database {$resource->name} started.";
|
||||
} elseif ($type === 'App\Models\StandaloneMariadb') {
|
||||
StartMariadb::run($resource);
|
||||
$resource->update([
|
||||
'started_at' => now(),
|
||||
]);
|
||||
$message = "Database {$resource->name} started.";
|
||||
} elseif ($type === 'App\Models\Service') {
|
||||
StartService::run($resource);
|
||||
$message = "Service {$resource->name} started. It could take a while, be patient.";
|
||||
}
|
||||
|
||||
return ['message' => $message, 'deployment_uuid' => $deployment_uuid];
|
||||
}
|
||||
}
|
||||
317
app/Http/Controllers/Api/DeployController.php
Normal file
317
app/Http/Controllers/Api/DeployController.php
Normal file
@@ -0,0 +1,317 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers\Api;
|
||||
|
||||
use App\Actions\Database\StartDatabase;
|
||||
use App\Actions\Service\StartService;
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Models\ApplicationDeploymentQueue;
|
||||
use App\Models\Server;
|
||||
use App\Models\Tag;
|
||||
use Illuminate\Http\Request;
|
||||
use OpenApi\Attributes as OA;
|
||||
use Visus\Cuid2\Cuid2;
|
||||
|
||||
class DeployController extends Controller
|
||||
{
|
||||
private function removeSensitiveData($deployment)
|
||||
{
|
||||
$token = auth()->user()->currentAccessToken();
|
||||
if ($token->can('view:sensitive')) {
|
||||
return serializeApiResponse($deployment);
|
||||
}
|
||||
|
||||
$deployment->makeHidden([
|
||||
'logs',
|
||||
]);
|
||||
|
||||
return serializeApiResponse($deployment);
|
||||
}
|
||||
|
||||
#[OA\Get(
|
||||
summary: 'List',
|
||||
description: 'List currently running deployments',
|
||||
path: '/deployments',
|
||||
security: [
|
||||
['bearerAuth' => []],
|
||||
],
|
||||
tags: ['Deployments'],
|
||||
responses: [
|
||||
new OA\Response(
|
||||
response: 200,
|
||||
description: 'Get all currently running deployments.',
|
||||
content: [
|
||||
|
||||
new OA\MediaType(
|
||||
mediaType: 'application/json',
|
||||
schema: new OA\Schema(
|
||||
type: 'array',
|
||||
items: new OA\Items(ref: '#/components/schemas/ApplicationDeploymentQueue'),
|
||||
)
|
||||
),
|
||||
]),
|
||||
new OA\Response(
|
||||
response: 401,
|
||||
ref: '#/components/responses/401',
|
||||
),
|
||||
new OA\Response(
|
||||
response: 400,
|
||||
ref: '#/components/responses/400',
|
||||
),
|
||||
]
|
||||
)]
|
||||
public function deployments(Request $request)
|
||||
{
|
||||
$teamId = getTeamIdFromToken();
|
||||
if (is_null($teamId)) {
|
||||
return invalidTokenResponse();
|
||||
}
|
||||
$servers = Server::whereTeamId($teamId)->get();
|
||||
$deployments_per_server = ApplicationDeploymentQueue::whereIn('status', ['in_progress', 'queued'])->whereIn('server_id', $servers->pluck('id'))->get()->sortBy('id');
|
||||
$deployments_per_server = $deployments_per_server->map(function ($deployment) {
|
||||
return $this->removeSensitiveData($deployment);
|
||||
});
|
||||
|
||||
return response()->json($deployments_per_server);
|
||||
}
|
||||
|
||||
#[OA\Get(
|
||||
summary: 'Get',
|
||||
description: 'Get deployment by UUID.',
|
||||
path: '/deployments/{uuid}',
|
||||
security: [
|
||||
['bearerAuth' => []],
|
||||
],
|
||||
tags: ['Deployments'],
|
||||
parameters: [
|
||||
new OA\Parameter(name: 'uuid', in: 'path', required: true, description: 'Deployment Uuid', schema: new OA\Schema(type: 'integer')),
|
||||
],
|
||||
responses: [
|
||||
new OA\Response(
|
||||
response: 200,
|
||||
description: 'Get deployment by UUID.',
|
||||
content: [
|
||||
new OA\MediaType(
|
||||
mediaType: 'application/json',
|
||||
schema: new OA\Schema(
|
||||
ref: '#/components/schemas/ApplicationDeploymentQueue',
|
||||
)
|
||||
),
|
||||
]),
|
||||
new OA\Response(
|
||||
response: 401,
|
||||
ref: '#/components/responses/401',
|
||||
),
|
||||
new OA\Response(
|
||||
response: 400,
|
||||
ref: '#/components/responses/400',
|
||||
),
|
||||
new OA\Response(
|
||||
response: 404,
|
||||
ref: '#/components/responses/404',
|
||||
),
|
||||
]
|
||||
)]
|
||||
public function deployment_by_uuid(Request $request)
|
||||
{
|
||||
$teamId = getTeamIdFromToken();
|
||||
if (is_null($teamId)) {
|
||||
return invalidTokenResponse();
|
||||
}
|
||||
$uuid = $request->route('uuid');
|
||||
if (! $uuid) {
|
||||
return response()->json(['message' => 'UUID is required.'], 400);
|
||||
}
|
||||
$deployment = ApplicationDeploymentQueue::where('deployment_uuid', $uuid)->first();
|
||||
if (! $deployment) {
|
||||
return response()->json(['message' => 'Deployment not found.'], 404);
|
||||
}
|
||||
|
||||
return response()->json($this->removeSensitiveData($deployment));
|
||||
}
|
||||
|
||||
#[OA\Get(
|
||||
summary: 'Deploy',
|
||||
description: 'Deploy by tag or uuid. `Post` request also accepted.',
|
||||
path: '/deploy',
|
||||
security: [
|
||||
['bearerAuth' => []],
|
||||
],
|
||||
tags: ['Deployments'],
|
||||
parameters: [
|
||||
new OA\Parameter(name: 'tag', in: 'query', description: 'Tag name(s). Comma separated list is also accepted.', schema: new OA\Schema(type: 'string')),
|
||||
new OA\Parameter(name: 'uuid', in: 'query', description: 'Resource UUID(s). Comma separated list is also accepted.', schema: new OA\Schema(type: 'string')),
|
||||
new OA\Parameter(name: 'force', in: 'query', description: 'Force rebuild (without cache)', schema: new OA\Schema(type: 'boolean')),
|
||||
],
|
||||
|
||||
responses: [
|
||||
new OA\Response(
|
||||
response: 200,
|
||||
description: 'Get deployment(s) Uuid\'s',
|
||||
content: [
|
||||
new OA\MediaType(
|
||||
mediaType: 'application/json',
|
||||
schema: new OA\Schema(
|
||||
type: 'object',
|
||||
properties: [
|
||||
'deployments' => new OA\Property(
|
||||
property: 'deployments',
|
||||
type: 'array',
|
||||
items: new OA\Items(
|
||||
type: 'object',
|
||||
properties: [
|
||||
'message' => ['type' => 'string'],
|
||||
'resource_uuid' => ['type' => 'string'],
|
||||
'deployment_uuid' => ['type' => 'string'],
|
||||
]
|
||||
),
|
||||
),
|
||||
],
|
||||
)
|
||||
),
|
||||
]),
|
||||
new OA\Response(
|
||||
response: 401,
|
||||
ref: '#/components/responses/401',
|
||||
),
|
||||
new OA\Response(
|
||||
response: 400,
|
||||
ref: '#/components/responses/400',
|
||||
),
|
||||
|
||||
]
|
||||
)]
|
||||
public function deploy(Request $request)
|
||||
{
|
||||
$teamId = getTeamIdFromToken();
|
||||
$uuids = $request->query->get('uuid');
|
||||
$tags = $request->query->get('tag');
|
||||
$force = $request->query->get('force') ?? false;
|
||||
|
||||
if ($uuids && $tags) {
|
||||
return response()->json(['message' => 'You can only use uuid or tag, not both.'], 400);
|
||||
}
|
||||
if (is_null($teamId)) {
|
||||
return invalidTokenResponse();
|
||||
}
|
||||
if ($tags) {
|
||||
return $this->by_tags($tags, $teamId, $force);
|
||||
} elseif ($uuids) {
|
||||
return $this->by_uuids($uuids, $teamId, $force);
|
||||
}
|
||||
|
||||
return response()->json(['message' => 'You must provide uuid or tag.'], 400);
|
||||
}
|
||||
|
||||
private function by_uuids(string $uuid, int $teamId, bool $force = false)
|
||||
{
|
||||
$uuids = explode(',', $uuid);
|
||||
$uuids = collect(array_filter($uuids));
|
||||
|
||||
if (count($uuids) === 0) {
|
||||
return response()->json(['message' => 'No UUIDs provided.'], 400);
|
||||
}
|
||||
$deployments = collect();
|
||||
$payload = collect();
|
||||
foreach ($uuids as $uuid) {
|
||||
$resource = getResourceByUuid($uuid, $teamId);
|
||||
if ($resource) {
|
||||
['message' => $return_message, 'deployment_uuid' => $deployment_uuid] = $this->deploy_resource($resource, $force);
|
||||
if ($deployment_uuid) {
|
||||
$deployments->push(['message' => $return_message, 'resource_uuid' => $uuid, 'deployment_uuid' => $deployment_uuid->toString()]);
|
||||
} else {
|
||||
$deployments->push(['message' => $return_message, 'resource_uuid' => $uuid]);
|
||||
}
|
||||
}
|
||||
}
|
||||
if ($deployments->count() > 0) {
|
||||
$payload->put('deployments', $deployments->toArray());
|
||||
|
||||
return response()->json(serializeApiResponse($payload->toArray()));
|
||||
}
|
||||
|
||||
return response()->json(['message' => 'No resources found.'], 404);
|
||||
}
|
||||
|
||||
public function by_tags(string $tags, int $team_id, bool $force = false)
|
||||
{
|
||||
$tags = explode(',', $tags);
|
||||
$tags = collect(array_filter($tags));
|
||||
|
||||
if (count($tags) === 0) {
|
||||
return response()->json(['message' => 'No TAGs provided.'], 400);
|
||||
}
|
||||
$message = collect([]);
|
||||
$deployments = collect();
|
||||
$payload = collect();
|
||||
foreach ($tags as $tag) {
|
||||
$found_tag = Tag::where(['name' => $tag, 'team_id' => $team_id])->first();
|
||||
if (! $found_tag) {
|
||||
// $message->push("Tag {$tag} not found.");
|
||||
continue;
|
||||
}
|
||||
$applications = $found_tag->applications()->get();
|
||||
$services = $found_tag->services()->get();
|
||||
if ($applications->count() === 0 && $services->count() === 0) {
|
||||
$message->push("No resources found for tag {$tag}.");
|
||||
|
||||
continue;
|
||||
}
|
||||
foreach ($applications as $resource) {
|
||||
['message' => $return_message, 'deployment_uuid' => $deployment_uuid] = $this->deploy_resource($resource, $force);
|
||||
if ($deployment_uuid) {
|
||||
$deployments->push(['resource_uuid' => $resource->uuid, 'deployment_uuid' => $deployment_uuid->toString()]);
|
||||
}
|
||||
$message = $message->merge($return_message);
|
||||
}
|
||||
foreach ($services as $resource) {
|
||||
['message' => $return_message] = $this->deploy_resource($resource, $force);
|
||||
$message = $message->merge($return_message);
|
||||
}
|
||||
}
|
||||
if ($message->count() > 0) {
|
||||
$payload->put('message', $message->toArray());
|
||||
if ($deployments->count() > 0) {
|
||||
$payload->put('details', $deployments->toArray());
|
||||
}
|
||||
|
||||
return response()->json(serializeApiResponse($payload->toArray()));
|
||||
}
|
||||
|
||||
return response()->json(['message' => 'No resources found with this tag.'], 404);
|
||||
}
|
||||
|
||||
public function deploy_resource($resource, bool $force = false): array
|
||||
{
|
||||
$message = null;
|
||||
$deployment_uuid = null;
|
||||
if (gettype($resource) !== 'object') {
|
||||
return ['message' => "Resource ($resource) not found.", 'deployment_uuid' => $deployment_uuid];
|
||||
}
|
||||
switch ($resource?->getMorphClass()) {
|
||||
case 'App\Models\Application':
|
||||
$deployment_uuid = new Cuid2(7);
|
||||
queue_application_deployment(
|
||||
application: $resource,
|
||||
deployment_uuid: $deployment_uuid,
|
||||
force_rebuild: $force,
|
||||
);
|
||||
$message = "Application {$resource->name} deployment queued.";
|
||||
break;
|
||||
case 'App\Models\Service':
|
||||
StartService::run($resource);
|
||||
$message = "Service {$resource->name} started. It could take a while, be patient.";
|
||||
break;
|
||||
default:
|
||||
// Database resource
|
||||
StartDatabase::dispatch($resource);
|
||||
$resource->update([
|
||||
'started_at' => now(),
|
||||
]);
|
||||
$message = "Database {$resource->name} started.";
|
||||
break;
|
||||
}
|
||||
|
||||
return ['message' => $message, 'deployment_uuid' => $deployment_uuid];
|
||||
}
|
||||
}
|
||||
@@ -1,54 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers\Api;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Models\Application;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\Validator;
|
||||
|
||||
class Domains extends Controller
|
||||
{
|
||||
public function deleteDomains(Request $request)
|
||||
{
|
||||
$teamId = get_team_id_from_token();
|
||||
if (is_null($teamId)) {
|
||||
return invalid_token();
|
||||
}
|
||||
$validator = Validator::make($request->all(), [
|
||||
'uuid' => 'required|string|exists:applications,uuid',
|
||||
'domains' => 'required|array',
|
||||
'domains.*' => 'required|string|distinct',
|
||||
]);
|
||||
|
||||
if ($validator->fails()) {
|
||||
return response()->json([
|
||||
'success' => false,
|
||||
'message' => 'Validation failed',
|
||||
'errors' => $validator->errors(),
|
||||
], 422);
|
||||
}
|
||||
|
||||
$application = Application::where('uuid', $request->uuid)->first();
|
||||
|
||||
if (! $application) {
|
||||
return response()->json([
|
||||
'success' => false,
|
||||
'message' => 'Application not found',
|
||||
], 404);
|
||||
}
|
||||
|
||||
$existingDomains = explode(',', $application->fqdn);
|
||||
$domainsToDelete = $request->domains;
|
||||
$updatedDomains = array_diff($existingDomains, $domainsToDelete);
|
||||
$application->fqdn = implode(',', $updatedDomains);
|
||||
$application->custom_labels = base64_encode(implode("\n ", generateLabelsApplication($application)));
|
||||
$application->save();
|
||||
|
||||
return response()->json([
|
||||
'success' => true,
|
||||
'message' => 'Domains updated successfully',
|
||||
'application' => $application,
|
||||
]);
|
||||
}
|
||||
}
|
||||
35
app/Http/Controllers/Api/EnvironmentVariablesController.php
Normal file
35
app/Http/Controllers/Api/EnvironmentVariablesController.php
Normal file
@@ -0,0 +1,35 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers\Api;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Models\EnvironmentVariable;
|
||||
use Illuminate\Http\Request;
|
||||
|
||||
class EnvironmentVariablesController extends Controller
|
||||
{
|
||||
public function delete_env_by_uuid(Request $request)
|
||||
{
|
||||
$teamId = getTeamIdFromToken();
|
||||
if (is_null($teamId)) {
|
||||
return invalidTokenResponse();
|
||||
}
|
||||
$env = EnvironmentVariable::where('uuid', $request->env_uuid)->first();
|
||||
if (! $env) {
|
||||
return response()->json([
|
||||
'message' => 'Environment variable not found.',
|
||||
], 404);
|
||||
}
|
||||
$found_app = $env->resource()->whereRelation('environment.project.team', 'id', $teamId)->first();
|
||||
if (! $found_app) {
|
||||
return response()->json([
|
||||
'message' => 'Environment variable not found.',
|
||||
], 404);
|
||||
}
|
||||
$env->delete();
|
||||
|
||||
return response()->json([
|
||||
'message' => 'Environment variable deleted.',
|
||||
]);
|
||||
}
|
||||
}
|
||||
51
app/Http/Controllers/Api/OpenApi.php
Normal file
51
app/Http/Controllers/Api/OpenApi.php
Normal file
@@ -0,0 +1,51 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers\Api;
|
||||
|
||||
use OpenApi\Attributes as OA;
|
||||
|
||||
#[OA\Info(title: 'Coolify', version: '0.1')]
|
||||
#[OA\Server(url: 'https://app.coolify.io/api/v1')]
|
||||
#[OA\SecurityScheme(
|
||||
type: 'http',
|
||||
scheme: 'bearer',
|
||||
securityScheme: 'bearerAuth',
|
||||
description: 'Go to `Keys & Tokens` / `API tokens` and create a new token. Use the token as the bearer token.')]
|
||||
#[OA\Components(
|
||||
responses: [
|
||||
new OA\Response(
|
||||
response: 400,
|
||||
description: 'Invalid token.',
|
||||
content: new OA\JsonContent(
|
||||
type: 'object',
|
||||
properties: [
|
||||
new OA\Property(property: 'message', type: 'string', example: 'Invalid token.'),
|
||||
]
|
||||
)),
|
||||
new OA\Response(
|
||||
response: 401,
|
||||
description: 'Unauthenticated.',
|
||||
content: new OA\JsonContent(
|
||||
type: 'object',
|
||||
properties: [
|
||||
new OA\Property(property: 'message', type: 'string', example: 'Unauthenticated.'),
|
||||
]
|
||||
)),
|
||||
new OA\Response(
|
||||
response: 404,
|
||||
description: 'Resource not found.',
|
||||
content: new OA\JsonContent(
|
||||
type: 'object',
|
||||
properties: [
|
||||
new OA\Property(property: 'message', type: 'string', example: 'Resource not found.'),
|
||||
]
|
||||
)),
|
||||
],
|
||||
)]
|
||||
class OpenApi
|
||||
{
|
||||
// This class is used to generate OpenAPI documentation
|
||||
// for the Coolify API. It is not a controller and does
|
||||
// not contain any routes. It is used to define the
|
||||
// OpenAPI metadata and security scheme for the API.
|
||||
}
|
||||
183
app/Http/Controllers/Api/OtherController.php
Normal file
183
app/Http/Controllers/Api/OtherController.php
Normal file
@@ -0,0 +1,183 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers\Api;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\Http;
|
||||
use OpenApi\Attributes as OA;
|
||||
|
||||
class OtherController extends Controller
|
||||
{
|
||||
#[OA\Get(
|
||||
summary: 'Version',
|
||||
description: 'Get Coolify version.',
|
||||
path: '/version',
|
||||
security: [
|
||||
['bearerAuth' => []],
|
||||
],
|
||||
responses: [
|
||||
new OA\Response(
|
||||
response: 200,
|
||||
description: 'Returns the version of the application',
|
||||
content: new OA\JsonContent(
|
||||
type: 'string',
|
||||
example: 'v4.0.0',
|
||||
)),
|
||||
new OA\Response(
|
||||
response: 401,
|
||||
ref: '#/components/responses/401',
|
||||
),
|
||||
new OA\Response(
|
||||
response: 400,
|
||||
ref: '#/components/responses/400',
|
||||
),
|
||||
]
|
||||
)]
|
||||
public function version(Request $request)
|
||||
{
|
||||
return response(config('version'));
|
||||
}
|
||||
|
||||
#[OA\Get(
|
||||
summary: 'Enable API',
|
||||
description: 'Enable API (only with root permissions).',
|
||||
path: '/enable',
|
||||
security: [
|
||||
['bearerAuth' => []],
|
||||
],
|
||||
responses: [
|
||||
new OA\Response(
|
||||
response: 200,
|
||||
description: 'Enable API.',
|
||||
content: new OA\JsonContent(
|
||||
type: 'object',
|
||||
properties: [
|
||||
new OA\Property(property: 'message', type: 'string', example: 'API enabled.'),
|
||||
]
|
||||
)),
|
||||
new OA\Response(
|
||||
response: 403,
|
||||
description: 'You are not allowed to enable the API.',
|
||||
content: new OA\JsonContent(
|
||||
type: 'object',
|
||||
properties: [
|
||||
new OA\Property(property: 'message', type: 'string', example: 'You are not allowed to enable the API.'),
|
||||
]
|
||||
)),
|
||||
new OA\Response(
|
||||
response: 401,
|
||||
ref: '#/components/responses/401',
|
||||
),
|
||||
new OA\Response(
|
||||
response: 400,
|
||||
ref: '#/components/responses/400',
|
||||
),
|
||||
]
|
||||
)]
|
||||
public function enable_api(Request $request)
|
||||
{
|
||||
$teamId = getTeamIdFromToken();
|
||||
if (is_null($teamId)) {
|
||||
return invalidTokenResponse();
|
||||
}
|
||||
if ($teamId !== '0') {
|
||||
return response()->json(['message' => 'You are not allowed to enable the API.'], 403);
|
||||
}
|
||||
$settings = \App\Models\InstanceSettings::get();
|
||||
$settings->update(['is_api_enabled' => true]);
|
||||
|
||||
return response()->json(['message' => 'API enabled.'], 200);
|
||||
}
|
||||
|
||||
#[OA\Get(
|
||||
summary: 'Disable API',
|
||||
description: 'Disable API (only with root permissions).',
|
||||
path: '/disable',
|
||||
security: [
|
||||
['bearerAuth' => []],
|
||||
],
|
||||
responses: [
|
||||
new OA\Response(
|
||||
response: 200,
|
||||
description: 'Disable API.',
|
||||
content: new OA\JsonContent(
|
||||
type: 'object',
|
||||
properties: [
|
||||
new OA\Property(property: 'message', type: 'string', example: 'API disabled.'),
|
||||
]
|
||||
)),
|
||||
new OA\Response(
|
||||
response: 403,
|
||||
description: 'You are not allowed to disable the API.',
|
||||
content: new OA\JsonContent(
|
||||
type: 'object',
|
||||
properties: [
|
||||
new OA\Property(property: 'message', type: 'string', example: 'You are not allowed to disable the API.'),
|
||||
]
|
||||
)),
|
||||
new OA\Response(
|
||||
response: 401,
|
||||
ref: '#/components/responses/401',
|
||||
),
|
||||
new OA\Response(
|
||||
response: 400,
|
||||
ref: '#/components/responses/400',
|
||||
),
|
||||
]
|
||||
)]
|
||||
public function disable_api(Request $request)
|
||||
{
|
||||
$teamId = getTeamIdFromToken();
|
||||
if (is_null($teamId)) {
|
||||
return invalidTokenResponse();
|
||||
}
|
||||
if ($teamId !== '0') {
|
||||
return response()->json(['message' => 'You are not allowed to disable the API.'], 403);
|
||||
}
|
||||
$settings = \App\Models\InstanceSettings::get();
|
||||
$settings->update(['is_api_enabled' => false]);
|
||||
|
||||
return response()->json(['message' => 'API disabled.'], 200);
|
||||
}
|
||||
|
||||
public function feedback(Request $request)
|
||||
{
|
||||
$content = $request->input('content');
|
||||
$webhook_url = config('coolify.feedback_discord_webhook');
|
||||
if ($webhook_url) {
|
||||
Http::post($webhook_url, [
|
||||
'content' => $content,
|
||||
]);
|
||||
}
|
||||
|
||||
return response()->json(['message' => 'Feedback sent.'], 200);
|
||||
}
|
||||
|
||||
#[OA\Get(
|
||||
summary: 'Healthcheck',
|
||||
description: 'Healthcheck endpoint.',
|
||||
path: '/healthcheck',
|
||||
responses: [
|
||||
new OA\Response(
|
||||
response: 200,
|
||||
description: 'Healthcheck endpoint.',
|
||||
content: new OA\JsonContent(
|
||||
type: 'string',
|
||||
example: 'OK',
|
||||
)),
|
||||
new OA\Response(
|
||||
response: 401,
|
||||
ref: '#/components/responses/401',
|
||||
),
|
||||
new OA\Response(
|
||||
response: 400,
|
||||
ref: '#/components/responses/400',
|
||||
),
|
||||
]
|
||||
)]
|
||||
public function healthcheck(Request $request)
|
||||
{
|
||||
return 'OK';
|
||||
}
|
||||
}
|
||||
@@ -1,44 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers\Api;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Models\Project as ModelsProject;
|
||||
use Illuminate\Http\Request;
|
||||
|
||||
class Project extends Controller
|
||||
{
|
||||
public function projects(Request $request)
|
||||
{
|
||||
$teamId = get_team_id_from_token();
|
||||
if (is_null($teamId)) {
|
||||
return invalid_token();
|
||||
}
|
||||
$projects = ModelsProject::whereTeamId($teamId)->select('id', 'name', 'uuid')->get();
|
||||
|
||||
return response()->json($projects);
|
||||
}
|
||||
|
||||
public function project_by_uuid(Request $request)
|
||||
{
|
||||
$teamId = get_team_id_from_token();
|
||||
if (is_null($teamId)) {
|
||||
return invalid_token();
|
||||
}
|
||||
$project = ModelsProject::whereTeamId($teamId)->whereUuid(request()->uuid)->first()->load(['environments']);
|
||||
|
||||
return response()->json($project);
|
||||
}
|
||||
|
||||
public function environment_details(Request $request)
|
||||
{
|
||||
$teamId = get_team_id_from_token();
|
||||
if (is_null($teamId)) {
|
||||
return invalid_token();
|
||||
}
|
||||
$project = ModelsProject::whereTeamId($teamId)->whereUuid(request()->uuid)->first();
|
||||
$environment = $project->environments()->whereName(request()->environment_name)->first()->load(['applications', 'postgresqls', 'redis', 'mongodbs', 'mysqls', 'mariadbs', 'services']);
|
||||
|
||||
return response()->json($environment);
|
||||
}
|
||||
}
|
||||
147
app/Http/Controllers/Api/ProjectController.php
Normal file
147
app/Http/Controllers/Api/ProjectController.php
Normal file
@@ -0,0 +1,147 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers\Api;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Models\Project;
|
||||
use Illuminate\Http\Request;
|
||||
use OpenApi\Attributes as OA;
|
||||
|
||||
class ProjectController extends Controller
|
||||
{
|
||||
#[OA\Get(
|
||||
summary: 'List',
|
||||
description: 'list projects.',
|
||||
path: '/projects',
|
||||
security: [
|
||||
['bearerAuth' => []],
|
||||
],
|
||||
tags: ['Projects'],
|
||||
responses: [
|
||||
new OA\Response(
|
||||
response: 200,
|
||||
description: 'Get all projects.',
|
||||
content: [
|
||||
new OA\MediaType(
|
||||
mediaType: 'application/json',
|
||||
schema: new OA\Schema(
|
||||
type: 'array',
|
||||
items: new OA\Items(ref: '#/components/schemas/Project')
|
||||
)
|
||||
),
|
||||
]),
|
||||
new OA\Response(
|
||||
response: 401,
|
||||
ref: '#/components/responses/401',
|
||||
),
|
||||
new OA\Response(
|
||||
response: 400,
|
||||
ref: '#/components/responses/400',
|
||||
),
|
||||
]
|
||||
)]
|
||||
public function projects(Request $request)
|
||||
{
|
||||
$teamId = getTeamIdFromToken();
|
||||
if (is_null($teamId)) {
|
||||
return invalidTokenResponse();
|
||||
}
|
||||
$projects = Project::whereTeamId($teamId)->select('id', 'name', 'uuid')->get();
|
||||
|
||||
return response()->json(serializeApiResponse($projects),
|
||||
);
|
||||
}
|
||||
|
||||
#[OA\Get(
|
||||
summary: 'Get',
|
||||
description: 'Get project by Uuid.',
|
||||
path: '/projects/{uuid}',
|
||||
security: [
|
||||
['bearerAuth' => []],
|
||||
],
|
||||
tags: ['Projects'],
|
||||
parameters: [
|
||||
new OA\Parameter(name: 'uuid', in: 'path', required: true, description: 'Project UUID', schema: new OA\Schema(type: 'integer')),
|
||||
],
|
||||
responses: [
|
||||
new OA\Response(
|
||||
response: 200,
|
||||
description: 'Project details',
|
||||
content: new OA\JsonContent(ref: '#/components/schemas/Project')),
|
||||
new OA\Response(
|
||||
response: 401,
|
||||
ref: '#/components/responses/401',
|
||||
),
|
||||
new OA\Response(
|
||||
response: 400,
|
||||
ref: '#/components/responses/400',
|
||||
),
|
||||
new OA\Response(
|
||||
response: 404,
|
||||
description: 'Project not found.',
|
||||
),
|
||||
]
|
||||
)]
|
||||
public function project_by_uuid(Request $request)
|
||||
{
|
||||
$teamId = getTeamIdFromToken();
|
||||
if (is_null($teamId)) {
|
||||
return invalidTokenResponse();
|
||||
}
|
||||
$project = Project::whereTeamId($teamId)->whereUuid(request()->uuid)->first()->load(['environments']);
|
||||
if (! $project) {
|
||||
return response()->json(['message' => 'Project not found.'], 404);
|
||||
}
|
||||
|
||||
return response()->json(
|
||||
serializeApiResponse($project),
|
||||
);
|
||||
}
|
||||
|
||||
#[OA\Get(
|
||||
summary: 'Environment',
|
||||
description: 'Get environment by name.',
|
||||
path: '/projects/{uuid}/{environment_name}',
|
||||
security: [
|
||||
['bearerAuth' => []],
|
||||
],
|
||||
tags: ['Projects'],
|
||||
parameters: [
|
||||
new OA\Parameter(name: 'uuid', in: 'path', required: true, description: 'Project UUID', schema: new OA\Schema(type: 'integer')),
|
||||
new OA\Parameter(name: 'environment_name', in: 'path', required: true, description: 'Environment name', schema: new OA\Schema(type: 'string')),
|
||||
],
|
||||
responses: [
|
||||
new OA\Response(
|
||||
response: 200,
|
||||
description: 'Project details',
|
||||
content: new OA\JsonContent(ref: '#/components/schemas/Environment')),
|
||||
new OA\Response(
|
||||
response: 401,
|
||||
ref: '#/components/responses/401',
|
||||
),
|
||||
new OA\Response(
|
||||
response: 400,
|
||||
ref: '#/components/responses/400',
|
||||
),
|
||||
new OA\Response(
|
||||
response: 404,
|
||||
ref: '#/components/responses/404',
|
||||
),
|
||||
]
|
||||
)]
|
||||
public function environment_details(Request $request)
|
||||
{
|
||||
$teamId = getTeamIdFromToken();
|
||||
if (is_null($teamId)) {
|
||||
return invalidTokenResponse();
|
||||
}
|
||||
$project = Project::whereTeamId($teamId)->whereUuid(request()->uuid)->first();
|
||||
$environment = $project->environments()->whereName(request()->environment_name)->first();
|
||||
if (! $environment) {
|
||||
return response()->json(['message' => 'Environment not found.'], 404);
|
||||
}
|
||||
$environment = $environment->load(['applications', 'postgresqls', 'redis', 'mongodbs', 'mysqls', 'mariadbs', 'services']);
|
||||
|
||||
return response()->json(serializeApiResponse($environment));
|
||||
}
|
||||
}
|
||||
@@ -5,14 +5,42 @@ namespace App\Http\Controllers\Api;
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Models\Project;
|
||||
use Illuminate\Http\Request;
|
||||
use OpenApi\Attributes as OA;
|
||||
|
||||
class Resources extends Controller
|
||||
class ResourcesController extends Controller
|
||||
{
|
||||
#[OA\Get(
|
||||
summary: 'List',
|
||||
description: 'Get all resources.',
|
||||
path: '/resources',
|
||||
security: [
|
||||
['bearerAuth' => []],
|
||||
],
|
||||
tags: ['Resources'],
|
||||
responses: [
|
||||
new OA\Response(
|
||||
response: 200,
|
||||
description: 'Get all resources',
|
||||
content: new OA\JsonContent(
|
||||
type: 'string',
|
||||
example: 'Content is very complex. Will be implemented later.',
|
||||
),
|
||||
),
|
||||
new OA\Response(
|
||||
response: 401,
|
||||
ref: '#/components/responses/401',
|
||||
),
|
||||
new OA\Response(
|
||||
response: 400,
|
||||
ref: '#/components/responses/400',
|
||||
),
|
||||
]
|
||||
)]
|
||||
public function resources(Request $request)
|
||||
{
|
||||
$teamId = get_team_id_from_token();
|
||||
$teamId = getTeamIdFromToken();
|
||||
if (is_null($teamId)) {
|
||||
return invalid_token();
|
||||
return invalidTokenResponse();
|
||||
}
|
||||
$projects = Project::where('team_id', $teamId)->get();
|
||||
$resources = collect();
|
||||
@@ -34,6 +62,6 @@ class Resources extends Controller
|
||||
return $payload;
|
||||
});
|
||||
|
||||
return response()->json($resources);
|
||||
return response()->json(serializeApiResponse($resources));
|
||||
}
|
||||
}
|
||||
372
app/Http/Controllers/Api/SecurityController.php
Normal file
372
app/Http/Controllers/Api/SecurityController.php
Normal file
@@ -0,0 +1,372 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers\Api;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Models\PrivateKey;
|
||||
use Illuminate\Http\Request;
|
||||
use OpenApi\Attributes as OA;
|
||||
|
||||
class SecurityController extends Controller
|
||||
{
|
||||
private function removeSensitiveData($team)
|
||||
{
|
||||
$token = auth()->user()->currentAccessToken();
|
||||
if ($token->can('view:sensitive')) {
|
||||
return serializeApiResponse($team);
|
||||
}
|
||||
$team->makeHidden([
|
||||
'private_key',
|
||||
]);
|
||||
|
||||
return serializeApiResponse($team);
|
||||
}
|
||||
|
||||
#[OA\Get(
|
||||
summary: 'List',
|
||||
description: 'List all private keys.',
|
||||
path: '/security/keys',
|
||||
security: [
|
||||
['bearerAuth' => []],
|
||||
],
|
||||
tags: ['Private Keys'],
|
||||
responses: [
|
||||
new OA\Response(
|
||||
response: 200,
|
||||
description: 'Get all private keys.',
|
||||
content: [
|
||||
new OA\MediaType(
|
||||
mediaType: 'application/json',
|
||||
schema: new OA\Schema(
|
||||
type: 'array',
|
||||
items: new OA\Items(ref: '#/components/schemas/PrivateKey')
|
||||
)
|
||||
),
|
||||
]),
|
||||
new OA\Response(
|
||||
response: 401,
|
||||
ref: '#/components/responses/401',
|
||||
),
|
||||
new OA\Response(
|
||||
response: 400,
|
||||
ref: '#/components/responses/400',
|
||||
),
|
||||
]
|
||||
)]
|
||||
public function keys(Request $request)
|
||||
{
|
||||
$teamId = getTeamIdFromToken();
|
||||
if (is_null($teamId)) {
|
||||
return invalidTokenResponse();
|
||||
}
|
||||
$keys = PrivateKey::where('team_id', $teamId)->get();
|
||||
|
||||
return response()->json($this->removeSensitiveData($keys));
|
||||
}
|
||||
|
||||
#[OA\Get(
|
||||
summary: 'Get',
|
||||
description: 'Get key by UUID.',
|
||||
path: '/security/keys/{uuid}',
|
||||
security: [
|
||||
['bearerAuth' => []],
|
||||
],
|
||||
tags: ['Private Keys'],
|
||||
parameters: [
|
||||
new OA\Parameter(name: 'uuid', in: 'path', required: true, description: 'Private Key Uuid', schema: new OA\Schema(type: 'integer')),
|
||||
],
|
||||
responses: [
|
||||
new OA\Response(
|
||||
response: 200,
|
||||
description: 'Get all private keys.',
|
||||
content: [
|
||||
new OA\MediaType(
|
||||
mediaType: 'application/json',
|
||||
schema: new OA\Schema(
|
||||
type: 'array',
|
||||
items: new OA\Items(ref: '#/components/schemas/PrivateKey')
|
||||
)
|
||||
),
|
||||
]),
|
||||
new OA\Response(
|
||||
response: 401,
|
||||
ref: '#/components/responses/401',
|
||||
),
|
||||
new OA\Response(
|
||||
response: 400,
|
||||
ref: '#/components/responses/400',
|
||||
),
|
||||
new OA\Response(
|
||||
response: 404,
|
||||
description: 'Private Key not found.',
|
||||
),
|
||||
]
|
||||
)]
|
||||
public function key_by_uuid(Request $request)
|
||||
{
|
||||
$teamId = getTeamIdFromToken();
|
||||
if (is_null($teamId)) {
|
||||
return invalidTokenResponse();
|
||||
}
|
||||
|
||||
$key = PrivateKey::where('team_id', $teamId)->where('uuid', $request->uuid)->first();
|
||||
|
||||
if (is_null($key)) {
|
||||
return response()->json([
|
||||
'message' => 'Private Key not found.',
|
||||
], 404);
|
||||
}
|
||||
|
||||
return response()->json($this->removeSensitiveData($key));
|
||||
}
|
||||
|
||||
#[OA\Post(
|
||||
summary: 'Create',
|
||||
description: 'Create a new private key.',
|
||||
path: '/security/keys',
|
||||
security: [
|
||||
['bearerAuth' => []],
|
||||
],
|
||||
tags: ['Private Keys'],
|
||||
requestBody: new OA\RequestBody(
|
||||
required: true,
|
||||
content: [
|
||||
'application/json' => new OA\MediaType(
|
||||
mediaType: 'application/json',
|
||||
schema: new OA\Schema(
|
||||
type: 'object',
|
||||
required: ['private_key'],
|
||||
properties: [
|
||||
'name' => ['type' => 'string'],
|
||||
'description' => ['type' => 'string'],
|
||||
'private_key' => ['type' => 'string'],
|
||||
],
|
||||
additionalProperties: false,
|
||||
)
|
||||
),
|
||||
]
|
||||
),
|
||||
responses: [
|
||||
new OA\Response(
|
||||
response: 201,
|
||||
description: 'The created private key\'s UUID.',
|
||||
content: [
|
||||
new OA\MediaType(
|
||||
mediaType: 'application/json',
|
||||
schema: new OA\Schema(
|
||||
type: 'object',
|
||||
properties: [
|
||||
'uuid' => ['type' => 'string'],
|
||||
]
|
||||
)
|
||||
),
|
||||
]),
|
||||
new OA\Response(
|
||||
response: 401,
|
||||
ref: '#/components/responses/401',
|
||||
),
|
||||
new OA\Response(
|
||||
response: 400,
|
||||
ref: '#/components/responses/400',
|
||||
),
|
||||
]
|
||||
)]
|
||||
public function create_key(Request $request)
|
||||
{
|
||||
$teamId = getTeamIdFromToken();
|
||||
if (is_null($teamId)) {
|
||||
return invalidTokenResponse();
|
||||
}
|
||||
$return = validateIncomingRequest($request);
|
||||
if ($return instanceof \Illuminate\Http\JsonResponse) {
|
||||
return $return;
|
||||
}
|
||||
$validator = customApiValidator($request->all(), [
|
||||
'name' => 'string|max:255',
|
||||
'description' => 'string|max:255',
|
||||
'private_key' => 'required|string',
|
||||
]);
|
||||
|
||||
if ($validator->fails()) {
|
||||
$errors = $validator->errors();
|
||||
|
||||
return response()->json([
|
||||
'message' => 'Validation failed.',
|
||||
'errors' => $errors,
|
||||
], 422);
|
||||
}
|
||||
if (! $request->name) {
|
||||
$request->offsetSet('name', generate_random_name());
|
||||
}
|
||||
if (! $request->description) {
|
||||
$request->offsetSet('description', 'Created by Coolify via API');
|
||||
}
|
||||
$key = PrivateKey::create([
|
||||
'team_id' => $teamId,
|
||||
'name' => $request->name,
|
||||
'description' => $request->description,
|
||||
'private_key' => $request->private_key,
|
||||
]);
|
||||
|
||||
return response()->json(serializeApiResponse([
|
||||
'uuid' => $key->uuid,
|
||||
]))->setStatusCode(201);
|
||||
}
|
||||
|
||||
#[OA\Patch(
|
||||
summary: 'Update',
|
||||
description: 'Update a private key.',
|
||||
path: '/security/keys',
|
||||
security: [
|
||||
['bearerAuth' => []],
|
||||
],
|
||||
tags: ['Private Keys'],
|
||||
requestBody: new OA\RequestBody(
|
||||
required: true,
|
||||
content: [
|
||||
'application/json' => new OA\MediaType(
|
||||
mediaType: 'application/json',
|
||||
schema: new OA\Schema(
|
||||
type: 'object',
|
||||
required: ['private_key'],
|
||||
properties: [
|
||||
'name' => ['type' => 'string'],
|
||||
'description' => ['type' => 'string'],
|
||||
'private_key' => ['type' => 'string'],
|
||||
],
|
||||
additionalProperties: false,
|
||||
)
|
||||
),
|
||||
]
|
||||
),
|
||||
responses: [
|
||||
new OA\Response(
|
||||
response: 201,
|
||||
description: 'The updated private key\'s UUID.',
|
||||
content: [
|
||||
new OA\MediaType(
|
||||
mediaType: 'application/json',
|
||||
schema: new OA\Schema(
|
||||
type: 'object',
|
||||
properties: [
|
||||
'uuid' => ['type' => 'string'],
|
||||
]
|
||||
)
|
||||
),
|
||||
]),
|
||||
new OA\Response(
|
||||
response: 401,
|
||||
ref: '#/components/responses/401',
|
||||
),
|
||||
new OA\Response(
|
||||
response: 400,
|
||||
ref: '#/components/responses/400',
|
||||
),
|
||||
]
|
||||
)]
|
||||
public function update_key(Request $request)
|
||||
{
|
||||
$allowedFields = ['name', 'description', 'private_key'];
|
||||
$teamId = getTeamIdFromToken();
|
||||
if (is_null($teamId)) {
|
||||
return invalidTokenResponse();
|
||||
}
|
||||
$return = validateIncomingRequest($request);
|
||||
if ($return instanceof \Illuminate\Http\JsonResponse) {
|
||||
return $return;
|
||||
}
|
||||
|
||||
$validator = customApiValidator($request->all(), [
|
||||
'name' => 'string|max:255',
|
||||
'description' => 'string|max:255',
|
||||
'private_key' => 'required|string',
|
||||
]);
|
||||
|
||||
$extraFields = array_diff(array_keys($request->all()), $allowedFields);
|
||||
if ($validator->fails() || ! empty($extraFields)) {
|
||||
$errors = $validator->errors();
|
||||
if (! empty($extraFields)) {
|
||||
foreach ($extraFields as $field) {
|
||||
$errors->add($field, 'This field is not allowed.');
|
||||
}
|
||||
}
|
||||
|
||||
return response()->json([
|
||||
'message' => 'Validation failed.',
|
||||
'errors' => $errors,
|
||||
], 422);
|
||||
}
|
||||
$foundKey = PrivateKey::where('team_id', $teamId)->where('uuid', $request->uuid)->first();
|
||||
if (is_null($foundKey)) {
|
||||
return response()->json([
|
||||
'message' => 'Private Key not found.',
|
||||
], 404);
|
||||
}
|
||||
$foundKey->update($request->all());
|
||||
|
||||
return response()->json(serializeApiResponse([
|
||||
'uuid' => $foundKey->uuid,
|
||||
]))->setStatusCode(201);
|
||||
}
|
||||
|
||||
#[OA\Delete(
|
||||
summary: 'Delete',
|
||||
description: 'Delete a private key.',
|
||||
path: '/security/keys/{uuid}',
|
||||
security: [
|
||||
['bearerAuth' => []],
|
||||
],
|
||||
tags: ['Private Keys'],
|
||||
parameters: [
|
||||
new OA\Parameter(name: 'uuid', in: 'path', required: true, description: 'Private Key Uuid', schema: new OA\Schema(type: 'integer')),
|
||||
],
|
||||
responses: [
|
||||
new OA\Response(
|
||||
response: 200,
|
||||
description: 'Private Key deleted.',
|
||||
content: [
|
||||
new OA\MediaType(
|
||||
mediaType: 'application/json',
|
||||
schema: new OA\Schema(
|
||||
type: 'object',
|
||||
properties: [
|
||||
'message' => ['type' => 'string', 'example' => 'Private Key deleted.'],
|
||||
]
|
||||
)
|
||||
),
|
||||
]),
|
||||
new OA\Response(
|
||||
response: 401,
|
||||
ref: '#/components/responses/401',
|
||||
),
|
||||
new OA\Response(
|
||||
response: 400,
|
||||
ref: '#/components/responses/400',
|
||||
),
|
||||
new OA\Response(
|
||||
response: 404,
|
||||
description: 'Private Key not found.',
|
||||
),
|
||||
]
|
||||
)]
|
||||
public function delete_key(Request $request)
|
||||
{
|
||||
$teamId = getTeamIdFromToken();
|
||||
if (is_null($teamId)) {
|
||||
return invalidTokenResponse();
|
||||
}
|
||||
if (! $request->uuid) {
|
||||
return response()->json(['message' => 'UUID is required.'], 422);
|
||||
}
|
||||
|
||||
$key = PrivateKey::where('team_id', $teamId)->where('uuid', $request->uuid)->first();
|
||||
if (is_null($key)) {
|
||||
return response()->json(['message' => 'Private Key not found.'], 404);
|
||||
}
|
||||
$key->forceDelete();
|
||||
|
||||
return response()->json([
|
||||
'message' => 'Private Key deleted.',
|
||||
]);
|
||||
}
|
||||
}
|
||||
@@ -1,167 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers\Api;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Models\Application;
|
||||
use App\Models\InstanceSettings;
|
||||
use App\Models\Project;
|
||||
use App\Models\Server as ModelsServer;
|
||||
use Illuminate\Http\Request;
|
||||
|
||||
class Server extends Controller
|
||||
{
|
||||
public function servers(Request $request)
|
||||
{
|
||||
$teamId = get_team_id_from_token();
|
||||
if (is_null($teamId)) {
|
||||
return invalid_token();
|
||||
}
|
||||
$servers = ModelsServer::whereTeamId($teamId)->select('id', 'name', 'uuid', 'ip', 'user', 'port')->get()->load(['settings'])->map(function ($server) {
|
||||
$server['is_reachable'] = $server->settings->is_reachable;
|
||||
$server['is_usable'] = $server->settings->is_usable;
|
||||
|
||||
return $server;
|
||||
});
|
||||
|
||||
return response()->json($servers);
|
||||
}
|
||||
|
||||
public function server_by_uuid(Request $request)
|
||||
{
|
||||
$with_resources = $request->query('resources');
|
||||
$teamId = get_team_id_from_token();
|
||||
if (is_null($teamId)) {
|
||||
return invalid_token();
|
||||
}
|
||||
$server = ModelsServer::whereTeamId($teamId)->whereUuid(request()->uuid)->first();
|
||||
if (is_null($server)) {
|
||||
return response()->json(['error' => 'Server not found.'], 404);
|
||||
}
|
||||
if ($with_resources) {
|
||||
$server['resources'] = $server->definedResources()->map(function ($resource) {
|
||||
$payload = [
|
||||
'id' => $resource->id,
|
||||
'uuid' => $resource->uuid,
|
||||
'name' => $resource->name,
|
||||
'type' => $resource->type(),
|
||||
'created_at' => $resource->created_at,
|
||||
'updated_at' => $resource->updated_at,
|
||||
];
|
||||
if ($resource->type() === 'service') {
|
||||
$payload['status'] = $resource->status();
|
||||
} else {
|
||||
$payload['status'] = $resource->status;
|
||||
}
|
||||
|
||||
return $payload;
|
||||
});
|
||||
} else {
|
||||
$server->load(['settings']);
|
||||
}
|
||||
|
||||
return response()->json($server);
|
||||
}
|
||||
|
||||
public function get_domains_by_server(Request $request)
|
||||
{
|
||||
$teamId = get_team_id_from_token();
|
||||
if (is_null($teamId)) {
|
||||
return invalid_token();
|
||||
}
|
||||
$uuid = $request->query->get('uuid');
|
||||
if ($uuid) {
|
||||
$domains = Application::getDomainsByUuid($uuid);
|
||||
|
||||
return response()->json([
|
||||
'uuid' => $uuid,
|
||||
'domains' => $domains,
|
||||
]);
|
||||
}
|
||||
$projects = Project::where('team_id', $teamId)->get();
|
||||
$domains = collect();
|
||||
$applications = $projects->pluck('applications')->flatten();
|
||||
$settings = InstanceSettings::get();
|
||||
if ($applications->count() > 0) {
|
||||
foreach ($applications as $application) {
|
||||
$ip = $application->destination->server->ip;
|
||||
$fqdn = str($application->fqdn)->explode(',')->map(function ($fqdn) {
|
||||
return str($fqdn)->replace('http://', '')->replace('https://', '')->replace('/', '');
|
||||
});
|
||||
if ($ip === 'host.docker.internal') {
|
||||
if ($settings->public_ipv4) {
|
||||
$domains->push([
|
||||
'domain' => $fqdn,
|
||||
'ip' => $settings->public_ipv4,
|
||||
]);
|
||||
}
|
||||
if ($settings->public_ipv6) {
|
||||
$domains->push([
|
||||
'domain' => $fqdn,
|
||||
'ip' => $settings->public_ipv6,
|
||||
]);
|
||||
}
|
||||
if (! $settings->public_ipv4 && ! $settings->public_ipv6) {
|
||||
$domains->push([
|
||||
'domain' => $fqdn,
|
||||
'ip' => $ip,
|
||||
]);
|
||||
}
|
||||
} else {
|
||||
$domains->push([
|
||||
'domain' => $fqdn,
|
||||
'ip' => $ip,
|
||||
]);
|
||||
}
|
||||
}
|
||||
}
|
||||
$services = $projects->pluck('services')->flatten();
|
||||
if ($services->count() > 0) {
|
||||
foreach ($services as $service) {
|
||||
$service_applications = $service->applications;
|
||||
if ($service_applications->count() > 0) {
|
||||
foreach ($service_applications as $application) {
|
||||
$fqdn = str($application->fqdn)->explode(',')->map(function ($fqdn) {
|
||||
return str($fqdn)->replace('http://', '')->replace('https://', '')->replace('/', '');
|
||||
});
|
||||
if ($ip === 'host.docker.internal') {
|
||||
if ($settings->public_ipv4) {
|
||||
$domains->push([
|
||||
'domain' => $fqdn,
|
||||
'ip' => $settings->public_ipv4,
|
||||
]);
|
||||
}
|
||||
if ($settings->public_ipv6) {
|
||||
$domains->push([
|
||||
'domain' => $fqdn,
|
||||
'ip' => $settings->public_ipv6,
|
||||
]);
|
||||
}
|
||||
if (! $settings->public_ipv4 && ! $settings->public_ipv6) {
|
||||
$domains->push([
|
||||
'domain' => $fqdn,
|
||||
'ip' => $ip,
|
||||
]);
|
||||
}
|
||||
} else {
|
||||
$domains->push([
|
||||
'domain' => $fqdn,
|
||||
'ip' => $ip,
|
||||
]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
$domains = $domains->groupBy('ip')->map(function ($domain) {
|
||||
return $domain->pluck('domain')->flatten();
|
||||
})->map(function ($domain, $ip) {
|
||||
return [
|
||||
'ip' => $ip,
|
||||
'domains' => $domain,
|
||||
];
|
||||
})->values();
|
||||
|
||||
return response()->json($domains);
|
||||
}
|
||||
}
|
||||
395
app/Http/Controllers/Api/ServersController.php
Normal file
395
app/Http/Controllers/Api/ServersController.php
Normal file
@@ -0,0 +1,395 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers\Api;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Models\Application;
|
||||
use App\Models\Project;
|
||||
use App\Models\Server as ModelsServer;
|
||||
use Illuminate\Http\Request;
|
||||
use OpenApi\Attributes as OA;
|
||||
use Stringable;
|
||||
|
||||
class ServersController extends Controller
|
||||
{
|
||||
private function removeSensitiveDataFromSettings($settings)
|
||||
{
|
||||
$token = auth()->user()->currentAccessToken();
|
||||
if ($token->can('view:sensitive')) {
|
||||
return serializeApiResponse($settings);
|
||||
}
|
||||
$settings = $settings->makeHidden([
|
||||
'metrics_token',
|
||||
]);
|
||||
|
||||
return serializeApiResponse($settings);
|
||||
}
|
||||
|
||||
private function removeSensitiveData($server)
|
||||
{
|
||||
$token = auth()->user()->currentAccessToken();
|
||||
$server->makeHidden([
|
||||
'id',
|
||||
]);
|
||||
if ($token->can('view:sensitive')) {
|
||||
return serializeApiResponse($server);
|
||||
}
|
||||
|
||||
return serializeApiResponse($server);
|
||||
}
|
||||
|
||||
#[OA\Get(
|
||||
summary: 'List',
|
||||
description: 'List all servers.',
|
||||
path: '/servers',
|
||||
security: [
|
||||
['bearerAuth' => []],
|
||||
],
|
||||
tags: ['Servers'],
|
||||
responses: [
|
||||
new OA\Response(
|
||||
response: 200,
|
||||
description: 'Get all servers.',
|
||||
content: [
|
||||
new OA\MediaType(
|
||||
mediaType: 'application/json',
|
||||
schema: new OA\Schema(
|
||||
type: 'array',
|
||||
items: new OA\Items(ref: '#/components/schemas/Server')
|
||||
)
|
||||
),
|
||||
]),
|
||||
new OA\Response(
|
||||
response: 401,
|
||||
ref: '#/components/responses/401',
|
||||
),
|
||||
new OA\Response(
|
||||
response: 400,
|
||||
ref: '#/components/responses/400',
|
||||
),
|
||||
]
|
||||
)]
|
||||
public function servers(Request $request)
|
||||
{
|
||||
$teamId = getTeamIdFromToken();
|
||||
if (is_null($teamId)) {
|
||||
return invalidTokenResponse();
|
||||
}
|
||||
$servers = ModelsServer::whereTeamId($teamId)->select('id', 'name', 'uuid', 'ip', 'user', 'port')->get()->load(['settings'])->map(function ($server) {
|
||||
$server['is_reachable'] = $server->settings->is_reachable;
|
||||
$server['is_usable'] = $server->settings->is_usable;
|
||||
|
||||
return $server;
|
||||
});
|
||||
$servers = $servers->map(function ($server) {
|
||||
$settings = $this->removeSensitiveDataFromSettings($server->settings);
|
||||
$server = $this->removeSensitiveData($server);
|
||||
data_set($server, 'settings', $settings);
|
||||
|
||||
return $server;
|
||||
});
|
||||
|
||||
return response()->json($servers);
|
||||
}
|
||||
|
||||
#[OA\Get(
|
||||
summary: 'Get',
|
||||
description: 'Get server by UUID.',
|
||||
path: '/servers/{uuid}',
|
||||
security: [
|
||||
['bearerAuth' => []],
|
||||
],
|
||||
tags: ['Servers'],
|
||||
parameters: [
|
||||
new OA\Parameter(name: 'uuid', in: 'path', required: true, description: 'Server\'s Uuid', schema: new OA\Schema(type: 'integer')),
|
||||
],
|
||||
responses: [
|
||||
new OA\Response(
|
||||
response: 200,
|
||||
description: 'Get server by UUID',
|
||||
content: [
|
||||
new OA\MediaType(
|
||||
mediaType: 'application/json',
|
||||
schema: new OA\Schema(
|
||||
ref: '#/components/schemas/Server'
|
||||
)
|
||||
),
|
||||
]),
|
||||
new OA\Response(
|
||||
response: 401,
|
||||
ref: '#/components/responses/401',
|
||||
),
|
||||
new OA\Response(
|
||||
response: 400,
|
||||
ref: '#/components/responses/400',
|
||||
),
|
||||
new OA\Response(
|
||||
response: 404,
|
||||
ref: '#/components/responses/404',
|
||||
),
|
||||
]
|
||||
)]
|
||||
public function server_by_uuid(Request $request)
|
||||
{
|
||||
$with_resources = $request->query('resources');
|
||||
$teamId = getTeamIdFromToken();
|
||||
if (is_null($teamId)) {
|
||||
return invalidTokenResponse();
|
||||
}
|
||||
$server = ModelsServer::whereTeamId($teamId)->whereUuid(request()->uuid)->first();
|
||||
if (is_null($server)) {
|
||||
return response()->json(['message' => 'Server not found.'], 404);
|
||||
}
|
||||
if ($with_resources) {
|
||||
$server['resources'] = $server->definedResources()->map(function ($resource) {
|
||||
$payload = [
|
||||
'id' => $resource->id,
|
||||
'uuid' => $resource->uuid,
|
||||
'name' => $resource->name,
|
||||
'type' => $resource->type(),
|
||||
'created_at' => $resource->created_at,
|
||||
'updated_at' => $resource->updated_at,
|
||||
];
|
||||
if ($resource->type() === 'service') {
|
||||
$payload['status'] = $resource->status();
|
||||
} else {
|
||||
$payload['status'] = $resource->status;
|
||||
}
|
||||
|
||||
return $payload;
|
||||
});
|
||||
} else {
|
||||
$server->load(['settings']);
|
||||
}
|
||||
|
||||
$settings = $this->removeSensitiveDataFromSettings($server->settings);
|
||||
$server = $this->removeSensitiveData($server);
|
||||
data_set($server, 'settings', $settings);
|
||||
|
||||
return response()->json(serializeApiResponse($server));
|
||||
}
|
||||
|
||||
#[OA\Get(
|
||||
summary: 'Resources',
|
||||
description: 'Get resources by server.',
|
||||
path: '/servers/{uuid}/resources',
|
||||
security: [
|
||||
['bearerAuth' => []],
|
||||
],
|
||||
tags: ['Servers'],
|
||||
parameters: [
|
||||
new OA\Parameter(name: 'uuid', in: 'path', required: true, description: 'Server\'s Uuid', schema: new OA\Schema(type: 'integer')),
|
||||
],
|
||||
responses: [
|
||||
new OA\Response(
|
||||
response: 200,
|
||||
description: 'Get resources by server',
|
||||
content: [
|
||||
new OA\MediaType(
|
||||
mediaType: 'application/json',
|
||||
schema: new OA\Schema(
|
||||
type: 'array',
|
||||
items: new OA\Items(
|
||||
type: 'object',
|
||||
properties: [
|
||||
'id' => ['type' => 'integer'],
|
||||
'uuid' => ['type' => 'string'],
|
||||
'name' => ['type' => 'string'],
|
||||
'type' => ['type' => 'string'],
|
||||
'created_at' => ['type' => 'string'],
|
||||
'updated_at' => ['type' => 'string'],
|
||||
'status' => ['type' => 'string'],
|
||||
]
|
||||
)
|
||||
)),
|
||||
]),
|
||||
new OA\Response(
|
||||
response: 401,
|
||||
ref: '#/components/responses/401',
|
||||
),
|
||||
new OA\Response(
|
||||
response: 400,
|
||||
ref: '#/components/responses/400',
|
||||
),
|
||||
]
|
||||
)]
|
||||
public function resources_by_server(Request $request)
|
||||
{
|
||||
$teamId = getTeamIdFromToken();
|
||||
if (is_null($teamId)) {
|
||||
return invalidTokenResponse();
|
||||
}
|
||||
$server = ModelsServer::whereTeamId($teamId)->whereUuid(request()->uuid)->first();
|
||||
if (is_null($server)) {
|
||||
return response()->json(['message' => 'Server not found.'], 404);
|
||||
}
|
||||
$server['resources'] = $server->definedResources()->map(function ($resource) {
|
||||
$payload = [
|
||||
'id' => $resource->id,
|
||||
'uuid' => $resource->uuid,
|
||||
'name' => $resource->name,
|
||||
'type' => $resource->type(),
|
||||
'created_at' => $resource->created_at,
|
||||
'updated_at' => $resource->updated_at,
|
||||
];
|
||||
if ($resource->type() === 'service') {
|
||||
$payload['status'] = $resource->status();
|
||||
} else {
|
||||
$payload['status'] = $resource->status;
|
||||
}
|
||||
|
||||
return $payload;
|
||||
});
|
||||
$server = $this->removeSensitiveData($server);
|
||||
ray($server);
|
||||
|
||||
return response()->json(serializeApiResponse(data_get($server, 'resources')));
|
||||
}
|
||||
|
||||
#[OA\Get(
|
||||
summary: 'Domains',
|
||||
description: 'Get domains by server.',
|
||||
path: '/servers/{uuid}/domains',
|
||||
security: [
|
||||
['bearerAuth' => []],
|
||||
],
|
||||
tags: ['Servers'],
|
||||
parameters: [
|
||||
new OA\Parameter(name: 'uuid', in: 'path', required: true, description: 'Server\'s Uuid', schema: new OA\Schema(type: 'integer')),
|
||||
],
|
||||
responses: [
|
||||
new OA\Response(
|
||||
response: 200,
|
||||
description: 'Get domains by server',
|
||||
content: [
|
||||
new OA\MediaType(
|
||||
mediaType: 'application/json',
|
||||
schema: new OA\Schema(
|
||||
type: 'array',
|
||||
items: new OA\Items(
|
||||
type: 'object',
|
||||
properties: [
|
||||
'ip' => ['type' => 'string'],
|
||||
'domains' => ['type' => 'array', 'items' => ['type' => 'string']],
|
||||
]
|
||||
)
|
||||
)),
|
||||
]),
|
||||
new OA\Response(
|
||||
response: 401,
|
||||
ref: '#/components/responses/401',
|
||||
),
|
||||
new OA\Response(
|
||||
response: 400,
|
||||
ref: '#/components/responses/400',
|
||||
),
|
||||
]
|
||||
)]
|
||||
public function domains_by_server(Request $request)
|
||||
{
|
||||
$teamId = getTeamIdFromToken();
|
||||
if (is_null($teamId)) {
|
||||
return invalidTokenResponse();
|
||||
}
|
||||
$uuid = $request->get('uuid');
|
||||
if ($uuid) {
|
||||
$domains = Application::getDomainsByUuid($uuid);
|
||||
|
||||
return response()->json(serializeApiResponse($domains));
|
||||
}
|
||||
$projects = Project::where('team_id', $teamId)->get();
|
||||
$domains = collect();
|
||||
$applications = $projects->pluck('applications')->flatten();
|
||||
$settings = \App\Models\InstanceSettings::get();
|
||||
if ($applications->count() > 0) {
|
||||
foreach ($applications as $application) {
|
||||
$ip = $application->destination->server->ip;
|
||||
$fqdn = str($application->fqdn)->explode(',')->map(function ($fqdn) {
|
||||
$f = str($fqdn)->replace('http://', '')->replace('https://', '')->explode('/');
|
||||
|
||||
return str(str($f[0])->explode(':')[0]);
|
||||
})->filter(function (Stringable $fqdn) {
|
||||
return $fqdn->isNotEmpty();
|
||||
});
|
||||
|
||||
if ($ip === 'host.docker.internal') {
|
||||
if ($settings->public_ipv4) {
|
||||
$domains->push([
|
||||
'domain' => $fqdn,
|
||||
'ip' => $settings->public_ipv4,
|
||||
]);
|
||||
}
|
||||
if ($settings->public_ipv6) {
|
||||
$domains->push([
|
||||
'domain' => $fqdn,
|
||||
'ip' => $settings->public_ipv6,
|
||||
]);
|
||||
}
|
||||
if (! $settings->public_ipv4 && ! $settings->public_ipv6) {
|
||||
$domains->push([
|
||||
'domain' => $fqdn,
|
||||
'ip' => $ip,
|
||||
]);
|
||||
}
|
||||
} else {
|
||||
$domains->push([
|
||||
'domain' => $fqdn,
|
||||
'ip' => $ip,
|
||||
]);
|
||||
}
|
||||
}
|
||||
}
|
||||
$services = $projects->pluck('services')->flatten();
|
||||
if ($services->count() > 0) {
|
||||
foreach ($services as $service) {
|
||||
$service_applications = $service->applications;
|
||||
if ($service_applications->count() > 0) {
|
||||
foreach ($service_applications as $application) {
|
||||
$fqdn = str($application->fqdn)->explode(',')->map(function ($fqdn) {
|
||||
$f = str($fqdn)->replace('http://', '')->replace('https://', '')->explode('/');
|
||||
|
||||
return str(str($f[0])->explode(':')[0]);
|
||||
})->filter(function (Stringable $fqdn) {
|
||||
return $fqdn->isNotEmpty();
|
||||
});
|
||||
if ($ip === 'host.docker.internal') {
|
||||
if ($settings->public_ipv4) {
|
||||
$domains->push([
|
||||
'domain' => $fqdn,
|
||||
'ip' => $settings->public_ipv4,
|
||||
]);
|
||||
}
|
||||
if ($settings->public_ipv6) {
|
||||
$domains->push([
|
||||
'domain' => $fqdn,
|
||||
'ip' => $settings->public_ipv6,
|
||||
]);
|
||||
}
|
||||
if (! $settings->public_ipv4 && ! $settings->public_ipv6) {
|
||||
$domains->push([
|
||||
'domain' => $fqdn,
|
||||
'ip' => $ip,
|
||||
]);
|
||||
}
|
||||
} else {
|
||||
$domains->push([
|
||||
'domain' => $fqdn,
|
||||
'ip' => $ip,
|
||||
]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
$domains = $domains->groupBy('ip')->map(function ($domain) {
|
||||
return $domain->pluck('domain')->flatten();
|
||||
})->map(function ($domain, $ip) {
|
||||
return [
|
||||
'ip' => $ip,
|
||||
'domains' => $domain,
|
||||
];
|
||||
})->values();
|
||||
|
||||
return response()->json(serializeApiResponse($domains));
|
||||
}
|
||||
}
|
||||
702
app/Http/Controllers/Api/ServicesController.php
Normal file
702
app/Http/Controllers/Api/ServicesController.php
Normal file
@@ -0,0 +1,702 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers\Api;
|
||||
|
||||
use App\Actions\Service\RestartService;
|
||||
use App\Actions\Service\StartService;
|
||||
use App\Actions\Service\StopService;
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Jobs\DeleteResourceJob;
|
||||
use App\Models\EnvironmentVariable;
|
||||
use App\Models\Project;
|
||||
use App\Models\Server;
|
||||
use App\Models\Service;
|
||||
use Illuminate\Http\Request;
|
||||
use OpenApi\Attributes as OA;
|
||||
|
||||
class ServicesController extends Controller
|
||||
{
|
||||
private function removeSensitiveData($service)
|
||||
{
|
||||
$token = auth()->user()->currentAccessToken();
|
||||
$service->makeHidden([
|
||||
'id',
|
||||
]);
|
||||
if ($token->can('view:sensitive')) {
|
||||
return serializeApiResponse($service);
|
||||
}
|
||||
|
||||
$service->makeHidden([
|
||||
'docker_compose_raw',
|
||||
'docker_compose',
|
||||
]);
|
||||
|
||||
return serializeApiResponse($service);
|
||||
}
|
||||
|
||||
#[OA\Get(
|
||||
summary: 'List',
|
||||
description: 'List all services.',
|
||||
path: '/services',
|
||||
security: [
|
||||
['bearerAuth' => []],
|
||||
],
|
||||
tags: ['Services'],
|
||||
responses: [
|
||||
new OA\Response(
|
||||
response: 200,
|
||||
description: 'Get all services',
|
||||
content: [
|
||||
new OA\MediaType(
|
||||
mediaType: 'application/json',
|
||||
schema: new OA\Schema(
|
||||
type: 'array',
|
||||
items: new OA\Items(ref: '#/components/schemas/Service')
|
||||
)
|
||||
),
|
||||
]
|
||||
),
|
||||
new OA\Response(
|
||||
response: 401,
|
||||
ref: '#/components/responses/401',
|
||||
),
|
||||
new OA\Response(
|
||||
response: 400,
|
||||
ref: '#/components/responses/400',
|
||||
),
|
||||
]
|
||||
)]
|
||||
public function services(Request $request)
|
||||
{
|
||||
$teamId = getTeamIdFromToken();
|
||||
if (is_null($teamId)) {
|
||||
return invalidTokenResponse();
|
||||
}
|
||||
$projects = Project::where('team_id', $teamId)->get();
|
||||
$services = collect();
|
||||
foreach ($projects as $project) {
|
||||
$services->push($project->services()->get());
|
||||
}
|
||||
foreach ($services as $service) {
|
||||
$service = $this->removeSensitiveData($service);
|
||||
}
|
||||
|
||||
return response()->json($services->flatten());
|
||||
}
|
||||
|
||||
#[OA\Post(
|
||||
summary: 'Create',
|
||||
description: 'Create a one-click service',
|
||||
path: '/services',
|
||||
security: [
|
||||
['bearerAuth' => []],
|
||||
],
|
||||
tags: ['Services'],
|
||||
requestBody: new OA\RequestBody(
|
||||
required: true,
|
||||
content: new OA\MediaType(
|
||||
mediaType: 'application/json',
|
||||
schema: new OA\Schema(
|
||||
type: 'object',
|
||||
required: ['server_uuid', 'project_uuid', 'environment_name', 'type'],
|
||||
properties: [
|
||||
'type' => [
|
||||
'description' => 'The one-click service type',
|
||||
'type' => 'string',
|
||||
'enum' => [
|
||||
'activepieces',
|
||||
'appsmith',
|
||||
'appwrite',
|
||||
'authentik',
|
||||
'babybuddy',
|
||||
'budge',
|
||||
'changedetection',
|
||||
'chatwoot',
|
||||
'classicpress-with-mariadb',
|
||||
'classicpress-with-mysql',
|
||||
'classicpress-without-database',
|
||||
'cloudflared',
|
||||
'code-server',
|
||||
'dashboard',
|
||||
'directus',
|
||||
'directus-with-postgresql',
|
||||
'docker-registry',
|
||||
'docuseal',
|
||||
'docuseal-with-postgres',
|
||||
'dokuwiki',
|
||||
'duplicati',
|
||||
'emby',
|
||||
'embystat',
|
||||
'fider',
|
||||
'filebrowser',
|
||||
'firefly',
|
||||
'formbricks',
|
||||
'ghost',
|
||||
'gitea',
|
||||
'gitea-with-mariadb',
|
||||
'gitea-with-mysql',
|
||||
'gitea-with-postgresql',
|
||||
'glance',
|
||||
'glances',
|
||||
'glitchtip',
|
||||
'grafana',
|
||||
'grafana-with-postgresql',
|
||||
'grocy',
|
||||
'heimdall',
|
||||
'homepage',
|
||||
'jellyfin',
|
||||
'kuzzle',
|
||||
'listmonk',
|
||||
'logto',
|
||||
'mediawiki',
|
||||
'meilisearch',
|
||||
'metabase',
|
||||
'metube',
|
||||
'minio',
|
||||
'moodle',
|
||||
'n8n',
|
||||
'n8n-with-postgresql',
|
||||
'next-image-transformation',
|
||||
'nextcloud',
|
||||
'nocodb',
|
||||
'odoo',
|
||||
'openblocks',
|
||||
'pairdrop',
|
||||
'penpot',
|
||||
'phpmyadmin',
|
||||
'pocketbase',
|
||||
'posthog',
|
||||
'reactive-resume',
|
||||
'rocketchat',
|
||||
'shlink',
|
||||
'slash',
|
||||
'snapdrop',
|
||||
'statusnook',
|
||||
'stirling-pdf',
|
||||
'supabase',
|
||||
'syncthing',
|
||||
'tolgee',
|
||||
'trigger',
|
||||
'trigger-with-external-database',
|
||||
'twenty',
|
||||
'umami',
|
||||
'unleash-with-postgresql',
|
||||
'unleash-without-database',
|
||||
'uptime-kuma',
|
||||
'vaultwarden',
|
||||
'vikunja',
|
||||
'weblate',
|
||||
'whoogle',
|
||||
'wordpress-with-mariadb',
|
||||
'wordpress-with-mysql',
|
||||
'wordpress-without-database',
|
||||
],
|
||||
],
|
||||
'name' => ['type' => 'string', 'maxLength' => 255, 'description' => 'Name of the service.'],
|
||||
'description' => ['type' => 'string', 'nullable' => true, 'description' => 'Description of the service.'],
|
||||
'project_uuid' => ['type' => 'string', 'description' => 'Project UUID.'],
|
||||
'environment_name' => ['type' => 'string', 'description' => 'Environment name.'],
|
||||
'server_uuid' => ['type' => 'string', 'description' => 'Server UUID.'],
|
||||
'destination_uuid' => ['type' => 'string', 'description' => 'Destination UUID. Required if server has multiple destinations.'],
|
||||
'instant_deploy' => ['type' => 'boolean', 'default' => false, 'description' => 'Start the service immediately after creation.'],
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
responses: [
|
||||
new OA\Response(
|
||||
response: 201,
|
||||
description: 'Create a service.',
|
||||
content: [
|
||||
new OA\MediaType(
|
||||
mediaType: 'application/json',
|
||||
schema: new OA\Schema(
|
||||
type: 'object',
|
||||
properties: [
|
||||
'uuid' => ['type' => 'string', 'description' => 'Service UUID.'],
|
||||
'domains' => ['type' => 'array', 'items' => ['type' => 'string'], 'description' => 'Service domains.'],
|
||||
]
|
||||
)
|
||||
),
|
||||
]
|
||||
),
|
||||
new OA\Response(
|
||||
response: 401,
|
||||
ref: '#/components/responses/401',
|
||||
),
|
||||
new OA\Response(
|
||||
response: 400,
|
||||
ref: '#/components/responses/400',
|
||||
),
|
||||
]
|
||||
)]
|
||||
public function create_service(Request $request)
|
||||
{
|
||||
$allowedFields = ['type', 'name', 'description', 'project_uuid', 'environment_name', 'server_uuid', 'destination_uuid', 'instant_deploy'];
|
||||
|
||||
$teamId = getTeamIdFromToken();
|
||||
if (is_null($teamId)) {
|
||||
return invalidTokenResponse();
|
||||
}
|
||||
|
||||
$return = validateIncomingRequest($request);
|
||||
if ($return instanceof \Illuminate\Http\JsonResponse) {
|
||||
return $return;
|
||||
}
|
||||
$validator = customApiValidator($request->all(), [
|
||||
'type' => 'string|required',
|
||||
'project_uuid' => 'string|required',
|
||||
'environment_name' => 'string|required',
|
||||
'server_uuid' => 'string|required',
|
||||
'destination_uuid' => 'string',
|
||||
'name' => 'string|max:255',
|
||||
'description' => 'string|nullable',
|
||||
'instant_deploy' => 'boolean',
|
||||
]);
|
||||
|
||||
$extraFields = array_diff(array_keys($request->all()), $allowedFields);
|
||||
if ($validator->fails() || ! empty($extraFields)) {
|
||||
$errors = $validator->errors();
|
||||
if (! empty($extraFields)) {
|
||||
foreach ($extraFields as $field) {
|
||||
$errors->add($field, 'This field is not allowed.');
|
||||
}
|
||||
}
|
||||
|
||||
return response()->json([
|
||||
'message' => 'Validation failed.',
|
||||
'errors' => $errors,
|
||||
], 422);
|
||||
}
|
||||
$serverUuid = $request->server_uuid;
|
||||
$instantDeploy = $request->instant_deploy ?? false;
|
||||
if ($request->is_public && ! $request->public_port) {
|
||||
$request->offsetSet('is_public', false);
|
||||
}
|
||||
$project = Project::whereTeamId($teamId)->whereUuid($request->project_uuid)->first();
|
||||
if (! $project) {
|
||||
return response()->json(['message' => 'Project not found.'], 404);
|
||||
}
|
||||
$environment = $project->environments()->where('name', $request->environment_name)->first();
|
||||
if (! $environment) {
|
||||
return response()->json(['message' => 'Environment not found.'], 404);
|
||||
}
|
||||
$server = Server::whereTeamId($teamId)->whereUuid($serverUuid)->first();
|
||||
if (! $server) {
|
||||
return response()->json(['message' => 'Server not found.'], 404);
|
||||
}
|
||||
$destinations = $server->destinations();
|
||||
if ($destinations->count() == 0) {
|
||||
return response()->json(['message' => 'Server has no destinations.'], 400);
|
||||
}
|
||||
if ($destinations->count() > 1 && ! $request->has('destination_uuid')) {
|
||||
return response()->json(['message' => 'Server has multiple destinations and you do not set destination_uuid.'], 400);
|
||||
}
|
||||
$destination = $destinations->first();
|
||||
$services = get_service_templates();
|
||||
$serviceKeys = $services->keys();
|
||||
if ($serviceKeys->contains($request->type)) {
|
||||
$oneClickServiceName = $request->type;
|
||||
$oneClickService = data_get($services, "$oneClickServiceName.compose");
|
||||
$oneClickDotEnvs = data_get($services, "$oneClickServiceName.envs", null);
|
||||
if ($oneClickDotEnvs) {
|
||||
$oneClickDotEnvs = str(base64_decode($oneClickDotEnvs))->split('/\r\n|\r|\n/')->filter(function ($value) {
|
||||
return ! empty($value);
|
||||
});
|
||||
}
|
||||
if ($oneClickService) {
|
||||
$service_payload = [
|
||||
'name' => "$oneClickServiceName-".str()->random(10),
|
||||
'docker_compose_raw' => base64_decode($oneClickService),
|
||||
'environment_id' => $environment->id,
|
||||
'service_type' => $oneClickServiceName,
|
||||
'server_id' => $server->id,
|
||||
'destination_id' => $destination->id,
|
||||
'destination_type' => $destination->getMorphClass(),
|
||||
];
|
||||
if ($oneClickServiceName === 'cloudflared') {
|
||||
data_set($service_payload, 'connect_to_docker_network', true);
|
||||
}
|
||||
$service = Service::create($service_payload);
|
||||
$service->name = "$oneClickServiceName-".$service->uuid;
|
||||
$service->save();
|
||||
if ($oneClickDotEnvs?->count() > 0) {
|
||||
$oneClickDotEnvs->each(function ($value) use ($service) {
|
||||
$key = str()->before($value, '=');
|
||||
$value = str(str()->after($value, '='));
|
||||
$generatedValue = $value;
|
||||
if ($value->contains('SERVICE_')) {
|
||||
$command = $value->after('SERVICE_')->beforeLast('_');
|
||||
$generatedValue = generateEnvValue($command->value(), $service);
|
||||
}
|
||||
EnvironmentVariable::create([
|
||||
'key' => $key,
|
||||
'value' => $generatedValue,
|
||||
'service_id' => $service->id,
|
||||
'is_build_time' => false,
|
||||
'is_preview' => false,
|
||||
]);
|
||||
});
|
||||
}
|
||||
$service->parse(isNew: true);
|
||||
if ($instantDeploy) {
|
||||
StartService::dispatch($service);
|
||||
}
|
||||
$domains = $service->applications()->get()->pluck('fqdn')->sort();
|
||||
$domains = $domains->map(function ($domain) {
|
||||
return str($domain)->beforeLast(':')->value();
|
||||
});
|
||||
|
||||
return response()->json([
|
||||
'uuid' => $service->uuid,
|
||||
'domains' => $domains,
|
||||
]);
|
||||
}
|
||||
|
||||
return response()->json(['message' => 'Service not found.'], 404);
|
||||
} else {
|
||||
return response()->json(['message' => 'Invalid service type.', 'valid_service_types' => $serviceKeys], 400);
|
||||
}
|
||||
|
||||
return response()->json(['message' => 'Invalid service type.'], 400);
|
||||
}
|
||||
|
||||
#[OA\Get(
|
||||
summary: 'Get',
|
||||
description: 'Get service by UUID.',
|
||||
path: '/services/{uuid}',
|
||||
security: [
|
||||
['bearerAuth' => []],
|
||||
],
|
||||
tags: ['Services'],
|
||||
parameters: [
|
||||
new OA\Parameter(name: 'uuid', in: 'path', required: true, description: 'Service UUID', schema: new OA\Schema(type: 'string')),
|
||||
],
|
||||
responses: [
|
||||
new OA\Response(
|
||||
response: 200,
|
||||
description: 'Get a service by Uuid.',
|
||||
content: [
|
||||
new OA\MediaType(
|
||||
mediaType: 'application/json',
|
||||
schema: new OA\Schema(
|
||||
ref: '#/components/schemas/Service'
|
||||
)
|
||||
),
|
||||
]
|
||||
),
|
||||
new OA\Response(
|
||||
response: 401,
|
||||
ref: '#/components/responses/401',
|
||||
),
|
||||
new OA\Response(
|
||||
response: 400,
|
||||
ref: '#/components/responses/400',
|
||||
),
|
||||
new OA\Response(
|
||||
response: 404,
|
||||
ref: '#/components/responses/404',
|
||||
),
|
||||
]
|
||||
)]
|
||||
public function service_by_uuid(Request $request)
|
||||
{
|
||||
$teamId = getTeamIdFromToken();
|
||||
if (is_null($teamId)) {
|
||||
return invalidTokenResponse();
|
||||
}
|
||||
if (! $request->uuid) {
|
||||
return response()->json(['message' => 'UUID is required.'], 404);
|
||||
}
|
||||
$service = Service::whereRelation('environment.project.team', 'id', $teamId)->whereUuid($request->uuid)->first();
|
||||
if (! $service) {
|
||||
return response()->json(['message' => 'Service not found.'], 404);
|
||||
}
|
||||
|
||||
return response()->json($this->removeSensitiveData($service));
|
||||
}
|
||||
|
||||
#[OA\Delete(
|
||||
summary: 'Delete',
|
||||
description: 'Delete service by UUID.',
|
||||
path: '/services/{uuid}',
|
||||
security: [
|
||||
['bearerAuth' => []],
|
||||
],
|
||||
tags: ['Services'],
|
||||
parameters: [
|
||||
new OA\Parameter(name: 'uuid', in: 'path', required: true, description: 'Service UUID', schema: new OA\Schema(type: 'string')),
|
||||
],
|
||||
responses: [
|
||||
new OA\Response(
|
||||
response: 200,
|
||||
description: 'Delete a service by Uuid',
|
||||
content: [
|
||||
new OA\MediaType(
|
||||
mediaType: 'application/json',
|
||||
schema: new OA\Schema(
|
||||
type: 'object',
|
||||
properties: [
|
||||
'message' => ['type' => 'string', 'example' => 'Service deletion request queued.'],
|
||||
],
|
||||
)
|
||||
),
|
||||
]
|
||||
),
|
||||
new OA\Response(
|
||||
response: 401,
|
||||
ref: '#/components/responses/401',
|
||||
),
|
||||
new OA\Response(
|
||||
response: 400,
|
||||
ref: '#/components/responses/400',
|
||||
),
|
||||
new OA\Response(
|
||||
response: 404,
|
||||
ref: '#/components/responses/404',
|
||||
),
|
||||
]
|
||||
)]
|
||||
public function delete_by_uuid(Request $request)
|
||||
{
|
||||
$teamId = getTeamIdFromToken();
|
||||
if (is_null($teamId)) {
|
||||
return invalidTokenResponse();
|
||||
}
|
||||
if (! $request->uuid) {
|
||||
return response()->json(['message' => 'UUID is required.'], 404);
|
||||
}
|
||||
$service = Service::whereRelation('environment.project.team', 'id', $teamId)->whereUuid($request->uuid)->first();
|
||||
if (! $service) {
|
||||
return response()->json(['message' => 'Service not found.'], 404);
|
||||
}
|
||||
DeleteResourceJob::dispatch($service);
|
||||
|
||||
return response()->json([
|
||||
'message' => 'Service deletion request queued.',
|
||||
]);
|
||||
}
|
||||
|
||||
#[OA\Get(
|
||||
summary: 'Start',
|
||||
description: 'Start service. `Post` request is also accepted.',
|
||||
path: '/services/{uuid}/start',
|
||||
security: [
|
||||
['bearerAuth' => []],
|
||||
],
|
||||
tags: ['Services'],
|
||||
parameters: [
|
||||
new OA\Parameter(
|
||||
name: 'uuid',
|
||||
in: 'path',
|
||||
description: 'UUID of the service.',
|
||||
required: true,
|
||||
schema: new OA\Schema(
|
||||
type: 'string',
|
||||
format: 'uuid',
|
||||
)
|
||||
),
|
||||
],
|
||||
responses: [
|
||||
new OA\Response(
|
||||
response: 200,
|
||||
description: 'Start service.',
|
||||
content: [
|
||||
new OA\MediaType(
|
||||
mediaType: 'application/json',
|
||||
schema: new OA\Schema(
|
||||
type: 'object',
|
||||
properties: [
|
||||
'message' => ['type' => 'string', 'example' => 'Service starting request queued.'],
|
||||
])
|
||||
),
|
||||
]),
|
||||
new OA\Response(
|
||||
response: 401,
|
||||
ref: '#/components/responses/401',
|
||||
),
|
||||
new OA\Response(
|
||||
response: 400,
|
||||
ref: '#/components/responses/400',
|
||||
),
|
||||
new OA\Response(
|
||||
response: 404,
|
||||
ref: '#/components/responses/404',
|
||||
),
|
||||
]
|
||||
)]
|
||||
public function action_deploy(Request $request)
|
||||
{
|
||||
$teamId = getTeamIdFromToken();
|
||||
if (is_null($teamId)) {
|
||||
return invalidTokenResponse();
|
||||
}
|
||||
$uuid = $request->route('uuid');
|
||||
if (! $uuid) {
|
||||
return response()->json(['message' => 'UUID is required.'], 400);
|
||||
}
|
||||
$service = Service::whereRelation('environment.project.team', 'id', $teamId)->whereUuid($request->uuid)->first();
|
||||
if (! $service) {
|
||||
return response()->json(['message' => 'Service not found.'], 404);
|
||||
}
|
||||
if (str($service->status())->contains('running')) {
|
||||
return response()->json(['message' => 'Service is already running.'], 400);
|
||||
}
|
||||
StartService::dispatch($service);
|
||||
|
||||
return response()->json(
|
||||
[
|
||||
'message' => 'Service starting request queued.',
|
||||
],
|
||||
200
|
||||
);
|
||||
}
|
||||
|
||||
#[OA\Get(
|
||||
summary: 'Stop',
|
||||
description: 'Stop service. `Post` request is also accepted.',
|
||||
path: '/services/{uuid}/stop',
|
||||
security: [
|
||||
['bearerAuth' => []],
|
||||
],
|
||||
tags: ['Services'],
|
||||
parameters: [
|
||||
new OA\Parameter(
|
||||
name: 'uuid',
|
||||
in: 'path',
|
||||
description: 'UUID of the service.',
|
||||
required: true,
|
||||
schema: new OA\Schema(
|
||||
type: 'string',
|
||||
format: 'uuid',
|
||||
)
|
||||
),
|
||||
],
|
||||
responses: [
|
||||
new OA\Response(
|
||||
response: 200,
|
||||
description: 'Stop service.',
|
||||
content: [
|
||||
new OA\MediaType(
|
||||
mediaType: 'application/json',
|
||||
schema: new OA\Schema(
|
||||
type: 'object',
|
||||
properties: [
|
||||
'message' => ['type' => 'string', 'example' => 'Service stopping request queued.'],
|
||||
])
|
||||
),
|
||||
]),
|
||||
new OA\Response(
|
||||
response: 401,
|
||||
ref: '#/components/responses/401',
|
||||
),
|
||||
new OA\Response(
|
||||
response: 400,
|
||||
ref: '#/components/responses/400',
|
||||
),
|
||||
new OA\Response(
|
||||
response: 404,
|
||||
ref: '#/components/responses/404',
|
||||
),
|
||||
]
|
||||
)]
|
||||
public function action_stop(Request $request)
|
||||
{
|
||||
$teamId = getTeamIdFromToken();
|
||||
if (is_null($teamId)) {
|
||||
return invalidTokenResponse();
|
||||
}
|
||||
$uuid = $request->route('uuid');
|
||||
if (! $uuid) {
|
||||
return response()->json(['message' => 'UUID is required.'], 400);
|
||||
}
|
||||
$service = Service::whereRelation('environment.project.team', 'id', $teamId)->whereUuid($request->uuid)->first();
|
||||
if (! $service) {
|
||||
return response()->json(['message' => 'Service not found.'], 404);
|
||||
}
|
||||
if (str($service->status())->contains('stopped') || str($service->status())->contains('exited')) {
|
||||
return response()->json(['message' => 'Service is already stopped.'], 400);
|
||||
}
|
||||
StopService::dispatch($service);
|
||||
|
||||
return response()->json(
|
||||
[
|
||||
'message' => 'Service stopping request queued.',
|
||||
],
|
||||
200
|
||||
);
|
||||
}
|
||||
|
||||
#[OA\Get(
|
||||
summary: 'Restart',
|
||||
description: 'Restart service. `Post` request is also accepted.',
|
||||
path: '/services/{uuid}/restart',
|
||||
security: [
|
||||
['bearerAuth' => []],
|
||||
],
|
||||
tags: ['Services'],
|
||||
parameters: [
|
||||
new OA\Parameter(
|
||||
name: 'uuid',
|
||||
in: 'path',
|
||||
description: 'UUID of the service.',
|
||||
required: true,
|
||||
schema: new OA\Schema(
|
||||
type: 'string',
|
||||
format: 'uuid',
|
||||
)
|
||||
),
|
||||
],
|
||||
responses: [
|
||||
new OA\Response(
|
||||
response: 200,
|
||||
description: 'Restart service.',
|
||||
content: [
|
||||
new OA\MediaType(
|
||||
mediaType: 'application/json',
|
||||
schema: new OA\Schema(
|
||||
type: 'object',
|
||||
properties: [
|
||||
'message' => ['type' => 'string', 'example' => 'Service restaring request queued.'],
|
||||
])
|
||||
),
|
||||
]),
|
||||
new OA\Response(
|
||||
response: 401,
|
||||
ref: '#/components/responses/401',
|
||||
),
|
||||
new OA\Response(
|
||||
response: 400,
|
||||
ref: '#/components/responses/400',
|
||||
),
|
||||
new OA\Response(
|
||||
response: 404,
|
||||
ref: '#/components/responses/404',
|
||||
),
|
||||
]
|
||||
)]
|
||||
public function action_restart(Request $request)
|
||||
{
|
||||
$teamId = getTeamIdFromToken();
|
||||
if (is_null($teamId)) {
|
||||
return invalidTokenResponse();
|
||||
}
|
||||
$uuid = $request->route('uuid');
|
||||
if (! $uuid) {
|
||||
return response()->json(['message' => 'UUID is required.'], 400);
|
||||
}
|
||||
$service = Service::whereRelation('environment.project.team', 'id', $teamId)->whereUuid($request->uuid)->first();
|
||||
if (! $service) {
|
||||
return response()->json(['message' => 'Service not found.'], 404);
|
||||
}
|
||||
RestartService::dispatch($service);
|
||||
|
||||
return response()->json(
|
||||
[
|
||||
'message' => 'Service restarting request queued.',
|
||||
],
|
||||
200
|
||||
);
|
||||
|
||||
}
|
||||
}
|
||||
@@ -1,74 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers\Api;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
use Illuminate\Http\Request;
|
||||
|
||||
class Team extends Controller
|
||||
{
|
||||
public function teams(Request $request)
|
||||
{
|
||||
$teamId = get_team_id_from_token();
|
||||
if (is_null($teamId)) {
|
||||
return invalid_token();
|
||||
}
|
||||
$teams = auth()->user()->teams;
|
||||
|
||||
return response()->json($teams);
|
||||
}
|
||||
|
||||
public function team_by_id(Request $request)
|
||||
{
|
||||
$id = $request->id;
|
||||
$teamId = get_team_id_from_token();
|
||||
if (is_null($teamId)) {
|
||||
return invalid_token();
|
||||
}
|
||||
$teams = auth()->user()->teams;
|
||||
$team = $teams->where('id', $id)->first();
|
||||
if (is_null($team)) {
|
||||
return response()->json(['error' => 'Team not found.', 'docs' => 'https://coolify.io/docs/api-reference/get-team-by-teamid'], 404);
|
||||
}
|
||||
|
||||
return response()->json($team);
|
||||
}
|
||||
|
||||
public function members_by_id(Request $request)
|
||||
{
|
||||
$id = $request->id;
|
||||
$teamId = get_team_id_from_token();
|
||||
if (is_null($teamId)) {
|
||||
return invalid_token();
|
||||
}
|
||||
$teams = auth()->user()->teams;
|
||||
$team = $teams->where('id', $id)->first();
|
||||
if (is_null($team)) {
|
||||
return response()->json(['error' => 'Team not found.', 'docs' => 'https://coolify.io/docs/api-reference/get-team-by-teamid-members'], 404);
|
||||
}
|
||||
|
||||
return response()->json($team->members);
|
||||
}
|
||||
|
||||
public function current_team(Request $request)
|
||||
{
|
||||
$teamId = get_team_id_from_token();
|
||||
if (is_null($teamId)) {
|
||||
return invalid_token();
|
||||
}
|
||||
$team = auth()->user()->currentTeam();
|
||||
|
||||
return response()->json($team);
|
||||
}
|
||||
|
||||
public function current_team_members(Request $request)
|
||||
{
|
||||
$teamId = get_team_id_from_token();
|
||||
if (is_null($teamId)) {
|
||||
return invalid_token();
|
||||
}
|
||||
$team = auth()->user()->currentTeam();
|
||||
|
||||
return response()->json($team->members);
|
||||
}
|
||||
}
|
||||
270
app/Http/Controllers/Api/TeamController.php
Normal file
270
app/Http/Controllers/Api/TeamController.php
Normal file
@@ -0,0 +1,270 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers\Api;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
use Illuminate\Http\Request;
|
||||
use OpenApi\Attributes as OA;
|
||||
|
||||
class TeamController extends Controller
|
||||
{
|
||||
private function removeSensitiveData($team)
|
||||
{
|
||||
$token = auth()->user()->currentAccessToken();
|
||||
$team->makeHidden([
|
||||
'custom_server_limit',
|
||||
'pivot',
|
||||
]);
|
||||
if ($token->can('view:sensitive')) {
|
||||
return serializeApiResponse($team);
|
||||
}
|
||||
$team->makeHidden([
|
||||
'smtp_username',
|
||||
'smtp_password',
|
||||
'resend_api_key',
|
||||
'telegram_token',
|
||||
]);
|
||||
|
||||
return serializeApiResponse($team);
|
||||
}
|
||||
|
||||
#[OA\Get(
|
||||
summary: 'List',
|
||||
description: 'Get all teams.',
|
||||
path: '/teams',
|
||||
security: [
|
||||
['bearerAuth' => []],
|
||||
],
|
||||
tags: ['Teams'],
|
||||
responses: [
|
||||
new OA\Response(
|
||||
response: 200,
|
||||
description: 'List of teams.',
|
||||
content: [
|
||||
new OA\MediaType(
|
||||
mediaType: 'application/json',
|
||||
schema: new OA\Schema(
|
||||
type: 'array',
|
||||
items: new OA\Items(ref: '#/components/schemas/Team')
|
||||
)
|
||||
),
|
||||
]),
|
||||
new OA\Response(
|
||||
response: 401,
|
||||
ref: '#/components/responses/401',
|
||||
),
|
||||
new OA\Response(
|
||||
response: 400,
|
||||
ref: '#/components/responses/400',
|
||||
),
|
||||
]
|
||||
)]
|
||||
public function teams(Request $request)
|
||||
{
|
||||
$teamId = getTeamIdFromToken();
|
||||
if (is_null($teamId)) {
|
||||
return invalidTokenResponse();
|
||||
}
|
||||
$teams = auth()->user()->teams->sortBy('id');
|
||||
$teams = $teams->map(function ($team) {
|
||||
return $this->removeSensitiveData($team);
|
||||
});
|
||||
|
||||
return response()->json(
|
||||
$teams,
|
||||
);
|
||||
}
|
||||
|
||||
#[OA\Get(
|
||||
summary: 'Get',
|
||||
description: 'Get team by TeamId.',
|
||||
path: '/teams/{id}',
|
||||
security: [
|
||||
['bearerAuth' => []],
|
||||
],
|
||||
tags: ['Teams'],
|
||||
parameters: [
|
||||
new OA\Parameter(name: 'id', in: 'path', required: true, description: 'Team ID', schema: new OA\Schema(type: 'integer')),
|
||||
],
|
||||
responses: [
|
||||
new OA\Response(
|
||||
response: 200,
|
||||
description: 'List of teams.',
|
||||
content: new OA\JsonContent(ref: '#/components/schemas/Team')
|
||||
),
|
||||
new OA\Response(
|
||||
response: 401,
|
||||
ref: '#/components/responses/401',
|
||||
),
|
||||
new OA\Response(
|
||||
response: 400,
|
||||
ref: '#/components/responses/400',
|
||||
),
|
||||
new OA\Response(
|
||||
response: 404,
|
||||
ref: '#/components/responses/404',
|
||||
),
|
||||
]
|
||||
)]
|
||||
public function team_by_id(Request $request)
|
||||
{
|
||||
$id = $request->id;
|
||||
$teamId = getTeamIdFromToken();
|
||||
if (is_null($teamId)) {
|
||||
return invalidTokenResponse();
|
||||
}
|
||||
$teams = auth()->user()->teams;
|
||||
$team = $teams->where('id', $id)->first();
|
||||
if (is_null($team)) {
|
||||
return response()->json(['message' => 'Team not found.'], 404);
|
||||
}
|
||||
$team = $this->removeSensitiveData($team);
|
||||
|
||||
return response()->json(
|
||||
serializeApiResponse($team),
|
||||
);
|
||||
}
|
||||
|
||||
#[OA\Get(
|
||||
summary: 'Members',
|
||||
description: 'Get members by TeamId.',
|
||||
path: '/teams/{id}/members',
|
||||
security: [
|
||||
['bearerAuth' => []],
|
||||
],
|
||||
tags: ['Teams'],
|
||||
parameters: [
|
||||
new OA\Parameter(name: 'id', in: 'path', required: true, description: 'Team ID', schema: new OA\Schema(type: 'integer')),
|
||||
],
|
||||
responses: [
|
||||
new OA\Response(
|
||||
response: 200,
|
||||
description: 'List of members.',
|
||||
content: [
|
||||
new OA\MediaType(
|
||||
mediaType: 'application/json',
|
||||
schema: new OA\Schema(
|
||||
type: 'array',
|
||||
items: new OA\Items(ref: '#/components/schemas/User')
|
||||
)
|
||||
),
|
||||
]),
|
||||
new OA\Response(
|
||||
response: 401,
|
||||
ref: '#/components/responses/401',
|
||||
),
|
||||
new OA\Response(
|
||||
response: 400,
|
||||
ref: '#/components/responses/400',
|
||||
),
|
||||
new OA\Response(
|
||||
response: 404,
|
||||
ref: '#/components/responses/404',
|
||||
),
|
||||
]
|
||||
)]
|
||||
public function members_by_id(Request $request)
|
||||
{
|
||||
$id = $request->id;
|
||||
$teamId = getTeamIdFromToken();
|
||||
if (is_null($teamId)) {
|
||||
return invalidTokenResponse();
|
||||
}
|
||||
$teams = auth()->user()->teams;
|
||||
$team = $teams->where('id', $id)->first();
|
||||
if (is_null($team)) {
|
||||
return response()->json(['message' => 'Team not found.'], 404);
|
||||
}
|
||||
$members = $team->members;
|
||||
$members->makeHidden([
|
||||
'pivot',
|
||||
]);
|
||||
|
||||
return response()->json(
|
||||
serializeApiResponse($members),
|
||||
);
|
||||
}
|
||||
|
||||
#[OA\Get(
|
||||
summary: 'Authenticated Team',
|
||||
description: 'Get currently authenticated team.',
|
||||
path: '/teams/current',
|
||||
security: [
|
||||
['bearerAuth' => []],
|
||||
],
|
||||
tags: ['Teams'],
|
||||
responses: [
|
||||
new OA\Response(
|
||||
response: 200,
|
||||
description: 'Current Team.',
|
||||
content: new OA\JsonContent(ref: '#/components/schemas/Team')),
|
||||
new OA\Response(
|
||||
response: 401,
|
||||
ref: '#/components/responses/401',
|
||||
),
|
||||
new OA\Response(
|
||||
response: 400,
|
||||
ref: '#/components/responses/400',
|
||||
),
|
||||
]
|
||||
)]
|
||||
public function current_team(Request $request)
|
||||
{
|
||||
$teamId = getTeamIdFromToken();
|
||||
if (is_null($teamId)) {
|
||||
return invalidTokenResponse();
|
||||
}
|
||||
$team = auth()->user()->currentTeam();
|
||||
|
||||
return response()->json(
|
||||
$this->removeSensitiveData($team),
|
||||
);
|
||||
}
|
||||
|
||||
#[OA\Get(
|
||||
summary: 'Authenticated Team Members',
|
||||
description: 'Get currently authenticated team members.',
|
||||
path: '/teams/current/members',
|
||||
security: [
|
||||
['bearerAuth' => []],
|
||||
],
|
||||
tags: ['Teams'],
|
||||
responses: [
|
||||
new OA\Response(
|
||||
response: 200,
|
||||
description: 'Currently authenticated team members.',
|
||||
content: [
|
||||
new OA\MediaType(
|
||||
mediaType: 'application/json',
|
||||
schema: new OA\Schema(
|
||||
type: 'array',
|
||||
items: new OA\Items(ref: '#/components/schemas/User')
|
||||
)
|
||||
),
|
||||
]),
|
||||
new OA\Response(
|
||||
response: 401,
|
||||
ref: '#/components/responses/401',
|
||||
),
|
||||
new OA\Response(
|
||||
response: 400,
|
||||
ref: '#/components/responses/400',
|
||||
),
|
||||
]
|
||||
)]
|
||||
public function current_team_members(Request $request)
|
||||
{
|
||||
$teamId = getTeamIdFromToken();
|
||||
if (is_null($teamId)) {
|
||||
return invalidTokenResponse();
|
||||
}
|
||||
$team = auth()->user()->currentTeam();
|
||||
$team->members->makeHidden([
|
||||
'pivot',
|
||||
]);
|
||||
|
||||
return response()->json(
|
||||
serializeApiResponse($team->members),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -81,8 +81,8 @@ class Controller extends BaseController
|
||||
$token = request()->get('token');
|
||||
if ($token) {
|
||||
$decrypted = Crypt::decryptString($token);
|
||||
$email = Str::of($decrypted)->before('@@@');
|
||||
$password = Str::of($decrypted)->after('@@@');
|
||||
$email = str($decrypted)->before('@@@');
|
||||
$password = str($decrypted)->after('@@@');
|
||||
$user = User::whereEmail($email)->first();
|
||||
if (! $user) {
|
||||
return redirect()->route('login');
|
||||
|
||||
@@ -54,6 +54,34 @@ class Stripe extends Controller
|
||||
$type = data_get($event, 'type');
|
||||
$data = data_get($event, 'data.object');
|
||||
switch ($type) {
|
||||
case 'radar.early_fraud_warning.created':
|
||||
$stripe = new \Stripe\StripeClient(config('subscription.stripe_api_key'));
|
||||
$id = data_get($data, 'id');
|
||||
$charge = data_get($data, 'charge');
|
||||
if ($charge) {
|
||||
$stripe->refunds->create(['charge' => $charge]);
|
||||
}
|
||||
$pi = data_get($data, 'payment_intent');
|
||||
$piData = $stripe->paymentIntents->retrieve($pi, []);
|
||||
$customerId = data_get($piData, 'customer');
|
||||
$subscription = Subscription::where('stripe_customer_id', $customerId)->first();
|
||||
if (! $subscription) {
|
||||
Sleep::for(5)->seconds();
|
||||
$subscription = Subscription::where('stripe_customer_id', $customerId)->first();
|
||||
}
|
||||
if (! $subscription) {
|
||||
Sleep::for(5)->seconds();
|
||||
$subscription = Subscription::where('stripe_customer_id', $customerId)->first();
|
||||
}
|
||||
if ($subscription) {
|
||||
$subscriptionId = data_get($subscription, 'stripe_subscription_id');
|
||||
$stripe->subscriptions->cancel($subscriptionId, []);
|
||||
$subscription->update([
|
||||
'stripe_invoice_paid' => false,
|
||||
]);
|
||||
}
|
||||
send_internal_notification("Early fraud warning created Refunded, subscription canceled. Charge: {$charge}, id: {$id}, pi: {$pi}");
|
||||
break;
|
||||
case 'checkout.session.completed':
|
||||
$clientReferenceId = data_get($data, 'client_reference_id');
|
||||
if (is_null($clientReferenceId)) {
|
||||
@@ -231,7 +259,7 @@ class Stripe extends Controller
|
||||
'stripe_plan_id' => null,
|
||||
'stripe_cancel_at_period_end' => false,
|
||||
'stripe_invoice_paid' => false,
|
||||
'stripe_trial_already_ended' => true,
|
||||
'stripe_trial_already_ended' => false,
|
||||
]);
|
||||
// send_internal_notification('customer.subscription.deleted for customer: '.$customerId);
|
||||
break;
|
||||
|
||||
@@ -67,5 +67,7 @@ class Kernel extends HttpKernel
|
||||
'signed' => \App\Http\Middleware\ValidateSignature::class,
|
||||
'throttle' => \Illuminate\Routing\Middleware\ThrottleRequests::class,
|
||||
'verified' => \Illuminate\Auth\Middleware\EnsureEmailIsVerified::class,
|
||||
'abilities' => \Laravel\Sanctum\Http\Middleware\CheckAbilities::class,
|
||||
'ability' => \Laravel\Sanctum\Http\Middleware\CheckForAnyAbility::class,
|
||||
];
|
||||
}
|
||||
|
||||
33
app/Http/Middleware/ApiAllowed.php
Normal file
33
app/Http/Middleware/ApiAllowed.php
Normal file
@@ -0,0 +1,33 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Middleware;
|
||||
|
||||
use Closure;
|
||||
use Illuminate\Http\Request;
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
|
||||
class ApiAllowed
|
||||
{
|
||||
public function handle(Request $request, Closure $next): Response
|
||||
{
|
||||
ray()->clearAll();
|
||||
if (isCloud()) {
|
||||
return $next($request);
|
||||
}
|
||||
$settings = \App\Models\InstanceSettings::get();
|
||||
if ($settings->is_api_enabled === false) {
|
||||
return response()->json(['success' => true, 'message' => 'API is disabled.'], 403);
|
||||
}
|
||||
|
||||
if (! isDev()) {
|
||||
if ($settings->allowed_ips) {
|
||||
$allowedIps = explode(',', $settings->allowed_ips);
|
||||
if (! in_array($request->ip(), $allowedIps)) {
|
||||
return response()->json(['success' => true, 'message' => 'You are not allowed to access the API.'], 403);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $next($request);
|
||||
}
|
||||
}
|
||||
28
app/Http/Middleware/IgnoreReadOnlyApiToken.php
Normal file
28
app/Http/Middleware/IgnoreReadOnlyApiToken.php
Normal file
@@ -0,0 +1,28 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Middleware;
|
||||
|
||||
use Closure;
|
||||
use Illuminate\Http\Request;
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
|
||||
class IgnoreReadOnlyApiToken
|
||||
{
|
||||
/**
|
||||
* Handle an incoming request.
|
||||
*
|
||||
* @param \Closure(\Illuminate\Http\Request): (\Symfony\Component\HttpFoundation\Response) $next
|
||||
*/
|
||||
public function handle(Request $request, Closure $next): Response
|
||||
{
|
||||
$token = auth()->user()->currentAccessToken();
|
||||
if ($token->can('*')) {
|
||||
return $next($request);
|
||||
}
|
||||
if ($token->can('read-only')) {
|
||||
return response()->json(['message' => 'You are not allowed to perform this action.'], 403);
|
||||
}
|
||||
|
||||
return $next($request);
|
||||
}
|
||||
}
|
||||
25
app/Http/Middleware/OnlyRootApiToken.php
Normal file
25
app/Http/Middleware/OnlyRootApiToken.php
Normal file
@@ -0,0 +1,25 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Middleware;
|
||||
|
||||
use Closure;
|
||||
use Illuminate\Http\Request;
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
|
||||
class OnlyRootApiToken
|
||||
{
|
||||
/**
|
||||
* Handle an incoming request.
|
||||
*
|
||||
* @param \Closure(\Illuminate\Http\Request): (\Symfony\Component\HttpFoundation\Response) $next
|
||||
*/
|
||||
public function handle(Request $request, Closure $next): Response
|
||||
{
|
||||
$token = auth()->user()->currentAccessToken();
|
||||
if ($token->can('*')) {
|
||||
return $next($request);
|
||||
}
|
||||
|
||||
return response()->json(['message' => 'You are not allowed to perform this action.'], 403);
|
||||
}
|
||||
}
|
||||
@@ -127,7 +127,7 @@ class ApplicationDeploymentJob implements ShouldBeEncrypted, ShouldQueue
|
||||
|
||||
private string $dockerfile_location = '/Dockerfile';
|
||||
|
||||
private string $docker_compose_location = '/docker-compose.yml';
|
||||
private string $docker_compose_location = '/docker-compose.yaml';
|
||||
|
||||
private ?string $docker_compose_custom_start_command = null;
|
||||
|
||||
@@ -194,6 +194,9 @@ class ApplicationDeploymentJob implements ShouldBeEncrypted, ShouldQueue
|
||||
$this->is_debug_enabled = $this->application->settings->is_debug_enabled;
|
||||
|
||||
$this->container_name = generateApplicationContainerName($this->application, $this->pull_request_id);
|
||||
if ($this->application->settings->custom_internal_name && ! $this->application->settings->is_consistent_container_name_enabled) {
|
||||
$this->container_name = $this->application->settings->custom_internal_name;
|
||||
}
|
||||
ray('New container name: ', $this->container_name);
|
||||
|
||||
savePrivateKeyToFs($this->server);
|
||||
@@ -459,7 +462,7 @@ class ApplicationDeploymentJob implements ShouldBeEncrypted, ShouldQueue
|
||||
if ($this->env_filename) {
|
||||
$command .= " --env-file {$this->workdir}/{$this->env_filename}";
|
||||
}
|
||||
$command .= " --project-directory {$this->workdir} -f {$this->workdir}{$this->docker_compose_location} build";
|
||||
$command .= " --project-name {$this->application->uuid} --project-directory {$this->workdir} -f {$this->workdir}{$this->docker_compose_location} build --pull";
|
||||
$this->execute_remote_command(
|
||||
[executeInDocker($this->deployment_uuid, $command), 'hidden' => true],
|
||||
);
|
||||
@@ -513,7 +516,7 @@ class ApplicationDeploymentJob implements ShouldBeEncrypted, ShouldQueue
|
||||
if ($this->env_filename) {
|
||||
$command .= " --env-file {$this->workdir}/{$this->env_filename}";
|
||||
}
|
||||
$command .= " --project-directory {$this->workdir} -f {$this->workdir}{$this->docker_compose_location} up -d";
|
||||
$command .= " --project-name {$this->application->uuid} --project-directory {$this->workdir} -f {$this->workdir}{$this->docker_compose_location} up -d";
|
||||
$this->execute_remote_command(
|
||||
[executeInDocker($this->deployment_uuid, $command), 'hidden' => true],
|
||||
);
|
||||
@@ -608,10 +611,10 @@ class ApplicationDeploymentJob implements ShouldBeEncrypted, ShouldQueue
|
||||
}
|
||||
$readme = generate_readme_file($this->application->name, $this->application_deployment_queue->updated_at);
|
||||
if ($this->pull_request_id === 0) {
|
||||
$composeFileName = "$this->configuration_dir/docker-compose.yml";
|
||||
$composeFileName = "$this->configuration_dir/docker-compose.yaml";
|
||||
} else {
|
||||
$composeFileName = "$this->configuration_dir/docker-compose-pr-{$this->pull_request_id}.yml";
|
||||
$this->docker_compose_location = "/docker-compose-pr-{$this->pull_request_id}.yml";
|
||||
$composeFileName = "$this->configuration_dir/docker-compose-pr-{$this->pull_request_id}.yaml";
|
||||
$this->docker_compose_location = "/docker-compose-pr-{$this->pull_request_id}.yaml";
|
||||
}
|
||||
$this->execute_remote_command(
|
||||
[
|
||||
@@ -965,6 +968,7 @@ class ApplicationDeploymentJob implements ShouldBeEncrypted, ShouldQueue
|
||||
$nixpacks_php_fallback_path = new EnvironmentVariable();
|
||||
$nixpacks_php_fallback_path->key = 'NIXPACKS_PHP_FALLBACK_PATH';
|
||||
$nixpacks_php_fallback_path->value = '/index.php';
|
||||
$nixpacks_php_fallback_path->is_build_time = false;
|
||||
$nixpacks_php_fallback_path->application_id = $this->application->id;
|
||||
$nixpacks_php_fallback_path->save();
|
||||
}
|
||||
@@ -972,6 +976,7 @@ class ApplicationDeploymentJob implements ShouldBeEncrypted, ShouldQueue
|
||||
$nixpacks_php_root_dir = new EnvironmentVariable();
|
||||
$nixpacks_php_root_dir->key = 'NIXPACKS_PHP_ROOT_DIR';
|
||||
$nixpacks_php_root_dir->value = '/app/public';
|
||||
$nixpacks_php_root_dir->is_build_time = false;
|
||||
$nixpacks_php_root_dir->application_id = $this->application->id;
|
||||
$nixpacks_php_root_dir->save();
|
||||
}
|
||||
@@ -1076,13 +1081,13 @@ class ApplicationDeploymentJob implements ShouldBeEncrypted, ShouldQueue
|
||||
$this->application_deployment_queue->addLogEntry("Healthcheck logs: {$health_check_logs} | Return code: {$health_check_return_code}");
|
||||
}
|
||||
|
||||
if (Str::of($this->saved_outputs->get('health_check'))->replace('"', '')->value() === 'healthy') {
|
||||
if (str($this->saved_outputs->get('health_check'))->replace('"', '')->value() === 'healthy') {
|
||||
$this->newVersionIsHealthy = true;
|
||||
$this->application->update(['status' => 'running']);
|
||||
$this->application_deployment_queue->addLogEntry('New container is healthy.');
|
||||
break;
|
||||
}
|
||||
if (Str::of($this->saved_outputs->get('health_check'))->replace('"', '')->value() === 'unhealthy') {
|
||||
if (str($this->saved_outputs->get('health_check'))->replace('"', '')->value() === 'unhealthy') {
|
||||
$this->newVersionIsHealthy = false;
|
||||
$this->query_logs();
|
||||
break;
|
||||
@@ -1094,7 +1099,7 @@ class ApplicationDeploymentJob implements ShouldBeEncrypted, ShouldQueue
|
||||
$sleeptime++;
|
||||
}
|
||||
}
|
||||
if (Str::of($this->saved_outputs->get('health_check'))->replace('"', '')->value() === 'starting') {
|
||||
if (str($this->saved_outputs->get('health_check'))->replace('"', '')->value() === 'starting') {
|
||||
$this->query_logs();
|
||||
}
|
||||
}
|
||||
@@ -1535,7 +1540,7 @@ class ApplicationDeploymentJob implements ShouldBeEncrypted, ShouldQueue
|
||||
$this->execute_remote_command([
|
||||
executeInDocker($this->deployment_uuid, "cat {$this->workdir}{$this->dockerfile_location}"), 'hidden' => true, 'save' => 'dockerfile_from_repo', 'ignore_errors' => true,
|
||||
]);
|
||||
$dockerfile = collect(Str::of($this->saved_outputs->get('dockerfile_from_repo'))->trim()->explode("\n"));
|
||||
$dockerfile = collect(str($this->saved_outputs->get('dockerfile_from_repo'))->trim()->explode("\n"));
|
||||
$this->application->parseHealthcheckFromDockerfile($dockerfile);
|
||||
}
|
||||
$docker_compose = [
|
||||
@@ -1568,23 +1573,6 @@ class ApplicationDeploymentJob implements ShouldBeEncrypted, ShouldQueue
|
||||
],
|
||||
],
|
||||
];
|
||||
if (isset($this->application->settings->custom_internal_name)) {
|
||||
$docker_compose['services'][$this->container_name]['networks'][$this->destination->network]['aliases'][] = $this->application->settings->custom_internal_name;
|
||||
}
|
||||
// if (str($this->saved_outputs->get('dotenv'))->isNotEmpty()) {
|
||||
// if (data_get($docker_compose, "services.{$this->container_name}.env_file")) {
|
||||
// $docker_compose['services'][$this->container_name]['env_file'][] = '.env';
|
||||
// } else {
|
||||
// $docker_compose['services'][$this->container_name]['env_file'] = ['.env'];
|
||||
// }
|
||||
// }
|
||||
// if ($this->env_filename) {
|
||||
// if (data_get($docker_compose, "services.{$this->container_name}.env_file")) {
|
||||
// $docker_compose['services'][$this->container_name]['env_file'][] = $this->env_filename;
|
||||
// } else {
|
||||
// $docker_compose['services'][$this->container_name]['env_file'] = [$this->env_filename];
|
||||
// }
|
||||
// }
|
||||
if (! is_null($this->env_filename)) {
|
||||
$docker_compose['services'][$this->container_name]['env_file'] = [$this->env_filename];
|
||||
}
|
||||
@@ -1636,12 +1624,15 @@ class ApplicationDeploymentJob implements ShouldBeEncrypted, ShouldQueue
|
||||
],
|
||||
],
|
||||
];
|
||||
if (data_get($this->application, 'swarm_placement_constraints')) {
|
||||
$swarm_placement_constraints = Yaml::parse(base64_decode(data_get($this->application, 'swarm_placement_constraints')));
|
||||
$docker_compose['services'][$this->container_name]['deploy'] = array_merge(
|
||||
$docker_compose['services'][$this->container_name]['deploy'],
|
||||
$swarm_placement_constraints
|
||||
);
|
||||
}
|
||||
if (data_get($this->application, 'settings.is_swarm_only_worker_nodes')) {
|
||||
$docker_compose['services'][$this->container_name]['deploy']['placement'] = [
|
||||
'constraints' => [
|
||||
'node.role == worker',
|
||||
],
|
||||
];
|
||||
$docker_compose['services'][$this->container_name]['deploy']['placement']['constraints'][] = 'node.role == worker';
|
||||
}
|
||||
if ($this->pull_request_id !== 0) {
|
||||
$docker_compose['services'][$this->container_name]['deploy']['replicas'] = 1;
|
||||
@@ -1695,32 +1686,28 @@ class ApplicationDeploymentJob implements ShouldBeEncrypted, ShouldQueue
|
||||
if (count($volume_names) > 0) {
|
||||
$docker_compose['volumes'] = $volume_names;
|
||||
}
|
||||
// if ($this->build_pack === 'dockerfile') {
|
||||
// $docker_compose['services'][$this->container_name]['build'] = [
|
||||
// 'context' => $this->workdir,
|
||||
// 'dockerfile' => $this->workdir . $this->dockerfile_location,
|
||||
// ];
|
||||
// }
|
||||
|
||||
if ($this->pull_request_id === 0) {
|
||||
$custom_compose = convert_docker_run_to_compose($this->application->custom_docker_run_options);
|
||||
if ((bool) $this->application->settings->is_consistent_container_name_enabled) {
|
||||
$docker_compose['services'][$this->application->uuid] = $docker_compose['services'][$this->container_name];
|
||||
if (count($custom_compose) > 0) {
|
||||
$ipv4 = data_get($custom_compose, 'ip.0');
|
||||
$ipv6 = data_get($custom_compose, 'ip6.0');
|
||||
data_forget($custom_compose, 'ip');
|
||||
data_forget($custom_compose, 'ip6');
|
||||
if ($ipv4 || $ipv6) {
|
||||
data_forget($docker_compose['services'][$this->application->uuid], 'networks');
|
||||
if (! $this->application->settings->custom_internal_name) {
|
||||
$docker_compose['services'][$this->application->uuid] = $docker_compose['services'][$this->container_name];
|
||||
if (count($custom_compose) > 0) {
|
||||
$ipv4 = data_get($custom_compose, 'ip.0');
|
||||
$ipv6 = data_get($custom_compose, 'ip6.0');
|
||||
data_forget($custom_compose, 'ip');
|
||||
data_forget($custom_compose, 'ip6');
|
||||
if ($ipv4 || $ipv6) {
|
||||
data_forget($docker_compose['services'][$this->application->uuid], 'networks');
|
||||
}
|
||||
if ($ipv4) {
|
||||
$docker_compose['services'][$this->application->uuid]['networks'][$this->destination->network]['ipv4_address'] = $ipv4;
|
||||
}
|
||||
if ($ipv6) {
|
||||
$docker_compose['services'][$this->application->uuid]['networks'][$this->destination->network]['ipv6_address'] = $ipv6;
|
||||
}
|
||||
$docker_compose['services'][$this->application->uuid] = array_merge_recursive($docker_compose['services'][$this->application->uuid], $custom_compose);
|
||||
}
|
||||
if ($ipv4) {
|
||||
$docker_compose['services'][$this->application->uuid]['networks'][$this->destination->network]['ipv4_address'] = $ipv4;
|
||||
}
|
||||
if ($ipv6) {
|
||||
$docker_compose['services'][$this->application->uuid]['networks'][$this->destination->network]['ipv6_address'] = $ipv6;
|
||||
}
|
||||
$docker_compose['services'][$this->application->uuid] = array_merge_recursive($docker_compose['services'][$this->application->uuid], $custom_compose);
|
||||
}
|
||||
} else {
|
||||
if (count($custom_compose) > 0) {
|
||||
@@ -1744,7 +1731,7 @@ class ApplicationDeploymentJob implements ShouldBeEncrypted, ShouldQueue
|
||||
|
||||
$this->docker_compose = Yaml::dump($docker_compose, 10);
|
||||
$this->docker_compose_base64 = base64_encode($this->docker_compose);
|
||||
$this->execute_remote_command([executeInDocker($this->deployment_uuid, "echo '{$this->docker_compose_base64}' | base64 -d | tee {$this->workdir}/docker-compose.yml > /dev/null"), 'hidden' => true]);
|
||||
$this->execute_remote_command([executeInDocker($this->deployment_uuid, "echo '{$this->docker_compose_base64}' | base64 -d | tee {$this->workdir}/docker-compose.yaml > /dev/null"), 'hidden' => true]);
|
||||
}
|
||||
|
||||
private function generate_local_persistent_volumes()
|
||||
@@ -2044,39 +2031,22 @@ COPY ./nginx.conf /etc/nginx/conf.d/default.conf");
|
||||
}
|
||||
}
|
||||
|
||||
private function build_by_compose_file()
|
||||
{
|
||||
$this->application_deployment_queue->addLogEntry('Pulling & building required images.');
|
||||
if ($this->application->build_pack === 'dockerimage') {
|
||||
$this->application_deployment_queue->addLogEntry('Pulling latest images from the registry.');
|
||||
$this->execute_remote_command(
|
||||
[executeInDocker($this->deployment_uuid, "docker compose --project-directory {$this->workdir} pull"), 'hidden' => true],
|
||||
[executeInDocker($this->deployment_uuid, "{$this->coolify_variables} docker compose --project-directory {$this->workdir} build"), 'hidden' => true],
|
||||
);
|
||||
} else {
|
||||
$this->execute_remote_command(
|
||||
[executeInDocker($this->deployment_uuid, "{$this->coolify_variables} docker compose --project-directory {$this->workdir} -f {$this->workdir}{$this->docker_compose_location} build"), 'hidden' => true],
|
||||
);
|
||||
}
|
||||
$this->application_deployment_queue->addLogEntry('New images built.');
|
||||
}
|
||||
|
||||
private function start_by_compose_file()
|
||||
{
|
||||
if ($this->application->build_pack === 'dockerimage') {
|
||||
$this->application_deployment_queue->addLogEntry('Pulling latest images from the registry.');
|
||||
$this->execute_remote_command(
|
||||
[executeInDocker($this->deployment_uuid, "docker compose --project-directory {$this->workdir} pull"), 'hidden' => true],
|
||||
[executeInDocker($this->deployment_uuid, "{$this->coolify_variables} docker compose --project-directory {$this->workdir} up --build -d"), 'hidden' => true],
|
||||
[executeInDocker($this->deployment_uuid, "docker compose --project-name {$this->application->uuid} --project-directory {$this->workdir} pull"), 'hidden' => true],
|
||||
[executeInDocker($this->deployment_uuid, "{$this->coolify_variables} docker compose --project-name {$this->application->uuid} --project-directory {$this->workdir} up --build -d"), 'hidden' => true],
|
||||
);
|
||||
} else {
|
||||
if ($this->use_build_server) {
|
||||
$this->execute_remote_command(
|
||||
["{$this->coolify_variables} docker compose --project-directory {$this->configuration_dir} -f {$this->configuration_dir}{$this->docker_compose_location} up --build -d", 'hidden' => true],
|
||||
["{$this->coolify_variables} docker compose --project-name {$this->application->uuid} --project-directory {$this->configuration_dir} -f {$this->configuration_dir}{$this->docker_compose_location} up --build -d", 'hidden' => true],
|
||||
);
|
||||
} else {
|
||||
$this->execute_remote_command(
|
||||
[executeInDocker($this->deployment_uuid, "{$this->coolify_variables} docker compose --project-directory {$this->workdir} -f {$this->workdir}{$this->docker_compose_location} up --build -d"), 'hidden' => true],
|
||||
[executeInDocker($this->deployment_uuid, "{$this->coolify_variables} docker compose --project-name {$this->application->uuid} --project-directory {$this->workdir} -f {$this->workdir}{$this->docker_compose_location} up --build -d"), 'hidden' => true],
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -2107,7 +2077,7 @@ COPY ./nginx.conf /etc/nginx/conf.d/default.conf");
|
||||
$this->execute_remote_command([
|
||||
executeInDocker($this->deployment_uuid, "cat {$this->workdir}{$this->dockerfile_location}"), 'hidden' => true, 'save' => 'dockerfile',
|
||||
]);
|
||||
$dockerfile = collect(Str::of($this->saved_outputs->get('dockerfile'))->trim()->explode("\n"));
|
||||
$dockerfile = collect(str($this->saved_outputs->get('dockerfile'))->trim()->explode("\n"));
|
||||
if ($this->pull_request_id === 0) {
|
||||
foreach ($this->application->build_environment_variables as $env) {
|
||||
if (data_get($env, 'is_multiline') === true) {
|
||||
|
||||
@@ -20,9 +20,9 @@ class CoolifyTask implements ShouldBeEncrypted, ShouldQueue
|
||||
*/
|
||||
public function __construct(
|
||||
public Activity $activity,
|
||||
public bool $ignore_errors = false,
|
||||
public $call_event_on_finish = null,
|
||||
public $call_event_data = null
|
||||
public bool $ignore_errors,
|
||||
public $call_event_on_finish,
|
||||
public $call_event_data,
|
||||
) {}
|
||||
|
||||
/**
|
||||
|
||||
@@ -98,7 +98,7 @@ class DatabaseBackupJob implements ShouldBeEncrypted, ShouldQueue
|
||||
|
||||
return;
|
||||
}
|
||||
$status = Str::of(data_get($this->database, 'status'));
|
||||
$status = str(data_get($this->database, 'status'));
|
||||
if (! $status->startsWith('running') && $this->database->id !== 0) {
|
||||
ray('database not running');
|
||||
|
||||
@@ -236,7 +236,7 @@ class DatabaseBackupJob implements ShouldBeEncrypted, ShouldQueue
|
||||
return;
|
||||
}
|
||||
}
|
||||
$this->backup_dir = backup_dir().'/databases/'.Str::of($this->team->name)->slug().'-'.$this->team->id.'/'.$this->directory_name;
|
||||
$this->backup_dir = backup_dir().'/databases/'.str($this->team->name)->slug().'-'.$this->team->id.'/'.$this->directory_name;
|
||||
|
||||
if ($this->database->name === 'coolify-db') {
|
||||
$databasesToBackup = ['coolify'];
|
||||
@@ -332,8 +332,7 @@ class DatabaseBackupJob implements ShouldBeEncrypted, ShouldQueue
|
||||
private function backup_standalone_mongodb(string $databaseWithCollections): void
|
||||
{
|
||||
try {
|
||||
ray($this->database->toArray());
|
||||
$url = $this->database->get_db_url(useInternal: true);
|
||||
$url = $this->database->internal_db_url;
|
||||
if ($databaseWithCollections === 'all') {
|
||||
$commands[] = 'mkdir -p '.$this->backup_dir;
|
||||
if (str($this->database->image)->startsWith('mongo:4.0')) {
|
||||
|
||||
@@ -28,14 +28,18 @@ class DeleteResourceJob implements ShouldBeEncrypted, ShouldQueue
|
||||
{
|
||||
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
|
||||
|
||||
public function __construct(public Application|Service|StandalonePostgresql|StandaloneRedis|StandaloneMongodb|StandaloneMysql|StandaloneMariadb|StandaloneKeydb|StandaloneDragonfly|StandaloneClickhouse $resource, public bool $deleteConfigurations = false) {}
|
||||
public function __construct(
|
||||
public Application|Service|StandalonePostgresql|StandaloneRedis|StandaloneMongodb|StandaloneMysql|StandaloneMariadb|StandaloneKeydb|StandaloneDragonfly|StandaloneClickhouse $resource,
|
||||
public bool $deleteConfigurations = false,
|
||||
public bool $deleteVolumes = false) {}
|
||||
|
||||
public function handle()
|
||||
{
|
||||
try {
|
||||
$this->resource->forceDelete();
|
||||
$persistentStorages = collect();
|
||||
switch ($this->resource->type()) {
|
||||
case 'application':
|
||||
$persistentStorages = $this->resource?->persistentStorages()?->get();
|
||||
StopApplication::run($this->resource);
|
||||
break;
|
||||
case 'standalone-postgresql':
|
||||
@@ -46,6 +50,7 @@ class DeleteResourceJob implements ShouldBeEncrypted, ShouldQueue
|
||||
case 'standalone-keydb':
|
||||
case 'standalone-dragonfly':
|
||||
case 'standalone-clickhouse':
|
||||
$persistentStorages = $this->resource?->persistentStorages()?->get();
|
||||
StopDatabase::run($this->resource);
|
||||
break;
|
||||
case 'service':
|
||||
@@ -53,6 +58,10 @@ class DeleteResourceJob implements ShouldBeEncrypted, ShouldQueue
|
||||
DeleteService::run($this->resource);
|
||||
break;
|
||||
}
|
||||
|
||||
if ($this->deleteVolumes && $this->resource->type() !== 'service') {
|
||||
$this->resource?->delete_volumes($persistentStorages);
|
||||
}
|
||||
if ($this->deleteConfigurations) {
|
||||
$this->resource?->delete_configurations();
|
||||
}
|
||||
@@ -61,6 +70,7 @@ class DeleteResourceJob implements ShouldBeEncrypted, ShouldQueue
|
||||
send_internal_notification('ContainerStoppingJob failed with: '.$e->getMessage());
|
||||
throw $e;
|
||||
} finally {
|
||||
$this->resource->forceDelete();
|
||||
Artisan::queue('cleanup:stucked-resources');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -35,9 +35,9 @@ class DockerCleanupJob implements ShouldBeEncrypted, ShouldQueue
|
||||
return;
|
||||
}
|
||||
});
|
||||
if ($isInprogress) {
|
||||
throw new RuntimeException('DockerCleanupJob: ApplicationDeploymentQueue is not empty, skipping...');
|
||||
}
|
||||
// if ($isInprogress) {
|
||||
// throw new RuntimeException('DockerCleanupJob: ApplicationDeploymentQueue is not empty, skipping...');
|
||||
// }
|
||||
if (! $this->server->isFunctional()) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
|
||||
namespace App\Jobs;
|
||||
|
||||
use App\Models\InstanceSettings;
|
||||
use App\Models\Server;
|
||||
use Illuminate\Bus\Queueable;
|
||||
use Illuminate\Contracts\Queue\ShouldBeEncrypted;
|
||||
@@ -36,7 +35,7 @@ class PullCoolifyImageJob implements ShouldBeEncrypted, ShouldQueue
|
||||
$latest_version = get_latest_version_of_coolify();
|
||||
instant_remote_process(["docker pull -q ghcr.io/coollabsio/coolify:{$latest_version}"], $server, false);
|
||||
|
||||
$settings = InstanceSettings::get();
|
||||
$settings = \App\Models\InstanceSettings::get();
|
||||
$current_version = config('version');
|
||||
if (! $settings->is_auto_update_enabled) {
|
||||
return;
|
||||
|
||||
@@ -51,7 +51,7 @@ class ServerStatusJob implements ShouldBeEncrypted, ShouldQueue
|
||||
}
|
||||
}
|
||||
} catch (\Throwable $e) {
|
||||
send_internal_notification('ServerStatusJob failed with: '.$e->getMessage());
|
||||
// send_internal_notification('ServerStatusJob failed with: '.$e->getMessage());
|
||||
ray($e->getMessage());
|
||||
|
||||
return handleError($e);
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
|
||||
namespace App\Livewire;
|
||||
|
||||
use App\Models\InstanceSettings;
|
||||
use DanHarrin\LivewireRateLimiting\WithRateLimiting;
|
||||
use Illuminate\Notifications\Messages\MailMessage;
|
||||
use Illuminate\Support\Facades\Http;
|
||||
@@ -48,7 +47,7 @@ class Help extends Component
|
||||
]
|
||||
);
|
||||
$mail->subject("[HELP]: {$this->subject}");
|
||||
$settings = InstanceSettings::get();
|
||||
$settings = \App\Models\InstanceSettings::get();
|
||||
$type = set_transanctional_email_settings($settings);
|
||||
if (! $type) {
|
||||
$url = 'https://app.coolify.io/api/feedback';
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
|
||||
namespace App\Livewire\Notifications;
|
||||
|
||||
use App\Models\InstanceSettings;
|
||||
use App\Models\Team;
|
||||
use App\Notifications\Test;
|
||||
use Livewire\Component;
|
||||
@@ -173,7 +172,7 @@ class Email extends Component
|
||||
|
||||
public function copyFromInstanceSettings()
|
||||
{
|
||||
$settings = InstanceSettings::get();
|
||||
$settings = \App\Models\InstanceSettings::get();
|
||||
if ($settings->smtp_enabled) {
|
||||
$team = currentTeam();
|
||||
$team->update([
|
||||
|
||||
@@ -5,7 +5,6 @@ namespace App\Livewire\Project\Application;
|
||||
use App\Models\Application;
|
||||
use App\Models\LocalFileVolume;
|
||||
use Illuminate\Support\Collection;
|
||||
use Illuminate\Support\Str;
|
||||
use Livewire\Component;
|
||||
use Visus\Cuid2\Cuid2;
|
||||
|
||||
@@ -41,8 +40,6 @@ class General extends Component
|
||||
|
||||
public ?string $initialDockerComposeLocation = null;
|
||||
|
||||
public ?string $initialDockerComposePrLocation = null;
|
||||
|
||||
public ?Collection $parsedServices;
|
||||
|
||||
public $parsedServiceDomains = [];
|
||||
@@ -73,11 +70,8 @@ class General extends Component
|
||||
'application.docker_registry_image_tag' => 'nullable',
|
||||
'application.dockerfile_location' => 'nullable',
|
||||
'application.docker_compose_location' => 'nullable',
|
||||
'application.docker_compose_pr_location' => 'nullable',
|
||||
'application.docker_compose' => 'nullable',
|
||||
'application.docker_compose_pr' => 'nullable',
|
||||
'application.docker_compose_raw' => 'nullable',
|
||||
'application.docker_compose_pr_raw' => 'nullable',
|
||||
'application.dockerfile_target_build' => 'nullable',
|
||||
'application.docker_compose_custom_start_command' => 'nullable',
|
||||
'application.docker_compose_custom_build_command' => 'nullable',
|
||||
@@ -115,11 +109,8 @@ class General extends Component
|
||||
'application.docker_registry_image_tag' => 'Docker registry image tag',
|
||||
'application.dockerfile_location' => 'Dockerfile location',
|
||||
'application.docker_compose_location' => 'Docker compose location',
|
||||
'application.docker_compose_pr_location' => 'Docker compose location',
|
||||
'application.docker_compose' => 'Docker compose',
|
||||
'application.docker_compose_pr' => 'Docker compose',
|
||||
'application.docker_compose_raw' => 'Docker compose raw',
|
||||
'application.docker_compose_pr_raw' => 'Docker compose raw',
|
||||
'application.custom_labels' => 'Custom labels',
|
||||
'application.dockerfile_target_build' => 'Dockerfile target build',
|
||||
'application.custom_docker_run_options' => 'Custom docker run commands',
|
||||
@@ -184,7 +175,7 @@ class General extends Component
|
||||
if ($isInit && $this->application->docker_compose_raw) {
|
||||
return;
|
||||
}
|
||||
['parsedServices' => $this->parsedServices, 'initialDockerComposeLocation' => $this->initialDockerComposeLocation, 'initialDockerComposePrLocation' => $this->initialDockerComposePrLocation] = $this->application->loadComposeFile($isInit);
|
||||
['parsedServices' => $this->parsedServices, 'initialDockerComposeLocation' => $this->initialDockerComposeLocation] = $this->application->loadComposeFile($isInit);
|
||||
if (is_null($this->parsedServices)) {
|
||||
$this->dispatch('error', 'Failed to parse your docker-compose file. Please check the syntax and try again.');
|
||||
|
||||
@@ -199,8 +190,8 @@ class General extends Component
|
||||
return str($volume)->startsWith('/data/coolify');
|
||||
})->unique()->values();
|
||||
foreach ($volumes as $volume) {
|
||||
$source = Str::of($volume)->before(':');
|
||||
$target = Str::of($volume)->after(':')->beforeLast(':');
|
||||
$source = str($volume)->before(':');
|
||||
$target = str($volume)->after(':')->beforeLast(':');
|
||||
|
||||
LocalFileVolume::updateOrCreate(
|
||||
[
|
||||
@@ -223,7 +214,6 @@ class General extends Component
|
||||
$this->dispatch('refreshEnvs');
|
||||
} catch (\Throwable $e) {
|
||||
$this->application->docker_compose_location = $this->initialDockerComposeLocation;
|
||||
$this->application->docker_compose_pr_location = $this->initialDockerComposePrLocation;
|
||||
$this->application->save();
|
||||
|
||||
return handleError($e, $this);
|
||||
@@ -360,7 +350,6 @@ class General extends Component
|
||||
$this->checkFqdns();
|
||||
|
||||
$this->application->save();
|
||||
|
||||
if (! $this->customLabels && $this->application->destination->server->proxyType() !== 'NONE') {
|
||||
$this->customLabels = str(implode('|coolify|', generateLabelsApplication($this->application)))->replace('|coolify|', "\n");
|
||||
$this->application->custom_labels = base64_encode($this->customLabels);
|
||||
@@ -374,6 +363,7 @@ class General extends Component
|
||||
}
|
||||
}
|
||||
$this->validate();
|
||||
|
||||
if ($this->ports_exposes !== $this->application->ports_exposes || $this->is_container_label_escape_enabled !== $this->application->settings->is_container_label_escape_enabled) {
|
||||
$this->resetDefaultLabels();
|
||||
}
|
||||
@@ -400,6 +390,7 @@ class General extends Component
|
||||
}
|
||||
if ($this->application->build_pack === 'dockercompose') {
|
||||
$this->application->docker_compose_domains = json_encode($this->parsedServiceDomains);
|
||||
|
||||
foreach ($this->parsedServiceDomains as $serviceName => $service) {
|
||||
$domain = data_get($service, 'domain');
|
||||
if ($domain) {
|
||||
@@ -409,6 +400,9 @@ class General extends Component
|
||||
check_domain_usage(resource: $this->application);
|
||||
}
|
||||
}
|
||||
if ($this->application->isDirty('docker_compose_domains')) {
|
||||
$this->resetDefaultLabels();
|
||||
}
|
||||
}
|
||||
$this->application->custom_labels = base64_encode($this->customLabels);
|
||||
$this->application->save();
|
||||
|
||||
@@ -3,7 +3,6 @@
|
||||
namespace App\Livewire\Project\Application\Preview;
|
||||
|
||||
use App\Models\Application;
|
||||
use Illuminate\Support\Str;
|
||||
use Livewire\Component;
|
||||
use Spatie\Url\Url;
|
||||
|
||||
@@ -32,10 +31,10 @@ class Form extends Component
|
||||
public function generate_real_url()
|
||||
{
|
||||
if (data_get($this->application, 'fqdn')) {
|
||||
$firstFqdn = Str::of($this->application->fqdn)->before(',');
|
||||
$firstFqdn = str($this->application->fqdn)->before(',');
|
||||
$url = Url::fromString($firstFqdn);
|
||||
$host = $url->getHost();
|
||||
$this->preview_url_template = Str::of($this->application->preview_url_template)->replace('{{domain}}', $host);
|
||||
$this->preview_url_template = str($this->application->preview_url_template)->replace('{{domain}}', $host);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -3,7 +3,6 @@
|
||||
namespace App\Livewire\Project\Application;
|
||||
|
||||
use App\Models\Application;
|
||||
use Illuminate\Support\Str;
|
||||
use Livewire\Component;
|
||||
use Visus\Cuid2\Cuid2;
|
||||
|
||||
@@ -50,16 +49,16 @@ class Rollback extends Component
|
||||
$output = instant_remote_process([
|
||||
"docker inspect --format='{{.Config.Image}}' {$this->application->uuid}",
|
||||
], $this->application->destination->server, throwError: false);
|
||||
$current_tag = Str::of($output)->trim()->explode(':');
|
||||
$current_tag = str($output)->trim()->explode(':');
|
||||
$this->current = data_get($current_tag, 1);
|
||||
|
||||
$output = instant_remote_process([
|
||||
"docker images --format '{{.Repository}}#{{.Tag}}#{{.CreatedAt}}'",
|
||||
], $this->application->destination->server);
|
||||
$this->images = Str::of($output)->trim()->explode("\n")->filter(function ($item) use ($image) {
|
||||
return Str::of($item)->contains($image);
|
||||
$this->images = str($output)->trim()->explode("\n")->filter(function ($item) use ($image) {
|
||||
return str($item)->contains($image);
|
||||
})->map(function ($item) {
|
||||
$item = Str::of($item)->explode('#');
|
||||
$item = str($item)->explode('#');
|
||||
if ($item[1] === $this->current) {
|
||||
// $is_current = true;
|
||||
}
|
||||
|
||||
@@ -46,10 +46,8 @@ class General extends Component
|
||||
|
||||
public function mount()
|
||||
{
|
||||
$this->db_url = $this->database->get_db_url(true);
|
||||
if ($this->database->is_public) {
|
||||
$this->db_url_public = $this->database->get_db_url();
|
||||
}
|
||||
$this->db_url = $this->database->internal_db_url;
|
||||
$this->db_url_public = $this->database->external_db_url;
|
||||
$this->server = data_get($this->database, 'destination.server');
|
||||
}
|
||||
|
||||
@@ -87,13 +85,12 @@ class General extends Component
|
||||
return;
|
||||
}
|
||||
StartDatabaseProxy::run($this->database);
|
||||
$this->db_url_public = $this->database->get_db_url();
|
||||
$this->dispatch('success', 'Database is now publicly accessible.');
|
||||
} else {
|
||||
StopDatabaseProxy::run($this->database);
|
||||
$this->db_url_public = null;
|
||||
$this->dispatch('success', 'Database is no longer publicly accessible.');
|
||||
}
|
||||
$this->db_url_public = $this->database->external_db_url;
|
||||
$this->database->save();
|
||||
} catch (\Throwable $e) {
|
||||
$this->database->is_public = ! $this->database->is_public;
|
||||
|
||||
@@ -44,10 +44,8 @@ class General extends Component
|
||||
|
||||
public function mount()
|
||||
{
|
||||
$this->db_url = $this->database->get_db_url(true);
|
||||
if ($this->database->is_public) {
|
||||
$this->db_url_public = $this->database->get_db_url();
|
||||
}
|
||||
$this->db_url = $this->database->internal_db_url;
|
||||
$this->db_url_public = $this->database->external_db_url;
|
||||
$this->server = data_get($this->database, 'destination.server');
|
||||
}
|
||||
|
||||
@@ -102,13 +100,12 @@ class General extends Component
|
||||
return;
|
||||
}
|
||||
StartDatabaseProxy::run($this->database);
|
||||
$this->db_url_public = $this->database->get_db_url();
|
||||
$this->dispatch('success', 'Database is now publicly accessible.');
|
||||
} else {
|
||||
StopDatabaseProxy::run($this->database);
|
||||
$this->db_url_public = null;
|
||||
$this->dispatch('success', 'Database is no longer publicly accessible.');
|
||||
}
|
||||
$this->db_url_public = $this->database->external_db_url;
|
||||
$this->database->save();
|
||||
} catch (\Throwable $e) {
|
||||
$this->database->is_public = ! $this->database->is_public;
|
||||
|
||||
@@ -2,14 +2,8 @@
|
||||
|
||||
namespace App\Livewire\Project\Database;
|
||||
|
||||
use App\Actions\Database\StartClickhouse;
|
||||
use App\Actions\Database\StartDragonfly;
|
||||
use App\Actions\Database\StartKeydb;
|
||||
use App\Actions\Database\StartMariadb;
|
||||
use App\Actions\Database\StartMongodb;
|
||||
use App\Actions\Database\StartMysql;
|
||||
use App\Actions\Database\StartPostgresql;
|
||||
use App\Actions\Database\StartRedis;
|
||||
use App\Actions\Database\RestartDatabase;
|
||||
use App\Actions\Database\StartDatabase;
|
||||
use App\Actions\Database\StopDatabase;
|
||||
use App\Actions\Docker\GetContainersStatus;
|
||||
use Livewire\Component;
|
||||
@@ -47,7 +41,6 @@ class Heading extends Component
|
||||
public function check_status($showNotification = false)
|
||||
{
|
||||
GetContainersStatus::run($this->database->destination->server);
|
||||
// dispatch_sync(new ContainerStatusJob($this->database->destination->server));
|
||||
$this->database->refresh();
|
||||
if ($showNotification) {
|
||||
$this->dispatch('success', 'Database status updated.');
|
||||
@@ -67,32 +60,15 @@ class Heading extends Component
|
||||
$this->check_status();
|
||||
}
|
||||
|
||||
public function restart()
|
||||
{
|
||||
$activity = RestartDatabase::run($this->database);
|
||||
$this->dispatch('activityMonitor', $activity->id);
|
||||
}
|
||||
|
||||
public function start()
|
||||
{
|
||||
if ($this->database->type() === 'standalone-postgresql') {
|
||||
$activity = StartPostgresql::run($this->database);
|
||||
$this->dispatch('activityMonitor', $activity->id);
|
||||
} elseif ($this->database->type() === 'standalone-redis') {
|
||||
$activity = StartRedis::run($this->database);
|
||||
$this->dispatch('activityMonitor', $activity->id);
|
||||
} elseif ($this->database->type() === 'standalone-mongodb') {
|
||||
$activity = StartMongodb::run($this->database);
|
||||
$this->dispatch('activityMonitor', $activity->id);
|
||||
} elseif ($this->database->type() === 'standalone-mysql') {
|
||||
$activity = StartMysql::run($this->database);
|
||||
$this->dispatch('activityMonitor', $activity->id);
|
||||
} elseif ($this->database->type() === 'standalone-mariadb') {
|
||||
$activity = StartMariadb::run($this->database);
|
||||
$this->dispatch('activityMonitor', $activity->id);
|
||||
} elseif ($this->database->type() === 'standalone-keydb') {
|
||||
$activity = StartKeydb::run($this->database);
|
||||
$this->dispatch('activityMonitor', $activity->id);
|
||||
} elseif ($this->database->type() === 'standalone-dragonfly') {
|
||||
$activity = StartDragonfly::run($this->database);
|
||||
$this->dispatch('activityMonitor', $activity->id);
|
||||
} elseif ($this->database->type() === 'standalone-clickhouse') {
|
||||
$activity = StartClickhouse::run($this->database);
|
||||
$this->dispatch('activityMonitor', $activity->id);
|
||||
}
|
||||
$activity = StartDatabase::run($this->database);
|
||||
$this->dispatch('activityMonitor', $activity->id);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -46,10 +46,8 @@ class General extends Component
|
||||
|
||||
public function mount()
|
||||
{
|
||||
$this->db_url = $this->database->get_db_url(true);
|
||||
if ($this->database->is_public) {
|
||||
$this->db_url_public = $this->database->get_db_url();
|
||||
}
|
||||
$this->db_url = $this->database->internal_db_url;
|
||||
$this->db_url_public = $this->database->external_db_url;
|
||||
$this->server = data_get($this->database, 'destination.server');
|
||||
|
||||
}
|
||||
@@ -108,13 +106,12 @@ class General extends Component
|
||||
return;
|
||||
}
|
||||
StartDatabaseProxy::run($this->database);
|
||||
$this->db_url_public = $this->database->get_db_url();
|
||||
$this->dispatch('success', 'Database is now publicly accessible.');
|
||||
} else {
|
||||
StopDatabaseProxy::run($this->database);
|
||||
$this->db_url_public = null;
|
||||
$this->dispatch('success', 'Database is no longer publicly accessible.');
|
||||
}
|
||||
$this->db_url_public = $this->database->external_db_url;
|
||||
$this->database->save();
|
||||
} catch (\Throwable $e) {
|
||||
$this->database->is_public = ! $this->database->is_public;
|
||||
|
||||
@@ -52,10 +52,8 @@ class General extends Component
|
||||
|
||||
public function mount()
|
||||
{
|
||||
$this->db_url = $this->database->get_db_url(true);
|
||||
if ($this->database->is_public) {
|
||||
$this->db_url_public = $this->database->get_db_url();
|
||||
}
|
||||
$this->db_url = $this->database->internal_db_url;
|
||||
$this->db_url_public = $this->database->external_db_url;
|
||||
$this->server = data_get($this->database, 'destination.server');
|
||||
|
||||
}
|
||||
@@ -114,13 +112,12 @@ class General extends Component
|
||||
return;
|
||||
}
|
||||
StartDatabaseProxy::run($this->database);
|
||||
$this->db_url_public = $this->database->get_db_url();
|
||||
$this->dispatch('success', 'Database is now publicly accessible.');
|
||||
} else {
|
||||
StopDatabaseProxy::run($this->database);
|
||||
$this->db_url_public = null;
|
||||
$this->dispatch('success', 'Database is no longer publicly accessible.');
|
||||
}
|
||||
$this->db_url_public = $this->database->external_db_url;
|
||||
$this->database->save();
|
||||
} catch (\Throwable $e) {
|
||||
$this->database->is_public = ! $this->database->is_public;
|
||||
|
||||
@@ -50,10 +50,8 @@ class General extends Component
|
||||
|
||||
public function mount()
|
||||
{
|
||||
$this->db_url = $this->database->get_db_url(true);
|
||||
if ($this->database->is_public) {
|
||||
$this->db_url_public = $this->database->get_db_url();
|
||||
}
|
||||
$this->db_url = $this->database->internal_db_url;
|
||||
$this->db_url_public = $this->database->external_db_url;
|
||||
$this->server = data_get($this->database, 'destination.server');
|
||||
|
||||
}
|
||||
@@ -115,13 +113,12 @@ class General extends Component
|
||||
return;
|
||||
}
|
||||
StartDatabaseProxy::run($this->database);
|
||||
$this->db_url_public = $this->database->get_db_url();
|
||||
$this->dispatch('success', 'Database is now publicly accessible.');
|
||||
} else {
|
||||
StopDatabaseProxy::run($this->database);
|
||||
$this->db_url_public = null;
|
||||
$this->dispatch('success', 'Database is no longer publicly accessible.');
|
||||
}
|
||||
$this->db_url_public = $this->database->external_db_url;
|
||||
$this->database->save();
|
||||
} catch (\Throwable $e) {
|
||||
$this->database->is_public = ! $this->database->is_public;
|
||||
|
||||
@@ -52,10 +52,8 @@ class General extends Component
|
||||
|
||||
public function mount()
|
||||
{
|
||||
$this->db_url = $this->database->get_db_url(true);
|
||||
if ($this->database->is_public) {
|
||||
$this->db_url_public = $this->database->get_db_url();
|
||||
}
|
||||
$this->db_url = $this->database->internal_db_url;
|
||||
$this->db_url_public = $this->database->external_db_url;
|
||||
$this->server = data_get($this->database, 'destination.server');
|
||||
}
|
||||
|
||||
@@ -113,13 +111,12 @@ class General extends Component
|
||||
return;
|
||||
}
|
||||
StartDatabaseProxy::run($this->database);
|
||||
$this->db_url_public = $this->database->get_db_url();
|
||||
$this->dispatch('success', 'Database is now publicly accessible.');
|
||||
} else {
|
||||
StopDatabaseProxy::run($this->database);
|
||||
$this->db_url_public = null;
|
||||
$this->dispatch('success', 'Database is no longer publicly accessible.');
|
||||
}
|
||||
$this->db_url_public = $this->database->external_db_url;
|
||||
$this->database->save();
|
||||
} catch (\Throwable $e) {
|
||||
$this->database->is_public = ! $this->database->is_public;
|
||||
|
||||
@@ -27,10 +27,7 @@ class General extends Component
|
||||
|
||||
public function getListeners()
|
||||
{
|
||||
$userId = auth()->user()->id;
|
||||
|
||||
return [
|
||||
"echo-private:user.{$userId},DatabaseStatusChanged" => 'database_stopped',
|
||||
'refresh',
|
||||
'save_init_script',
|
||||
'delete_init_script',
|
||||
@@ -72,18 +69,11 @@ class General extends Component
|
||||
|
||||
public function mount()
|
||||
{
|
||||
$this->db_url = $this->database->get_db_url(true);
|
||||
if ($this->database->is_public) {
|
||||
$this->db_url_public = $this->database->get_db_url();
|
||||
}
|
||||
$this->db_url = $this->database->internal_db_url;
|
||||
$this->db_url_public = $this->database->external_db_url;
|
||||
$this->server = data_get($this->database, 'destination.server');
|
||||
}
|
||||
|
||||
public function database_stopped()
|
||||
{
|
||||
$this->dispatch('success', 'Database proxy stopped. Database is no longer publicly accessible.');
|
||||
}
|
||||
|
||||
public function instantSaveAdvanced()
|
||||
{
|
||||
try {
|
||||
@@ -118,13 +108,12 @@ class General extends Component
|
||||
return;
|
||||
}
|
||||
StartDatabaseProxy::run($this->database);
|
||||
$this->db_url_public = $this->database->get_db_url();
|
||||
$this->dispatch('success', 'Database is now publicly accessible.');
|
||||
} else {
|
||||
StopDatabaseProxy::run($this->database);
|
||||
$this->db_url_public = null;
|
||||
$this->dispatch('success', 'Database is no longer publicly accessible.');
|
||||
}
|
||||
$this->db_url_public = $this->database->external_db_url;
|
||||
$this->database->save();
|
||||
} catch (\Throwable $e) {
|
||||
$this->database->is_public = ! $this->database->is_public;
|
||||
|
||||
@@ -46,10 +46,8 @@ class General extends Component
|
||||
|
||||
public function mount()
|
||||
{
|
||||
$this->db_url = $this->database->get_db_url(true);
|
||||
if ($this->database->is_public) {
|
||||
$this->db_url_public = $this->database->get_db_url();
|
||||
}
|
||||
$this->db_url = $this->database->internal_db_url;
|
||||
$this->db_url_public = $this->database->external_db_url;
|
||||
$this->server = data_get($this->database, 'destination.server');
|
||||
|
||||
}
|
||||
@@ -102,13 +100,12 @@ class General extends Component
|
||||
return;
|
||||
}
|
||||
StartDatabaseProxy::run($this->database);
|
||||
$this->db_url_public = $this->database->get_db_url();
|
||||
$this->dispatch('success', 'Database is now publicly accessible.');
|
||||
} else {
|
||||
StopDatabaseProxy::run($this->database);
|
||||
$this->db_url_public = null;
|
||||
$this->dispatch('success', 'Database is no longer publicly accessible.');
|
||||
}
|
||||
$this->db_url_public = $this->database->external_db_url;
|
||||
$this->database->save();
|
||||
} catch (\Throwable $e) {
|
||||
$this->database->is_public = ! $this->database->is_public;
|
||||
|
||||
@@ -6,7 +6,6 @@ use App\Models\Application;
|
||||
use App\Models\Project;
|
||||
use App\Models\StandaloneDocker;
|
||||
use App\Models\SwarmDocker;
|
||||
use Illuminate\Support\Str;
|
||||
use Livewire\Component;
|
||||
use Visus\Cuid2\Cuid2;
|
||||
|
||||
@@ -29,9 +28,9 @@ class DockerImage extends Component
|
||||
$this->validate([
|
||||
'dockerImage' => 'required',
|
||||
]);
|
||||
$image = Str::of($this->dockerImage)->before(':');
|
||||
if (Str::of($this->dockerImage)->contains(':')) {
|
||||
$tag = Str::of($this->dockerImage)->after(':');
|
||||
$image = str($this->dockerImage)->before(':');
|
||||
if (str($this->dockerImage)->contains(':')) {
|
||||
$tag = str($this->dockerImage)->after(':');
|
||||
} else {
|
||||
$tag = 'latest';
|
||||
}
|
||||
|
||||
@@ -53,6 +53,12 @@ class GithubPrivateRepository extends Component
|
||||
|
||||
public ?string $publish_directory = null;
|
||||
|
||||
// In case of docker compose
|
||||
public ?string $base_directory = null;
|
||||
|
||||
public ?string $docker_compose_location = '/docker-compose.yaml';
|
||||
// End of docker compose
|
||||
|
||||
protected int $page = 1;
|
||||
|
||||
public $build_pack = 'nixpacks';
|
||||
@@ -68,6 +74,16 @@ class GithubPrivateRepository extends Component
|
||||
$this->github_apps = GithubApp::private();
|
||||
}
|
||||
|
||||
public function updatedBaseDirectory()
|
||||
{
|
||||
if ($this->base_directory) {
|
||||
$this->base_directory = rtrim($this->base_directory, '/');
|
||||
if (! str($this->base_directory)->startsWith('/')) {
|
||||
$this->base_directory = '/'.$this->base_directory;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public function updatedBuildPack()
|
||||
{
|
||||
if ($this->build_pack === 'nixpacks') {
|
||||
@@ -184,6 +200,10 @@ class GithubPrivateRepository extends Component
|
||||
if ($this->build_pack === 'dockerfile' || $this->build_pack === 'dockerimage') {
|
||||
$application->health_check_enabled = false;
|
||||
}
|
||||
if ($this->build_pack === 'dockercompose') {
|
||||
$application['docker_compose_location'] = $this->docker_compose_location;
|
||||
$application['base_directory'] = $this->base_directory;
|
||||
}
|
||||
$fqdn = generateFqdn($destination->server, $application->uuid);
|
||||
$application->fqdn = $fqdn;
|
||||
|
||||
|
||||
@@ -33,6 +33,12 @@ class GithubPrivateRepositoryDeployKey extends Component
|
||||
|
||||
public ?string $publish_directory = null;
|
||||
|
||||
// In case of docker compose
|
||||
public ?string $base_directory = null;
|
||||
|
||||
public ?string $docker_compose_location = '/docker-compose.yaml';
|
||||
// End of docker compose
|
||||
|
||||
public string $repository_url;
|
||||
|
||||
public string $branch;
|
||||
@@ -163,6 +169,10 @@ class GithubPrivateRepositoryDeployKey extends Component
|
||||
if ($this->build_pack === 'dockerfile' || $this->build_pack === 'dockerimage') {
|
||||
$application_init['health_check_enabled'] = false;
|
||||
}
|
||||
if ($this->build_pack === 'dockercompose') {
|
||||
$application_init['docker_compose_location'] = $this->docker_compose_location;
|
||||
$application_init['base_directory'] = $this->base_directory;
|
||||
}
|
||||
$application = Application::create($application_init);
|
||||
$application->settings->is_static = $this->is_static;
|
||||
$application->settings->save();
|
||||
@@ -193,7 +203,7 @@ class GithubPrivateRepositoryDeployKey extends Component
|
||||
|
||||
return;
|
||||
}
|
||||
if (Str::of($this->repository_url)->startsWith('http')) {
|
||||
if (str($this->repository_url)->startsWith('http')) {
|
||||
$this->git_host = $this->repository_url_parsed->getHost();
|
||||
$this->git_repository = $this->repository_url_parsed->getSegment(1).'/'.$this->repository_url_parsed->getSegment(2);
|
||||
$this->git_repository = Str::finish("git@$this->git_host:$this->git_repository", '.git');
|
||||
|
||||
@@ -33,6 +33,12 @@ class PublicGitRepository extends Component
|
||||
|
||||
public ?string $publish_directory = null;
|
||||
|
||||
// In case of docker compose
|
||||
public ?string $base_directory = null;
|
||||
|
||||
public ?string $docker_compose_location = '/docker-compose.yaml';
|
||||
// End of docker compose
|
||||
|
||||
public string $git_branch = 'main';
|
||||
|
||||
public int $rate_limit_remaining = 0;
|
||||
@@ -59,6 +65,8 @@ class PublicGitRepository extends Component
|
||||
'is_static' => 'required|boolean',
|
||||
'publish_directory' => 'nullable|string',
|
||||
'build_pack' => 'required|string',
|
||||
'base_directory' => 'nullable|string',
|
||||
'docker_compose_location' => 'nullable|string',
|
||||
];
|
||||
|
||||
protected $validationAttributes = [
|
||||
@@ -67,6 +75,8 @@ class PublicGitRepository extends Component
|
||||
'is_static' => 'static',
|
||||
'publish_directory' => 'publish directory',
|
||||
'build_pack' => 'build pack',
|
||||
'base_directory' => 'base directory',
|
||||
'docker_compose_location' => 'docker compose location',
|
||||
];
|
||||
|
||||
public function mount()
|
||||
@@ -79,6 +89,16 @@ class PublicGitRepository extends Component
|
||||
$this->query = request()->query();
|
||||
}
|
||||
|
||||
public function updatedBaseDirectory()
|
||||
{
|
||||
if ($this->base_directory) {
|
||||
$this->base_directory = rtrim($this->base_directory, '/');
|
||||
if (! str($this->base_directory)->startsWith('/')) {
|
||||
$this->base_directory = '/'.$this->base_directory;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public function updatedBuildPack()
|
||||
{
|
||||
if ($this->build_pack === 'nixpacks') {
|
||||
@@ -261,6 +281,10 @@ class PublicGitRepository extends Component
|
||||
if ($this->build_pack === 'dockerfile' || $this->build_pack === 'dockerimage') {
|
||||
$application_init['health_check_enabled'] = false;
|
||||
}
|
||||
if ($this->build_pack === 'dockercompose') {
|
||||
$application_init['docker_compose_location'] = $this->docker_compose_location;
|
||||
$application_init['base_directory'] = $this->base_directory;
|
||||
}
|
||||
$application = Application::create($application_init);
|
||||
|
||||
$application->settings->is_static = $this->is_static;
|
||||
|
||||
@@ -11,7 +11,7 @@ class EditCompose extends Component
|
||||
|
||||
public $serviceId;
|
||||
|
||||
protected $listeners = ['refreshEnvs' => 'mount'];
|
||||
protected $listeners = ['refreshEnvs', 'envsUpdated'];
|
||||
|
||||
protected $rules = [
|
||||
'service.docker_compose_raw' => 'required',
|
||||
@@ -19,6 +19,17 @@ class EditCompose extends Component
|
||||
'service.is_container_label_escape_enabled' => 'required',
|
||||
];
|
||||
|
||||
public function envsUpdated()
|
||||
{
|
||||
$this->dispatch('saveCompose', $this->service->docker_compose_raw);
|
||||
$this->refreshEnvs();
|
||||
}
|
||||
|
||||
public function refreshEnvs()
|
||||
{
|
||||
$this->service = Service::find($this->serviceId);
|
||||
}
|
||||
|
||||
public function mount()
|
||||
{
|
||||
$this->service = Service::find($this->serviceId);
|
||||
|
||||
@@ -14,7 +14,6 @@ use App\Models\StandaloneMongodb;
|
||||
use App\Models\StandaloneMysql;
|
||||
use App\Models\StandalonePostgresql;
|
||||
use App\Models\StandaloneRedis;
|
||||
use Illuminate\Support\Str;
|
||||
use Livewire\Component;
|
||||
|
||||
class FileStorage extends Component
|
||||
@@ -37,9 +36,9 @@ class FileStorage extends Component
|
||||
public function mount()
|
||||
{
|
||||
$this->resource = $this->fileStorage->service;
|
||||
if (Str::of($this->fileStorage->fs_path)->startsWith('.')) {
|
||||
if (str($this->fileStorage->fs_path)->startsWith('.')) {
|
||||
$this->workdir = $this->resource->service?->workdir();
|
||||
$this->fs_path = Str::of($this->fileStorage->fs_path)->after('.');
|
||||
$this->fs_path = str($this->fileStorage->fs_path)->after('.');
|
||||
} else {
|
||||
$this->workdir = null;
|
||||
$this->fs_path = $this->fileStorage->fs_path;
|
||||
|
||||
@@ -81,7 +81,6 @@ class StackForm extends Component
|
||||
return handleError($e, $this);
|
||||
} finally {
|
||||
if (is_null($this->service->config_hash)) {
|
||||
ray('asdf');
|
||||
$this->service->isConfigurationChanged(true);
|
||||
} else {
|
||||
$this->dispatch('configurationChanged');
|
||||
|
||||
@@ -16,6 +16,8 @@ class Danger extends Component
|
||||
|
||||
public bool $delete_configurations = true;
|
||||
|
||||
public bool $delete_volumes = true;
|
||||
|
||||
public ?string $modalId = null;
|
||||
|
||||
public function mount()
|
||||
@@ -31,7 +33,7 @@ class Danger extends Component
|
||||
try {
|
||||
// $this->authorize('delete', $this->resource);
|
||||
$this->resource->delete();
|
||||
DeleteResourceJob::dispatch($this->resource, $this->delete_configurations);
|
||||
DeleteResourceJob::dispatch($this->resource, $this->delete_configurations, $this->delete_volumes);
|
||||
|
||||
return redirect()->route('project.resource.index', [
|
||||
'project_uuid' => $this->projectUuid,
|
||||
|
||||
@@ -112,7 +112,7 @@ class Show extends Component
|
||||
$this->serialize();
|
||||
$this->env->save();
|
||||
$this->dispatch('success', 'Environment variable updated.');
|
||||
$this->dispatch('refreshEnvs');
|
||||
$this->dispatch('envsUpdated');
|
||||
} catch (\Exception $e) {
|
||||
return handleError($e);
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
namespace App\Livewire\Project\Shared;
|
||||
|
||||
use App\Actions\Server\RunCommand;
|
||||
use App\Models\Application;
|
||||
use App\Models\Server;
|
||||
use App\Models\Service;
|
||||
@@ -137,7 +138,7 @@ class ExecuteContainerCommand extends Component
|
||||
} else {
|
||||
$exec = "docker exec {$container_name} {$cmd}";
|
||||
}
|
||||
$activity = remote_process([$exec], $server, ignore_errors: true);
|
||||
$activity = RunCommand::run(server: $server, command: $exec);
|
||||
$this->dispatch('activityMonitor', $activity->id);
|
||||
} catch (\Throwable $e) {
|
||||
return handleError($e, $this);
|
||||
|
||||
@@ -4,7 +4,6 @@ namespace App\Livewire\Project\Shared\Storages;
|
||||
|
||||
use App\Models\LocalPersistentVolume;
|
||||
use Livewire\Component;
|
||||
use Visus\Cuid2\Cuid2;
|
||||
|
||||
class Show extends Component
|
||||
{
|
||||
@@ -12,8 +11,6 @@ class Show extends Component
|
||||
|
||||
public bool $isReadOnly = false;
|
||||
|
||||
public ?string $modalId = null;
|
||||
|
||||
public bool $isFirst = true;
|
||||
|
||||
public bool $isService = false;
|
||||
@@ -32,11 +29,6 @@ class Show extends Component
|
||||
'host_path' => 'host',
|
||||
];
|
||||
|
||||
public function mount()
|
||||
{
|
||||
$this->modalId = new Cuid2(7);
|
||||
}
|
||||
|
||||
public function submit()
|
||||
{
|
||||
$this->validate();
|
||||
|
||||
@@ -3,32 +3,63 @@
|
||||
namespace App\Livewire\Project\Shared;
|
||||
|
||||
use App\Models\Tag;
|
||||
use Livewire\Attributes\Validate;
|
||||
use Livewire\Component;
|
||||
|
||||
// Refactored ✅
|
||||
class Tags extends Component
|
||||
{
|
||||
public $resource = null;
|
||||
|
||||
public ?string $new_tag = null;
|
||||
#[Validate('required|string|min:2')]
|
||||
public string $newTags;
|
||||
|
||||
public $tags = [];
|
||||
|
||||
protected $listeners = [
|
||||
'refresh' => '$refresh',
|
||||
];
|
||||
|
||||
protected $rules = [
|
||||
'resource.tags.*.name' => 'required|string|min:2',
|
||||
'new_tag' => 'required|string|min:2',
|
||||
];
|
||||
|
||||
protected $validationAttributes = [
|
||||
'new_tag' => 'tag',
|
||||
];
|
||||
public $filteredTags = [];
|
||||
|
||||
public function mount()
|
||||
{
|
||||
$this->loadTags();
|
||||
}
|
||||
|
||||
public function loadTags()
|
||||
{
|
||||
$this->tags = Tag::ownedByCurrentTeam()->get();
|
||||
$this->filteredTags = $this->tags->filter(function ($tag) {
|
||||
return ! $this->resource->tags->contains($tag);
|
||||
});
|
||||
}
|
||||
|
||||
public function submit()
|
||||
{
|
||||
try {
|
||||
$this->validate();
|
||||
$tags = str($this->newTags)->trim()->explode(' ');
|
||||
foreach ($tags as $tag) {
|
||||
if (strlen($tag) < 2) {
|
||||
$this->dispatch('error', 'Invalid tag.', "Tag <span class='dark:text-warning'>$tag</span> is invalid. Min length is 2.");
|
||||
|
||||
continue;
|
||||
}
|
||||
if ($this->resource->tags()->where('name', $tag)->exists()) {
|
||||
$this->dispatch('error', 'Duplicate tags.', "Tag <span class='dark:text-warning'>$tag</span> already added.");
|
||||
|
||||
continue;
|
||||
}
|
||||
$found = Tag::ownedByCurrentTeam()->where(['name' => $tag])->exists();
|
||||
if (! $found) {
|
||||
$found = Tag::create([
|
||||
'name' => $tag,
|
||||
'team_id' => currentTeam()->id,
|
||||
]);
|
||||
}
|
||||
$this->resource->tags()->attach($found->id);
|
||||
}
|
||||
$this->refresh();
|
||||
} catch (\Exception $e) {
|
||||
return handleError($e, $this);
|
||||
}
|
||||
}
|
||||
|
||||
public function addTag(string $id, string $name)
|
||||
@@ -39,8 +70,9 @@ class Tags extends Component
|
||||
|
||||
return;
|
||||
}
|
||||
$this->resource->tags()->syncWithoutDetaching($id);
|
||||
$this->resource->tags()->attach($id);
|
||||
$this->refresh();
|
||||
$this->dispatch('success', 'Tag added.');
|
||||
} catch (\Exception $e) {
|
||||
return handleError($e, $this);
|
||||
}
|
||||
@@ -50,12 +82,12 @@ class Tags extends Component
|
||||
{
|
||||
try {
|
||||
$this->resource->tags()->detach($id);
|
||||
|
||||
$found_more_tags = Tag::where(['id' => $id, 'team_id' => currentTeam()->id])->first();
|
||||
if ($found_more_tags->applications()->count() == 0 && $found_more_tags->services()->count() == 0) {
|
||||
$found_more_tags = Tag::ownedByCurrentTeam()->find($id);
|
||||
if ($found_more_tags && $found_more_tags->applications()->count() == 0 && $found_more_tags->services()->count() == 0) {
|
||||
$found_more_tags->delete();
|
||||
}
|
||||
$this->refresh();
|
||||
$this->dispatch('success', 'Tag deleted.');
|
||||
} catch (\Exception $e) {
|
||||
return handleError($e, $this);
|
||||
}
|
||||
@@ -63,41 +95,8 @@ class Tags extends Component
|
||||
|
||||
public function refresh()
|
||||
{
|
||||
$this->resource->load(['tags']);
|
||||
$this->tags = Tag::ownedByCurrentTeam()->get();
|
||||
$this->new_tag = null;
|
||||
}
|
||||
|
||||
public function submit()
|
||||
{
|
||||
try {
|
||||
$this->validate([
|
||||
'new_tag' => 'required|string|min:2',
|
||||
]);
|
||||
$tags = str($this->new_tag)->trim()->explode(' ');
|
||||
foreach ($tags as $tag) {
|
||||
if ($this->resource->tags()->where('name', $tag)->exists()) {
|
||||
$this->dispatch('error', 'Duplicate tags.', "Tag <span class='dark:text-warning'>$tag</span> already added.");
|
||||
|
||||
continue;
|
||||
}
|
||||
$found = Tag::where(['name' => $tag, 'team_id' => currentTeam()->id])->first();
|
||||
if (! $found) {
|
||||
$found = Tag::create([
|
||||
'name' => $tag,
|
||||
'team_id' => currentTeam()->id,
|
||||
]);
|
||||
}
|
||||
$this->resource->tags()->syncWithoutDetaching($found->id);
|
||||
}
|
||||
$this->refresh();
|
||||
} catch (\Exception $e) {
|
||||
return handleError($e, $this);
|
||||
}
|
||||
}
|
||||
|
||||
public function render()
|
||||
{
|
||||
return view('livewire.project.shared.tags');
|
||||
$this->resource->refresh(); // Remove this when legacy_model_binding is false
|
||||
$this->loadTags();
|
||||
$this->reset('newTags');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,49 +4,61 @@ namespace App\Livewire\Project\Shared;
|
||||
|
||||
use Livewire\Component;
|
||||
|
||||
// Refactored ✅
|
||||
class Webhooks extends Component
|
||||
{
|
||||
public $resource;
|
||||
|
||||
public ?string $deploywebhook = null;
|
||||
public ?string $deploywebhook;
|
||||
|
||||
public ?string $githubManualWebhook = null;
|
||||
public ?string $githubManualWebhook;
|
||||
|
||||
public ?string $gitlabManualWebhook = null;
|
||||
public ?string $gitlabManualWebhook;
|
||||
|
||||
public ?string $bitbucketManualWebhook = null;
|
||||
public ?string $bitbucketManualWebhook;
|
||||
|
||||
public ?string $giteaManualWebhook = null;
|
||||
public ?string $giteaManualWebhook;
|
||||
|
||||
protected $rules = [
|
||||
'resource.manual_webhook_secret_github' => 'nullable|string',
|
||||
'resource.manual_webhook_secret_gitlab' => 'nullable|string',
|
||||
'resource.manual_webhook_secret_bitbucket' => 'nullable|string',
|
||||
'resource.manual_webhook_secret_gitea' => 'nullable|string',
|
||||
];
|
||||
public ?string $githubManualWebhookSecret = null;
|
||||
|
||||
public function saveSecret()
|
||||
public ?string $gitlabManualWebhookSecret = null;
|
||||
|
||||
public ?string $bitbucketManualWebhookSecret = null;
|
||||
|
||||
public ?string $giteaManualWebhookSecret = null;
|
||||
|
||||
public function mount()
|
||||
{
|
||||
// ray()->clearAll();
|
||||
// ray()->showQueries();
|
||||
$this->deploywebhook = generateDeployWebhook($this->resource);
|
||||
|
||||
$this->githubManualWebhookSecret = data_get($this->resource, 'manual_webhook_secret_github');
|
||||
$this->githubManualWebhook = generateGitManualWebhook($this->resource, 'github');
|
||||
|
||||
$this->gitlabManualWebhookSecret = data_get($this->resource, 'manual_webhook_secret_gitlab');
|
||||
$this->gitlabManualWebhook = generateGitManualWebhook($this->resource, 'gitlab');
|
||||
|
||||
$this->bitbucketManualWebhookSecret = data_get($this->resource, 'manual_webhook_secret_bitbucket');
|
||||
$this->bitbucketManualWebhook = generateGitManualWebhook($this->resource, 'bitbucket');
|
||||
|
||||
$this->giteaManualWebhookSecret = data_get($this->resource, 'manual_webhook_secret_gitea');
|
||||
$this->giteaManualWebhook = generateGitManualWebhook($this->resource, 'gitea');
|
||||
}
|
||||
|
||||
public function submit()
|
||||
{
|
||||
try {
|
||||
$this->validate();
|
||||
$this->resource->save();
|
||||
$this->authorize('update', $this->resource);
|
||||
$this->resource->update([
|
||||
'manual_webhook_secret_github' => $this->githubManualWebhookSecret,
|
||||
'manual_webhook_secret_gitlab' => $this->gitlabManualWebhookSecret,
|
||||
'manual_webhook_secret_bitbucket' => $this->bitbucketManualWebhookSecret,
|
||||
'manual_webhook_secret_gitea' => $this->giteaManualWebhookSecret,
|
||||
]);
|
||||
$this->dispatch('success', 'Secret Saved.');
|
||||
} catch (\Exception $e) {
|
||||
return handleError($e, $this);
|
||||
}
|
||||
}
|
||||
|
||||
public function mount()
|
||||
{
|
||||
$this->deploywebhook = generateDeployWebhook($this->resource);
|
||||
$this->githubManualWebhook = generateGitManualWebhook($this->resource, 'github');
|
||||
$this->gitlabManualWebhook = generateGitManualWebhook($this->resource, 'gitlab');
|
||||
$this->bitbucketManualWebhook = generateGitManualWebhook($this->resource, 'bitbucket');
|
||||
$this->giteaManualWebhook = generateGitManualWebhook($this->resource, 'gitea');
|
||||
}
|
||||
|
||||
public function render()
|
||||
{
|
||||
return view('livewire.project.shared.webhooks');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
namespace App\Livewire;
|
||||
|
||||
use App\Actions\Server\RunCommand as ServerRunCommand;
|
||||
use App\Models\Server;
|
||||
use Livewire\Component;
|
||||
|
||||
@@ -33,7 +34,7 @@ class RunCommand extends Component
|
||||
{
|
||||
$this->validate();
|
||||
try {
|
||||
$activity = remote_process([$this->command], Server::where('uuid', $this->server)->first(), ignore_errors: true);
|
||||
$activity = ServerRunCommand::run(server: Server::where('uuid', $this->server)->first(), command: $this->command);
|
||||
$this->dispatch('activityMonitor', $activity->id);
|
||||
} catch (\Throwable $e) {
|
||||
return handleError($e, $this);
|
||||
|
||||
@@ -10,6 +10,12 @@ class ApiTokens extends Component
|
||||
|
||||
public $tokens = [];
|
||||
|
||||
public bool $viewSensitiveData = false;
|
||||
|
||||
public bool $readOnly = true;
|
||||
|
||||
public array $permissions = ['read-only'];
|
||||
|
||||
public function render()
|
||||
{
|
||||
return view('livewire.security.api-tokens');
|
||||
@@ -17,7 +23,33 @@ class ApiTokens extends Component
|
||||
|
||||
public function mount()
|
||||
{
|
||||
$this->tokens = auth()->user()->tokens;
|
||||
$this->tokens = auth()->user()->tokens->sortByDesc('created_at');
|
||||
}
|
||||
|
||||
public function updatedViewSensitiveData()
|
||||
{
|
||||
if ($this->viewSensitiveData) {
|
||||
$this->permissions[] = 'view:sensitive';
|
||||
$this->permissions = array_diff($this->permissions, ['*']);
|
||||
} else {
|
||||
$this->permissions = array_diff($this->permissions, ['view:sensitive']);
|
||||
}
|
||||
if (count($this->permissions) == 0) {
|
||||
$this->permissions = ['*'];
|
||||
}
|
||||
}
|
||||
|
||||
public function updatedReadOnly()
|
||||
{
|
||||
if ($this->readOnly) {
|
||||
$this->permissions[] = 'read-only';
|
||||
$this->permissions = array_diff($this->permissions, ['*']);
|
||||
} else {
|
||||
$this->permissions = array_diff($this->permissions, ['read-only']);
|
||||
}
|
||||
if (count($this->permissions) == 0) {
|
||||
$this->permissions = ['*'];
|
||||
}
|
||||
}
|
||||
|
||||
public function addNewToken()
|
||||
@@ -26,7 +58,13 @@ class ApiTokens extends Component
|
||||
$this->validate([
|
||||
'description' => 'required|min:3|max:255',
|
||||
]);
|
||||
$token = auth()->user()->createToken($this->description);
|
||||
// if ($this->viewSensitiveData) {
|
||||
// $this->permissions[] = 'view:sensitive';
|
||||
// }
|
||||
// if ($this->readOnly) {
|
||||
// $this->permissions[] = 'read-only';
|
||||
// }
|
||||
$token = auth()->user()->createToken($this->description, $this->permissions);
|
||||
$this->tokens = auth()->user()->tokens;
|
||||
session()->flash('token', $token->plainTextToken);
|
||||
} catch (\Exception $e) {
|
||||
|
||||
@@ -6,7 +6,6 @@ use App\Actions\Proxy\CheckConfiguration;
|
||||
use App\Actions\Proxy\SaveConfiguration;
|
||||
use App\Actions\Proxy\StartProxy;
|
||||
use App\Models\Server;
|
||||
use Illuminate\Support\Str;
|
||||
use Livewire\Component;
|
||||
|
||||
class Proxy extends Component
|
||||
@@ -79,7 +78,7 @@ class Proxy extends Component
|
||||
{
|
||||
try {
|
||||
$this->proxy_settings = CheckConfiguration::run($this->server);
|
||||
if (Str::of($this->proxy_settings)->contains('--api.dashboard=true') && Str::of($this->proxy_settings)->contains('--api.insecure=true')) {
|
||||
if (str($this->proxy_settings)->contains('--api.dashboard=true') && str($this->proxy_settings)->contains('--api.insecure=true')) {
|
||||
$this->dispatch('traefikDashboardAvailable', true);
|
||||
} else {
|
||||
$this->dispatch('traefikDashboardAvailable', false);
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user