mirror of
https://github.com/ershisan99/coolify.git
synced 2025-12-18 12:33:06 +00:00
Compare commits
63 Commits
v4.0.0-bet
...
v4.0.0-bet
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
93fb14884e | ||
|
|
26ccc4afb4 | ||
|
|
5fda1bb932 | ||
|
|
409ba8a1bb | ||
|
|
49f5240ff8 | ||
|
|
0c3ed3d393 | ||
|
|
6e3dc474f2 | ||
|
|
d3eb87561e | ||
|
|
8b58c8f856 | ||
|
|
8c60ef5bd6 | ||
|
|
1d59383c78 | ||
|
|
60f590454d | ||
|
|
dcb61a553e | ||
|
|
e06e31642f | ||
|
|
9dfce48380 | ||
|
|
8eed87e2f7 | ||
|
|
d56d4eb8fc | ||
|
|
fd32cd04ab | ||
|
|
1d3b7ffd3b | ||
|
|
0b5baf60a5 | ||
|
|
bc31df6fb2 | ||
|
|
818399bc23 | ||
|
|
e7fdff0f69 | ||
|
|
6312c0ba84 | ||
|
|
44efe0b5e1 | ||
|
|
de7d584648 | ||
|
|
b9f12d2586 | ||
|
|
c76e8bb0de | ||
|
|
3b655f8e3f | ||
|
|
2b9df41444 | ||
|
|
628fec6904 | ||
|
|
f36135cbfc | ||
|
|
75fe005055 | ||
|
|
8ff7aeb78b | ||
|
|
f1a9e28d5a | ||
|
|
843cd90ee5 | ||
|
|
1cbfd03912 | ||
|
|
ce60a39dc5 | ||
|
|
f1e4395a83 | ||
|
|
52fd7ad571 | ||
|
|
5f797ec0ae | ||
|
|
21e77bf0c1 | ||
|
|
0686e48e89 | ||
|
|
1cef233db2 | ||
|
|
907e52572c | ||
|
|
795c8abf64 | ||
|
|
cc641d8cba | ||
|
|
d4668ef44a | ||
|
|
e8b539c3bd | ||
|
|
6555f0b50c | ||
|
|
bb05058dda | ||
|
|
9667cd4a7a | ||
|
|
3ae9501814 | ||
|
|
73d0948734 | ||
|
|
c3e2a741ea | ||
|
|
4792146f1d | ||
|
|
09b9305aa3 | ||
|
|
ff7d0d442d | ||
|
|
9a127bdc80 | ||
|
|
919e88afb4 | ||
|
|
1d1ec20cb7 | ||
|
|
5c29ecdf10 | ||
|
|
9e09c449cf |
@@ -11,6 +11,9 @@ class StopApplication
|
|||||||
public function handle(Application $application)
|
public function handle(Application $application)
|
||||||
{
|
{
|
||||||
$server = $application->destination->server;
|
$server = $application->destination->server;
|
||||||
|
if (!$server->isFunctional()) {
|
||||||
|
return 'Server is not functional';
|
||||||
|
}
|
||||||
if ($server->isSwarm()) {
|
if ($server->isSwarm()) {
|
||||||
instant_remote_process(["docker stack rm {$application->uuid}" ], $server);
|
instant_remote_process(["docker stack rm {$application->uuid}" ], $server);
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -17,6 +17,9 @@ class StopDatabase
|
|||||||
public function handle(StandaloneRedis|StandalonePostgresql|StandaloneMongodb|StandaloneMysql|StandaloneMariadb $database)
|
public function handle(StandaloneRedis|StandalonePostgresql|StandaloneMongodb|StandaloneMysql|StandaloneMariadb $database)
|
||||||
{
|
{
|
||||||
$server = $database->destination->server;
|
$server = $database->destination->server;
|
||||||
|
if (!$server->isFunctional()) {
|
||||||
|
return 'Server is not functional';
|
||||||
|
}
|
||||||
instant_remote_process(
|
instant_remote_process(
|
||||||
["docker rm -f {$database->uuid}"],
|
["docker rm -f {$database->uuid}"],
|
||||||
$server
|
$server
|
||||||
|
|||||||
@@ -27,7 +27,7 @@ class StartProxy
|
|||||||
$server->save();
|
$server->save();
|
||||||
if ($server->isSwarm()) {
|
if ($server->isSwarm()) {
|
||||||
$commands = $commands->merge([
|
$commands = $commands->merge([
|
||||||
"mkdir -p $proxy_path && cd $proxy_path",
|
"mkdir -p $proxy_path/dynamic && cd $proxy_path",
|
||||||
"echo 'Creating required Docker Compose file.'",
|
"echo 'Creating required Docker Compose file.'",
|
||||||
"echo 'Starting coolify-proxy.'",
|
"echo 'Starting coolify-proxy.'",
|
||||||
"cd $proxy_path && docker stack deploy -c docker-compose.yml coolify-proxy",
|
"cd $proxy_path && docker stack deploy -c docker-compose.yml coolify-proxy",
|
||||||
@@ -35,7 +35,7 @@ class StartProxy
|
|||||||
]);
|
]);
|
||||||
} else {
|
} else {
|
||||||
$commands = $commands->merge([
|
$commands = $commands->merge([
|
||||||
"mkdir -p $proxy_path && cd $proxy_path",
|
"mkdir -p $proxy_path/dynamic && cd $proxy_path",
|
||||||
"echo 'Creating required Docker Compose file.'",
|
"echo 'Creating required Docker Compose file.'",
|
||||||
"echo 'Pulling docker image.'",
|
"echo 'Pulling docker image.'",
|
||||||
'docker compose pull',
|
'docker compose pull',
|
||||||
|
|||||||
@@ -43,6 +43,7 @@ class InstallDocker
|
|||||||
"echo 'Restarting Docker Engine...'",
|
"echo 'Restarting Docker Engine...'",
|
||||||
"ls -l /tmp"
|
"ls -l /tmp"
|
||||||
]);
|
]);
|
||||||
|
return remote_process($command, $server);
|
||||||
} else {
|
} else {
|
||||||
if ($supported_os_type->contains('debian')) {
|
if ($supported_os_type->contains('debian')) {
|
||||||
$command = $command->merge([
|
$command = $command->merge([
|
||||||
@@ -89,7 +90,6 @@ class InstallDocker
|
|||||||
"echo 'Done!'",
|
"echo 'Done!'",
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
return remote_process($command, $server);
|
return remote_process($command, $server);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,35 +10,42 @@ class DeleteService
|
|||||||
use AsAction;
|
use AsAction;
|
||||||
public function handle(Service $service)
|
public function handle(Service $service)
|
||||||
{
|
{
|
||||||
$server = data_get($service, 'server');
|
try {
|
||||||
if ($server->isFunctional()) {
|
$server = data_get($service, 'server');
|
||||||
StopService::run($service);
|
if ($server->isFunctional()) {
|
||||||
}
|
$storagesToDelete = collect([]);
|
||||||
$storagesToDelete = collect([]);
|
|
||||||
|
|
||||||
$service->environment_variables()->delete();
|
$service->environment_variables()->delete();
|
||||||
$commands = [];
|
$commands = [];
|
||||||
foreach ($service->applications()->get() as $application) {
|
foreach ($service->applications()->get() as $application) {
|
||||||
$storages = $application->persistentStorages()->get();
|
$storages = $application->persistentStorages()->get();
|
||||||
foreach ($storages as $storage) {
|
foreach ($storages as $storage) {
|
||||||
$storagesToDelete->push($storage);
|
$storagesToDelete->push($storage);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
foreach ($service->databases()->get() as $database) {
|
||||||
|
$storages = $database->persistentStorages()->get();
|
||||||
|
foreach ($storages as $storage) {
|
||||||
|
$storagesToDelete->push($storage);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
foreach ($storagesToDelete as $storage) {
|
||||||
|
$commands[] = "docker volume rm -f $storage->name";
|
||||||
|
}
|
||||||
|
$commands[] = "docker rm -f $service->uuid";
|
||||||
|
|
||||||
|
instant_remote_process($commands, $server, false);
|
||||||
}
|
}
|
||||||
$application->forceDelete();
|
} catch (\Exception $e) {
|
||||||
}
|
throw new \Exception($e->getMessage());
|
||||||
foreach ($service->databases()->get() as $database) {
|
} finally {
|
||||||
$storages = $database->persistentStorages()->get();
|
foreach ($service->applications()->get() as $application) {
|
||||||
foreach ($storages as $storage) {
|
$application->forceDelete();
|
||||||
$storagesToDelete->push($storage);
|
|
||||||
}
|
}
|
||||||
$database->forceDelete();
|
foreach ($service->databases()->get() as $database) {
|
||||||
|
$database->forceDelete();
|
||||||
|
}
|
||||||
|
$service->tags()->detach();
|
||||||
}
|
}
|
||||||
foreach ($storagesToDelete as $storage) {
|
|
||||||
$commands[] = "docker volume rm -f $storage->name";
|
|
||||||
}
|
|
||||||
$commands[] = "docker rm -f $service->uuid";
|
|
||||||
|
|
||||||
instant_remote_process($commands, $server, false);
|
|
||||||
|
|
||||||
$service->forceDelete();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,6 +10,10 @@ class StopService
|
|||||||
use AsAction;
|
use AsAction;
|
||||||
public function handle(Service $service)
|
public function handle(Service $service)
|
||||||
{
|
{
|
||||||
|
$server = $service->destination->server;
|
||||||
|
if (!$server->isFunctional()) {
|
||||||
|
return 'Server is not functional';
|
||||||
|
}
|
||||||
ray('Stopping service: ' . $service->name);
|
ray('Stopping service: ' . $service->name);
|
||||||
$applications = $service->applications()->get();
|
$applications = $service->applications()->get();
|
||||||
foreach ($applications as $application) {
|
foreach ($applications as $application) {
|
||||||
|
|||||||
295
app/Console/Commands/CleanupStuckedResources.php
Normal file
295
app/Console/Commands/CleanupStuckedResources.php
Normal file
@@ -0,0 +1,295 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Console\Commands;
|
||||||
|
|
||||||
|
use App\Models\Application;
|
||||||
|
use App\Models\Service;
|
||||||
|
use App\Models\ServiceApplication;
|
||||||
|
use App\Models\ServiceDatabase;
|
||||||
|
use App\Models\StandaloneMariadb;
|
||||||
|
use App\Models\StandaloneMongodb;
|
||||||
|
use App\Models\StandaloneMysql;
|
||||||
|
use App\Models\StandalonePostgresql;
|
||||||
|
use App\Models\StandaloneRedis;
|
||||||
|
use Illuminate\Console\Command;
|
||||||
|
|
||||||
|
class CleanupStuckedResources extends Command
|
||||||
|
{
|
||||||
|
protected $signature = 'cleanup:stucked-resources';
|
||||||
|
protected $description = 'Cleanup Stucked Resources';
|
||||||
|
|
||||||
|
public function handle()
|
||||||
|
{
|
||||||
|
echo "Running cleanup stucked...\n";
|
||||||
|
$this->cleanup_stucked_resources();
|
||||||
|
}
|
||||||
|
private function cleanup_stucked_resources()
|
||||||
|
{
|
||||||
|
|
||||||
|
try {
|
||||||
|
$applications = Application::withTrashed()->whereNotNull('deleted_at')->get();
|
||||||
|
foreach ($applications as $application) {
|
||||||
|
echo "Deleting stuck application: {$application->name}\n";
|
||||||
|
$application->forceDelete();
|
||||||
|
}
|
||||||
|
} catch (\Throwable $e) {
|
||||||
|
echo "Error in cleaning stuck application: {$e->getMessage()}\n";
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
$postgresqls = StandalonePostgresql::withTrashed()->whereNotNull('deleted_at')->get();
|
||||||
|
foreach ($postgresqls as $postgresql) {
|
||||||
|
echo "Deleting stuck postgresql: {$postgresql->name}\n";
|
||||||
|
$postgresql->forceDelete();
|
||||||
|
}
|
||||||
|
} catch (\Throwable $e) {
|
||||||
|
echo "Error in cleaning stuck postgresql: {$e->getMessage()}\n";
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
$redis = StandaloneRedis::withTrashed()->whereNotNull('deleted_at')->get();
|
||||||
|
foreach ($redis as $redis) {
|
||||||
|
echo "Deleting stuck redis: {$redis->name}\n";
|
||||||
|
$redis->forceDelete();
|
||||||
|
}
|
||||||
|
} catch (\Throwable $e) {
|
||||||
|
echo "Error in cleaning stuck redis: {$e->getMessage()}\n";
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
$mongodbs = StandaloneMongodb::withTrashed()->whereNotNull('deleted_at')->get();
|
||||||
|
foreach ($mongodbs as $mongodb) {
|
||||||
|
echo "Deleting stuck mongodb: {$mongodb->name}\n";
|
||||||
|
$mongodb->forceDelete();
|
||||||
|
}
|
||||||
|
} catch (\Throwable $e) {
|
||||||
|
echo "Error in cleaning stuck mongodb: {$e->getMessage()}\n";
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
$mysqls = StandaloneMysql::withTrashed()->whereNotNull('deleted_at')->get();
|
||||||
|
foreach ($mysqls as $mysql) {
|
||||||
|
echo "Deleting stuck mysql: {$mysql->name}\n";
|
||||||
|
$mysql->forceDelete();
|
||||||
|
}
|
||||||
|
} catch (\Throwable $e) {
|
||||||
|
echo "Error in cleaning stuck mysql: {$e->getMessage()}\n";
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
$mariadbs = StandaloneMariadb::withTrashed()->whereNotNull('deleted_at')->get();
|
||||||
|
foreach ($mariadbs as $mariadb) {
|
||||||
|
echo "Deleting stuck mariadb: {$mariadb->name}\n";
|
||||||
|
$mariadb->forceDelete();
|
||||||
|
}
|
||||||
|
} catch (\Throwable $e) {
|
||||||
|
echo "Error in cleaning stuck mariadb: {$e->getMessage()}\n";
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
$services = Service::withTrashed()->whereNotNull('deleted_at')->get();
|
||||||
|
foreach ($services as $service) {
|
||||||
|
echo "Deleting stuck service: {$service->name}\n";
|
||||||
|
$service->forceDelete();
|
||||||
|
}
|
||||||
|
} catch (\Throwable $e) {
|
||||||
|
echo "Error in cleaning stuck service: {$e->getMessage()}\n";
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
$serviceApps = ServiceApplication::withTrashed()->whereNotNull('deleted_at')->get();
|
||||||
|
foreach ($serviceApps as $serviceApp) {
|
||||||
|
echo "Deleting stuck serviceapp: {$serviceApp->name}\n";
|
||||||
|
$serviceApp->forceDelete();
|
||||||
|
}
|
||||||
|
} catch (\Throwable $e) {
|
||||||
|
echo "Error in cleaning stuck serviceapp: {$e->getMessage()}\n";
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
$serviceDbs = ServiceDatabase::withTrashed()->whereNotNull('deleted_at')->get();
|
||||||
|
foreach ($serviceDbs as $serviceDb) {
|
||||||
|
echo "Deleting stuck serviceapp: {$serviceDb->name}\n";
|
||||||
|
$serviceDb->forceDelete();
|
||||||
|
}
|
||||||
|
} catch (\Throwable $e) {
|
||||||
|
echo "Error in cleaning stuck serviceapp: {$e->getMessage()}\n";
|
||||||
|
}
|
||||||
|
|
||||||
|
// Cleanup any resources that are not attached to any environment or destination or server
|
||||||
|
try {
|
||||||
|
$applications = Application::all();
|
||||||
|
foreach ($applications as $application) {
|
||||||
|
if (!data_get($application, 'environment')) {
|
||||||
|
echo 'Application without environment: ' . $application->name . ' soft deleting\n';
|
||||||
|
$application->delete();
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (!$application->destination()) {
|
||||||
|
echo 'Application without destination: ' . $application->name . ' soft deleting\n';
|
||||||
|
$application->delete();
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (!data_get($application, 'destination.server')) {
|
||||||
|
echo 'Application without server: ' . $application->name . ' soft deleting\n';
|
||||||
|
$application->delete();
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (\Throwable $e) {
|
||||||
|
echo "Error in application: {$e->getMessage()}\n";
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
$postgresqls = StandalonePostgresql::all()->where('id', '!=', 0);
|
||||||
|
foreach ($postgresqls as $postgresql) {
|
||||||
|
if (!data_get($postgresql, 'environment')) {
|
||||||
|
echo 'Postgresql without environment: ' . $postgresql->name . ' soft deleting\n';
|
||||||
|
$postgresql->delete();
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (!$postgresql->destination()) {
|
||||||
|
echo 'Postgresql without destination: ' . $postgresql->name . ' soft deleting\n';
|
||||||
|
$postgresql->delete();
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (!data_get($postgresql, 'destination.server')) {
|
||||||
|
echo 'Postgresql without server: ' . $postgresql->name . ' soft deleting\n';
|
||||||
|
$postgresql->delete();
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (\Throwable $e) {
|
||||||
|
echo "Error in postgresql: {$e->getMessage()}\n";
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
$redis = StandaloneRedis::all();
|
||||||
|
foreach ($redis as $redis) {
|
||||||
|
if (!data_get($redis, 'environment')) {
|
||||||
|
echo 'Redis without environment: ' . $redis->name . ' soft deleting\n';
|
||||||
|
$redis->delete();
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (!$redis->destination()) {
|
||||||
|
echo 'Redis without destination: ' . $redis->name . ' soft deleting\n';
|
||||||
|
$redis->delete();
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (!data_get($redis, 'destination.server')) {
|
||||||
|
echo 'Redis without server: ' . $redis->name . ' soft deleting\n';
|
||||||
|
$redis->delete();
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (\Throwable $e) {
|
||||||
|
echo "Error in redis: {$e->getMessage()}\n";
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
$mongodbs = StandaloneMongodb::all();
|
||||||
|
foreach ($mongodbs as $mongodb) {
|
||||||
|
if (!data_get($mongodb, 'environment')) {
|
||||||
|
echo 'Mongodb without environment: ' . $mongodb->name . ' soft deleting\n';
|
||||||
|
$mongodb->delete();
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (!$mongodb->destination()) {
|
||||||
|
echo 'Mongodb without destination: ' . $mongodb->name . ' soft deleting\n';
|
||||||
|
$mongodb->delete();
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (!data_get($mongodb, 'destination.server')) {
|
||||||
|
echo 'Mongodb without server: ' . $mongodb->name . ' soft deleting\n';
|
||||||
|
$mongodb->delete();
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (\Throwable $e) {
|
||||||
|
echo "Error in mongodb: {$e->getMessage()}\n";
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
$mysqls = StandaloneMysql::all();
|
||||||
|
foreach ($mysqls as $mysql) {
|
||||||
|
if (!data_get($mysql, 'environment')) {
|
||||||
|
echo 'Mysql without environment: ' . $mysql->name . ' soft deleting\n';
|
||||||
|
$mysql->delete();
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (!$mysql->destination()) {
|
||||||
|
echo 'Mysql without destination: ' . $mysql->name . ' soft deleting\n';
|
||||||
|
$mysql->delete();
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (!data_get($mysql, 'destination.server')) {
|
||||||
|
echo 'Mysql without server: ' . $mysql->name . ' soft deleting\n';
|
||||||
|
$mysql->delete();
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (\Throwable $e) {
|
||||||
|
echo "Error in mysql: {$e->getMessage()}\n";
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
$mariadbs = StandaloneMariadb::all();
|
||||||
|
foreach ($mariadbs as $mariadb) {
|
||||||
|
if (!data_get($mariadb, 'environment')) {
|
||||||
|
echo 'Mariadb without environment: ' . $mariadb->name . ' soft deleting\n';
|
||||||
|
$mariadb->delete();
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (!$mariadb->destination()) {
|
||||||
|
echo 'Mariadb without destination: ' . $mariadb->name . ' soft deleting\n';
|
||||||
|
$mariadb->delete();
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (!data_get($mariadb, 'destination.server')) {
|
||||||
|
echo 'Mariadb without server: ' . $mariadb->name . ' soft deleting\n';
|
||||||
|
$mariadb->delete();
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (\Throwable $e) {
|
||||||
|
echo "Error in mariadb: {$e->getMessage()}\n";
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
$services = Service::all();
|
||||||
|
foreach ($services as $service) {
|
||||||
|
if (!data_get($service, 'environment')) {
|
||||||
|
echo 'Service without environment: ' . $service->name . ' soft deleting\n';
|
||||||
|
$service->delete();
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (!$service->destination()) {
|
||||||
|
echo 'Service without destination: ' . $service->name . ' soft deleting\n';
|
||||||
|
$service->delete();
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (!data_get($service, 'server')) {
|
||||||
|
echo 'Service without server: ' . $service->name . ' soft deleting\n';
|
||||||
|
$service->delete();
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (\Throwable $e) {
|
||||||
|
echo "Error in service: {$e->getMessage()}\n";
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
$serviceApplications = ServiceApplication::all();
|
||||||
|
foreach ($serviceApplications as $service) {
|
||||||
|
if (!data_get($service, 'service')) {
|
||||||
|
echo 'ServiceApplication without service: ' . $service->name . ' soft deleting\n';
|
||||||
|
$service->delete();
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (\Throwable $e) {
|
||||||
|
echo "Error in serviceApplications: {$e->getMessage()}\n";
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
$serviceDatabases = ServiceDatabase::all();
|
||||||
|
foreach ($serviceDatabases as $service) {
|
||||||
|
if (!data_get($service, 'service')) {
|
||||||
|
echo 'ServiceDatabase without service: ' . $service->name . ' soft deleting\n';
|
||||||
|
$service->delete();
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (\Throwable $e) {
|
||||||
|
echo "Error in ServiceDatabases: {$e->getMessage()}\n";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
26
app/Console/Commands/CleanupUnreachableServers.php
Normal file
26
app/Console/Commands/CleanupUnreachableServers.php
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Console\Commands;
|
||||||
|
|
||||||
|
use App\Models\Server;
|
||||||
|
use Illuminate\Console\Command;
|
||||||
|
|
||||||
|
class CleanupUnreachableServers extends Command
|
||||||
|
{
|
||||||
|
protected $signature = 'cleanup:unreachable-servers';
|
||||||
|
protected $description = 'Cleanup Unreachable Servers (3 days)';
|
||||||
|
|
||||||
|
public function handle()
|
||||||
|
{
|
||||||
|
echo "Running unreachable server cleanup...\n";
|
||||||
|
$servers = Server::where('unreachable_count', 3)->where('unreachable_notification_sent', true)->where('updated_at', '<', now()->subDays(3))->get();
|
||||||
|
if ($servers->count() > 0) {
|
||||||
|
foreach ($servers as $server) {
|
||||||
|
echo "Cleanup unreachable server ($server->id) with name $server->name";
|
||||||
|
$server->update([
|
||||||
|
'ip' => '1.2.3.4'
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -4,19 +4,11 @@ namespace App\Console\Commands;
|
|||||||
|
|
||||||
use App\Enums\ApplicationDeploymentStatus;
|
use App\Enums\ApplicationDeploymentStatus;
|
||||||
use App\Jobs\CleanupHelperContainersJob;
|
use App\Jobs\CleanupHelperContainersJob;
|
||||||
use App\Models\Application;
|
|
||||||
use App\Models\ApplicationDeploymentQueue;
|
use App\Models\ApplicationDeploymentQueue;
|
||||||
use App\Models\InstanceSettings;
|
use App\Models\InstanceSettings;
|
||||||
use App\Models\ScheduledDatabaseBackup;
|
use App\Models\ScheduledDatabaseBackup;
|
||||||
use App\Models\Server;
|
use App\Models\Server;
|
||||||
use App\Models\Service;
|
|
||||||
use App\Models\ServiceApplication;
|
|
||||||
use App\Models\ServiceDatabase;
|
|
||||||
use App\Models\StandaloneMariadb;
|
|
||||||
use App\Models\StandaloneMongodb;
|
|
||||||
use App\Models\StandaloneMysql;
|
|
||||||
use App\Models\StandalonePostgresql;
|
use App\Models\StandalonePostgresql;
|
||||||
use App\Models\StandaloneRedis;
|
|
||||||
use Illuminate\Console\Command;
|
use Illuminate\Console\Command;
|
||||||
use Illuminate\Support\Facades\Http;
|
use Illuminate\Support\Facades\Http;
|
||||||
|
|
||||||
@@ -31,7 +23,7 @@ class Init extends Command
|
|||||||
$cleanup = $this->option('cleanup');
|
$cleanup = $this->option('cleanup');
|
||||||
if ($cleanup) {
|
if ($cleanup) {
|
||||||
echo "Running cleanups...\n";
|
echo "Running cleanups...\n";
|
||||||
$this->cleanup_stucked_resources();
|
$this->call('cleanup:stucked-resources');
|
||||||
// Required for falsely deleted coolify db
|
// Required for falsely deleted coolify db
|
||||||
$this->restore_coolify_db_backup();
|
$this->restore_coolify_db_backup();
|
||||||
|
|
||||||
@@ -137,273 +129,5 @@ class Init extends Command
|
|||||||
echo "Error: {$e->getMessage()}\n";
|
echo "Error: {$e->getMessage()}\n";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
private function cleanup_stucked_resources()
|
|
||||||
{
|
|
||||||
|
|
||||||
try {
|
|
||||||
$applications = Application::withTrashed()->whereNotNull('deleted_at')->get();
|
|
||||||
foreach ($applications as $application) {
|
|
||||||
echo "Deleting stuck application: {$application->name}\n";
|
|
||||||
$application->forceDelete();
|
|
||||||
}
|
|
||||||
} catch (\Throwable $e) {
|
|
||||||
echo "Error in cleaning stuck application: {$e->getMessage()}\n";
|
|
||||||
}
|
|
||||||
try {
|
|
||||||
$postgresqls = StandalonePostgresql::withTrashed()->whereNotNull('deleted_at')->get();
|
|
||||||
foreach ($postgresqls as $postgresql) {
|
|
||||||
echo "Deleting stuck postgresql: {$postgresql->name}\n";
|
|
||||||
$postgresql->forceDelete();
|
|
||||||
}
|
|
||||||
} catch (\Throwable $e) {
|
|
||||||
echo "Error in cleaning stuck postgresql: {$e->getMessage()}\n";
|
|
||||||
}
|
|
||||||
try {
|
|
||||||
$redis = StandaloneRedis::withTrashed()->whereNotNull('deleted_at')->get();
|
|
||||||
foreach ($redis as $redis) {
|
|
||||||
echo "Deleting stuck redis: {$redis->name}\n";
|
|
||||||
$redis->forceDelete();
|
|
||||||
}
|
|
||||||
} catch (\Throwable $e) {
|
|
||||||
echo "Error in cleaning stuck redis: {$e->getMessage()}\n";
|
|
||||||
}
|
|
||||||
try {
|
|
||||||
$mongodbs = StandaloneMongodb::withTrashed()->whereNotNull('deleted_at')->get();
|
|
||||||
foreach ($mongodbs as $mongodb) {
|
|
||||||
echo "Deleting stuck mongodb: {$mongodb->name}\n";
|
|
||||||
$mongodb->forceDelete();
|
|
||||||
}
|
|
||||||
} catch (\Throwable $e) {
|
|
||||||
echo "Error in cleaning stuck mongodb: {$e->getMessage()}\n";
|
|
||||||
}
|
|
||||||
try {
|
|
||||||
$mysqls = StandaloneMysql::withTrashed()->whereNotNull('deleted_at')->get();
|
|
||||||
foreach ($mysqls as $mysql) {
|
|
||||||
echo "Deleting stuck mysql: {$mysql->name}\n";
|
|
||||||
$mysql->forceDelete();
|
|
||||||
}
|
|
||||||
} catch (\Throwable $e) {
|
|
||||||
echo "Error in cleaning stuck mysql: {$e->getMessage()}\n";
|
|
||||||
}
|
|
||||||
try {
|
|
||||||
$mariadbs = StandaloneMariadb::withTrashed()->whereNotNull('deleted_at')->get();
|
|
||||||
foreach ($mariadbs as $mariadb) {
|
|
||||||
echo "Deleting stuck mariadb: {$mariadb->name}\n";
|
|
||||||
$mariadb->forceDelete();
|
|
||||||
}
|
|
||||||
} catch (\Throwable $e) {
|
|
||||||
echo "Error in cleaning stuck mariadb: {$e->getMessage()}\n";
|
|
||||||
}
|
|
||||||
try {
|
|
||||||
$services = Service::withTrashed()->whereNotNull('deleted_at')->get();
|
|
||||||
foreach ($services as $service) {
|
|
||||||
echo "Deleting stuck service: {$service->name}\n";
|
|
||||||
$service->forceDelete();
|
|
||||||
}
|
|
||||||
} catch (\Throwable $e) {
|
|
||||||
echo "Error in cleaning stuck service: {$e->getMessage()}\n";
|
|
||||||
}
|
|
||||||
try {
|
|
||||||
$serviceApps = ServiceApplication::withTrashed()->whereNotNull('deleted_at')->get();
|
|
||||||
foreach ($serviceApps as $serviceApp) {
|
|
||||||
echo "Deleting stuck serviceapp: {$serviceApp->name}\n";
|
|
||||||
$serviceApp->forceDelete();
|
|
||||||
}
|
|
||||||
} catch (\Throwable $e) {
|
|
||||||
echo "Error in cleaning stuck serviceapp: {$e->getMessage()}\n";
|
|
||||||
}
|
|
||||||
try {
|
|
||||||
$serviceDbs = ServiceDatabase::withTrashed()->whereNotNull('deleted_at')->get();
|
|
||||||
foreach ($serviceDbs as $serviceDb) {
|
|
||||||
echo "Deleting stuck serviceapp: {$serviceDb->name}\n";
|
|
||||||
$serviceDb->forceDelete();
|
|
||||||
}
|
|
||||||
} catch (\Throwable $e) {
|
|
||||||
echo "Error in cleaning stuck serviceapp: {$e->getMessage()}\n";
|
|
||||||
}
|
|
||||||
|
|
||||||
// Cleanup any resources that are not attached to any environment or destination or server
|
|
||||||
try {
|
|
||||||
$applications = Application::all();
|
|
||||||
foreach ($applications as $application) {
|
|
||||||
if (!data_get($application, 'environment')) {
|
|
||||||
echo 'Application without environment: ' . $application->name . ' soft deleting\n';
|
|
||||||
$application->delete();
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
if (!$application->destination()) {
|
|
||||||
echo 'Application without destination: ' . $application->name . ' soft deleting\n';
|
|
||||||
$application->delete();
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
if (!data_get($application, 'destination.server')) {
|
|
||||||
echo 'Application without server: ' . $application->name . ' soft deleting\n';
|
|
||||||
$application->delete();
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} catch (\Throwable $e) {
|
|
||||||
echo "Error in application: {$e->getMessage()}\n";
|
|
||||||
}
|
|
||||||
try {
|
|
||||||
$postgresqls = StandalonePostgresql::all()->where('id', '!=', 0);
|
|
||||||
foreach ($postgresqls as $postgresql) {
|
|
||||||
if (!data_get($postgresql, 'environment')) {
|
|
||||||
echo 'Postgresql without environment: ' . $postgresql->name . ' soft deleting\n';
|
|
||||||
$postgresql->delete();
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
if (!$postgresql->destination()) {
|
|
||||||
echo 'Postgresql without destination: ' . $postgresql->name . ' soft deleting\n';
|
|
||||||
$postgresql->delete();
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
if (!data_get($postgresql, 'destination.server')) {
|
|
||||||
echo 'Postgresql without server: ' . $postgresql->name . ' soft deleting\n';
|
|
||||||
$postgresql->delete();
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} catch (\Throwable $e) {
|
|
||||||
echo "Error in postgresql: {$e->getMessage()}\n";
|
|
||||||
}
|
|
||||||
try {
|
|
||||||
$redis = StandaloneRedis::all();
|
|
||||||
foreach ($redis as $redis) {
|
|
||||||
if (!data_get($redis, 'environment')) {
|
|
||||||
echo 'Redis without environment: ' . $redis->name . ' soft deleting\n';
|
|
||||||
$redis->delete();
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
if (!$redis->destination()) {
|
|
||||||
echo 'Redis without destination: ' . $redis->name . ' soft deleting\n';
|
|
||||||
$redis->delete();
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
if (!data_get($redis, 'destination.server')) {
|
|
||||||
echo 'Redis without server: ' . $redis->name . ' soft deleting\n';
|
|
||||||
$redis->delete();
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} catch (\Throwable $e) {
|
|
||||||
echo "Error in redis: {$e->getMessage()}\n";
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
$mongodbs = StandaloneMongodb::all();
|
|
||||||
foreach ($mongodbs as $mongodb) {
|
|
||||||
if (!data_get($mongodb, 'environment')) {
|
|
||||||
echo 'Mongodb without environment: ' . $mongodb->name . ' soft deleting\n';
|
|
||||||
$mongodb->delete();
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
if (!$mongodb->destination()) {
|
|
||||||
echo 'Mongodb without destination: ' . $mongodb->name . ' soft deleting\n';
|
|
||||||
$mongodb->delete();
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
if (!data_get($mongodb, 'destination.server')) {
|
|
||||||
echo 'Mongodb without server: ' . $mongodb->name . ' soft deleting\n';
|
|
||||||
$mongodb->delete();
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} catch (\Throwable $e) {
|
|
||||||
echo "Error in mongodb: {$e->getMessage()}\n";
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
$mysqls = StandaloneMysql::all();
|
|
||||||
foreach ($mysqls as $mysql) {
|
|
||||||
if (!data_get($mysql, 'environment')) {
|
|
||||||
echo 'Mysql without environment: ' . $mysql->name . ' soft deleting\n';
|
|
||||||
$mysql->delete();
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
if (!$mysql->destination()) {
|
|
||||||
echo 'Mysql without destination: ' . $mysql->name . ' soft deleting\n';
|
|
||||||
$mysql->delete();
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
if (!data_get($mysql, 'destination.server')) {
|
|
||||||
echo 'Mysql without server: ' . $mysql->name . ' soft deleting\n';
|
|
||||||
$mysql->delete();
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} catch (\Throwable $e) {
|
|
||||||
echo "Error in mysql: {$e->getMessage()}\n";
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
$mariadbs = StandaloneMariadb::all();
|
|
||||||
foreach ($mariadbs as $mariadb) {
|
|
||||||
if (!data_get($mariadb, 'environment')) {
|
|
||||||
echo 'Mariadb without environment: ' . $mariadb->name . ' soft deleting\n';
|
|
||||||
$mariadb->delete();
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
if (!$mariadb->destination()) {
|
|
||||||
echo 'Mariadb without destination: ' . $mariadb->name . ' soft deleting\n';
|
|
||||||
$mariadb->delete();
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
if (!data_get($mariadb, 'destination.server')) {
|
|
||||||
echo 'Mariadb without server: ' . $mariadb->name . ' soft deleting\n';
|
|
||||||
$mariadb->delete();
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} catch (\Throwable $e) {
|
|
||||||
echo "Error in mariadb: {$e->getMessage()}\n";
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
$services = Service::all();
|
|
||||||
foreach ($services as $service) {
|
|
||||||
if (!data_get($service, 'environment')) {
|
|
||||||
echo 'Service without environment: ' . $service->name . ' soft deleting\n';
|
|
||||||
$service->delete();
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
if (!$service->destination()) {
|
|
||||||
echo 'Service without destination: ' . $service->name . ' soft deleting\n';
|
|
||||||
$service->delete();
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
if (!data_get($service, 'server')) {
|
|
||||||
echo 'Service without server: ' . $service->name . ' soft deleting\n';
|
|
||||||
$service->delete();
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} catch (\Throwable $e) {
|
|
||||||
echo "Error in service: {$e->getMessage()}\n";
|
|
||||||
}
|
|
||||||
try {
|
|
||||||
$serviceApplications = ServiceApplication::all();
|
|
||||||
foreach ($serviceApplications as $service) {
|
|
||||||
if (!data_get($service, 'service')) {
|
|
||||||
echo 'ServiceApplication without service: ' . $service->name . ' soft deleting\n';
|
|
||||||
$service->delete();
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} catch (\Throwable $e) {
|
|
||||||
echo "Error in serviceApplications: {$e->getMessage()}\n";
|
|
||||||
}
|
|
||||||
try {
|
|
||||||
$serviceDatabases = ServiceDatabase::all();
|
|
||||||
foreach ($serviceDatabases as $service) {
|
|
||||||
if (!data_get($service, 'service')) {
|
|
||||||
echo 'ServiceDatabase without service: ' . $service->name . ' soft deleting\n';
|
|
||||||
$service->delete();
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} catch (\Throwable $e) {
|
|
||||||
echo "Error in ServiceDatabases: {$e->getMessage()}\n";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -48,7 +48,7 @@ class SyncBunny extends Command
|
|||||||
|
|
||||||
$versions = "versions.json";
|
$versions = "versions.json";
|
||||||
|
|
||||||
PendingRequest::macro('storage', function ($fileName) use($that) {
|
PendingRequest::macro('storage', function ($fileName) use ($that) {
|
||||||
$headers = [
|
$headers = [
|
||||||
'AccessKey' => env('BUNNY_STORAGE_API_KEY'),
|
'AccessKey' => env('BUNNY_STORAGE_API_KEY'),
|
||||||
'Accept' => 'application/json',
|
'Accept' => 'application/json',
|
||||||
@@ -76,23 +76,26 @@ class SyncBunny extends Command
|
|||||||
}
|
}
|
||||||
if ($only_template) {
|
if ($only_template) {
|
||||||
$this->info('About to sync service-templates.json to BunnyCDN.');
|
$this->info('About to sync service-templates.json to BunnyCDN.');
|
||||||
}
|
$confirmed = confirm("Are you sure you want to sync?");
|
||||||
if ($only_version) {
|
if (!$confirmed) {
|
||||||
$this->info('About to sync versions.json to BunnyCDN.');
|
return;
|
||||||
}
|
}
|
||||||
$confirmed = confirm('Are you sure you want to sync?');
|
|
||||||
if (!$confirmed) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if ($only_template) {
|
|
||||||
Http::pool(fn (Pool $pool) => [
|
Http::pool(fn (Pool $pool) => [
|
||||||
$pool->storage(fileName: "$parent_dir/templates/$service_template")->put("/$bunny_cdn_storage_name/$bunny_cdn_path/$service_template"),
|
$pool->storage(fileName: "$parent_dir/templates/$service_template")->put("/$bunny_cdn_storage_name/$bunny_cdn_path/$service_template"),
|
||||||
$pool->purge("$bunny_cdn/$bunny_cdn_path/$service_template"),
|
$pool->purge("$bunny_cdn/$bunny_cdn_path/$service_template"),
|
||||||
]);
|
]);
|
||||||
$this->info('Service template uploaded & purged...');
|
$this->info('Service template uploaded & purged...');
|
||||||
return;
|
return;
|
||||||
}
|
} else if ($only_version) {
|
||||||
if ($only_version) {
|
$this->info('About to sync versions.json to BunnyCDN.');
|
||||||
|
$file = file_get_contents("$parent_dir/$versions");
|
||||||
|
$json = json_decode($file, true);
|
||||||
|
$actual_version = data_get($json, 'coolify.v4.version');
|
||||||
|
|
||||||
|
$confirmed = confirm("Are you sure you want to sync to {$actual_version}?");
|
||||||
|
if (!$confirmed) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
Http::pool(fn (Pool $pool) => [
|
Http::pool(fn (Pool $pool) => [
|
||||||
$pool->storage(fileName: "$parent_dir/$versions")->put("/$bunny_cdn_storage_name/$bunny_cdn_path/$versions"),
|
$pool->storage(fileName: "$parent_dir/$versions")->put("/$bunny_cdn_storage_name/$bunny_cdn_path/$versions"),
|
||||||
$pool->purge("$bunny_cdn/$bunny_cdn_path/$versions"),
|
$pool->purge("$bunny_cdn/$bunny_cdn_path/$versions"),
|
||||||
@@ -101,6 +104,7 @@ class SyncBunny extends Command
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
Http::pool(fn (Pool $pool) => [
|
Http::pool(fn (Pool $pool) => [
|
||||||
$pool->storage(fileName: "$parent_dir/$compose_file")->put("/$bunny_cdn_storage_name/$bunny_cdn_path/$compose_file"),
|
$pool->storage(fileName: "$parent_dir/$compose_file")->put("/$bunny_cdn_storage_name/$bunny_cdn_path/$compose_file"),
|
||||||
$pool->storage(fileName: "$parent_dir/$compose_file_prod")->put("/$bunny_cdn_storage_name/$bunny_cdn_path/$compose_file_prod"),
|
$pool->storage(fileName: "$parent_dir/$compose_file_prod")->put("/$bunny_cdn_storage_name/$bunny_cdn_path/$compose_file_prod"),
|
||||||
|
|||||||
@@ -36,6 +36,8 @@ class Kernel extends ConsoleKernel
|
|||||||
} else {
|
} else {
|
||||||
// Instance Jobs
|
// Instance Jobs
|
||||||
$schedule->command('horizon:snapshot')->everyFiveMinutes();
|
$schedule->command('horizon:snapshot')->everyFiveMinutes();
|
||||||
|
$schedule->command('cleanup:unreachable-servers')->daily();
|
||||||
|
|
||||||
$schedule->job(new CleanupInstanceStuffsJob)->everyTwoMinutes()->onOneServer();
|
$schedule->job(new CleanupInstanceStuffsJob)->everyTwoMinutes()->onOneServer();
|
||||||
// $schedule->job(new CheckResaleLicenseJob)->hourly()->onOneServer();
|
// $schedule->job(new CheckResaleLicenseJob)->hourly()->onOneServer();
|
||||||
|
|
||||||
|
|||||||
159
app/Http/Controllers/Api/Deploy.php
Normal file
159
app/Http/Controllers/Api/Deploy.php
Normal file
@@ -0,0 +1,159 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Http\Controllers\Api;
|
||||||
|
|
||||||
|
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\Tag;
|
||||||
|
use Illuminate\Http\Request;
|
||||||
|
use Illuminate\Support\Collection;
|
||||||
|
use Visus\Cuid2\Cuid2;
|
||||||
|
|
||||||
|
class Deploy extends Controller
|
||||||
|
{
|
||||||
|
public function deploy(Request $request)
|
||||||
|
{
|
||||||
|
$token = auth()->user()->currentAccessToken();
|
||||||
|
$teamId = data_get($token, 'team_id');
|
||||||
|
$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/deploy-webhook'], 400);
|
||||||
|
}
|
||||||
|
if (is_null($teamId)) {
|
||||||
|
return response()->json(['error' => 'Invalid token.', 'docs' => 'https://coolify.io/docs/api/authentication'], 400);
|
||||||
|
}
|
||||||
|
if ($tags) {
|
||||||
|
return $this->by_tags($tags, $teamId, $force);
|
||||||
|
} else if ($uuids) {
|
||||||
|
return $this->by_uuids($uuids, $teamId, $force);
|
||||||
|
}
|
||||||
|
return response()->json(['error' => 'You must provide uuid or tag.', 'docs' => 'https://coolify.io/docs/api/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/deploy-webhook'], 400);
|
||||||
|
}
|
||||||
|
$message = collect([]);
|
||||||
|
foreach ($uuids as $uuid) {
|
||||||
|
$resource = getResourceByUuid($uuid, $teamId);
|
||||||
|
if ($resource) {
|
||||||
|
$return_message = $this->deploy_resource($resource, $force);
|
||||||
|
$message = $message->merge($return_message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if ($message->count() > 0) {
|
||||||
|
return response()->json(['message' => $message->toArray()], 200);
|
||||||
|
}
|
||||||
|
return response()->json(['error' => "No resources found.", 'docs' => 'https://coolify.io/docs/api/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/deploy-webhook'], 400);
|
||||||
|
}
|
||||||
|
$message = 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();
|
||||||
|
$services = $found_tag->services();
|
||||||
|
if ($applications->count() === 0 && $services->count() === 0) {
|
||||||
|
$message->push("No resources found for tag {$tag}.");
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
foreach ($applications as $resource) {
|
||||||
|
$return_message = $this->deploy_resource($resource, $force);
|
||||||
|
$message = $message->merge($return_message);
|
||||||
|
}
|
||||||
|
foreach ($services as $resource) {
|
||||||
|
$return_message = $this->deploy_resource($resource, $force);
|
||||||
|
$message = $message->merge($return_message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if ($message->count() > 0) {
|
||||||
|
return response()->json(['message' => $message->toArray()], 200);
|
||||||
|
}
|
||||||
|
|
||||||
|
return response()->json(['error' => "No resources found.", 'docs' => 'https://coolify.io/docs/api/deploy-webhook'], 404);
|
||||||
|
}
|
||||||
|
public function deploy_resource($resource, bool $force = false): Collection
|
||||||
|
{
|
||||||
|
$message = collect([]);
|
||||||
|
$type = $resource->getMorphClass();
|
||||||
|
if ($type === 'App\Models\Application') {
|
||||||
|
queue_application_deployment(
|
||||||
|
application: $resource,
|
||||||
|
deployment_uuid: new Cuid2(7),
|
||||||
|
force_rebuild: $force,
|
||||||
|
);
|
||||||
|
$message->push("Application {$resource->name} deployment queued.");
|
||||||
|
} else if ($type === 'App\Models\StandalonePostgresql') {
|
||||||
|
if (str($resource->status)->startsWith('running')) {
|
||||||
|
$message->push("Database {$resource->name} already running.");
|
||||||
|
}
|
||||||
|
StartPostgresql::run($resource);
|
||||||
|
$resource->update([
|
||||||
|
'started_at' => now(),
|
||||||
|
]);
|
||||||
|
$message->push("Database {$resource->name} started.");
|
||||||
|
} else if ($type === 'App\Models\StandaloneRedis') {
|
||||||
|
if (str($resource->status)->startsWith('running')) {
|
||||||
|
$message->push("Database {$resource->name} already running.");
|
||||||
|
}
|
||||||
|
StartRedis::run($resource);
|
||||||
|
$resource->update([
|
||||||
|
'started_at' => now(),
|
||||||
|
]);
|
||||||
|
$message->push("Database {$resource->name} started.");
|
||||||
|
} else if ($type === 'App\Models\StandaloneMongodb') {
|
||||||
|
if (str($resource->status)->startsWith('running')) {
|
||||||
|
$message->push("Database {$resource->name} already running.");
|
||||||
|
}
|
||||||
|
StartMongodb::run($resource);
|
||||||
|
$resource->update([
|
||||||
|
'started_at' => now(),
|
||||||
|
]);
|
||||||
|
$message->push("Database {$resource->name} started.");
|
||||||
|
} else if ($type === 'App\Models\StandaloneMysql') {
|
||||||
|
if (str($resource->status)->startsWith('running')) {
|
||||||
|
$message->push("Database {$resource->name} already running.");
|
||||||
|
}
|
||||||
|
StartMysql::run($resource);
|
||||||
|
$resource->update([
|
||||||
|
'started_at' => now(),
|
||||||
|
]);
|
||||||
|
$message->push("Database {$resource->name} started.");
|
||||||
|
} else if ($type === 'App\Models\StandaloneMariadb') {
|
||||||
|
if (str($resource->status)->startsWith('running')) {
|
||||||
|
$message->push("Database {$resource->name} already running.");
|
||||||
|
}
|
||||||
|
StartMariadb::run($resource);
|
||||||
|
$resource->update([
|
||||||
|
'started_at' => now(),
|
||||||
|
]);
|
||||||
|
$message->push("Database {$resource->name} started.");
|
||||||
|
} else if ($type === 'App\Models\Service') {
|
||||||
|
StartService::run($resource);
|
||||||
|
$message->push("Service {$resource->name} started. It could take a while, be patient.");
|
||||||
|
}
|
||||||
|
return $message;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -122,7 +122,8 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
|
|||||||
if ($source) {
|
if ($source) {
|
||||||
$this->source = $source->getMorphClass()::where('id', $this->application->source->id)->first();
|
$this->source = $source->getMorphClass()::where('id', $this->application->source->id)->first();
|
||||||
}
|
}
|
||||||
$this->destination = $this->application->destination->getMorphClass()::where('id', $this->application->destination->id)->first();
|
$this->server = Server::find($this->application_deployment_queue->server_id);
|
||||||
|
$this->destination = $this->server->destinations()->where('id', $this->application_deployment_queue->destination_id)->first();
|
||||||
$this->server = $this->mainServer = $this->destination->server;
|
$this->server = $this->mainServer = $this->destination->server;
|
||||||
$this->serverUser = $this->server->user;
|
$this->serverUser = $this->server->user;
|
||||||
$this->basedir = $this->application->generateBaseDir($this->deployment_uuid);
|
$this->basedir = $this->application->generateBaseDir($this->deployment_uuid);
|
||||||
@@ -166,10 +167,6 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
|
|||||||
|
|
||||||
public function handle(): void
|
public function handle(): void
|
||||||
{
|
{
|
||||||
$this->application_deployment_queue->update([
|
|
||||||
'status' => ApplicationDeploymentStatus::IN_PROGRESS->value,
|
|
||||||
]);
|
|
||||||
|
|
||||||
// Generate custom host<->ip mapping
|
// Generate custom host<->ip mapping
|
||||||
$allContainers = instant_remote_process(["docker network inspect {$this->destination->network} -f '{{json .Containers}}' "], $this->server);
|
$allContainers = instant_remote_process(["docker network inspect {$this->destination->network} -f '{{json .Containers}}' "], $this->server);
|
||||||
if (!is_null($allContainers)) {
|
if (!is_null($allContainers)) {
|
||||||
@@ -348,9 +345,9 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
|
|||||||
],
|
],
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
$this->application_deployment_queue->addLogEntry("Image pushed to docker registry.'");
|
$this->application_deployment_queue->addLogEntry("Image pushed to docker registry.");
|
||||||
} catch (Exception $e) {
|
} catch (Exception $e) {
|
||||||
$this->application_deployment_queue->addLogEntry("Failed to push image to docker registry. Please check debug logs for more information.'");
|
$this->application_deployment_queue->addLogEntry("Failed to push image to docker registry. Please check debug logs for more information.");
|
||||||
if ($forceFail) {
|
if ($forceFail) {
|
||||||
throw $e;
|
throw $e;
|
||||||
}
|
}
|
||||||
@@ -488,10 +485,7 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
|
|||||||
} else {
|
} else {
|
||||||
$this->application_deployment_queue->addLogEntry("Starting pull request (#{$this->pull_request_id}) deployment of {$this->customRepository}:{$this->application->git_branch}.");
|
$this->application_deployment_queue->addLogEntry("Starting pull request (#{$this->pull_request_id}) deployment of {$this->customRepository}:{$this->application->git_branch}.");
|
||||||
}
|
}
|
||||||
$this->server->executeRemoteCommand(
|
$this->prepare_builder_image();
|
||||||
commands: $this->application->prepareHelperImage($this->deployment_uuid),
|
|
||||||
loggingModel: $this->application_deployment_queue
|
|
||||||
);
|
|
||||||
$this->check_git_if_build_needed();
|
$this->check_git_if_build_needed();
|
||||||
$this->clone_repository();
|
$this->clone_repository();
|
||||||
$this->generate_image_names();
|
$this->generate_image_names();
|
||||||
@@ -568,12 +562,7 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
|
|||||||
$this->generate_build_env_variables();
|
$this->generate_build_env_variables();
|
||||||
$this->add_build_env_variables_to_dockerfile();
|
$this->add_build_env_variables_to_dockerfile();
|
||||||
$this->build_image();
|
$this->build_image();
|
||||||
// if ($this->application->additional_destinations) {
|
|
||||||
// $this->push_to_docker_registry();
|
|
||||||
// $this->deploy_to_additional_destinations();
|
|
||||||
// } else {
|
|
||||||
$this->rolling_update();
|
$this->rolling_update();
|
||||||
// }
|
|
||||||
}
|
}
|
||||||
private function deploy_nixpacks_buildpack()
|
private function deploy_nixpacks_buildpack()
|
||||||
{
|
{
|
||||||
@@ -798,7 +787,18 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
|
|||||||
}
|
}
|
||||||
private function deploy_to_additional_destinations()
|
private function deploy_to_additional_destinations()
|
||||||
{
|
{
|
||||||
|
if (str($this->application->additional_destinations)->isEmpty()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
$destination_ids = collect(str($this->application->additional_destinations)->explode(','));
|
$destination_ids = collect(str($this->application->additional_destinations)->explode(','));
|
||||||
|
if ($this->server->isSwarm()) {
|
||||||
|
$this->application_deployment_queue->addLogEntry("Additional destinations are not supported in swarm mode.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if ($destination_ids->contains($this->destination->id)) {
|
||||||
|
ray('Same destination found in additional destinations. Skipping.');
|
||||||
|
return;
|
||||||
|
}
|
||||||
foreach ($destination_ids as $destination_id) {
|
foreach ($destination_ids as $destination_id) {
|
||||||
$destination = StandaloneDocker::find($destination_id);
|
$destination = StandaloneDocker::find($destination_id);
|
||||||
$server = $destination->server;
|
$server = $destination->server;
|
||||||
@@ -806,11 +806,21 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
|
|||||||
$this->application_deployment_queue->addLogEntry("Skipping deployment to {$server->name}. Not in the same team?!");
|
$this->application_deployment_queue->addLogEntry("Skipping deployment to {$server->name}. Not in the same team?!");
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
$this->server = $server;
|
// ray('Deploying to additional destination: ', $server->name);
|
||||||
$this->application_deployment_queue->addLogEntry("Deploying to {$this->server->name}.");
|
$deployment_uuid = new Cuid2();
|
||||||
$this->prepare_builder_image();
|
queue_application_deployment(
|
||||||
$this->generate_image_names();
|
deployment_uuid: $deployment_uuid,
|
||||||
$this->rolling_update();
|
application: $this->application,
|
||||||
|
server: $server,
|
||||||
|
destination: $destination,
|
||||||
|
no_questions_asked: true,
|
||||||
|
);
|
||||||
|
$this->application_deployment_queue->addLogEntry("Deploying to additional server: {$server->name}. Click here to see the deployment status: " . route('project.application.deployment.show', [
|
||||||
|
'project_uuid' => data_get($this->application, 'environment.project.uuid'),
|
||||||
|
'application_uuid' => data_get($this->application, 'uuid'),
|
||||||
|
'deployment_uuid' => $deployment_uuid,
|
||||||
|
'environment_name' => data_get($this->application, 'environment.name'),
|
||||||
|
]));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
private function set_base_dir()
|
private function set_base_dir()
|
||||||
@@ -1128,6 +1138,11 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
|
|||||||
|
|
||||||
data_forget($docker_compose, 'services.' . $this->container_name);
|
data_forget($docker_compose, 'services.' . $this->container_name);
|
||||||
|
|
||||||
|
$custom_compose = convert_docker_run_to_compose($this->application->custom_docker_run_options);
|
||||||
|
if (count($custom_compose) > 0) {
|
||||||
|
$docker_compose['services'][$this->application->uuid] = array_merge_recursive($docker_compose['services'][$this->application->uuid], $custom_compose);
|
||||||
|
}
|
||||||
|
|
||||||
$this->docker_compose = Yaml::dump($docker_compose, 10);
|
$this->docker_compose = Yaml::dump($docker_compose, 10);
|
||||||
$this->docker_compose_base64 = base64_encode($this->docker_compose);
|
$this->docker_compose_base64 = base64_encode($this->docker_compose);
|
||||||
$this->execute_remote_command([executeInDocker($this->deployment_uuid, "echo '{$this->docker_compose_base64}' | base64 -d > {$this->workdir}/docker-compose.yml"), "hidden" => true]);
|
$this->execute_remote_command([executeInDocker($this->deployment_uuid, "echo '{$this->docker_compose_base64}' | base64 -d > {$this->workdir}/docker-compose.yml"), "hidden" => true]);
|
||||||
@@ -1509,11 +1524,13 @@ COPY ./nginx.conf /etc/nginx/conf.d/default.conf");
|
|||||||
'status' => $status,
|
'status' => $status,
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
if ($status === ApplicationDeploymentStatus::FINISHED->value) {
|
|
||||||
$this->application->environment->project->team?->notify(new DeploymentSuccess($this->application, $this->deployment_uuid, $this->preview));
|
|
||||||
}
|
|
||||||
if ($status === ApplicationDeploymentStatus::FAILED->value) {
|
if ($status === ApplicationDeploymentStatus::FAILED->value) {
|
||||||
$this->application->environment->project->team?->notify(new DeploymentFailed($this->application, $this->deployment_uuid, $this->preview));
|
$this->application->environment->project->team?->notify(new DeploymentFailed($this->application, $this->deployment_uuid, $this->preview));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if ($status === ApplicationDeploymentStatus::FINISHED->value) {
|
||||||
|
// $this->deploy_to_additional_destinations();
|
||||||
|
$this->application->environment->project->team?->notify(new DeploymentSuccess($this->application, $this->deployment_uuid, $this->preview));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,180 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
namespace App\Jobs;
|
|
||||||
|
|
||||||
use App\Enums\ApplicationDeploymentStatus;
|
|
||||||
use App\Models\Application;
|
|
||||||
use App\Models\ApplicationDeploymentQueue;
|
|
||||||
use App\Models\Server;
|
|
||||||
use App\Traits\ExecuteRemoteCommand;
|
|
||||||
use Illuminate\Bus\Queueable;
|
|
||||||
use Illuminate\Contracts\Queue\ShouldBeEncrypted;
|
|
||||||
use Illuminate\Contracts\Queue\ShouldQueue;
|
|
||||||
use Illuminate\Foundation\Bus\Dispatchable;
|
|
||||||
use Illuminate\Queue\InteractsWithQueue;
|
|
||||||
use Illuminate\Queue\SerializesModels;
|
|
||||||
use RuntimeException;
|
|
||||||
use Throwable;
|
|
||||||
|
|
||||||
class ApplicationDeploymentNewJob implements ShouldQueue, ShouldBeEncrypted
|
|
||||||
{
|
|
||||||
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels, ExecuteRemoteCommand;
|
|
||||||
|
|
||||||
public $timeout = 3600;
|
|
||||||
public $tries = 1;
|
|
||||||
|
|
||||||
public static int $batch_counter = 0;
|
|
||||||
public Server $mainServer;
|
|
||||||
public $servers;
|
|
||||||
public string $basedir;
|
|
||||||
public string $workdir;
|
|
||||||
|
|
||||||
public string $deploymentUuid;
|
|
||||||
public int $pullRequestId = 0;
|
|
||||||
|
|
||||||
// Git related
|
|
||||||
public string $gitImportCommands;
|
|
||||||
public ?string $gitType = null;
|
|
||||||
public string $gitRepository;
|
|
||||||
public string $gitBranch;
|
|
||||||
public int $gitPort;
|
|
||||||
public string $gitFullRepoUrl;
|
|
||||||
|
|
||||||
public function __construct(public ApplicationDeploymentQueue $deployment, public Application $application)
|
|
||||||
{
|
|
||||||
$this->mainServer = data_get($this->application, 'destination.server');
|
|
||||||
$this->deploymentUuid = data_get($this->deployment, 'deployment_uuid');
|
|
||||||
$this->pullRequestId = data_get($this->deployment, 'pull_request_id', 0);
|
|
||||||
$this->gitType = data_get($this->deployment, 'git_type');
|
|
||||||
|
|
||||||
$this->basedir = $this->application->generateBaseDir($this->deploymentUuid);
|
|
||||||
$this->workdir = $this->basedir . rtrim($this->application->base_directory, '/');
|
|
||||||
}
|
|
||||||
public function handle()
|
|
||||||
{
|
|
||||||
try {
|
|
||||||
ray()->clearAll();
|
|
||||||
$this->deployment->setStatus(ApplicationDeploymentStatus::IN_PROGRESS->value);
|
|
||||||
|
|
||||||
$hostIpMappings = $this->mainServer->getHostIPMappings($this->application->destination->network);
|
|
||||||
if ($this->application->dockerfile_target_build) {
|
|
||||||
$buildTarget = " --target {$this->application->dockerfile_target_build} ";
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get the git repository and port (custom port or default port)
|
|
||||||
[
|
|
||||||
'repository' => $this->gitRepository,
|
|
||||||
'port' => $this->gitPort
|
|
||||||
] = $this->application->customRepository();
|
|
||||||
|
|
||||||
// Get the git branch and git import commands
|
|
||||||
[
|
|
||||||
'commands' => $this->gitImportCommands,
|
|
||||||
'branch' => $this->gitBranch,
|
|
||||||
'fullRepoUrl' => $this->gitFullRepoUrl
|
|
||||||
] = $this->application->generateGitImportCommands($this->deploymentUuid, $this->pullRequestId, $this->gitType);
|
|
||||||
|
|
||||||
$this->servers = $this->application->servers();
|
|
||||||
|
|
||||||
if ($this->deployment->restart_only) {
|
|
||||||
if ($this->application->build_pack === 'dockerimage') {
|
|
||||||
throw new \Exception('Restart only is not supported for docker image based deployments');
|
|
||||||
}
|
|
||||||
$this->deployment->addLogEntry("Starting deployment of {$this->application->name}.");
|
|
||||||
$this->servers->each(function ($server) {
|
|
||||||
$this->deployment->addLogEntry("Restarting {$this->application->name} on {$server->name}.");
|
|
||||||
$this->restartOnly($server);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
$this->next(ApplicationDeploymentStatus::FINISHED->value);
|
|
||||||
} catch (Throwable $exception) {
|
|
||||||
$this->fail($exception);
|
|
||||||
} finally {
|
|
||||||
$this->servers->each(function ($server) {
|
|
||||||
$this->deployment->addLogEntry("Cleaning up temporary containers on {$server->name}.");
|
|
||||||
$server->executeRemoteCommand(
|
|
||||||
commands: collect([])->push([
|
|
||||||
"command" => "docker rm -f {$this->deploymentUuid}",
|
|
||||||
"hidden" => true,
|
|
||||||
"ignoreErrors" => true,
|
|
||||||
]),
|
|
||||||
loggingModel: $this->deployment
|
|
||||||
);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
public function restartOnly(Server $server)
|
|
||||||
{
|
|
||||||
$server->executeRemoteCommand(
|
|
||||||
commands: $this->application->prepareHelperImage($this->deploymentUuid),
|
|
||||||
loggingModel: $this->deployment
|
|
||||||
);
|
|
||||||
|
|
||||||
$privateKey = data_get($this->application, 'private_key.private_key', null);
|
|
||||||
$gitLsRemoteCommand = collect([]);
|
|
||||||
if ($privateKey) {
|
|
||||||
$privateKey = base64_decode($privateKey);
|
|
||||||
$gitLsRemoteCommand
|
|
||||||
->push([
|
|
||||||
"command" => executeInDocker($this->deploymentUuid, "mkdir -p /root/.ssh")
|
|
||||||
])
|
|
||||||
->push([
|
|
||||||
"command" => executeInDocker($this->deploymentUuid, "echo '{$privateKey}' | base64 -d > /root/.ssh/id_rsa")
|
|
||||||
])
|
|
||||||
->push([
|
|
||||||
"command" => executeInDocker($this->deploymentUuid, "chmod 600 /root/.ssh/id_rsa")
|
|
||||||
])
|
|
||||||
->push([
|
|
||||||
"name" => "git_commit_sha",
|
|
||||||
"command" => executeInDocker($this->deploymentUuid, "GIT_SSH_COMMAND=\"ssh -o ConnectTimeout=30 -p {$this->gitPort} -o Port={$this->gitPort} -o LogLevel=ERROR -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -i /root/.ssh/id_rsa\" git ls-remote {$this->gitFullRepoUrl} {$this->gitBranch}"),
|
|
||||||
"hidden" => true,
|
|
||||||
]);
|
|
||||||
} else {
|
|
||||||
$gitLsRemoteCommand->push([
|
|
||||||
"name" => "git_commit_sha",
|
|
||||||
"command" => executeInDocker($this->deploymentUuid, "GIT_SSH_COMMAND=\"ssh -o ConnectTimeout=30 -p {$this->gitPort} -o Port={$this->gitPort} -o LogLevel=ERROR -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null\" git ls-remote {$this->gitFullRepoUrl} {$this->gitBranch}"),
|
|
||||||
"hidden" => true,
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
$this->deployment->addLogEntry("Checking if there is any new commit on {$this->gitBranch} branch.");
|
|
||||||
|
|
||||||
$server->executeRemoteCommand(
|
|
||||||
commands: $gitLsRemoteCommand,
|
|
||||||
loggingModel: $this->deployment
|
|
||||||
);
|
|
||||||
$commit = str($this->deployment->getOutput('git_commit_sha'))->before("\t");
|
|
||||||
|
|
||||||
[
|
|
||||||
'productionImageName' => $productionImageName
|
|
||||||
] = $this->application->generateImageNames($commit, $this->pullRequestId);
|
|
||||||
|
|
||||||
$this->deployment->addLogEntry("Checking if the image {$productionImageName} already exists.");
|
|
||||||
$server->checkIfDockerImageExists($productionImageName, $this->deployment);
|
|
||||||
|
|
||||||
if (str($this->deployment->getOutput('local_image_found'))->isNotEmpty()) {
|
|
||||||
$this->deployment->addLogEntry("Image {$productionImageName} already exists. Skipping the build.");
|
|
||||||
|
|
||||||
$server->createWorkDirForDeployment($this->workdir, $this->deployment);
|
|
||||||
|
|
||||||
$this->application->generateDockerComposeFile($server, $this->deployment, $this->workdir);
|
|
||||||
$this->application->rollingUpdateApplication($server, $this->deployment, $this->workdir);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
throw new RuntimeException('Cannot find image anywhere. Please redeploy the application.');
|
|
||||||
}
|
|
||||||
public function failed(Throwable $exception): void
|
|
||||||
{
|
|
||||||
ray($exception);
|
|
||||||
$this->next(ApplicationDeploymentStatus::FAILED->value);
|
|
||||||
}
|
|
||||||
private function next(string $status)
|
|
||||||
{
|
|
||||||
// If the deployment is cancelled by the user, don't update the status
|
|
||||||
if ($this->deployment->status !== ApplicationDeploymentStatus::CANCELLED_BY_USER->value) {
|
|
||||||
$this->deployment->update([
|
|
||||||
'status' => $status,
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
queue_next_deployment($this->application, isNew: true);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -5,6 +5,7 @@ namespace App\Jobs;
|
|||||||
use App\Actions\Application\StopApplication;
|
use App\Actions\Application\StopApplication;
|
||||||
use App\Actions\Database\StopDatabase;
|
use App\Actions\Database\StopDatabase;
|
||||||
use App\Actions\Service\DeleteService;
|
use App\Actions\Service\DeleteService;
|
||||||
|
use App\Actions\Service\StopService;
|
||||||
use App\Models\Application;
|
use App\Models\Application;
|
||||||
use App\Models\Service;
|
use App\Models\Service;
|
||||||
use App\Models\StandaloneMariadb;
|
use App\Models\StandaloneMariadb;
|
||||||
@@ -30,41 +31,22 @@ class DeleteResourceJob implements ShouldQueue, ShouldBeEncrypted
|
|||||||
public function handle()
|
public function handle()
|
||||||
{
|
{
|
||||||
try {
|
try {
|
||||||
$server = $this->resource->destination->server;
|
$this->resource->forceDelete();
|
||||||
$this->resource->delete();
|
|
||||||
if (!$server->isFunctional()) {
|
|
||||||
if ($this->resource->type() === 'service') {
|
|
||||||
ray('dispatching delete service');
|
|
||||||
DeleteService::dispatch($this->resource);
|
|
||||||
} else {
|
|
||||||
$this->resource->forceDelete();
|
|
||||||
}
|
|
||||||
return 'Server is not functional';
|
|
||||||
}
|
|
||||||
switch ($this->resource->type()) {
|
switch ($this->resource->type()) {
|
||||||
case 'application':
|
case 'application':
|
||||||
StopApplication::run($this->resource);
|
StopApplication::run($this->resource);
|
||||||
break;
|
break;
|
||||||
case 'standalone-postgresql':
|
case 'standalone-postgresql':
|
||||||
StopDatabase::run($this->resource);
|
|
||||||
break;
|
|
||||||
case 'standalone-redis':
|
case 'standalone-redis':
|
||||||
StopDatabase::run($this->resource);
|
|
||||||
break;
|
|
||||||
case 'standalone-mongodb':
|
case 'standalone-mongodb':
|
||||||
StopDatabase::run($this->resource);
|
|
||||||
break;
|
|
||||||
case 'standalone-mysql':
|
case 'standalone-mysql':
|
||||||
StopDatabase::run($this->resource);
|
|
||||||
break;
|
|
||||||
case 'standalone-mariadb':
|
case 'standalone-mariadb':
|
||||||
StopDatabase::run($this->resource);
|
StopDatabase::run($this->resource);
|
||||||
break;
|
break;
|
||||||
}
|
case 'service':
|
||||||
if ($this->resource->type() === 'service') {
|
StopService::run($this->resource);
|
||||||
DeleteService::dispatch($this->resource);
|
DeleteService::run($this->resource);
|
||||||
} else {
|
break;
|
||||||
$this->resource->forceDelete();
|
|
||||||
}
|
}
|
||||||
} catch (\Throwable $e) {
|
} catch (\Throwable $e) {
|
||||||
send_internal_notification('ContainerStoppingJob failed with: ' . $e->getMessage());
|
send_internal_notification('ContainerStoppingJob failed with: ' . $e->getMessage());
|
||||||
|
|||||||
@@ -39,7 +39,7 @@ class ScheduledTaskJob implements ShouldQueue
|
|||||||
} else if ($application = $task->application()->first()) {
|
} else if ($application = $task->application()->first()) {
|
||||||
$this->resource = $application;
|
$this->resource = $application;
|
||||||
} else {
|
} else {
|
||||||
throw new \Exception('ScheduledTaskJob failed: No resource found.');
|
throw new \RuntimeException('ScheduledTaskJob failed: No resource found.');
|
||||||
}
|
}
|
||||||
$this->team = Team::find($task->team_id);
|
$this->team = Team::find($task->team_id);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ class ActivityMonitor extends Component
|
|||||||
public $isPollingActive = false;
|
public $isPollingActive = false;
|
||||||
|
|
||||||
protected $activity;
|
protected $activity;
|
||||||
protected $listeners = ['newMonitorActivity'];
|
protected $listeners = ['activityMonitor' => 'newMonitorActivity'];
|
||||||
|
|
||||||
public function newMonitorActivity($activityId, $eventToDispatch = 'activityFinished')
|
public function newMonitorActivity($activityId, $eventToDispatch = 'activityFinished')
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ use Livewire\Component;
|
|||||||
|
|
||||||
class Index extends Component
|
class Index extends Component
|
||||||
{
|
{
|
||||||
|
protected $listeners = ['serverInstalled' => 'validateServer'];
|
||||||
public string $currentState = 'welcome';
|
public string $currentState = 'welcome';
|
||||||
|
|
||||||
public ?string $selectedServerType = null;
|
public ?string $selectedServerType = null;
|
||||||
@@ -93,7 +94,11 @@ uZx9iFkCELtxrh31QJ68AAAAEXNhaWxANzZmZjY2ZDJlMmRkAQIDBA==
|
|||||||
$this->serverPublicKey = $this->createdServer->privateKey->publicKey();
|
$this->serverPublicKey = $this->createdServer->privateKey->publicKey();
|
||||||
return $this->validateServer('localhost');
|
return $this->validateServer('localhost');
|
||||||
} elseif ($this->selectedServerType === 'remote') {
|
} elseif ($this->selectedServerType === 'remote') {
|
||||||
$this->privateKeys = PrivateKey::ownedByCurrentTeam(['name'])->where('id', '!=', 0)->get();
|
if (isDev()) {
|
||||||
|
$this->privateKeys = PrivateKey::ownedByCurrentTeam(['name'])->get();
|
||||||
|
} else {
|
||||||
|
$this->privateKeys = PrivateKey::ownedByCurrentTeam(['name'])->where('id', '!=', 0)->get();
|
||||||
|
}
|
||||||
if ($this->privateKeys->count() > 0) {
|
if ($this->privateKeys->count() > 0) {
|
||||||
$this->selectedExistingPrivateKey = $this->privateKeys->first()->id;
|
$this->selectedExistingPrivateKey = $this->privateKeys->first()->id;
|
||||||
}
|
}
|
||||||
@@ -190,6 +195,10 @@ uZx9iFkCELtxrh31QJ68AAAAEXNhaWxANzZmZjY2ZDJlMmRkAQIDBA==
|
|||||||
$this->createdServer->addInitialNetwork();
|
$this->createdServer->addInitialNetwork();
|
||||||
$this->validateServer();
|
$this->validateServer();
|
||||||
}
|
}
|
||||||
|
public function installServer()
|
||||||
|
{
|
||||||
|
$this->dispatch('validateServer', true);
|
||||||
|
}
|
||||||
public function validateServer()
|
public function validateServer()
|
||||||
{
|
{
|
||||||
try {
|
try {
|
||||||
@@ -228,7 +237,7 @@ uZx9iFkCELtxrh31QJ68AAAAEXNhaWxANzZmZjY2ZDJlMmRkAQIDBA==
|
|||||||
$this->dockerInstallationStarted = true;
|
$this->dockerInstallationStarted = true;
|
||||||
$activity = InstallDocker::run($this->createdServer);
|
$activity = InstallDocker::run($this->createdServer);
|
||||||
$this->dispatch('installDocker');
|
$this->dispatch('installDocker');
|
||||||
$this->dispatch('newMonitorActivity', $activity->id);
|
$this->dispatch('activityMonitor', $activity->id);
|
||||||
} catch (\Throwable $e) {
|
} catch (\Throwable $e) {
|
||||||
$this->dockerInstallationStarted = false;
|
$this->dockerInstallationStarted = false;
|
||||||
return handleError(error: $e, livewire: $this);
|
return handleError(error: $e, livewire: $this);
|
||||||
|
|||||||
63
app/Livewire/NewActivityMonitor.php
Normal file
63
app/Livewire/NewActivityMonitor.php
Normal file
@@ -0,0 +1,63 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Livewire;
|
||||||
|
|
||||||
|
use App\Models\User;
|
||||||
|
use Livewire\Component;
|
||||||
|
use Spatie\Activitylog\Models\Activity;
|
||||||
|
|
||||||
|
class NewActivityMonitor extends Component
|
||||||
|
{
|
||||||
|
public ?string $header = null;
|
||||||
|
public $activityId;
|
||||||
|
public $eventToDispatch = 'activityFinished';
|
||||||
|
public $isPollingActive = false;
|
||||||
|
|
||||||
|
protected $activity;
|
||||||
|
protected $listeners = ['newActivityMonitor' => 'newMonitorActivity'];
|
||||||
|
|
||||||
|
public function newMonitorActivity($activityId, $eventToDispatch = 'activityFinished')
|
||||||
|
{
|
||||||
|
$this->activityId = $activityId;
|
||||||
|
$this->eventToDispatch = $eventToDispatch;
|
||||||
|
|
||||||
|
$this->hydrateActivity();
|
||||||
|
|
||||||
|
$this->isPollingActive = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function hydrateActivity()
|
||||||
|
{
|
||||||
|
$this->activity = Activity::find($this->activityId);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function polling()
|
||||||
|
{
|
||||||
|
$this->hydrateActivity();
|
||||||
|
// $this->setStatus(ProcessStatus::IN_PROGRESS);
|
||||||
|
$exit_code = data_get($this->activity, 'properties.exitCode');
|
||||||
|
if ($exit_code !== null) {
|
||||||
|
// if ($exit_code === 0) {
|
||||||
|
// // $this->setStatus(ProcessStatus::FINISHED);
|
||||||
|
// } else {
|
||||||
|
// // $this->setStatus(ProcessStatus::ERROR);
|
||||||
|
// }
|
||||||
|
$this->isPollingActive = false;
|
||||||
|
if ($this->eventToDispatch !== null) {
|
||||||
|
if (str($this->eventToDispatch)->startsWith('App\\Events\\')) {
|
||||||
|
$causer_id = data_get($this->activity, 'causer_id');
|
||||||
|
$user = User::find($causer_id);
|
||||||
|
if ($user) {
|
||||||
|
foreach ($user->teams as $team) {
|
||||||
|
$teamId = $team->id;
|
||||||
|
$this->eventToDispatch::dispatch($teamId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
$this->dispatch($this->eventToDispatch);
|
||||||
|
ray('Dispatched event: ' . $this->eventToDispatch);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -7,8 +7,6 @@ use App\Models\Application;
|
|||||||
use App\Models\ApplicationDeploymentQueue;
|
use App\Models\ApplicationDeploymentQueue;
|
||||||
use App\Models\Server;
|
use App\Models\Server;
|
||||||
use Illuminate\Support\Carbon;
|
use Illuminate\Support\Carbon;
|
||||||
use Illuminate\Support\Facades\Process;
|
|
||||||
use Illuminate\Support\Str;
|
|
||||||
use Livewire\Component;
|
use Livewire\Component;
|
||||||
|
|
||||||
class DeploymentNavbar extends Component
|
class DeploymentNavbar extends Component
|
||||||
@@ -37,7 +35,15 @@ class DeploymentNavbar extends Component
|
|||||||
$this->is_debug_enabled = $this->application->settings->is_debug_enabled;
|
$this->is_debug_enabled = $this->application->settings->is_debug_enabled;
|
||||||
$this->dispatch('refreshQueue');
|
$this->dispatch('refreshQueue');
|
||||||
}
|
}
|
||||||
|
public function force_start()
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
force_start_deployment($this->application_deployment_queue);
|
||||||
|
} catch (\Throwable $e) {
|
||||||
|
ray($e);
|
||||||
|
return handleError($e, $this);
|
||||||
|
}
|
||||||
|
}
|
||||||
public function cancel()
|
public function cancel()
|
||||||
{
|
{
|
||||||
try {
|
try {
|
||||||
@@ -67,7 +73,6 @@ class DeploymentNavbar extends Component
|
|||||||
'current_process_id' => null,
|
'current_process_id' => null,
|
||||||
'status' => ApplicationDeploymentStatus::CANCELLED_BY_USER->value,
|
'status' => ApplicationDeploymentStatus::CANCELLED_BY_USER->value,
|
||||||
]);
|
]);
|
||||||
// queue_next_deployment($this->application);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -61,11 +61,12 @@ class General extends Component
|
|||||||
'application.docker_compose_pr' => 'nullable',
|
'application.docker_compose_pr' => 'nullable',
|
||||||
'application.docker_compose_raw' => 'nullable',
|
'application.docker_compose_raw' => 'nullable',
|
||||||
'application.docker_compose_pr_raw' => 'nullable',
|
'application.docker_compose_pr_raw' => 'nullable',
|
||||||
'application.custom_labels' => 'nullable',
|
|
||||||
'application.dockerfile_target_build' => 'nullable',
|
'application.dockerfile_target_build' => 'nullable',
|
||||||
'application.settings.is_static' => 'boolean|required',
|
|
||||||
'application.docker_compose_custom_start_command' => 'nullable',
|
'application.docker_compose_custom_start_command' => 'nullable',
|
||||||
'application.docker_compose_custom_build_command' => 'nullable',
|
'application.docker_compose_custom_build_command' => 'nullable',
|
||||||
|
'application.custom_labels' => 'nullable',
|
||||||
|
'application.custom_docker_run_options' => 'nullable',
|
||||||
|
'application.settings.is_static' => 'boolean|required',
|
||||||
'application.settings.is_raw_compose_deployment_enabled' => 'boolean|required',
|
'application.settings.is_raw_compose_deployment_enabled' => 'boolean|required',
|
||||||
'application.settings.is_build_server_enabled' => 'boolean|required',
|
'application.settings.is_build_server_enabled' => 'boolean|required',
|
||||||
];
|
];
|
||||||
@@ -97,9 +98,10 @@ class General extends Component
|
|||||||
'application.docker_compose_pr_raw' => 'Docker compose raw',
|
'application.docker_compose_pr_raw' => 'Docker compose raw',
|
||||||
'application.custom_labels' => 'Custom labels',
|
'application.custom_labels' => 'Custom labels',
|
||||||
'application.dockerfile_target_build' => 'Dockerfile target build',
|
'application.dockerfile_target_build' => 'Dockerfile target build',
|
||||||
'application.settings.is_static' => 'Is static',
|
'application.custom_docker_run_options' => 'Custom docker run commands',
|
||||||
'application.docker_compose_custom_start_command' => 'Docker compose custom start command',
|
'application.docker_compose_custom_start_command' => 'Docker compose custom start command',
|
||||||
'application.docker_compose_custom_build_command' => 'Docker compose custom build command',
|
'application.docker_compose_custom_build_command' => 'Docker compose custom build command',
|
||||||
|
'application.settings.is_static' => 'Is static',
|
||||||
'application.settings.is_raw_compose_deployment_enabled' => 'Is raw compose deployment enabled',
|
'application.settings.is_raw_compose_deployment_enabled' => 'Is raw compose deployment enabled',
|
||||||
'application.settings.is_build_server_enabled' => 'Is build server enabled',
|
'application.settings.is_build_server_enabled' => 'Is build server enabled',
|
||||||
];
|
];
|
||||||
@@ -243,12 +245,16 @@ class General extends Component
|
|||||||
$domains = $domains->unique();
|
$domains = $domains->unique();
|
||||||
foreach ($domains as $domain) {
|
foreach ($domains as $domain) {
|
||||||
if (!validate_dns_entry($domain, $this->application->destination->server)) {
|
if (!validate_dns_entry($domain, $this->application->destination->server)) {
|
||||||
$showToaster && $this->dispatch('error', "Validating DNS settings for: $domain failed.<br>Make sure you have added the DNS records correctly.<br><br>Check this <a target='_blank' class='underline' href='https://coolify.io/docs/dns-settings'>documentation</a> for further help.");
|
$showToaster && $this->dispatch('error', "Validating DNS ($domain) failed.","Make sure you have added the DNS records correctly.<br><br>Check this <a target='_blank' class='text-white underline' href='https://coolify.io/docs/dns-settings'>documentation</a> for further help.");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
check_fqdn_usage($this->application);
|
||||||
$this->application->fqdn = $domains->implode(',');
|
$this->application->fqdn = $domains->implode(',');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (data_get($this->application, 'custom_docker_run_options')) {
|
||||||
|
$this->application->custom_docker_run_options = str($this->application->custom_docker_run_options)->trim();
|
||||||
|
}
|
||||||
if (data_get($this->application, 'dockerfile')) {
|
if (data_get($this->application, 'dockerfile')) {
|
||||||
$port = get_port_from_dockerfile($this->application->dockerfile);
|
$port = get_port_from_dockerfile($this->application->dockerfile);
|
||||||
if ($port && !$this->application->ports_exposes) {
|
if ($port && !$this->application->ports_exposes) {
|
||||||
|
|||||||
@@ -46,26 +46,6 @@ class Heading extends Component
|
|||||||
$this->deploy(force_rebuild: true);
|
$this->deploy(force_rebuild: true);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function deployNew()
|
|
||||||
{
|
|
||||||
if ($this->application->build_pack === 'dockercompose' && is_null($this->application->docker_compose_raw)) {
|
|
||||||
$this->dispatch('error', 'Please load a Compose file first.');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
$this->setDeploymentUuid();
|
|
||||||
queue_application_deployment(
|
|
||||||
application: $this->application,
|
|
||||||
deployment_uuid: $this->deploymentUuid,
|
|
||||||
force_rebuild: false,
|
|
||||||
is_new_deployment: true,
|
|
||||||
);
|
|
||||||
return redirect()->route('project.application.deployment.show', [
|
|
||||||
'project_uuid' => $this->parameters['project_uuid'],
|
|
||||||
'application_uuid' => $this->parameters['application_uuid'],
|
|
||||||
'deployment_uuid' => $this->deploymentUuid,
|
|
||||||
'environment_name' => $this->parameters['environment_name'],
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
public function deploy(bool $force_rebuild = false)
|
public function deploy(bool $force_rebuild = false)
|
||||||
{
|
{
|
||||||
if ($this->application->build_pack === 'dockercompose' && is_null($this->application->docker_compose_raw)) {
|
if ($this->application->build_pack === 'dockercompose' && is_null($this->application->docker_compose_raw)) {
|
||||||
|
|||||||
@@ -22,12 +22,12 @@ class CloneMe extends Component
|
|||||||
public ?int $selectedDestination = null;
|
public ?int $selectedDestination = null;
|
||||||
public ?Server $server = null;
|
public ?Server $server = null;
|
||||||
public $resources = [];
|
public $resources = [];
|
||||||
public string $newProjectName = '';
|
public string $newName = '';
|
||||||
|
|
||||||
protected $messages = [
|
protected $messages = [
|
||||||
'selectedServer' => 'Please select a server.',
|
'selectedServer' => 'Please select a server.',
|
||||||
'selectedDestination' => 'Please select a server & destination.',
|
'selectedDestination' => 'Please select a server & destination.',
|
||||||
'newProjectName' => 'Please enter a name for the new project.',
|
'newName' => 'Please enter a name for the new project or environment.',
|
||||||
];
|
];
|
||||||
public function mount($project_uuid)
|
public function mount($project_uuid)
|
||||||
{
|
{
|
||||||
@@ -36,7 +36,7 @@ class CloneMe extends Component
|
|||||||
$this->environment = $this->project->environments->where('name', $this->environment_name)->first();
|
$this->environment = $this->project->environments->where('name', $this->environment_name)->first();
|
||||||
$this->project_id = $this->project->id;
|
$this->project_id = $this->project->id;
|
||||||
$this->servers = currentTeam()->servers;
|
$this->servers = currentTeam()->servers;
|
||||||
$this->newProjectName = str($this->project->name . '-clone-' . (string)new Cuid2(7))->slug();
|
$this->newName = str($this->project->name . '-clone-' . (string)new Cuid2(7))->slug();
|
||||||
}
|
}
|
||||||
|
|
||||||
public function render()
|
public function render()
|
||||||
@@ -46,34 +46,50 @@ class CloneMe extends Component
|
|||||||
|
|
||||||
public function selectServer($server_id, $destination_id)
|
public function selectServer($server_id, $destination_id)
|
||||||
{
|
{
|
||||||
|
if ($server_id == $this->selectedServer && $destination_id == $this->selectedDestination) {
|
||||||
|
$this->selectedServer = null;
|
||||||
|
$this->selectedDestination = null;
|
||||||
|
$this->server = null;
|
||||||
|
return;
|
||||||
|
}
|
||||||
$this->selectedServer = $server_id;
|
$this->selectedServer = $server_id;
|
||||||
$this->selectedDestination = $destination_id;
|
$this->selectedDestination = $destination_id;
|
||||||
$this->server = $this->servers->where('id', $server_id)->first();
|
$this->server = $this->servers->where('id', $server_id)->first();
|
||||||
}
|
}
|
||||||
|
|
||||||
public function clone()
|
public function clone(string $type)
|
||||||
{
|
{
|
||||||
try {
|
try {
|
||||||
$this->validate([
|
$this->validate([
|
||||||
'selectedDestination' => 'required',
|
'selectedDestination' => 'required',
|
||||||
'newProjectName' => 'required',
|
'newName' => 'required',
|
||||||
]);
|
]);
|
||||||
$foundProject = Project::where('name', $this->newProjectName)->first();
|
if ($type === 'project') {
|
||||||
if ($foundProject) {
|
$foundProject = Project::where('name', $this->newName)->first();
|
||||||
throw new \Exception('Project with the same name already exists.');
|
if ($foundProject) {
|
||||||
}
|
throw new \Exception('Project with the same name already exists.');
|
||||||
$newProject = Project::create([
|
}
|
||||||
'name' => $this->newProjectName,
|
$project = Project::create([
|
||||||
'team_id' => currentTeam()->id,
|
'name' => $this->newName,
|
||||||
'description' => $this->project->description . ' (clone)',
|
'team_id' => currentTeam()->id,
|
||||||
]);
|
'description' => $this->project->description . ' (clone)',
|
||||||
if ($this->environment->name !== 'production') {
|
]);
|
||||||
$newProject->environments()->create([
|
if ($this->environment->name !== 'production') {
|
||||||
'name' => $this->environment->name,
|
$project->environments()->create([
|
||||||
|
'name' => $this->environment->name,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
$environment = $project->environments->where('name', $this->environment->name)->first();
|
||||||
|
} else {
|
||||||
|
$foundEnv = $this->project->environments()->where('name', $this->newName)->first();
|
||||||
|
if ($foundEnv) {
|
||||||
|
throw new \Exception('Environment with the same name already exists.');
|
||||||
|
}
|
||||||
|
$project = $this->project;
|
||||||
|
$environment = $this->project->environments()->create([
|
||||||
|
'name' => $this->newName,
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
$newEnvironment = $newProject->environments->where('name', $this->environment->name)->first();
|
|
||||||
// Clone Applications
|
|
||||||
$applications = $this->environment->applications;
|
$applications = $this->environment->applications;
|
||||||
$databases = $this->environment->databases();
|
$databases = $this->environment->databases();
|
||||||
$services = $this->environment->services;
|
$services = $this->environment->services;
|
||||||
@@ -83,7 +99,7 @@ class CloneMe extends Component
|
|||||||
'uuid' => $uuid,
|
'uuid' => $uuid,
|
||||||
'fqdn' => generateFqdn($this->server, $uuid),
|
'fqdn' => generateFqdn($this->server, $uuid),
|
||||||
'status' => 'exited',
|
'status' => 'exited',
|
||||||
'environment_id' => $newEnvironment->id,
|
'environment_id' => $environment->id,
|
||||||
// This is not correct, but we need to set it to something
|
// This is not correct, but we need to set it to something
|
||||||
'destination_id' => $this->selectedDestination,
|
'destination_id' => $this->selectedDestination,
|
||||||
]);
|
]);
|
||||||
@@ -110,7 +126,7 @@ class CloneMe extends Component
|
|||||||
'uuid' => $uuid,
|
'uuid' => $uuid,
|
||||||
'status' => 'exited',
|
'status' => 'exited',
|
||||||
'started_at' => null,
|
'started_at' => null,
|
||||||
'environment_id' => $newEnvironment->id,
|
'environment_id' => $environment->id,
|
||||||
'destination_id' => $this->selectedDestination,
|
'destination_id' => $this->selectedDestination,
|
||||||
]);
|
]);
|
||||||
$newDatabase->save();
|
$newDatabase->save();
|
||||||
@@ -136,7 +152,7 @@ class CloneMe extends Component
|
|||||||
$uuid = (string)new Cuid2(7);
|
$uuid = (string)new Cuid2(7);
|
||||||
$newService = $service->replicate()->fill([
|
$newService = $service->replicate()->fill([
|
||||||
'uuid' => $uuid,
|
'uuid' => $uuid,
|
||||||
'environment_id' => $newEnvironment->id,
|
'environment_id' => $environment->id,
|
||||||
'destination_id' => $this->selectedDestination,
|
'destination_id' => $this->selectedDestination,
|
||||||
]);
|
]);
|
||||||
$newService->save();
|
$newService->save();
|
||||||
@@ -153,8 +169,8 @@ class CloneMe extends Component
|
|||||||
$newService->parse();
|
$newService->parse();
|
||||||
}
|
}
|
||||||
return redirect()->route('project.resource.index', [
|
return redirect()->route('project.resource.index', [
|
||||||
'project_uuid' => $newProject->uuid,
|
'project_uuid' => $project->uuid,
|
||||||
'environment_name' => $newEnvironment->name,
|
'environment_name' => $environment->name,
|
||||||
]);
|
]);
|
||||||
} catch (\Exception $e) {
|
} catch (\Exception $e) {
|
||||||
return handleError($e, $this);
|
return handleError($e, $this);
|
||||||
|
|||||||
@@ -58,19 +58,19 @@ class Heading extends Component
|
|||||||
{
|
{
|
||||||
if ($this->database->type() === 'standalone-postgresql') {
|
if ($this->database->type() === 'standalone-postgresql') {
|
||||||
$activity = StartPostgresql::run($this->database);
|
$activity = StartPostgresql::run($this->database);
|
||||||
$this->dispatch('newMonitorActivity', $activity->id);
|
$this->dispatch('activityMonitor', $activity->id);
|
||||||
} else if ($this->database->type() === 'standalone-redis') {
|
} else if ($this->database->type() === 'standalone-redis') {
|
||||||
$activity = StartRedis::run($this->database);
|
$activity = StartRedis::run($this->database);
|
||||||
$this->dispatch('newMonitorActivity', $activity->id);
|
$this->dispatch('activityMonitor', $activity->id);
|
||||||
} else if ($this->database->type() === 'standalone-mongodb') {
|
} else if ($this->database->type() === 'standalone-mongodb') {
|
||||||
$activity = StartMongodb::run($this->database);
|
$activity = StartMongodb::run($this->database);
|
||||||
$this->dispatch('newMonitorActivity', $activity->id);
|
$this->dispatch('activityMonitor', $activity->id);
|
||||||
} else if ($this->database->type() === 'standalone-mysql') {
|
} else if ($this->database->type() === 'standalone-mysql') {
|
||||||
$activity = StartMysql::run($this->database);
|
$activity = StartMysql::run($this->database);
|
||||||
$this->dispatch('newMonitorActivity', $activity->id);
|
$this->dispatch('activityMonitor', $activity->id);
|
||||||
} else if ($this->database->type() === 'standalone-mariadb') {
|
} else if ($this->database->type() === 'standalone-mariadb') {
|
||||||
$activity = StartMariadb::run($this->database);
|
$activity = StartMariadb::run($this->database);
|
||||||
$this->dispatch('newMonitorActivity', $activity->id);
|
$this->dispatch('activityMonitor', $activity->id);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -129,7 +129,7 @@ class Import extends Component
|
|||||||
|
|
||||||
if (!empty($this->importCommands)) {
|
if (!empty($this->importCommands)) {
|
||||||
$activity = remote_process($this->importCommands, $this->server, ignore_errors: true);
|
$activity = remote_process($this->importCommands, $this->server, ignore_errors: true);
|
||||||
$this->dispatch('newMonitorActivity', $activity->id);
|
$this->dispatch('activityMonitor', $activity->id);
|
||||||
}
|
}
|
||||||
} catch (\Throwable $e) {
|
} catch (\Throwable $e) {
|
||||||
$this->validated = false;
|
$this->validated = false;
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ class DeleteEnvironment extends Component
|
|||||||
{
|
{
|
||||||
public array $parameters;
|
public array $parameters;
|
||||||
public int $environment_id;
|
public int $environment_id;
|
||||||
|
public bool $disabled = false;
|
||||||
|
|
||||||
public function mount()
|
public function mount()
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ class DeleteProject extends Component
|
|||||||
{
|
{
|
||||||
public array $parameters;
|
public array $parameters;
|
||||||
public int $project_id;
|
public int $project_id;
|
||||||
|
public bool $disabled = false;
|
||||||
|
|
||||||
public function mount()
|
public function mount()
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ use App\Models\PrivateKey;
|
|||||||
use App\Models\Project;
|
use App\Models\Project;
|
||||||
use App\Models\StandaloneDocker;
|
use App\Models\StandaloneDocker;
|
||||||
use App\Models\SwarmDocker;
|
use App\Models\SwarmDocker;
|
||||||
|
use Illuminate\Support\Collection;
|
||||||
use Livewire\Component;
|
use Livewire\Component;
|
||||||
use Spatie\Url\Url;
|
use Spatie\Url\Url;
|
||||||
use Illuminate\Support\Str;
|
use Illuminate\Support\Str;
|
||||||
@@ -18,7 +19,7 @@ class GithubPrivateRepositoryDeployKey extends Component
|
|||||||
public $current_step = 'private_keys';
|
public $current_step = 'private_keys';
|
||||||
public $parameters;
|
public $parameters;
|
||||||
public $query;
|
public $query;
|
||||||
public $private_keys;
|
public $private_keys =[];
|
||||||
public int $private_key_id;
|
public int $private_key_id;
|
||||||
|
|
||||||
public int $port = 3000;
|
public int $port = 3000;
|
||||||
@@ -33,6 +34,11 @@ class GithubPrivateRepositoryDeployKey extends Component
|
|||||||
public $build_pack = 'nixpacks';
|
public $build_pack = 'nixpacks';
|
||||||
public bool $show_is_static = true;
|
public bool $show_is_static = true;
|
||||||
|
|
||||||
|
private object $repository_url_parsed;
|
||||||
|
private GithubApp|GitlabApp|string $git_source = 'other';
|
||||||
|
private ?string $git_host = null;
|
||||||
|
private string $git_repository;
|
||||||
|
|
||||||
protected $rules = [
|
protected $rules = [
|
||||||
'repository_url' => 'required',
|
'repository_url' => 'required',
|
||||||
'branch' => 'required|string',
|
'branch' => 'required|string',
|
||||||
@@ -49,10 +55,7 @@ class GithubPrivateRepositoryDeployKey extends Component
|
|||||||
'publish_directory' => 'Publish directory',
|
'publish_directory' => 'Publish directory',
|
||||||
'build_pack' => 'Build pack',
|
'build_pack' => 'Build pack',
|
||||||
];
|
];
|
||||||
private object $repository_url_parsed;
|
|
||||||
private GithubApp|GitlabApp|string $git_source = 'other';
|
|
||||||
private ?string $git_host = null;
|
|
||||||
private string $git_repository;
|
|
||||||
|
|
||||||
public function mount()
|
public function mount()
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -29,7 +29,7 @@ class Index extends Component
|
|||||||
}
|
}
|
||||||
$this->project = $project;
|
$this->project = $project;
|
||||||
$this->environment = $environment;
|
$this->environment = $environment;
|
||||||
$this->applications = $environment->applications->sortBy('name');
|
$this->applications = $environment->applications->load(['tags']);
|
||||||
$this->applications = $this->applications->map(function ($application) {
|
$this->applications = $this->applications->map(function ($application) {
|
||||||
if (data_get($application, 'environment.project.uuid')) {
|
if (data_get($application, 'environment.project.uuid')) {
|
||||||
$application->hrefLink = route('project.application.configuration', [
|
$application->hrefLink = route('project.application.configuration', [
|
||||||
@@ -40,8 +40,9 @@ class Index extends Component
|
|||||||
}
|
}
|
||||||
return $application;
|
return $application;
|
||||||
});
|
});
|
||||||
$this->postgresqls = $environment->postgresqls->sortBy('name');
|
ray($this->applications);
|
||||||
$this->postgresqls = $this->postgresqls->map(function ($postgresql) {
|
$this->postgresqls = $environment->postgresqls->load(['tags'])->sortBy('name');
|
||||||
|
$this->postgresqls = $this->postgresqls->map(function ($postgresql) {
|
||||||
if (data_get($postgresql, 'environment.project.uuid')) {
|
if (data_get($postgresql, 'environment.project.uuid')) {
|
||||||
$postgresql->hrefLink = route('project.database.configuration', [
|
$postgresql->hrefLink = route('project.database.configuration', [
|
||||||
'project_uuid' => data_get($postgresql, 'environment.project.uuid'),
|
'project_uuid' => data_get($postgresql, 'environment.project.uuid'),
|
||||||
@@ -51,7 +52,7 @@ class Index extends Component
|
|||||||
}
|
}
|
||||||
return $postgresql;
|
return $postgresql;
|
||||||
});
|
});
|
||||||
$this->redis = $environment->redis->sortBy('name');
|
$this->redis = $environment->redis->load(['tags'])->sortBy('name');
|
||||||
$this->redis = $this->redis->map(function ($redis) {
|
$this->redis = $this->redis->map(function ($redis) {
|
||||||
if (data_get($redis, 'environment.project.uuid')) {
|
if (data_get($redis, 'environment.project.uuid')) {
|
||||||
$redis->hrefLink = route('project.database.configuration', [
|
$redis->hrefLink = route('project.database.configuration', [
|
||||||
@@ -62,7 +63,7 @@ class Index extends Component
|
|||||||
}
|
}
|
||||||
return $redis;
|
return $redis;
|
||||||
});
|
});
|
||||||
$this->mongodbs = $environment->mongodbs->sortBy('name');
|
$this->mongodbs = $environment->mongodbs->load(['tags'])->sortBy('name');
|
||||||
$this->mongodbs = $this->mongodbs->map(function ($mongodb) {
|
$this->mongodbs = $this->mongodbs->map(function ($mongodb) {
|
||||||
if (data_get($mongodb, 'environment.project.uuid')) {
|
if (data_get($mongodb, 'environment.project.uuid')) {
|
||||||
$mongodb->hrefLink = route('project.database.configuration', [
|
$mongodb->hrefLink = route('project.database.configuration', [
|
||||||
@@ -73,7 +74,7 @@ class Index extends Component
|
|||||||
}
|
}
|
||||||
return $mongodb;
|
return $mongodb;
|
||||||
});
|
});
|
||||||
$this->mysqls = $environment->mysqls->sortBy('name');
|
$this->mysqls = $environment->mysqls->load(['tags'])->sortBy('name');
|
||||||
$this->mysqls = $this->mysqls->map(function ($mysql) {
|
$this->mysqls = $this->mysqls->map(function ($mysql) {
|
||||||
if (data_get($mysql, 'environment.project.uuid')) {
|
if (data_get($mysql, 'environment.project.uuid')) {
|
||||||
$mysql->hrefLink = route('project.database.configuration', [
|
$mysql->hrefLink = route('project.database.configuration', [
|
||||||
@@ -84,7 +85,7 @@ class Index extends Component
|
|||||||
}
|
}
|
||||||
return $mysql;
|
return $mysql;
|
||||||
});
|
});
|
||||||
$this->mariadbs = $environment->mariadbs->sortBy('name');
|
$this->mariadbs = $environment->mariadbs->load(['tags'])->sortBy('name');
|
||||||
$this->mariadbs = $this->mariadbs->map(function ($mariadb) {
|
$this->mariadbs = $this->mariadbs->map(function ($mariadb) {
|
||||||
if (data_get($mariadb, 'environment.project.uuid')) {
|
if (data_get($mariadb, 'environment.project.uuid')) {
|
||||||
$mariadb->hrefLink = route('project.database.configuration', [
|
$mariadb->hrefLink = route('project.database.configuration', [
|
||||||
@@ -95,7 +96,7 @@ class Index extends Component
|
|||||||
}
|
}
|
||||||
return $mariadb;
|
return $mariadb;
|
||||||
});
|
});
|
||||||
$this->services = $environment->services->sortBy('name');
|
$this->services = $environment->services->load(['tags'])->sortBy('name');
|
||||||
$this->services = $this->services->map(function ($service) {
|
$this->services = $this->services->map(function ($service) {
|
||||||
if (data_get($service, 'environment.project.uuid')) {
|
if (data_get($service, 'environment.project.uuid')) {
|
||||||
$service->hrefLink = route('project.service.configuration', [
|
$service->hrefLink = route('project.service.configuration', [
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ use Livewire\Component;
|
|||||||
|
|
||||||
class Configuration extends Component
|
class Configuration extends Component
|
||||||
{
|
{
|
||||||
public Service $service;
|
public ?Service $service = null;
|
||||||
public $applications;
|
public $applications;
|
||||||
public $databases;
|
public $databases;
|
||||||
public array $parameters;
|
public array $parameters;
|
||||||
|
|||||||
@@ -57,7 +57,7 @@ class Navbar extends Component
|
|||||||
}
|
}
|
||||||
$this->service->parse();
|
$this->service->parse();
|
||||||
$activity = StartService::run($this->service);
|
$activity = StartService::run($this->service);
|
||||||
$this->dispatch('newMonitorActivity', $activity->id);
|
$this->dispatch('activityMonitor', $activity->id);
|
||||||
}
|
}
|
||||||
public function stop(bool $forceCleanup = false)
|
public function stop(bool $forceCleanup = false)
|
||||||
{
|
{
|
||||||
@@ -82,6 +82,6 @@ class Navbar extends Component
|
|||||||
StopService::run($this->service);
|
StopService::run($this->service);
|
||||||
$this->service->parse();
|
$this->service->parse();
|
||||||
$activity = StartService::run($this->service);
|
$activity = StartService::run($this->service);
|
||||||
$this->dispatch('newMonitorActivity', $activity->id);
|
$this->dispatch('activityMonitor', $activity->id);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ namespace App\Livewire\Project\Service;
|
|||||||
use App\Models\ServiceApplication;
|
use App\Models\ServiceApplication;
|
||||||
use Livewire\Component;
|
use Livewire\Component;
|
||||||
|
|
||||||
class Application extends Component
|
class ServiceApplicationView extends Component
|
||||||
{
|
{
|
||||||
public ServiceApplication $application;
|
public ServiceApplication $application;
|
||||||
public $parameters;
|
public $parameters;
|
||||||
@@ -20,7 +20,7 @@ class Application extends Component
|
|||||||
];
|
];
|
||||||
public function render()
|
public function render()
|
||||||
{
|
{
|
||||||
return view('livewire.project.service.application');
|
return view('livewire.project.service.service-application-view');
|
||||||
}
|
}
|
||||||
public function instantSave()
|
public function instantSave()
|
||||||
{
|
{
|
||||||
@@ -53,6 +53,7 @@ class Application extends Component
|
|||||||
public function submit()
|
public function submit()
|
||||||
{
|
{
|
||||||
try {
|
try {
|
||||||
|
check_fqdn_usage($this->application);
|
||||||
$this->validate();
|
$this->validate();
|
||||||
$this->application->save();
|
$this->application->save();
|
||||||
updateCompose($this->application);
|
updateCompose($this->application);
|
||||||
@@ -17,14 +17,15 @@ class Danger extends Component
|
|||||||
{
|
{
|
||||||
$this->modalId = new Cuid2(7);
|
$this->modalId = new Cuid2(7);
|
||||||
$parameters = get_route_parameters();
|
$parameters = get_route_parameters();
|
||||||
$this->projectUuid = $parameters['project_uuid'];
|
$this->projectUuid = data_get($parameters, 'project_uuid');
|
||||||
$this->environmentName = $parameters['environment_name'];
|
$this->environmentName = data_get($parameters, 'environment_name');
|
||||||
}
|
}
|
||||||
|
|
||||||
public function delete()
|
public function delete()
|
||||||
{
|
{
|
||||||
try {
|
try {
|
||||||
DeleteResourceJob::dispatchSync($this->resource);
|
$this->resource->delete();
|
||||||
|
DeleteResourceJob::dispatch($this->resource);
|
||||||
return redirect()->route('project.resource.index', [
|
return redirect()->route('project.resource.index', [
|
||||||
'project_uuid' => $this->projectUuid,
|
'project_uuid' => $this->projectUuid,
|
||||||
'environment_name' => $this->environmentName
|
'environment_name' => $this->environmentName
|
||||||
|
|||||||
@@ -32,6 +32,13 @@ class Add extends Component
|
|||||||
public function submit()
|
public function submit()
|
||||||
{
|
{
|
||||||
$this->validate();
|
$this->validate();
|
||||||
|
if (str($this->value)->startsWith('{{') && str($this->value)->endsWith('}}')) {
|
||||||
|
$type = str($this->value)->after("{{")->before(".")->value;
|
||||||
|
if (!collect(SHARED_VARIABLE_TYPES)->contains($type)) {
|
||||||
|
$this->dispatch('error', 'Invalid shared variable type.', "Valid types are: team, project, environment.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
$this->dispatch('saveKey', [
|
$this->dispatch('saveKey', [
|
||||||
'key' => $this->key,
|
'key' => $this->key,
|
||||||
'value' => $this->value,
|
'value' => $this->value,
|
||||||
|
|||||||
@@ -71,12 +71,26 @@ class All extends Component
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
$found->value = $variable;
|
$found->value = $variable;
|
||||||
|
if (str($found->value)->startsWith('{{') && str($found->value)->endsWith('}}')) {
|
||||||
|
$type = str($found->value)->after("{{")->before(".")->value;
|
||||||
|
if (!collect(SHARED_VARIABLE_TYPES)->contains($type)) {
|
||||||
|
$this->dispatch('error', 'Invalid shared variable type.', "Valid types are: team, project, environment.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
$found->save();
|
$found->save();
|
||||||
continue;
|
continue;
|
||||||
} else {
|
} else {
|
||||||
$environment = new EnvironmentVariable();
|
$environment = new EnvironmentVariable();
|
||||||
$environment->key = $key;
|
$environment->key = $key;
|
||||||
$environment->value = $variable;
|
$environment->value = $variable;
|
||||||
|
if (str($environment->value)->startsWith('{{') && str($environment->value)->endsWith('}}')) {
|
||||||
|
$type = str($environment->value)->after("{{")->before(".")->value;
|
||||||
|
if (!collect(SHARED_VARIABLE_TYPES)->contains($type)) {
|
||||||
|
$this->dispatch('error', 'Invalid shared variable type.', "Valid types are: team, project, environment.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
$environment->is_build_time = false;
|
$environment->is_build_time = false;
|
||||||
$environment->is_preview = $isPreview ? true : false;
|
$environment->is_preview = $isPreview ? true : false;
|
||||||
switch ($this->resource->type()) {
|
switch ($this->resource->type()) {
|
||||||
|
|||||||
@@ -50,7 +50,8 @@ class Show extends Component
|
|||||||
$this->isLocked = true;
|
$this->isLocked = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
public function serialize() {
|
public function serialize()
|
||||||
|
{
|
||||||
data_forget($this->env, 'real_value');
|
data_forget($this->env, 'real_value');
|
||||||
if ($this->env->getMorphClass() === 'App\Models\SharedEnvironmentVariable') {
|
if ($this->env->getMorphClass() === 'App\Models\SharedEnvironmentVariable') {
|
||||||
data_forget($this->env, 'is_build_time');
|
data_forget($this->env, 'is_build_time');
|
||||||
@@ -80,11 +81,18 @@ class Show extends Component
|
|||||||
} else {
|
} else {
|
||||||
$this->validate();
|
$this->validate();
|
||||||
}
|
}
|
||||||
|
if (str($this->env->value)->startsWith('{{') && str($this->env->value)->endsWith('}}')) {
|
||||||
|
$type = str($this->env->value)->after("{{")->before(".")->value;
|
||||||
|
if (!collect(SHARED_VARIABLE_TYPES)->contains($type)) {
|
||||||
|
$this->dispatch('error', 'Invalid shared variable type.', "Valid types are: team, project, environment.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
$this->serialize();
|
$this->serialize();
|
||||||
$this->env->save();
|
$this->env->save();
|
||||||
$this->dispatch('success', 'Environment variable updated successfully.');
|
$this->dispatch('success', 'Environment variable updated successfully.');
|
||||||
$this->dispatch('refreshEnvs');
|
$this->dispatch('refreshEnvs');
|
||||||
} catch(\Exception $e) {
|
} catch (\Exception $e) {
|
||||||
return handleError($e);
|
return handleError($e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -115,7 +115,7 @@ class ExecuteContainerCommand extends Component
|
|||||||
$exec = "docker exec {$this->container} {$cmd}";
|
$exec = "docker exec {$this->container} {$cmd}";
|
||||||
}
|
}
|
||||||
$activity = remote_process([$exec], $this->server, ignore_errors: true);
|
$activity = remote_process([$exec], $this->server, ignore_errors: true);
|
||||||
$this->dispatch('newMonitorActivity', $activity->id);
|
$this->dispatch('activityMonitor', $activity->id);
|
||||||
} catch (\Throwable $e) {
|
} catch (\Throwable $e) {
|
||||||
return handleError($e, $this);
|
return handleError($e, $this);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -20,8 +20,8 @@ class ResourceOperations extends Component
|
|||||||
public function mount()
|
public function mount()
|
||||||
{
|
{
|
||||||
$parameters = get_route_parameters();
|
$parameters = get_route_parameters();
|
||||||
$this->projectUuid = $parameters['project_uuid'];
|
$this->projectUuid = data_get($parameters, 'project_uuid');
|
||||||
$this->environmentName = $parameters['environment_name'];
|
$this->environmentName = data_get($parameters, 'environment_name');
|
||||||
$this->projects = Project::ownedByCurrentTeam()->get();
|
$this->projects = Project::ownedByCurrentTeam()->get();
|
||||||
$this->servers = currentTeam()->servers;
|
$this->servers = currentTeam()->servers;
|
||||||
}
|
}
|
||||||
@@ -45,6 +45,11 @@ class ResourceOperations extends Component
|
|||||||
'destination_id' => $new_destination->id,
|
'destination_id' => $new_destination->id,
|
||||||
]);
|
]);
|
||||||
$new_resource->save();
|
$new_resource->save();
|
||||||
|
if ($new_resource->destination->server->proxyType() === 'TRAEFIK_V2') {
|
||||||
|
$customLabels = str(implode("|", generateLabelsApplication($new_resource)))->replace("|", "\n");
|
||||||
|
$new_resource->custom_labels = base64_encode($customLabels);
|
||||||
|
$new_resource->save();
|
||||||
|
}
|
||||||
$environmentVaribles = $this->resource->environment_variables()->get();
|
$environmentVaribles = $this->resource->environment_variables()->get();
|
||||||
foreach ($environmentVaribles as $environmentVarible) {
|
foreach ($environmentVaribles as $environmentVarible) {
|
||||||
$newEnvironmentVariable = $environmentVarible->replicate()->fill([
|
$newEnvironmentVariable = $environmentVarible->replicate()->fill([
|
||||||
|
|||||||
90
app/Livewire/Project/Shared/Tags.php
Normal file
90
app/Livewire/Project/Shared/Tags.php
Normal file
@@ -0,0 +1,90 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Livewire\Project\Shared;
|
||||||
|
|
||||||
|
use App\Models\Tag;
|
||||||
|
use Livewire\Component;
|
||||||
|
|
||||||
|
class Tags extends Component
|
||||||
|
{
|
||||||
|
public $resource = null;
|
||||||
|
public ?string $new_tag = null;
|
||||||
|
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 function mount()
|
||||||
|
{
|
||||||
|
$this->tags = Tag::ownedByCurrentTeam()->get();
|
||||||
|
}
|
||||||
|
public function addTag(string $id, string $name)
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
if ($this->resource->tags()->where('id', $id)->exists()) {
|
||||||
|
$this->dispatch('error', 'Duplicate tags.', "Tag <span class='text-warning'>$name</span> already added.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
$this->resource->tags()->syncWithoutDetaching($id);
|
||||||
|
$this->refresh();
|
||||||
|
} catch (\Exception $e) {
|
||||||
|
return handleError($e, $this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
public function deleteTag(string $id)
|
||||||
|
{
|
||||||
|
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->delete();
|
||||||
|
}
|
||||||
|
$this->refresh();
|
||||||
|
} catch (\Exception $e) {
|
||||||
|
return handleError($e, $this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
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='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');
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -31,7 +31,7 @@ class RunCommand extends Component
|
|||||||
$this->validate();
|
$this->validate();
|
||||||
try {
|
try {
|
||||||
$activity = remote_process([$this->command], Server::where('uuid', $this->server)->first(), ignore_errors: true);
|
$activity = remote_process([$this->command], Server::where('uuid', $this->server)->first(), ignore_errors: true);
|
||||||
$this->dispatch('newMonitorActivity', $activity->id);
|
$this->dispatch('activityMonitor', $activity->id);
|
||||||
} catch (\Throwable $e) {
|
} catch (\Throwable $e) {
|
||||||
return handleError($e, $this);
|
return handleError($e, $this);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,7 +2,6 @@
|
|||||||
|
|
||||||
namespace App\Livewire\Server;
|
namespace App\Livewire\Server;
|
||||||
|
|
||||||
use App\Actions\Server\InstallDocker;
|
|
||||||
use App\Models\Server;
|
use App\Models\Server;
|
||||||
use Livewire\Component;
|
use Livewire\Component;
|
||||||
|
|
||||||
@@ -14,7 +13,8 @@ class Form extends Component
|
|||||||
public ?string $wildcard_domain = null;
|
public ?string $wildcard_domain = null;
|
||||||
public int $cleanup_after_percentage;
|
public int $cleanup_after_percentage;
|
||||||
public bool $dockerInstallationStarted = false;
|
public bool $dockerInstallationStarted = false;
|
||||||
protected $listeners = ['serverRefresh'];
|
|
||||||
|
protected $listeners = ['serverInstalled'];
|
||||||
|
|
||||||
protected $rules = [
|
protected $rules = [
|
||||||
'server.name' => 'required',
|
'server.name' => 'required',
|
||||||
@@ -49,9 +49,10 @@ class Form extends Component
|
|||||||
$this->wildcard_domain = $this->server->settings->wildcard_domain;
|
$this->wildcard_domain = $this->server->settings->wildcard_domain;
|
||||||
$this->cleanup_after_percentage = $this->server->settings->cleanup_after_percentage;
|
$this->cleanup_after_percentage = $this->server->settings->cleanup_after_percentage;
|
||||||
}
|
}
|
||||||
public function serverRefresh($install = true)
|
public function serverInstalled()
|
||||||
{
|
{
|
||||||
$this->validateServer($install);
|
$this->server->refresh();
|
||||||
|
$this->server->settings->refresh();
|
||||||
}
|
}
|
||||||
public function instantSave()
|
public function instantSave()
|
||||||
{
|
{
|
||||||
@@ -64,13 +65,6 @@ class Form extends Component
|
|||||||
return handleError($e, $this);
|
return handleError($e, $this);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
public function installDocker()
|
|
||||||
{
|
|
||||||
$this->dispatch('installDocker');
|
|
||||||
$this->dockerInstallationStarted = true;
|
|
||||||
$activity = InstallDocker::run($this->server);
|
|
||||||
$this->dispatch('newMonitorActivity', $activity->id);
|
|
||||||
}
|
|
||||||
public function checkLocalhostConnection()
|
public function checkLocalhostConnection()
|
||||||
{
|
{
|
||||||
$uptime = $this->server->validateConnection();
|
$uptime = $this->server->validateConnection();
|
||||||
@@ -80,48 +74,13 @@ class Form extends Component
|
|||||||
$this->server->settings->is_usable = true;
|
$this->server->settings->is_usable = true;
|
||||||
$this->server->settings->save();
|
$this->server->settings->save();
|
||||||
} else {
|
} else {
|
||||||
$this->dispatch('error', 'Server is not reachable.<br>Please validate your configuration and connection.<br><br>Check this <a target="_blank" class="underline" href="https://coolify.io/docs/server/openssh">documentation</a> for further help.');
|
$this->dispatch('error', 'Server is not reachable.', 'Please validate your configuration and connection.<br><br>Check this <a target="_blank" class="underline" href="https://coolify.io/docs/server/openssh">documentation</a> for further help.');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
public function validateServer($install = true)
|
public function validateServer($install = true)
|
||||||
{
|
{
|
||||||
try {
|
$this->dispatch('validateServer', $install);
|
||||||
$uptime = $this->server->validateConnection();
|
|
||||||
if (!$uptime) {
|
|
||||||
$install && $this->dispatch('error', 'Server is not reachable.<br>Please validate your configuration and connection.<br><br>Check this <a target="_blank" class="underline" href="https://coolify.io/docs/server/openssh">documentation</a> for further help.');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
$supported_os_type = $this->server->validateOS();
|
|
||||||
if (!$supported_os_type) {
|
|
||||||
$install && $this->dispatch('error', 'Server OS type is not supported for automated installation. Please install Docker manually before continuing: <a target="_blank" class="underline" href="https://docs.docker.com/engine/install/#server">documentation</a>.');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
$dockerInstalled = $this->server->validateDockerEngine();
|
|
||||||
if ($dockerInstalled) {
|
|
||||||
$install && $this->dispatch('success', 'Docker Engine is installed.<br> Checking version.');
|
|
||||||
} else {
|
|
||||||
$install && $this->installDocker();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
$dockerVersion = $this->server->validateDockerEngineVersion();
|
|
||||||
if ($dockerVersion) {
|
|
||||||
$install && $this->dispatch('success', 'Docker Engine version is 22+.');
|
|
||||||
} else {
|
|
||||||
$install && $this->installDocker();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if ($this->server->isSwarm()) {
|
|
||||||
$swarmInstalled = $this->server->validateDockerSwarm();
|
|
||||||
if ($swarmInstalled) {
|
|
||||||
$install && $this->dispatch('success', 'Docker Swarm is initiated.');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} catch (\Throwable $e) {
|
|
||||||
return handleError($e, $this);
|
|
||||||
} finally {
|
|
||||||
$this->dispatch('proxyStatusUpdated');
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public function submit()
|
public function submit()
|
||||||
|
|||||||
@@ -71,7 +71,7 @@ class Deploy extends Component
|
|||||||
{
|
{
|
||||||
try {
|
try {
|
||||||
$activity = StartProxy::run($this->server);
|
$activity = StartProxy::run($this->server);
|
||||||
$this->dispatch('newMonitorActivity', $activity->id, ProxyStatusChanged::class);
|
$this->dispatch('activityMonitor', $activity->id, ProxyStatusChanged::class);
|
||||||
} catch (\Throwable $e) {
|
} catch (\Throwable $e) {
|
||||||
return handleError($e, $this);
|
return handleError($e, $this);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,10 +12,8 @@ class Status extends Component
|
|||||||
public Server $server;
|
public Server $server;
|
||||||
public bool $polling = false;
|
public bool $polling = false;
|
||||||
public int $numberOfPolls = 0;
|
public int $numberOfPolls = 0;
|
||||||
|
|
||||||
protected $listeners = ['proxyStatusUpdated', 'startProxyPolling'];
|
protected $listeners = ['proxyStatusUpdated', 'startProxyPolling'];
|
||||||
public function mount() {
|
|
||||||
}
|
|
||||||
public function startProxyPolling()
|
public function startProxyPolling()
|
||||||
{
|
{
|
||||||
$this->checkProxy();
|
$this->checkProxy();
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ class Show extends Component
|
|||||||
use AuthorizesRequests;
|
use AuthorizesRequests;
|
||||||
public ?Server $server = null;
|
public ?Server $server = null;
|
||||||
public $parameters = [];
|
public $parameters = [];
|
||||||
|
protected $listeners = ['serverInstalled' => '$refresh'];
|
||||||
public function mount()
|
public function mount()
|
||||||
{
|
{
|
||||||
$this->parameters = get_route_parameters();
|
$this->parameters = get_route_parameters();
|
||||||
@@ -19,14 +20,13 @@ class Show extends Component
|
|||||||
if (is_null($this->server)) {
|
if (is_null($this->server)) {
|
||||||
return redirect()->route('server.index');
|
return redirect()->route('server.index');
|
||||||
}
|
}
|
||||||
|
|
||||||
} catch (\Throwable $e) {
|
} catch (\Throwable $e) {
|
||||||
return handleError($e, $this);
|
return handleError($e, $this);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
public function submit()
|
public function submit()
|
||||||
{
|
{
|
||||||
$this->dispatch('serverRefresh',false);
|
$this->dispatch('serverRefresh', false);
|
||||||
}
|
}
|
||||||
public function render()
|
public function render()
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -39,7 +39,7 @@ class ShowPrivateKey extends Component
|
|||||||
if ($uptime) {
|
if ($uptime) {
|
||||||
$this->dispatch('success', 'Server is reachable.');
|
$this->dispatch('success', 'Server is reachable.');
|
||||||
} else {
|
} else {
|
||||||
$this->dispatch('error', 'Server is not reachable.<br>Please validate your configuration and connection.<br><br>Check this <a target="_blank" class="underline" href="https://coolify.io/docs/configuration#openssh-server">documentation</a> for further help.');
|
$this->dispatch('error', 'Server is not reachable.<br>Please validate your configuration and connection.<br><br>Check this <a target="_blank" class="underline" href="https://coolify.io/docs/server/openssh#openssh">documentation</a> for further help.');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
} catch (\Throwable $e) {
|
} catch (\Throwable $e) {
|
||||||
|
|||||||
105
app/Livewire/Server/ValidateAndInstall.php
Normal file
105
app/Livewire/Server/ValidateAndInstall.php
Normal file
@@ -0,0 +1,105 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Livewire\Server;
|
||||||
|
|
||||||
|
use App\Models\Server;
|
||||||
|
use Livewire\Component;
|
||||||
|
|
||||||
|
class ValidateAndInstall extends Component
|
||||||
|
{
|
||||||
|
public Server $server;
|
||||||
|
public int $number_of_tries = 0;
|
||||||
|
public int $max_tries = 1;
|
||||||
|
public bool $install = true;
|
||||||
|
public $uptime = null;
|
||||||
|
public $supported_os_type = null;
|
||||||
|
public $docker_installed = null;
|
||||||
|
public $docker_version = null;
|
||||||
|
public $error = null;
|
||||||
|
|
||||||
|
protected $listeners = ['validateServer' => 'init', 'validateDockerEngine', 'validateServerNow' => 'validateServer'];
|
||||||
|
|
||||||
|
public function init(bool $install = true)
|
||||||
|
{
|
||||||
|
$this->install = $install;
|
||||||
|
$this->uptime = null;
|
||||||
|
$this->supported_os_type = null;
|
||||||
|
$this->docker_installed = null;
|
||||||
|
$this->docker_version = null;
|
||||||
|
$this->error = null;
|
||||||
|
$this->number_of_tries = 0;
|
||||||
|
$this->dispatch('validateServerNow');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function validateServer()
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
$this->validateConnection();
|
||||||
|
$this->validateOS();
|
||||||
|
$this->validateDockerEngine();
|
||||||
|
|
||||||
|
if ($this->server->isSwarm()) {
|
||||||
|
$swarmInstalled = $this->server->validateDockerSwarm();
|
||||||
|
if ($swarmInstalled) {
|
||||||
|
$this->dispatch('success', 'Docker Swarm is initiated.');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (\Throwable $e) {
|
||||||
|
return handleError($e, $this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
public function validateConnection()
|
||||||
|
{
|
||||||
|
$this->uptime = $this->server->validateConnection();
|
||||||
|
if (!$this->uptime) {
|
||||||
|
$this->error = 'Server is not reachable. Please validate your configuration and connection.<br><br>Check this <a target="_blank" class="underline" href="https://coolify.io/docs/server/openssh">documentation</a> for further help.';
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
public function validateOS()
|
||||||
|
{
|
||||||
|
$this->supported_os_type = $this->server->validateOS();
|
||||||
|
if (!$this->supported_os_type) {
|
||||||
|
$this->error = 'Server OS type is not supported. Please install Docker manually before continuing: <a target="_blank" class="underline" href="https://docs.docker.com/engine/install/#server">documentation</a>.';
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
public function validateDockerEngine()
|
||||||
|
{
|
||||||
|
$this->docker_installed = $this->server->validateDockerEngine();
|
||||||
|
if (!$this->docker_installed) {
|
||||||
|
if ($this->install) {
|
||||||
|
ray($this->number_of_tries, $this->max_tries);
|
||||||
|
if ($this->number_of_tries == $this->max_tries) {
|
||||||
|
$this->error = 'Docker Engine could not be installed. Please install Docker manually before continuing: <a target="_blank" class="underline" href="https://docs.docker.com/engine/install/#server">documentation</a>.';
|
||||||
|
return;
|
||||||
|
} else {
|
||||||
|
$activity = $this->server->installDocker();
|
||||||
|
$this->number_of_tries++;
|
||||||
|
$this->dispatch('newActivityMonitor', $activity->id, 'validateDockerEngine');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
$this->error = 'Docker Engine is not installed. Please install Docker manually before continuing: <a target="_blank" class="underline" href="https://docs.docker.com/engine/install/#server">documentation</a>.';
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
$this->validateDockerVersion();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
public function validateDockerVersion()
|
||||||
|
{
|
||||||
|
$this->docker_version = $this->server->validateDockerEngineVersion();
|
||||||
|
if ($this->docker_version) {
|
||||||
|
$this->dispatch('serverInstalled');
|
||||||
|
$this->dispatch('success', 'Server validated successfully.');
|
||||||
|
} else {
|
||||||
|
$this->error = 'Docker Engine version is not 22+. Please install Docker manually before continuing: <a target="_blank" class="underline" href="https://docs.docker.com/engine/install/#server">documentation</a>.';
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
public function render()
|
||||||
|
{
|
||||||
|
return view('livewire.server.validate-and-install');
|
||||||
|
}
|
||||||
|
}
|
||||||
18
app/Livewire/Tags/Index.php
Normal file
18
app/Livewire/Tags/Index.php
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Livewire\Tags;
|
||||||
|
|
||||||
|
use App\Models\Tag;
|
||||||
|
use Livewire\Component;
|
||||||
|
|
||||||
|
class Index extends Component
|
||||||
|
{
|
||||||
|
public $tags = [];
|
||||||
|
public function mount() {
|
||||||
|
$this->tags = Tag::where('team_id', currentTeam()->id)->get()->unique('name')->sortBy('name');
|
||||||
|
}
|
||||||
|
public function render()
|
||||||
|
{
|
||||||
|
return view('livewire.tags.index');
|
||||||
|
}
|
||||||
|
}
|
||||||
71
app/Livewire/Tags/Show.php
Normal file
71
app/Livewire/Tags/Show.php
Normal file
@@ -0,0 +1,71 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Livewire\Tags;
|
||||||
|
|
||||||
|
use App\Http\Controllers\Api\Deploy;
|
||||||
|
use App\Models\ApplicationDeploymentQueue;
|
||||||
|
use App\Models\Tag;
|
||||||
|
use Livewire\Component;
|
||||||
|
|
||||||
|
class Show extends Component
|
||||||
|
{
|
||||||
|
public $tags;
|
||||||
|
public Tag $tag;
|
||||||
|
public $applications;
|
||||||
|
public $services;
|
||||||
|
public $webhook = null;
|
||||||
|
public $deployments_per_tag_per_server = [];
|
||||||
|
|
||||||
|
public function mount()
|
||||||
|
{
|
||||||
|
$this->tags = Tag::ownedByCurrentTeam()->get()->unique('name')->sortBy('name');
|
||||||
|
$tag = $this->tags->where('name', request()->tag_name)->first();
|
||||||
|
if (!$tag) {
|
||||||
|
return redirect()->route('tags.index');
|
||||||
|
}
|
||||||
|
$this->webhook = generatTagDeployWebhook($tag->name);
|
||||||
|
$this->applications = $tag->applications()->get();
|
||||||
|
$this->services = $tag->services()->get();
|
||||||
|
$this->tag = $tag;
|
||||||
|
$this->get_deployments();
|
||||||
|
}
|
||||||
|
public function get_deployments()
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
$resource_ids = $this->applications->pluck('id');
|
||||||
|
$this->deployments_per_tag_per_server = ApplicationDeploymentQueue::whereIn("status", ["in_progress", "queued"])->whereIn('application_id', $resource_ids)->get([
|
||||||
|
"id",
|
||||||
|
"application_id",
|
||||||
|
"application_name",
|
||||||
|
"deployment_url",
|
||||||
|
"pull_request_id",
|
||||||
|
"server_name",
|
||||||
|
"server_id",
|
||||||
|
"status"
|
||||||
|
])->sortBy('id')->groupBy('server_name')->toArray();
|
||||||
|
} catch (\Exception $e) {
|
||||||
|
return handleError($e, $this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
public function redeploy_all()
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
$this->applications->each(function ($resource) {
|
||||||
|
$deploy = new Deploy();
|
||||||
|
$deploy->deploy_resource($resource);
|
||||||
|
});
|
||||||
|
$this->services->each(function ($resource) {
|
||||||
|
$deploy = new Deploy();
|
||||||
|
$deploy->deploy_resource($resource);
|
||||||
|
});
|
||||||
|
$this->dispatch('success', 'Mass deployment started.');
|
||||||
|
} catch (\Exception $e) {
|
||||||
|
return handleError($e, $this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public function render()
|
||||||
|
{
|
||||||
|
return view('livewire.tags.show');
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -12,7 +12,11 @@ class Invitations extends Component
|
|||||||
|
|
||||||
public function deleteInvitation(int $invitation_id)
|
public function deleteInvitation(int $invitation_id)
|
||||||
{
|
{
|
||||||
TeamInvitation::find($invitation_id)->delete();
|
$initiation_found = TeamInvitation::find($invitation_id);
|
||||||
|
if (!$initiation_found) {
|
||||||
|
return $this->dispatch('error', 'Invitation not found.');
|
||||||
|
}
|
||||||
|
$initiation_found->delete();
|
||||||
$this->refreshInvitations();
|
$this->refreshInvitations();
|
||||||
$this->dispatch('success', 'Invitation revoked.');
|
$this->dispatch('success', 'Invitation revoked.');
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -67,7 +67,8 @@ class Create extends Component
|
|||||||
$this->storage->save();
|
$this->storage->save();
|
||||||
return redirect()->route('team.storage.show', $this->storage->uuid);
|
return redirect()->route('team.storage.show', $this->storage->uuid);
|
||||||
} catch (\Throwable $e) {
|
} catch (\Throwable $e) {
|
||||||
return handleError($e, $this);
|
$this->dispatch('error', 'Failed to create storage.', $e->getMessage());
|
||||||
|
// return handleError($e, $this);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -33,9 +33,9 @@ class Form extends Component
|
|||||||
{
|
{
|
||||||
try {
|
try {
|
||||||
$this->storage->testConnection(shouldSave: true);
|
$this->storage->testConnection(shouldSave: true);
|
||||||
return $this->dispatch('success', 'Connection is working. Tested with "ListObjectsV2" action.');
|
return $this->dispatch('success', 'Connection is working.', 'Tested with "ListObjectsV2" action.');
|
||||||
} catch (\Throwable $e) {
|
} catch (\Throwable $e) {
|
||||||
return handleError($e, $this);
|
$this->dispatch('error', 'Failed to create storage.', $e->getMessage());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -6,11 +6,9 @@ use App\Enums\ApplicationDeploymentStatus;
|
|||||||
use Illuminate\Database\Eloquent\Casts\Attribute;
|
use Illuminate\Database\Eloquent\Casts\Attribute;
|
||||||
use Illuminate\Database\Eloquent\Relations\HasMany;
|
use Illuminate\Database\Eloquent\Relations\HasMany;
|
||||||
use Illuminate\Database\Eloquent\SoftDeletes;
|
use Illuminate\Database\Eloquent\SoftDeletes;
|
||||||
use Illuminate\Support\Collection;
|
|
||||||
use Spatie\Activitylog\Models\Activity;
|
use Spatie\Activitylog\Models\Activity;
|
||||||
use Illuminate\Support\Str;
|
use Illuminate\Support\Str;
|
||||||
use RuntimeException;
|
use RuntimeException;
|
||||||
use Symfony\Component\Yaml\Yaml;
|
|
||||||
use Visus\Cuid2\Cuid2;
|
use Visus\Cuid2\Cuid2;
|
||||||
|
|
||||||
class Application extends BaseModel
|
class Application extends BaseModel
|
||||||
@@ -39,6 +37,7 @@ class Application extends BaseModel
|
|||||||
]);
|
]);
|
||||||
});
|
});
|
||||||
static::deleting(function ($application) {
|
static::deleting(function ($application) {
|
||||||
|
$application->update(['fqdn' => null]);
|
||||||
$application->settings()->delete();
|
$application->settings()->delete();
|
||||||
$storages = $application->persistentStorages()->get();
|
$storages = $application->persistentStorages()->get();
|
||||||
$server = data_get($application, 'destination.server');
|
$server = data_get($application, 'destination.server');
|
||||||
@@ -50,66 +49,9 @@ class Application extends BaseModel
|
|||||||
$application->persistentStorages()->delete();
|
$application->persistentStorages()->delete();
|
||||||
$application->environment_variables()->delete();
|
$application->environment_variables()->delete();
|
||||||
$application->environment_variables_preview()->delete();
|
$application->environment_variables_preview()->delete();
|
||||||
|
$application->tags()->detach();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
// Build packs / deployment types
|
|
||||||
|
|
||||||
|
|
||||||
public function servers(): Collection
|
|
||||||
{
|
|
||||||
$mainServer = data_get($this, 'destination.server');
|
|
||||||
$additionalDestinations = data_get($this, 'additional_destinations', null);
|
|
||||||
$additionalServers = collect([]);
|
|
||||||
if ($this->isMultipleServerDeployment()) {
|
|
||||||
ray('asd');
|
|
||||||
if (str($additionalDestinations)->isNotEmpty()) {
|
|
||||||
$additionalDestinations = str($additionalDestinations)->explode(',');
|
|
||||||
foreach ($additionalDestinations as $destinationId) {
|
|
||||||
$destination = StandaloneDocker::find($destinationId)->whereNot('id', $mainServer->id)->first();
|
|
||||||
$server = data_get($destination, 'server');
|
|
||||||
$additionalServers->push($server);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return collect([$mainServer])->merge($additionalServers);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function generateImageNames(string $commit, int $pullRequestId)
|
|
||||||
{
|
|
||||||
if ($this->dockerfile) {
|
|
||||||
if ($this->docker_registry_image_name) {
|
|
||||||
$buildImageName = Str::lower("{$this->docker_registry_image_name}:build");
|
|
||||||
$productionImageName = Str::lower("{$this->docker_registry_image_name}:latest");
|
|
||||||
} else {
|
|
||||||
$buildImageName = Str::lower("{$this->uuid}:build");
|
|
||||||
$productionImageName = Str::lower("{$this->uuid}:latest");
|
|
||||||
}
|
|
||||||
} else if ($this->build_pack === 'dockerimage') {
|
|
||||||
$productionImageName = Str::lower("{$this->docker_registry_image_name}:{$this->docker_registry_image_tag}");
|
|
||||||
} else if ($pullRequestId === 0) {
|
|
||||||
$dockerImageTag = str($commit)->substr(0, 128);
|
|
||||||
if ($this->docker_registry_image_name) {
|
|
||||||
$buildImageName = Str::lower("{$this->docker_registry_image_name}:{$dockerImageTag}-build");
|
|
||||||
$productionImageName = Str::lower("{$this->docker_registry_image_name}:{$dockerImageTag}");
|
|
||||||
} else {
|
|
||||||
$buildImageName = Str::lower("{$this->uuid}:{$dockerImageTag}-build");
|
|
||||||
$productionImageName = Str::lower("{$this->uuid}:{$dockerImageTag}");
|
|
||||||
}
|
|
||||||
} else if ($pullRequestId !== 0) {
|
|
||||||
if ($this->docker_registry_image_name) {
|
|
||||||
$buildImageName = Str::lower("{$this->docker_registry_image_name}:pr-{$pullRequestId}-build");
|
|
||||||
$productionImageName = Str::lower("{$this->docker_registry_image_name}:pr-{$pullRequestId}");
|
|
||||||
} else {
|
|
||||||
$buildImageName = Str::lower("{$this->uuid}:pr-{$pullRequestId}-build");
|
|
||||||
$productionImageName = Str::lower("{$this->uuid}:pr-{$pullRequestId}");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return [
|
|
||||||
'buildImageName' => $buildImageName,
|
|
||||||
'productionImageName' => $productionImageName,
|
|
||||||
];
|
|
||||||
}
|
|
||||||
// End of build packs / deployment types
|
|
||||||
|
|
||||||
public function is_github_based(): bool
|
public function is_github_based(): bool
|
||||||
{
|
{
|
||||||
@@ -270,6 +212,13 @@ class Application extends BaseModel
|
|||||||
: explode(',', $this->ports_exposes)
|
: explode(',', $this->ports_exposes)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
public function tags()
|
||||||
|
{
|
||||||
|
return $this->morphToMany(Tag::class, 'taggable');
|
||||||
|
}
|
||||||
|
public function project() {
|
||||||
|
return data_get($this, 'environment.project');
|
||||||
|
}
|
||||||
public function team()
|
public function team()
|
||||||
{
|
{
|
||||||
return data_get($this, 'environment.project.team');
|
return data_get($this, 'environment.project.team');
|
||||||
@@ -465,31 +414,6 @@ class Application extends BaseModel
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
public function isMultipleServerDeployment()
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
if (data_get($this, 'additional_destinations') && data_get($this, 'docker_registry_image_name')) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
public function healthCheckUrl()
|
|
||||||
{
|
|
||||||
if ($this->dockerfile || $this->build_pack === 'dockerfile' || $this->build_pack === 'dockerimage') {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
if (!$this->health_check_port) {
|
|
||||||
$health_check_port = $this->ports_exposes_array[0];
|
|
||||||
} else {
|
|
||||||
$health_check_port = $this->health_check_port;
|
|
||||||
}
|
|
||||||
if ($this->health_check_path) {
|
|
||||||
$full_healthcheck_url = "{$this->health_check_scheme}://{$this->health_check_host}:{$health_check_port}{$this->health_check_path}";
|
|
||||||
} else {
|
|
||||||
$full_healthcheck_url = "{$this->health_check_scheme}://{$this->health_check_host}:{$health_check_port}/";
|
|
||||||
}
|
|
||||||
return $full_healthcheck_url;
|
|
||||||
}
|
|
||||||
function customRepository()
|
function customRepository()
|
||||||
{
|
{
|
||||||
preg_match('/(?<=:)\d+(?=\/)/', $this->git_repository, $matches);
|
preg_match('/(?<=:)\d+(?=\/)/', $this->git_repository, $matches);
|
||||||
@@ -511,296 +435,7 @@ class Application extends BaseModel
|
|||||||
{
|
{
|
||||||
return "/artifacts/{$uuid}";
|
return "/artifacts/{$uuid}";
|
||||||
}
|
}
|
||||||
function generateHealthCheckCommands()
|
function setGitImportSettings(string $deployment_uuid, string $git_clone_command)
|
||||||
{
|
|
||||||
if ($this->dockerfile || $this->build_pack === 'dockerfile' || $this->build_pack === 'dockerimage') {
|
|
||||||
// TODO: disabled HC because there are several ways to hc a simple docker image, hard to figure out a good way. Like some docker images (pocketbase) does not have curl.
|
|
||||||
return 'exit 0';
|
|
||||||
}
|
|
||||||
if (!$this->health_check_port) {
|
|
||||||
$health_check_port = $this->ports_exposes_array[0];
|
|
||||||
} else {
|
|
||||||
$health_check_port = $this->health_check_port;
|
|
||||||
}
|
|
||||||
if ($this->health_check_path) {
|
|
||||||
$this->full_healthcheck_url = "{$this->health_check_method}: {$this->health_check_scheme}://{$this->health_check_host}:{$health_check_port}{$this->health_check_path}";
|
|
||||||
$generated_healthchecks_commands = [
|
|
||||||
"curl -s -X {$this->health_check_method} -f {$this->health_check_scheme}://{$this->health_check_host}:{$health_check_port}{$this->health_check_path} > /dev/null"
|
|
||||||
];
|
|
||||||
} else {
|
|
||||||
$this->full_healthcheck_url = "{$this->health_check_method}: {$this->health_check_scheme}://{$this->health_check_host}:{$health_check_port}/";
|
|
||||||
$generated_healthchecks_commands = [
|
|
||||||
"curl -s -X {$this->health_check_method} -f {$this->health_check_scheme}://{$this->health_check_host}:{$health_check_port}/"
|
|
||||||
];
|
|
||||||
}
|
|
||||||
return implode(' ', $generated_healthchecks_commands);
|
|
||||||
}
|
|
||||||
function generateLocalPersistentVolumes(int $pullRequestId)
|
|
||||||
{
|
|
||||||
$persistentStorages = [];
|
|
||||||
$volumeNames = [];
|
|
||||||
foreach ($this->persistentStorages as $persistentStorage) {
|
|
||||||
$volume_name = $persistentStorage->host_path ?? $persistentStorage->name;
|
|
||||||
if ($pullRequestId !== 0) {
|
|
||||||
$volume_name = $volume_name . '-pr-' . $pullRequestId;
|
|
||||||
}
|
|
||||||
$persistentStorages[] = $volume_name . ':' . $persistentStorage->mount_path;
|
|
||||||
|
|
||||||
if ($persistentStorage->host_path) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
$name = $persistentStorage->name;
|
|
||||||
|
|
||||||
if ($pullRequestId !== 0) {
|
|
||||||
$name = $name . '-pr-' . $pullRequestId;
|
|
||||||
}
|
|
||||||
|
|
||||||
$volumeNames[$name] = [
|
|
||||||
'name' => $name,
|
|
||||||
'external' => false,
|
|
||||||
];
|
|
||||||
}
|
|
||||||
|
|
||||||
return [
|
|
||||||
'persistentStorages' => $persistentStorages,
|
|
||||||
'volumeNames' => $volumeNames,
|
|
||||||
];
|
|
||||||
}
|
|
||||||
public function generateEnvironmentVariables($ports)
|
|
||||||
{
|
|
||||||
$environmentVariables = collect();
|
|
||||||
// ray('Generate Environment Variables')->green();
|
|
||||||
if ($this->pull_request_id === 0) {
|
|
||||||
// ray($this->runtime_environment_variables)->green();
|
|
||||||
foreach ($this->runtime_environment_variables as $env) {
|
|
||||||
$environmentVariables->push("$env->key=$env->value");
|
|
||||||
}
|
|
||||||
foreach ($this->nixpacks_environment_variables as $env) {
|
|
||||||
$environmentVariables->push("$env->key=$env->value");
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// ray($this->runtime_environment_variables_preview)->green();
|
|
||||||
foreach ($this->runtime_environment_variables_preview as $env) {
|
|
||||||
$environmentVariables->push("$env->key=$env->value");
|
|
||||||
}
|
|
||||||
foreach ($this->nixpacks_environment_variables_preview as $env) {
|
|
||||||
$environmentVariables->push("$env->key=$env->value");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// Add PORT if not exists, use the first port as default
|
|
||||||
if ($environmentVariables->filter(fn ($env) => Str::of($env)->contains('PORT'))->isEmpty()) {
|
|
||||||
$environmentVariables->push("PORT={$ports[0]}");
|
|
||||||
}
|
|
||||||
return $environmentVariables->all();
|
|
||||||
}
|
|
||||||
function generateDockerComposeFile(Server $server, ApplicationDeploymentQueue $deployment, string $workdir)
|
|
||||||
{
|
|
||||||
$pullRequestId = $deployment->pull_request_id;
|
|
||||||
$ports = $this->settings->is_static ? [80] : $this->ports_exposes_array;
|
|
||||||
$container_name = generateApplicationContainerName($this, $this->pull_request_id);
|
|
||||||
$commit = str($deployment->getOutput('git_commit_sha'))->before("\t");
|
|
||||||
|
|
||||||
[
|
|
||||||
'productionImageName' => $productionImageName
|
|
||||||
] = $this->generateImageNames($commit, $pullRequestId);
|
|
||||||
|
|
||||||
[
|
|
||||||
'persistentStorages' => $persistentStorages,
|
|
||||||
'volumeNames' => $volumeNames
|
|
||||||
] = $this->generateLocalPersistentVolumes($pullRequestId);
|
|
||||||
|
|
||||||
$environmentVariables = $this->generateEnvironmentVariables($ports);
|
|
||||||
|
|
||||||
if (data_get($this, 'custom_labels')) {
|
|
||||||
$labels = collect(str($this->custom_labels)->explode(','));
|
|
||||||
$labels = $labels->filter(function ($value, $key) {
|
|
||||||
return !Str::startsWith($value, 'coolify.');
|
|
||||||
});
|
|
||||||
$this->custom_labels = $labels->implode(',');
|
|
||||||
$this->save();
|
|
||||||
} else {
|
|
||||||
$labels = collect(generateLabelsApplication($this, $this->preview));
|
|
||||||
}
|
|
||||||
if ($this->pull_request_id !== 0) {
|
|
||||||
$labels = collect(generateLabelsApplication($this, $this->preview));
|
|
||||||
}
|
|
||||||
$labels = $labels->merge(defaultLabels($this->id, $this->uuid, $this->pull_request_id))->toArray();
|
|
||||||
$docker_compose = [
|
|
||||||
'version' => '3.8',
|
|
||||||
'services' => [
|
|
||||||
$container_name => [
|
|
||||||
'image' => $productionImageName,
|
|
||||||
'container_name' => $container_name,
|
|
||||||
'restart' => RESTART_MODE,
|
|
||||||
'environment' => $environmentVariables,
|
|
||||||
'expose' => $ports,
|
|
||||||
'networks' => [
|
|
||||||
$this->destination->network,
|
|
||||||
],
|
|
||||||
'healthcheck' => [
|
|
||||||
'test' => [
|
|
||||||
'CMD-SHELL',
|
|
||||||
$this->generateHealthCheckCommands()
|
|
||||||
],
|
|
||||||
'interval' => $this->health_check_interval . 's',
|
|
||||||
'timeout' => $this->health_check_timeout . 's',
|
|
||||||
'retries' => $this->health_check_retries,
|
|
||||||
'start_period' => $this->health_check_start_period . 's'
|
|
||||||
],
|
|
||||||
'mem_limit' => $this->limits_memory,
|
|
||||||
'memswap_limit' => $this->limits_memory_swap,
|
|
||||||
'mem_swappiness' => $this->limits_memory_swappiness,
|
|
||||||
'mem_reservation' => $this->limits_memory_reservation,
|
|
||||||
'cpus' => (float) $this->limits_cpus,
|
|
||||||
'cpu_shares' => $this->limits_cpu_shares,
|
|
||||||
]
|
|
||||||
],
|
|
||||||
'networks' => [
|
|
||||||
$this->destination->network => [
|
|
||||||
'external' => true,
|
|
||||||
'name' => $this->destination->network,
|
|
||||||
'attachable' => true
|
|
||||||
]
|
|
||||||
]
|
|
||||||
];
|
|
||||||
if (!is_null($this->limits_cpuset)) {
|
|
||||||
data_set($docker_compose, "services.{$container_name}.cpuset", $this->limits_cpuset);
|
|
||||||
}
|
|
||||||
if ($server->isSwarm()) {
|
|
||||||
data_forget($docker_compose, 'services.' . $container_name . '.container_name');
|
|
||||||
data_forget($docker_compose, 'services.' . $container_name . '.expose');
|
|
||||||
data_forget($docker_compose, 'services.' . $container_name . '.restart');
|
|
||||||
|
|
||||||
data_forget($docker_compose, 'services.' . $container_name . '.mem_limit');
|
|
||||||
data_forget($docker_compose, 'services.' . $container_name . '.memswap_limit');
|
|
||||||
data_forget($docker_compose, 'services.' . $container_name . '.mem_swappiness');
|
|
||||||
data_forget($docker_compose, 'services.' . $container_name . '.mem_reservation');
|
|
||||||
data_forget($docker_compose, 'services.' . $container_name . '.cpus');
|
|
||||||
data_forget($docker_compose, 'services.' . $container_name . '.cpuset');
|
|
||||||
data_forget($docker_compose, 'services.' . $container_name . '.cpu_shares');
|
|
||||||
|
|
||||||
$docker_compose['services'][$container_name]['deploy'] = [
|
|
||||||
'placement' => [
|
|
||||||
'constraints' => [
|
|
||||||
'node.role == worker'
|
|
||||||
]
|
|
||||||
],
|
|
||||||
'mode' => 'replicated',
|
|
||||||
'replicas' => 1,
|
|
||||||
'update_config' => [
|
|
||||||
'order' => 'start-first'
|
|
||||||
],
|
|
||||||
'rollback_config' => [
|
|
||||||
'order' => 'start-first'
|
|
||||||
],
|
|
||||||
'labels' => $labels,
|
|
||||||
'resources' => [
|
|
||||||
'limits' => [
|
|
||||||
'cpus' => $this->limits_cpus,
|
|
||||||
'memory' => $this->limits_memory,
|
|
||||||
],
|
|
||||||
'reservations' => [
|
|
||||||
'cpus' => $this->limits_cpus,
|
|
||||||
'memory' => $this->limits_memory,
|
|
||||||
]
|
|
||||||
]
|
|
||||||
];
|
|
||||||
} else {
|
|
||||||
$docker_compose['services'][$container_name]['labels'] = $labels;
|
|
||||||
}
|
|
||||||
if ($server->isLogDrainEnabled() && $this->isLogDrainEnabled()) {
|
|
||||||
$docker_compose['services'][$container_name]['logging'] = [
|
|
||||||
'driver' => 'fluentd',
|
|
||||||
'options' => [
|
|
||||||
'fluentd-address' => "tcp://127.0.0.1:24224",
|
|
||||||
'fluentd-async' => "true",
|
|
||||||
'fluentd-sub-second-precision' => "true",
|
|
||||||
]
|
|
||||||
];
|
|
||||||
}
|
|
||||||
if ($this->settings->is_gpu_enabled) {
|
|
||||||
$docker_compose['services'][$container_name]['deploy']['resources']['reservations']['devices'] = [
|
|
||||||
[
|
|
||||||
'driver' => data_get($this, 'settings.gpu_driver', 'nvidia'),
|
|
||||||
'capabilities' => ['gpu'],
|
|
||||||
'options' => data_get($this, 'settings.gpu_options', [])
|
|
||||||
]
|
|
||||||
];
|
|
||||||
if (data_get($this, 'settings.gpu_count')) {
|
|
||||||
$count = data_get($this, 'settings.gpu_count');
|
|
||||||
if ($count === 'all') {
|
|
||||||
$docker_compose['services'][$container_name]['deploy']['resources']['reservations']['devices'][0]['count'] = $count;
|
|
||||||
} else {
|
|
||||||
$docker_compose['services'][$container_name]['deploy']['resources']['reservations']['devices'][0]['count'] = (int) $count;
|
|
||||||
}
|
|
||||||
} else if (data_get($this, 'settings.gpu_device_ids')) {
|
|
||||||
$docker_compose['services'][$container_name]['deploy']['resources']['reservations']['devices'][0]['ids'] = data_get($this, 'settings.gpu_device_ids');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if ($this->isHealthcheckDisabled()) {
|
|
||||||
data_forget($docker_compose, 'services.' . $container_name . '.healthcheck');
|
|
||||||
}
|
|
||||||
if (count($this->ports_mappings_array) > 0 && $this->pull_request_id === 0) {
|
|
||||||
$docker_compose['services'][$container_name]['ports'] = $this->ports_mappings_array;
|
|
||||||
}
|
|
||||||
if (count($persistentStorages) > 0) {
|
|
||||||
$docker_compose['services'][$container_name]['volumes'] = $persistentStorages;
|
|
||||||
}
|
|
||||||
if (count($volumeNames) > 0) {
|
|
||||||
$docker_compose['volumes'] = $volumeNames;
|
|
||||||
}
|
|
||||||
|
|
||||||
$docker_compose['services'][$this->uuid] = $docker_compose['services'][$container_name];
|
|
||||||
|
|
||||||
data_forget($docker_compose, 'services.' . $container_name);
|
|
||||||
|
|
||||||
$docker_compose = Yaml::dump($docker_compose, 10);
|
|
||||||
$docker_compose_base64 = base64_encode($docker_compose);
|
|
||||||
$server->executeRemoteCommand(
|
|
||||||
commands: collect([])->push([
|
|
||||||
'command' => executeInDocker($deployment->deployment_uuid, "echo '{$docker_compose_base64}' | base64 -d > {$workdir}/docker-compose.yml"),
|
|
||||||
'hidden' => true,
|
|
||||||
'ignoreErrors' => true
|
|
||||||
]),
|
|
||||||
loggingModel: $deployment
|
|
||||||
);
|
|
||||||
}
|
|
||||||
function rollingUpdateApplication(Server $server, ApplicationDeploymentQueue $deployment, string $workdir)
|
|
||||||
{
|
|
||||||
$pullRequestId = $deployment->pull_request_id;
|
|
||||||
$containerName = generateApplicationContainerName($this, $pullRequestId);
|
|
||||||
// if (count($this->ports_mappings_array) > 0) {
|
|
||||||
// $deployment->addLogEntry('Application has ports mapped to the host system, rolling update is not supported.');
|
|
||||||
$containers = getCurrentApplicationContainerStatus($server, $this->id, $pullRequestId);
|
|
||||||
// if ($pullRequestId === 0) {
|
|
||||||
// $containers = $containers->filter(function ($container) use ($containerName) {
|
|
||||||
// return data_get($container, 'Names') !== $containerName;
|
|
||||||
// });
|
|
||||||
// }
|
|
||||||
$containers->each(function ($container) use ($server, $deployment) {
|
|
||||||
$removingContainerName = data_get($container, 'Names');
|
|
||||||
$server->executeRemoteCommand(
|
|
||||||
commands: collect([])->push([
|
|
||||||
'command' => "docker rm -f $removingContainerName",
|
|
||||||
'hidden' => true,
|
|
||||||
'ignoreErrors' => true
|
|
||||||
]),
|
|
||||||
loggingModel: $deployment
|
|
||||||
);
|
|
||||||
});
|
|
||||||
// }
|
|
||||||
$server->executeRemoteCommand(
|
|
||||||
commands: collect([])->push([
|
|
||||||
'command' => executeInDocker($deployment->deployment_uuid, "docker compose --project-directory {$workdir} up --build -d"),
|
|
||||||
'hidden' => true,
|
|
||||||
'ignoreErrors' => true
|
|
||||||
]),
|
|
||||||
loggingModel: $deployment
|
|
||||||
);
|
|
||||||
$deployment->addLogEntry("New container started.");
|
|
||||||
}
|
|
||||||
function setGitImportSettings(string $deployment_uuid, string $git_clone_command)
|
|
||||||
{
|
{
|
||||||
$baseDir = $this->generateBaseDir($deployment_uuid);
|
$baseDir = $this->generateBaseDir($deployment_uuid);
|
||||||
if ($this->git_commit_sha !== 'HEAD') {
|
if ($this->git_commit_sha !== 'HEAD') {
|
||||||
@@ -905,8 +540,7 @@ class Application extends BaseModel
|
|||||||
$commands->push("echo 'Checking out $branch'");
|
$commands->push("echo 'Checking out $branch'");
|
||||||
}
|
}
|
||||||
$git_clone_command = "{$git_clone_command} && cd {$baseDir} && GIT_SSH_COMMAND=\"ssh -o ConnectTimeout=30 -p {$customPort} -o Port={$customPort} -o LogLevel=ERROR -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -i /root/.ssh/id_rsa\" git fetch origin $branch && git checkout $pr_branch_name";
|
$git_clone_command = "{$git_clone_command} && cd {$baseDir} && GIT_SSH_COMMAND=\"ssh -o ConnectTimeout=30 -p {$customPort} -o Port={$customPort} -o LogLevel=ERROR -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -i /root/.ssh/id_rsa\" git fetch origin $branch && git checkout $pr_branch_name";
|
||||||
}
|
} else if ($git_type === 'github') {
|
||||||
if ($git_type === 'github') {
|
|
||||||
$branch = "pull/{$pull_request_id}/head:$pr_branch_name";
|
$branch = "pull/{$pull_request_id}/head:$pr_branch_name";
|
||||||
if ($exec_in_docker) {
|
if ($exec_in_docker) {
|
||||||
$commands->push(executeInDocker($deployment_uuid, "echo 'Checking out $branch'"));
|
$commands->push(executeInDocker($deployment_uuid, "echo 'Checking out $branch'"));
|
||||||
@@ -914,6 +548,13 @@ class Application extends BaseModel
|
|||||||
$commands->push("echo 'Checking out $branch'");
|
$commands->push("echo 'Checking out $branch'");
|
||||||
}
|
}
|
||||||
$git_clone_command = "{$git_clone_command} && cd {$baseDir} && GIT_SSH_COMMAND=\"ssh -o ConnectTimeout=30 -p {$customPort} -o Port={$customPort} -o LogLevel=ERROR -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -i /root/.ssh/id_rsa\" git fetch origin $branch && git checkout $pr_branch_name";
|
$git_clone_command = "{$git_clone_command} && cd {$baseDir} && GIT_SSH_COMMAND=\"ssh -o ConnectTimeout=30 -p {$customPort} -o Port={$customPort} -o LogLevel=ERROR -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -i /root/.ssh/id_rsa\" git fetch origin $branch && git checkout $pr_branch_name";
|
||||||
|
} else if ($git_type === 'bitbucket') {
|
||||||
|
if ($exec_in_docker) {
|
||||||
|
$commands->push(executeInDocker($deployment_uuid, "echo 'Checking out $branch'"));
|
||||||
|
} else {
|
||||||
|
$commands->push("echo 'Checking out $branch'");
|
||||||
|
}
|
||||||
|
$git_clone_command = "{$git_clone_command} && cd {$baseDir} && GIT_SSH_COMMAND=\"ssh -o ConnectTimeout=30 -p {$customPort} -o Port={$customPort} -o LogLevel=ERROR -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -i /root/.ssh/id_rsa\" git checkout $commit";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -972,34 +613,6 @@ class Application extends BaseModel
|
|||||||
];
|
];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
public function prepareHelperImage(string $deploymentUuid)
|
|
||||||
{
|
|
||||||
$basedir = $this->generateBaseDir($deploymentUuid);
|
|
||||||
$helperImage = config('coolify.helper_image');
|
|
||||||
$server = data_get($this, 'destination.server');
|
|
||||||
$network = data_get($this, 'destination.network');
|
|
||||||
|
|
||||||
$serverUserHomeDir = instant_remote_process(["echo \$HOME"], $server);
|
|
||||||
$dockerConfigFileExists = instant_remote_process(["test -f {$serverUserHomeDir}/.docker/config.json && echo 'OK' || echo 'NOK'"], $server);
|
|
||||||
|
|
||||||
$commands = collect([]);
|
|
||||||
if ($dockerConfigFileExists === 'OK') {
|
|
||||||
$commands->push([
|
|
||||||
"command" => "docker run -d --network $network --name $deploymentUuid --rm -v {$serverUserHomeDir}/.docker/config.json:/root/.docker/config.json:ro -v /var/run/docker.sock:/var/run/docker.sock $helperImage",
|
|
||||||
"hidden" => true,
|
|
||||||
]);
|
|
||||||
} else {
|
|
||||||
$commands->push([
|
|
||||||
"command" => "docker run -d --network {$network} --name {$deploymentUuid} --rm -v /var/run/docker.sock:/var/run/docker.sock {$helperImage}",
|
|
||||||
"hidden" => true,
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
$commands->push([
|
|
||||||
"command" => executeInDocker($deploymentUuid, "mkdir -p {$basedir}"),
|
|
||||||
"hidden" => true,
|
|
||||||
]);
|
|
||||||
return $commands;
|
|
||||||
}
|
|
||||||
function parseCompose(int $pull_request_id = 0)
|
function parseCompose(int $pull_request_id = 0)
|
||||||
{
|
{
|
||||||
if ($this->docker_compose_raw) {
|
if ($this->docker_compose_raw) {
|
||||||
@@ -1110,4 +723,12 @@ class Application extends BaseModel
|
|||||||
$this->save();
|
$this->save();
|
||||||
return $customLabels;
|
return $customLabels;
|
||||||
}
|
}
|
||||||
|
public function fqdns(): Attribute
|
||||||
|
{
|
||||||
|
return Attribute::make(
|
||||||
|
get: fn () => is_null($this->fqdn)
|
||||||
|
? []
|
||||||
|
: explode(',', $this->fqdn),
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -91,7 +91,7 @@ class EnvironmentVariable extends Model
|
|||||||
}
|
}
|
||||||
private function get_real_environment_variables(?string $environment_variable = null, $resource = null): string|null
|
private function get_real_environment_variables(?string $environment_variable = null, $resource = null): string|null
|
||||||
{
|
{
|
||||||
if (!$environment_variable) {
|
if (!$environment_variable || !$resource) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
$environment_variable = trim($environment_variable);
|
$environment_variable = trim($environment_variable);
|
||||||
@@ -100,6 +100,9 @@ class EnvironmentVariable extends Model
|
|||||||
$variable = Str::after($environment_variable, "{$type}.");
|
$variable = Str::after($environment_variable, "{$type}.");
|
||||||
$variable = Str::before($variable, '}}');
|
$variable = Str::before($variable, '}}');
|
||||||
$variable = Str::of($variable)->trim()->value;
|
$variable = Str::of($variable)->trim()->value;
|
||||||
|
if (!collect(SHARED_VARIABLE_TYPES)->contains($type)) {
|
||||||
|
return $variable;
|
||||||
|
}
|
||||||
if ($type === 'environment') {
|
if ($type === 'environment') {
|
||||||
$id = $resource->environment->id;
|
$id = $resource->environment->id;
|
||||||
} else if ($type === 'project') {
|
} else if ($type === 'project') {
|
||||||
|
|||||||
@@ -64,10 +64,13 @@ class Project extends BaseModel
|
|||||||
}
|
}
|
||||||
public function mysqls()
|
public function mysqls()
|
||||||
{
|
{
|
||||||
return $this->hasMany(StandaloneMysql::class, Environment::class);
|
return $this->hasManyThrough(StandaloneMysql::class, Environment::class);
|
||||||
}
|
}
|
||||||
public function mariadbs()
|
public function mariadbs()
|
||||||
{
|
{
|
||||||
return $this->hasMany(StandaloneMariadb::class, Environment::class);
|
return $this->hasManyThrough(StandaloneMariadb::class, Environment::class);
|
||||||
|
}
|
||||||
|
public function resource_count() {
|
||||||
|
return $this->applications()->count() + $this->postgresqls()->count() + $this->redis()->count() + $this->mongodbs()->count() + $this->mysqls()->count() + $this->mariadbs()->count();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,16 +2,13 @@
|
|||||||
|
|
||||||
namespace App\Models;
|
namespace App\Models;
|
||||||
|
|
||||||
use App\Enums\ApplicationDeploymentStatus;
|
use App\Actions\Server\InstallDocker;
|
||||||
use App\Enums\ProxyStatus;
|
use App\Enums\ProxyStatus;
|
||||||
use App\Enums\ProxyTypes;
|
use App\Enums\ProxyTypes;
|
||||||
use App\Notifications\Server\Revived;
|
use App\Notifications\Server\Revived;
|
||||||
use App\Notifications\Server\Unreachable;
|
use App\Notifications\Server\Unreachable;
|
||||||
use Illuminate\Database\Eloquent\Builder;
|
use Illuminate\Database\Eloquent\Builder;
|
||||||
use Illuminate\Database\Eloquent\Casts\Attribute;
|
use Illuminate\Database\Eloquent\Casts\Attribute;
|
||||||
use Illuminate\Support\Carbon;
|
|
||||||
use Illuminate\Support\Collection;
|
|
||||||
use Illuminate\Support\Facades\Process;
|
|
||||||
use Spatie\SchemalessAttributes\Casts\SchemalessAttributes;
|
use Spatie\SchemalessAttributes\Casts\SchemalessAttributes;
|
||||||
use Spatie\SchemalessAttributes\SchemalessAttributesTrait;
|
use Spatie\SchemalessAttributes\SchemalessAttributesTrait;
|
||||||
use Illuminate\Support\Str;
|
use Illuminate\Support\Str;
|
||||||
@@ -335,20 +332,6 @@ class Server extends BaseModel
|
|||||||
if ($this->proxyType() === ProxyTypes::NONE->value || $this->settings->is_build_server) {
|
if ($this->proxyType() === ProxyTypes::NONE->value || $this->settings->is_build_server) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
// foreach ($this->applications() as $application) {
|
|
||||||
// if (data_get($application, 'fqdn')) {
|
|
||||||
// $shouldRun = true;
|
|
||||||
// break;
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// ray($this->services()->get());
|
|
||||||
|
|
||||||
// if ($this->id === 0) {
|
|
||||||
// $settings = InstanceSettings::get();
|
|
||||||
// if (data_get($settings, 'fqdn')) {
|
|
||||||
// $shouldRun = true;
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
public function isFunctional()
|
public function isFunctional()
|
||||||
@@ -429,6 +412,11 @@ class Server extends BaseModel
|
|||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
public function installDocker()
|
||||||
|
{
|
||||||
|
$activity = InstallDocker::run($this);
|
||||||
|
return $activity;
|
||||||
|
}
|
||||||
public function validateDockerEngine($throwError = false)
|
public function validateDockerEngine($throwError = false)
|
||||||
{
|
{
|
||||||
$dockerBinary = instant_remote_process(["command -v docker"], $this, false);
|
$dockerBinary = instant_remote_process(["command -v docker"], $this, false);
|
||||||
@@ -483,153 +471,4 @@ class Server extends BaseModel
|
|||||||
return instant_remote_process(["docker network create coolify --attachable >/dev/null 2>&1 || true"], $this, false);
|
return instant_remote_process(["docker network create coolify --attachable >/dev/null 2>&1 || true"], $this, false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
public function executeRemoteCommand(Collection $commands, ?ApplicationDeploymentQueue $loggingModel = null)
|
|
||||||
{
|
|
||||||
static::$batch_counter++;
|
|
||||||
foreach ($commands as $command) {
|
|
||||||
$realCommand = data_get($command, 'command');
|
|
||||||
if (is_null($realCommand)) {
|
|
||||||
throw new \RuntimeException('Command is not set');
|
|
||||||
}
|
|
||||||
$hidden = data_get($command, 'hidden', false);
|
|
||||||
$ignoreErrors = data_get($command, 'ignoreErrors', false);
|
|
||||||
$customOutputType = data_get($command, 'customOutputType');
|
|
||||||
$name = data_get($command, 'name');
|
|
||||||
$remoteCommand = generateSshCommand($this, $realCommand);
|
|
||||||
|
|
||||||
$process = Process::timeout(3600)->idleTimeout(3600)->start($remoteCommand, function (string $type, string $output) use ($realCommand, $hidden, $customOutputType, $loggingModel, $name) {
|
|
||||||
$output = str($output)->trim();
|
|
||||||
if ($output->startsWith('╔')) {
|
|
||||||
$output = "\n" . $output;
|
|
||||||
}
|
|
||||||
$newLogEntry = [
|
|
||||||
'command' => remove_iip($realCommand),
|
|
||||||
'output' => remove_iip($output),
|
|
||||||
'type' => $customOutputType ?? $type === 'err' ? 'stderr' : 'stdout',
|
|
||||||
'timestamp' => Carbon::now('UTC'),
|
|
||||||
'hidden' => $hidden,
|
|
||||||
'batch' => static::$batch_counter,
|
|
||||||
];
|
|
||||||
if ($loggingModel) {
|
|
||||||
if (!$loggingModel->logs) {
|
|
||||||
$newLogEntry['order'] = 1;
|
|
||||||
} else {
|
|
||||||
$previousLogs = json_decode($loggingModel->logs, associative: true, flags: JSON_THROW_ON_ERROR);
|
|
||||||
$newLogEntry['order'] = count($previousLogs) + 1;
|
|
||||||
}
|
|
||||||
if ($name) {
|
|
||||||
$newLogEntry['name'] = $name;
|
|
||||||
}
|
|
||||||
|
|
||||||
$previousLogs[] = $newLogEntry;
|
|
||||||
$loggingModel->logs = json_encode($previousLogs, flags: JSON_THROW_ON_ERROR);
|
|
||||||
$loggingModel->save();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
if ($loggingModel) {
|
|
||||||
$loggingModel->update([
|
|
||||||
'current_process_id' => $process->id(),
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
$processResult = $process->wait();
|
|
||||||
if ($processResult->exitCode() !== 0) {
|
|
||||||
if (!$ignoreErrors) {
|
|
||||||
if ($loggingModel) {
|
|
||||||
$status = ApplicationDeploymentStatus::FAILED->value;
|
|
||||||
$loggingModel->status = $status;
|
|
||||||
$loggingModel->save();
|
|
||||||
}
|
|
||||||
throw new \RuntimeException($processResult->errorOutput());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
public function stopApplicationRelatedRunningContainers(string $applicationId, string $containerName)
|
|
||||||
{
|
|
||||||
$containers = getCurrentApplicationContainerStatus($this, $applicationId, 0);
|
|
||||||
$containers = $containers->filter(function ($container) use ($containerName) {
|
|
||||||
return data_get($container, 'Names') !== $containerName;
|
|
||||||
});
|
|
||||||
$containers->each(function ($container) {
|
|
||||||
$removableContainer = data_get($container, 'Names');
|
|
||||||
$this->server->executeRemoteCommand(
|
|
||||||
commands: collect([
|
|
||||||
'command' => "docker rm -f $removableContainer >/dev/null 2>&1",
|
|
||||||
'hidden' => true,
|
|
||||||
'ignoreErrors' => true
|
|
||||||
]),
|
|
||||||
loggingModel: $this->deploymentQueueEntry
|
|
||||||
);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
public function getHostIPMappings($network)
|
|
||||||
{
|
|
||||||
$addHosts = null;
|
|
||||||
$allContainers = instant_remote_process(["docker network inspect {$network} -f '{{json .Containers}}' "], $this);
|
|
||||||
if (!is_null($allContainers)) {
|
|
||||||
$allContainers = format_docker_command_output_to_json($allContainers);
|
|
||||||
$ips = collect([]);
|
|
||||||
if (count($allContainers) > 0) {
|
|
||||||
$allContainers = $allContainers[0];
|
|
||||||
foreach ($allContainers as $container) {
|
|
||||||
$containerName = data_get($container, 'Name');
|
|
||||||
if ($containerName === 'coolify-proxy') {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
$containerIp = data_get($container, 'IPv4Address');
|
|
||||||
if ($containerName && $containerIp) {
|
|
||||||
$containerIp = str($containerIp)->before('/');
|
|
||||||
$ips->put($containerName, $containerIp->value());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
$addHosts = $ips->map(function ($ip, $name) {
|
|
||||||
return "--add-host $name:$ip";
|
|
||||||
})->implode(' ');
|
|
||||||
}
|
|
||||||
return $addHosts;
|
|
||||||
}
|
|
||||||
public function checkIfDockerImageExists(string $imageName, ApplicationDeploymentQueue $deployment)
|
|
||||||
{
|
|
||||||
$this->executeRemoteCommand(
|
|
||||||
commands: collect([
|
|
||||||
[
|
|
||||||
"name" => "local_image_found",
|
|
||||||
"command" => "docker images -q {$imageName} 2>/dev/null",
|
|
||||||
"hidden" => true,
|
|
||||||
]
|
|
||||||
]),
|
|
||||||
loggingModel: $deployment
|
|
||||||
);
|
|
||||||
if (str($deployment->getOutput('local_image_found'))->isEmpty()) {
|
|
||||||
$this->executeRemoteCommand(
|
|
||||||
commands: collect([
|
|
||||||
[
|
|
||||||
"command" => "docker pull {$imageName} 2>/dev/null",
|
|
||||||
"ignoreErrors" => true,
|
|
||||||
"hidden" => true
|
|
||||||
],
|
|
||||||
[
|
|
||||||
"name" => "local_image_found",
|
|
||||||
"command" => "docker images -q {$imageName} 2>/dev/null",
|
|
||||||
"hidden" => true,
|
|
||||||
]
|
|
||||||
]),
|
|
||||||
loggingModel: $deployment
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
public function createWorkDirForDeployment(string $workdir, ApplicationDeploymentQueue $deployment)
|
|
||||||
{
|
|
||||||
$this->executeRemoteCommand(
|
|
||||||
commands: collect([
|
|
||||||
[
|
|
||||||
"command" => executeInDocker($deployment->deployment_uuid, "mkdir -p {$workdir}"),
|
|
||||||
"ignoreErrors" => true,
|
|
||||||
"hidden" => true
|
|
||||||
],
|
|
||||||
]),
|
|
||||||
loggingModel: $deployment
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,13 +2,11 @@
|
|||||||
|
|
||||||
namespace App\Models;
|
namespace App\Models;
|
||||||
|
|
||||||
use App\Actions\Service\DeleteService;
|
|
||||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||||
use Illuminate\Database\Eloquent\Relations\HasMany;
|
use Illuminate\Database\Eloquent\Relations\HasMany;
|
||||||
use Illuminate\Database\Eloquent\SoftDeletes;
|
use Illuminate\Database\Eloquent\SoftDeletes;
|
||||||
use Illuminate\Support\Collection;
|
use Illuminate\Support\Collection;
|
||||||
use Illuminate\Support\Str;
|
use Illuminate\Support\Str;
|
||||||
use Symfony\Component\Yaml\Yaml;
|
|
||||||
|
|
||||||
class Service extends BaseModel
|
class Service extends BaseModel
|
||||||
{
|
{
|
||||||
@@ -18,10 +16,18 @@ class Service extends BaseModel
|
|||||||
{
|
{
|
||||||
return 'service';
|
return 'service';
|
||||||
}
|
}
|
||||||
|
public function project()
|
||||||
|
{
|
||||||
|
return data_get($this, 'environment.project');
|
||||||
|
}
|
||||||
public function team()
|
public function team()
|
||||||
{
|
{
|
||||||
return data_get($this, 'environment.project.team');
|
return data_get($this, 'environment.project.team');
|
||||||
}
|
}
|
||||||
|
public function tags()
|
||||||
|
{
|
||||||
|
return $this->morphToMany(Tag::class, 'taggable');
|
||||||
|
}
|
||||||
public function extraFields()
|
public function extraFields()
|
||||||
{
|
{
|
||||||
$fields = collect([]);
|
$fields = collect([]);
|
||||||
|
|||||||
@@ -5,7 +5,6 @@ namespace App\Models;
|
|||||||
use Illuminate\Database\Eloquent\Casts\Attribute;
|
use Illuminate\Database\Eloquent\Casts\Attribute;
|
||||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||||
use Illuminate\Database\Eloquent\SoftDeletes;
|
use Illuminate\Database\Eloquent\SoftDeletes;
|
||||||
use Illuminate\Support\Facades\Cache;
|
|
||||||
|
|
||||||
class ServiceApplication extends BaseModel
|
class ServiceApplication extends BaseModel
|
||||||
{
|
{
|
||||||
@@ -15,6 +14,7 @@ class ServiceApplication extends BaseModel
|
|||||||
protected static function booted()
|
protected static function booted()
|
||||||
{
|
{
|
||||||
static::deleting(function ($service) {
|
static::deleting(function ($service) {
|
||||||
|
$service->update(['fqdn' => null]);
|
||||||
$service->persistentStorages()->delete();
|
$service->persistentStorages()->delete();
|
||||||
$service->fileStorages()->delete();
|
$service->fileStorages()->delete();
|
||||||
});
|
});
|
||||||
@@ -55,7 +55,6 @@ class ServiceApplication extends BaseModel
|
|||||||
get: fn () => is_null($this->fqdn)
|
get: fn () => is_null($this->fqdn)
|
||||||
? []
|
? []
|
||||||
: explode(',', $this->fqdn),
|
: explode(',', $this->fqdn),
|
||||||
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
public function getFilesFromServer(bool $isInit = false)
|
public function getFilesFromServer(bool $isInit = false)
|
||||||
|
|||||||
@@ -40,8 +40,14 @@ class StandaloneMariadb extends BaseModel
|
|||||||
$database->scheduledBackups()->delete();
|
$database->scheduledBackups()->delete();
|
||||||
$database->persistentStorages()->delete();
|
$database->persistentStorages()->delete();
|
||||||
$database->environment_variables()->delete();
|
$database->environment_variables()->delete();
|
||||||
|
$database->tags()->detach();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function tags()
|
||||||
|
{
|
||||||
|
return $this->morphToMany(Tag::class, 'taggable');
|
||||||
|
}
|
||||||
public function team()
|
public function team()
|
||||||
{
|
{
|
||||||
return data_get($this, 'environment.project.team');
|
return data_get($this, 'environment.project.team');
|
||||||
|
|||||||
@@ -43,8 +43,14 @@ class StandaloneMongodb extends BaseModel
|
|||||||
$database->scheduledBackups()->delete();
|
$database->scheduledBackups()->delete();
|
||||||
$database->persistentStorages()->delete();
|
$database->persistentStorages()->delete();
|
||||||
$database->environment_variables()->delete();
|
$database->environment_variables()->delete();
|
||||||
|
$database->tags()->detach();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function tags()
|
||||||
|
{
|
||||||
|
return $this->morphToMany(Tag::class, 'taggable');
|
||||||
|
}
|
||||||
public function team()
|
public function team()
|
||||||
{
|
{
|
||||||
return data_get($this, 'environment.project.team');
|
return data_get($this, 'environment.project.team');
|
||||||
|
|||||||
@@ -40,8 +40,14 @@ class StandaloneMysql extends BaseModel
|
|||||||
$database->scheduledBackups()->delete();
|
$database->scheduledBackups()->delete();
|
||||||
$database->persistentStorages()->delete();
|
$database->persistentStorages()->delete();
|
||||||
$database->environment_variables()->delete();
|
$database->environment_variables()->delete();
|
||||||
|
$database->tags()->detach();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function tags()
|
||||||
|
{
|
||||||
|
return $this->morphToMany(Tag::class, 'taggable');
|
||||||
|
}
|
||||||
public function team()
|
public function team()
|
||||||
{
|
{
|
||||||
return data_get($this, 'environment.project.team');
|
return data_get($this, 'environment.project.team');
|
||||||
|
|||||||
@@ -40,8 +40,14 @@ class StandalonePostgresql extends BaseModel
|
|||||||
$database->scheduledBackups()->delete();
|
$database->scheduledBackups()->delete();
|
||||||
$database->persistentStorages()->delete();
|
$database->persistentStorages()->delete();
|
||||||
$database->environment_variables()->delete();
|
$database->environment_variables()->delete();
|
||||||
|
$database->tags()->detach();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function tags()
|
||||||
|
{
|
||||||
|
return $this->morphToMany(Tag::class, 'taggable');
|
||||||
|
}
|
||||||
public function link()
|
public function link()
|
||||||
{
|
{
|
||||||
if (data_get($this, 'environment.project.uuid')) {
|
if (data_get($this, 'environment.project.uuid')) {
|
||||||
|
|||||||
@@ -35,8 +35,14 @@ class StandaloneRedis extends BaseModel
|
|||||||
}
|
}
|
||||||
$database->persistentStorages()->delete();
|
$database->persistentStorages()->delete();
|
||||||
$database->environment_variables()->delete();
|
$database->environment_variables()->delete();
|
||||||
|
$database->tags()->detach();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function tags()
|
||||||
|
{
|
||||||
|
return $this->morphToMany(Tag::class, 'taggable');
|
||||||
|
}
|
||||||
public function team()
|
public function team()
|
||||||
{
|
{
|
||||||
return data_get($this, 'environment.project.team');
|
return data_get($this, 'environment.project.team');
|
||||||
|
|||||||
31
app/Models/Tag.php
Normal file
31
app/Models/Tag.php
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Models;
|
||||||
|
|
||||||
|
use Illuminate\Database\Eloquent\Casts\Attribute;
|
||||||
|
|
||||||
|
class Tag extends BaseModel
|
||||||
|
{
|
||||||
|
protected $guarded = [];
|
||||||
|
|
||||||
|
|
||||||
|
public function name(): Attribute
|
||||||
|
{
|
||||||
|
return Attribute::make(
|
||||||
|
get: fn ($value) => strtolower($value),
|
||||||
|
set: fn ($value) => strtolower($value)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
static public function ownedByCurrentTeam()
|
||||||
|
{
|
||||||
|
return Tag::whereTeamId(currentTeam()->id)->orderBy('name');
|
||||||
|
}
|
||||||
|
public function applications()
|
||||||
|
{
|
||||||
|
return $this->morphedByMany(Application::class, 'taggable');
|
||||||
|
}
|
||||||
|
public function services()
|
||||||
|
{
|
||||||
|
return $this->morphedByMany(Service::class, 'taggable');
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,77 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
namespace App\Traits;
|
|
||||||
|
|
||||||
use App\Enums\ApplicationDeploymentStatus;
|
|
||||||
use App\Models\Server;
|
|
||||||
use Carbon\Carbon;
|
|
||||||
use Illuminate\Support\Collection;
|
|
||||||
use Illuminate\Support\Facades\Process;
|
|
||||||
|
|
||||||
trait ExecuteRemoteCommandNew
|
|
||||||
{
|
|
||||||
public static $batch_counter = 0;
|
|
||||||
public function executeRemoteCommand(Server $server, $logModel, $commands)
|
|
||||||
{
|
|
||||||
static::$batch_counter++;
|
|
||||||
if ($commands instanceof Collection) {
|
|
||||||
$commandsText = $commands;
|
|
||||||
} else {
|
|
||||||
$commandsText = collect($commands);
|
|
||||||
}
|
|
||||||
$commandsText->each(function ($singleCommand) use ($server, $logModel) {
|
|
||||||
$command = data_get($singleCommand, 'command') ?? $singleCommand[0] ?? null;
|
|
||||||
if ($command === null) {
|
|
||||||
throw new \RuntimeException('Command is not set');
|
|
||||||
}
|
|
||||||
$hidden = data_get($singleCommand, 'hidden', false);
|
|
||||||
$customType = data_get($singleCommand, 'type');
|
|
||||||
$ignoreErrors = data_get($singleCommand, 'ignore_errors', false);
|
|
||||||
$save = data_get($singleCommand, 'save');
|
|
||||||
|
|
||||||
$remote_command = generateSshCommand($server, $command);
|
|
||||||
$process = Process::timeout(3600)->idleTimeout(3600)->start($remote_command, function (string $type, string $output) use ($command, $hidden, $customType, $logModel, $save) {
|
|
||||||
$output = str($output)->trim();
|
|
||||||
if ($output->startsWith('╔')) {
|
|
||||||
$output = "\n" . $output;
|
|
||||||
}
|
|
||||||
$newLogEntry = [
|
|
||||||
'command' => remove_iip($command),
|
|
||||||
'output' => remove_iip($output),
|
|
||||||
'type' => $customType ?? $type === 'err' ? 'stderr' : 'stdout',
|
|
||||||
'timestamp' => Carbon::now('UTC'),
|
|
||||||
'hidden' => $hidden,
|
|
||||||
'batch' => static::$batch_counter,
|
|
||||||
];
|
|
||||||
|
|
||||||
if (!$logModel->logs) {
|
|
||||||
$newLogEntry['order'] = 1;
|
|
||||||
} else {
|
|
||||||
$previousLogs = json_decode($logModel->logs, associative: true, flags: JSON_THROW_ON_ERROR);
|
|
||||||
$newLogEntry['order'] = count($previousLogs) + 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
$previousLogs[] = $newLogEntry;
|
|
||||||
$logModel->logs = json_encode($previousLogs, flags: JSON_THROW_ON_ERROR);
|
|
||||||
$logModel->save();
|
|
||||||
|
|
||||||
if ($save) {
|
|
||||||
$this->remoteCommandOutputs[$save] = str($output)->trim();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
$logModel->update([
|
|
||||||
'current_process_id' => $process->id(),
|
|
||||||
]);
|
|
||||||
|
|
||||||
$processResult = $process->wait();
|
|
||||||
if ($processResult->exitCode() !== 0) {
|
|
||||||
if (!$ignoreErrors) {
|
|
||||||
$status = ApplicationDeploymentStatus::FAILED->value;
|
|
||||||
$logModel->status = $status;
|
|
||||||
$logModel->save();
|
|
||||||
throw new \RuntimeException($processResult->errorOutput());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,26 +1,35 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
|
use App\Enums\ApplicationDeploymentStatus;
|
||||||
use App\Jobs\ApplicationDeploymentJob;
|
use App\Jobs\ApplicationDeploymentJob;
|
||||||
use App\Jobs\ApplicationDeploymentNewJob;
|
|
||||||
use App\Models\Application;
|
use App\Models\Application;
|
||||||
use App\Models\ApplicationDeploymentQueue;
|
use App\Models\ApplicationDeploymentQueue;
|
||||||
use App\Models\ApplicationPreview;
|
|
||||||
use App\Models\Server;
|
use App\Models\Server;
|
||||||
|
use App\Models\StandaloneDocker;
|
||||||
use Spatie\Url\Url;
|
use Spatie\Url\Url;
|
||||||
use Symfony\Component\Yaml\Yaml;
|
|
||||||
|
|
||||||
function queue_application_deployment(Application $application, string $deployment_uuid, int | null $pull_request_id = 0, string $commit = 'HEAD', bool $force_rebuild = false, bool $is_webhook = false, bool $restart_only = false, ?string $git_type = null, bool $is_new_deployment = false)
|
function queue_application_deployment(Application $application, string $deployment_uuid, int | null $pull_request_id = 0, string $commit = 'HEAD', bool $force_rebuild = false, bool $is_webhook = false, bool $restart_only = false, ?string $git_type = null, bool $no_questions_asked = false, Server $server = null, StandaloneDocker $destination = null)
|
||||||
{
|
{
|
||||||
$application_id = $application->id;
|
$application_id = $application->id;
|
||||||
$deployment_link = Url::fromString($application->link() . "/deployment/{$deployment_uuid}");
|
$deployment_link = Url::fromString($application->link() . "/deployment/{$deployment_uuid}");
|
||||||
$deployment_url = $deployment_link->getPath();
|
$deployment_url = $deployment_link->getPath();
|
||||||
$server_id = $application->destination->server->id;
|
$server_id = $application->destination->server->id;
|
||||||
$server_name = $application->destination->server->name;
|
$server_name = $application->destination->server->name;
|
||||||
|
$destination_id = $application->destination->id;
|
||||||
|
|
||||||
|
if ($server) {
|
||||||
|
$server_id = $server->id;
|
||||||
|
$server_name = $server->name;
|
||||||
|
}
|
||||||
|
if ($destination) {
|
||||||
|
$destination_id = $destination->id;
|
||||||
|
}
|
||||||
$deployment = ApplicationDeploymentQueue::create([
|
$deployment = ApplicationDeploymentQueue::create([
|
||||||
'application_id' => $application_id,
|
'application_id' => $application_id,
|
||||||
'application_name' => $application->name,
|
'application_name' => $application->name,
|
||||||
'server_id' => $server_id,
|
'server_id' => $server_id,
|
||||||
'server_name' => $server_name,
|
'server_name' => $server_name,
|
||||||
|
'destination_id' => $destination_id,
|
||||||
'deployment_uuid' => $deployment_uuid,
|
'deployment_uuid' => $deployment_uuid,
|
||||||
'deployment_url' => $deployment_url,
|
'deployment_url' => $deployment_url,
|
||||||
'pull_request_id' => $pull_request_id,
|
'pull_request_id' => $pull_request_id,
|
||||||
@@ -31,13 +40,54 @@ function queue_application_deployment(Application $application, string $deployme
|
|||||||
'git_type' => $git_type
|
'git_type' => $git_type
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
if ($no_questions_asked) {
|
||||||
|
$deployment->update([
|
||||||
|
'status' => ApplicationDeploymentStatus::IN_PROGRESS->value,
|
||||||
|
]);
|
||||||
|
dispatch(new ApplicationDeploymentJob(
|
||||||
|
application_deployment_queue_id: $deployment->id,
|
||||||
|
));
|
||||||
|
} else if (next_queuable($server_id, $application_id)) {
|
||||||
|
$deployment->update([
|
||||||
|
'status' => ApplicationDeploymentStatus::IN_PROGRESS->value,
|
||||||
|
]);
|
||||||
|
dispatch(new ApplicationDeploymentJob(
|
||||||
|
application_deployment_queue_id: $deployment->id,
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
function force_start_deployment(ApplicationDeploymentQueue $deployment)
|
||||||
|
{
|
||||||
|
$deployment->update([
|
||||||
|
'status' => ApplicationDeploymentStatus::IN_PROGRESS->value,
|
||||||
|
]);
|
||||||
|
dispatch(new ApplicationDeploymentJob(
|
||||||
|
application_deployment_queue_id: $deployment->id,
|
||||||
|
));
|
||||||
|
}
|
||||||
|
function queue_next_deployment(Application $application)
|
||||||
|
{
|
||||||
|
$server_id = $application->destination->server_id;
|
||||||
|
$next_found = ApplicationDeploymentQueue::where('server_id', $server_id)->where('status', 'queued')->get()->sortBy('created_at')->first();
|
||||||
|
if ($next_found) {
|
||||||
|
$next_found->update([
|
||||||
|
'status' => ApplicationDeploymentStatus::IN_PROGRESS->value,
|
||||||
|
]);
|
||||||
|
dispatch(new ApplicationDeploymentJob(
|
||||||
|
application_deployment_queue_id: $next_found->id,
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function next_queuable(string $server_id, string $application_id): bool
|
||||||
|
{
|
||||||
$deployments = ApplicationDeploymentQueue::where('server_id', $server_id)->whereIn('status', ['in_progress', 'queued'])->get()->sortByDesc('created_at');
|
$deployments = ApplicationDeploymentQueue::where('server_id', $server_id)->whereIn('status', ['in_progress', 'queued'])->get()->sortByDesc('created_at');
|
||||||
$same_application_deployments = $deployments->where('application_id', $application_id);
|
$same_application_deployments = $deployments->where('application_id', $application_id);
|
||||||
$in_progress = $same_application_deployments->filter(function ($value, $key) {
|
$in_progress = $same_application_deployments->filter(function ($value, $key) {
|
||||||
return $value->status === 'in_progress';
|
return $value->status === 'in_progress';
|
||||||
});
|
});
|
||||||
if ($in_progress->count() > 0) {
|
if ($in_progress->count() > 0) {
|
||||||
return;
|
return false;
|
||||||
}
|
}
|
||||||
$server = Server::find($server_id);
|
$server = Server::find($server_id);
|
||||||
$concurrent_builds = $server->settings->concurrent_builds;
|
$concurrent_builds = $server->settings->concurrent_builds;
|
||||||
@@ -45,296 +95,7 @@ function queue_application_deployment(Application $application, string $deployme
|
|||||||
ray("serverId:{$server->id}", "concurrentBuilds:{$concurrent_builds}", "deployments:{$deployments->count()}", "sameApplicationDeployments:{$same_application_deployments->count()}");
|
ray("serverId:{$server->id}", "concurrentBuilds:{$concurrent_builds}", "deployments:{$deployments->count()}", "sameApplicationDeployments:{$same_application_deployments->count()}");
|
||||||
|
|
||||||
if ($deployments->count() > $concurrent_builds) {
|
if ($deployments->count() > $concurrent_builds) {
|
||||||
return;
|
return false;
|
||||||
}
|
|
||||||
if ($is_new_deployment) {
|
|
||||||
dispatch(new ApplicationDeploymentNewJob(
|
|
||||||
deployment: $deployment,
|
|
||||||
application: Application::find($application_id)
|
|
||||||
));
|
|
||||||
} else {
|
|
||||||
dispatch(new ApplicationDeploymentJob(
|
|
||||||
application_deployment_queue_id: $deployment->id,
|
|
||||||
));
|
|
||||||
}
|
}
|
||||||
}
|
return true;
|
||||||
|
|
||||||
function queue_next_deployment(Application $application, bool $isNew = false)
|
|
||||||
{
|
|
||||||
$server_id = $application->destination->server_id;
|
|
||||||
$next_found = ApplicationDeploymentQueue::where('server_id', $server_id)->where('status', 'queued')->get()->sortBy('created_at')->first();;
|
|
||||||
// ray($next_found, $server_id);
|
|
||||||
if ($next_found) {
|
|
||||||
if ($isNew) {
|
|
||||||
dispatch(new ApplicationDeploymentNewJob(
|
|
||||||
deployment: $next_found,
|
|
||||||
application: $application
|
|
||||||
));
|
|
||||||
} else {
|
|
||||||
dispatch(new ApplicationDeploymentJob(
|
|
||||||
application_deployment_queue_id: $next_found->id,
|
|
||||||
));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// Deployment things
|
|
||||||
function generateHostIpMapping(Server $server, string $network)
|
|
||||||
{
|
|
||||||
// Generate custom host<->ip hostnames
|
|
||||||
$allContainers = instant_remote_process(["docker network inspect {$network} -f '{{json .Containers}}' "], $server);
|
|
||||||
$allContainers = format_docker_command_output_to_json($allContainers);
|
|
||||||
$ips = collect([]);
|
|
||||||
if (count($allContainers) > 0) {
|
|
||||||
$allContainers = $allContainers[0];
|
|
||||||
foreach ($allContainers as $container) {
|
|
||||||
$containerName = data_get($container, 'Name');
|
|
||||||
if ($containerName === 'coolify-proxy') {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
$containerIp = data_get($container, 'IPv4Address');
|
|
||||||
if ($containerName && $containerIp) {
|
|
||||||
$containerIp = str($containerIp)->before('/');
|
|
||||||
$ips->put($containerName, $containerIp->value());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return $ips->map(function ($ip, $name) {
|
|
||||||
return "--add-host $name:$ip";
|
|
||||||
})->implode(' ');
|
|
||||||
}
|
|
||||||
|
|
||||||
function generateBaseDir(string $deplyomentUuid)
|
|
||||||
{
|
|
||||||
return "/artifacts/$deplyomentUuid";
|
|
||||||
}
|
|
||||||
function generateWorkdir(string $deplyomentUuid, Application $application)
|
|
||||||
{
|
|
||||||
return generateBaseDir($deplyomentUuid) . rtrim($application->base_directory, '/');
|
|
||||||
}
|
|
||||||
|
|
||||||
function prepareHelperContainer(Server $server, string $network, string $deploymentUuid)
|
|
||||||
{
|
|
||||||
$basedir = generateBaseDir($deploymentUuid);
|
|
||||||
$helperImage = config('coolify.helper_image');
|
|
||||||
|
|
||||||
$serverUserHomeDir = instant_remote_process(["echo \$HOME"], $server);
|
|
||||||
$dockerConfigFileExists = instant_remote_process(["test -f {$serverUserHomeDir}/.docker/config.json && echo 'OK' || echo 'NOK'"], $server);
|
|
||||||
|
|
||||||
$commands = collect([]);
|
|
||||||
if ($dockerConfigFileExists === 'OK') {
|
|
||||||
$commands->push([
|
|
||||||
"command" => "docker run -d --network $network --name $deploymentUuid --rm -v {$serverUserHomeDir}/.docker/config.json:/root/.docker/config.json:ro -v /var/run/docker.sock:/var/run/docker.sock $helperImage",
|
|
||||||
"hidden" => true,
|
|
||||||
]);
|
|
||||||
} else {
|
|
||||||
$commands->push([
|
|
||||||
"command" => "docker run -d --network {$network} --name {$deploymentUuid} --rm -v /var/run/docker.sock:/var/run/docker.sock {$helperImage}",
|
|
||||||
"hidden" => true,
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
$commands->push([
|
|
||||||
"command" => executeInDocker($deploymentUuid, "mkdir -p {$basedir}"),
|
|
||||||
"hidden" => true,
|
|
||||||
]);
|
|
||||||
return $commands;
|
|
||||||
}
|
|
||||||
|
|
||||||
function generateComposeFile(string $deploymentUuid, Server $server, string $network, Application $application, string $containerName, string $imageName, ?ApplicationPreview $preview = null, int $pullRequestId = 0)
|
|
||||||
{
|
|
||||||
$ports = $application->settings->is_static ? [80] : $application->ports_exposes_array;
|
|
||||||
$workDir = generateWorkdir($deploymentUuid, $application);
|
|
||||||
$persistent_storages = generateLocalPersistentVolumes($application, $pullRequestId);
|
|
||||||
$volume_names = generateLocalPersistentVolumesOnlyVolumeNames($application, $pullRequestId);
|
|
||||||
$environment_variables = generateEnvironmentVariables($application, $ports, $pullRequestId);
|
|
||||||
|
|
||||||
if (data_get($application, 'custom_labels')) {
|
|
||||||
$labels = collect(str($application->custom_labels)->explode(','));
|
|
||||||
$labels = $labels->filter(function ($value, $key) {
|
|
||||||
return !str($value)->startsWith('coolify.');
|
|
||||||
});
|
|
||||||
$application->custom_labels = $labels->implode(',');
|
|
||||||
$application->save();
|
|
||||||
} else {
|
|
||||||
$labels = collect(generateLabelsApplication($application, $preview));
|
|
||||||
}
|
|
||||||
if ($pullRequestId !== 0) {
|
|
||||||
$labels = collect(generateLabelsApplication($application, $preview));
|
|
||||||
}
|
|
||||||
$labels = $labels->merge(defaultLabels($application->id, $application->uuid, 0))->toArray();
|
|
||||||
$docker_compose = [
|
|
||||||
'version' => '3.8',
|
|
||||||
'services' => [
|
|
||||||
$containerName => [
|
|
||||||
'image' => $imageName,
|
|
||||||
'container_name' => $containerName,
|
|
||||||
'restart' => RESTART_MODE,
|
|
||||||
'environment' => $environment_variables,
|
|
||||||
'labels' => $labels,
|
|
||||||
'expose' => $ports,
|
|
||||||
'networks' => [
|
|
||||||
$network,
|
|
||||||
],
|
|
||||||
'mem_limit' => $application->limits_memory,
|
|
||||||
'memswap_limit' => $application->limits_memory_swap,
|
|
||||||
'mem_swappiness' => $application->limits_memory_swappiness,
|
|
||||||
'mem_reservation' => $application->limits_memory_reservation,
|
|
||||||
'cpus' => (int) $application->limits_cpus,
|
|
||||||
'cpu_shares' => $application->limits_cpu_shares,
|
|
||||||
]
|
|
||||||
],
|
|
||||||
'networks' => [
|
|
||||||
$network => [
|
|
||||||
'external' => true,
|
|
||||||
'name' => $network,
|
|
||||||
'attachable' => true
|
|
||||||
]
|
|
||||||
]
|
|
||||||
];
|
|
||||||
if (!is_null($application->limits_cpuset)) {
|
|
||||||
data_set($docker_compose, "services.{$containerName}.cpuset", $application->limits_cpuset);
|
|
||||||
}
|
|
||||||
if ($server->isLogDrainEnabled() && $application->isLogDrainEnabled()) {
|
|
||||||
$docker_compose['services'][$containerName]['logging'] = [
|
|
||||||
'driver' => 'fluentd',
|
|
||||||
'options' => [
|
|
||||||
'fluentd-address' => "tcp://127.0.0.1:24224",
|
|
||||||
'fluentd-async' => "true",
|
|
||||||
'fluentd-sub-second-precision' => "true",
|
|
||||||
]
|
|
||||||
];
|
|
||||||
}
|
|
||||||
if ($application->settings->is_gpu_enabled) {
|
|
||||||
$docker_compose['services'][$containerName]['deploy']['resources']['reservations']['devices'] = [
|
|
||||||
[
|
|
||||||
'driver' => data_get($application, 'settings.gpu_driver', 'nvidia'),
|
|
||||||
'capabilities' => ['gpu'],
|
|
||||||
'options' => data_get($application, 'settings.gpu_options', [])
|
|
||||||
]
|
|
||||||
];
|
|
||||||
if (data_get($application, 'settings.gpu_count')) {
|
|
||||||
$count = data_get($application, 'settings.gpu_count');
|
|
||||||
if ($count === 'all') {
|
|
||||||
$docker_compose['services'][$containerName]['deploy']['resources']['reservations']['devices'][0]['count'] = $count;
|
|
||||||
} else {
|
|
||||||
$docker_compose['services'][$containerName]['deploy']['resources']['reservations']['devices'][0]['count'] = (int) $count;
|
|
||||||
}
|
|
||||||
} else if (data_get($application, 'settings.gpu_device_ids')) {
|
|
||||||
$docker_compose['services'][$containerName]['deploy']['resources']['reservations']['devices'][0]['ids'] = data_get($application, 'settings.gpu_device_ids');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if ($application->isHealthcheckDisabled()) {
|
|
||||||
data_forget($docker_compose, 'services.' . $containerName . '.healthcheck');
|
|
||||||
}
|
|
||||||
if (count($application->ports_mappings_array) > 0 && $pullRequestId === 0) {
|
|
||||||
$docker_compose['services'][$containerName]['ports'] = $application->ports_mappings_array;
|
|
||||||
}
|
|
||||||
if (count($persistent_storages) > 0) {
|
|
||||||
$docker_compose['services'][$containerName]['volumes'] = $persistent_storages;
|
|
||||||
}
|
|
||||||
if (count($volume_names) > 0) {
|
|
||||||
$docker_compose['volumes'] = $volume_names;
|
|
||||||
}
|
|
||||||
$docker_compose = Yaml::dump($docker_compose, 10);
|
|
||||||
$docker_compose_base64 = base64_encode($docker_compose);
|
|
||||||
$commands = collect([]);
|
|
||||||
$commands->push([
|
|
||||||
"command" => executeInDocker($deploymentUuid, "echo '{$docker_compose_base64}' | base64 -d > {$workDir}/docker-compose.yml"),
|
|
||||||
"hidden" => true,
|
|
||||||
]);
|
|
||||||
return $commands;
|
|
||||||
}
|
|
||||||
function generateLocalPersistentVolumes(Application $application, int $pullRequestId = 0)
|
|
||||||
{
|
|
||||||
$local_persistent_volumes = [];
|
|
||||||
foreach ($application->persistentStorages as $persistentStorage) {
|
|
||||||
$volume_name = $persistentStorage->host_path ?? $persistentStorage->name;
|
|
||||||
if ($pullRequestId !== 0) {
|
|
||||||
$volume_name = $volume_name . '-pr-' . $pullRequestId;
|
|
||||||
}
|
|
||||||
$local_persistent_volumes[] = $volume_name . ':' . $persistentStorage->mount_path;
|
|
||||||
}
|
|
||||||
return $local_persistent_volumes;
|
|
||||||
}
|
|
||||||
|
|
||||||
function generateLocalPersistentVolumesOnlyVolumeNames(Application $application, int $pullRequestId = 0)
|
|
||||||
{
|
|
||||||
$local_persistent_volumes_names = [];
|
|
||||||
foreach ($application->persistentStorages as $persistentStorage) {
|
|
||||||
if ($persistentStorage->host_path) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
$name = $persistentStorage->name;
|
|
||||||
|
|
||||||
if ($pullRequestId !== 0) {
|
|
||||||
$name = $name . '-pr-' . $pullRequestId;
|
|
||||||
}
|
|
||||||
|
|
||||||
$local_persistent_volumes_names[$name] = [
|
|
||||||
'name' => $name,
|
|
||||||
'external' => false,
|
|
||||||
];
|
|
||||||
}
|
|
||||||
return $local_persistent_volumes_names;
|
|
||||||
}
|
|
||||||
function generateEnvironmentVariables(Application $application, $ports, int $pullRequestId = 0)
|
|
||||||
{
|
|
||||||
$environment_variables = collect();
|
|
||||||
// ray('Generate Environment Variables')->green();
|
|
||||||
if ($pullRequestId === 0) {
|
|
||||||
// ray($this->application->runtime_environment_variables)->green();
|
|
||||||
foreach ($application->runtime_environment_variables as $env) {
|
|
||||||
$environment_variables->push("$env->key=$env->value");
|
|
||||||
}
|
|
||||||
foreach ($application->nixpacks_environment_variables as $env) {
|
|
||||||
$environment_variables->push("$env->key=$env->value");
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// ray($this->application->runtime_environment_variables_preview)->green();
|
|
||||||
foreach ($application->runtime_environment_variables_preview as $env) {
|
|
||||||
$environment_variables->push("$env->key=$env->value");
|
|
||||||
}
|
|
||||||
foreach ($application->nixpacks_environment_variables_preview as $env) {
|
|
||||||
$environment_variables->push("$env->key=$env->value");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// Add PORT if not exists, use the first port as default
|
|
||||||
if ($environment_variables->filter(fn ($env) => str($env)->contains('PORT'))->isEmpty()) {
|
|
||||||
$environment_variables->push("PORT={$ports[0]}");
|
|
||||||
}
|
|
||||||
return $environment_variables->all();
|
|
||||||
}
|
|
||||||
|
|
||||||
function startNewApplication(Application $application, string $deploymentUuid, ApplicationDeploymentQueue $loggingModel)
|
|
||||||
{
|
|
||||||
$commands = collect([]);
|
|
||||||
$workDir = generateWorkdir($deploymentUuid, $application);
|
|
||||||
if ($application->build_pack === 'dockerimage') {
|
|
||||||
$loggingModel->addLogEntry('Pulling latest images from the registry.');
|
|
||||||
$commands->push(
|
|
||||||
[
|
|
||||||
"command" => executeInDocker($deploymentUuid, "docker compose --project-directory {$workDir} pull"),
|
|
||||||
"hidden" => true
|
|
||||||
],
|
|
||||||
[
|
|
||||||
"command" => executeInDocker($deploymentUuid, "docker compose --project-directory {$workDir} up --build -d"),
|
|
||||||
"hidden" => true
|
|
||||||
],
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
$commands->push(
|
|
||||||
[
|
|
||||||
"command" => executeInDocker($deploymentUuid, "docker compose --project-directory {$workDir} up --build -d"),
|
|
||||||
"hidden" => true
|
|
||||||
],
|
|
||||||
);
|
|
||||||
}
|
|
||||||
return $commands;
|
|
||||||
}
|
|
||||||
function removeOldDeployment(string $containerName)
|
|
||||||
{
|
|
||||||
$commands = collect([]);
|
|
||||||
$commands->push(
|
|
||||||
["docker rm -f $containerName >/dev/null 2>&1"],
|
|
||||||
);
|
|
||||||
return $commands;
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -34,3 +34,5 @@ const SUPPORTED_OS = [
|
|||||||
'centos fedora rhel ol rocky',
|
'centos fedora rhel ol rocky',
|
||||||
'sles opensuse-leap opensuse-tumbleweed'
|
'sles opensuse-leap opensuse-tumbleweed'
|
||||||
];
|
];
|
||||||
|
|
||||||
|
const SHARED_VARIABLE_TYPES = ['team', 'project', 'environment'];
|
||||||
|
|||||||
@@ -3,9 +3,7 @@
|
|||||||
use App\Models\Application;
|
use App\Models\Application;
|
||||||
use App\Models\ApplicationPreview;
|
use App\Models\ApplicationPreview;
|
||||||
use App\Models\Server;
|
use App\Models\Server;
|
||||||
use App\Models\Service;
|
|
||||||
use App\Models\ServiceApplication;
|
use App\Models\ServiceApplication;
|
||||||
use App\Models\ServiceDatabase;
|
|
||||||
use Illuminate\Support\Collection;
|
use Illuminate\Support\Collection;
|
||||||
use Illuminate\Support\Str;
|
use Illuminate\Support\Str;
|
||||||
use Spatie\Url\Url;
|
use Spatie\Url\Url;
|
||||||
@@ -323,3 +321,89 @@ function isDatabaseImage(?string $image = null)
|
|||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function convert_docker_run_to_compose(?string $custom_docker_run_options = null)
|
||||||
|
{
|
||||||
|
$options = [];
|
||||||
|
$compose_options = collect([]);
|
||||||
|
preg_match_all('/(--\w+(?:-\w+)*)(?:\s|=)?([^\s-]+)?/', $custom_docker_run_options, $matches, PREG_SET_ORDER);
|
||||||
|
$list_options = collect([
|
||||||
|
'--cap-add',
|
||||||
|
'--cap-drop',
|
||||||
|
'--security-opt',
|
||||||
|
'--sysctl',
|
||||||
|
'--ulimit',
|
||||||
|
'--device'
|
||||||
|
]);
|
||||||
|
$mapping = collect([
|
||||||
|
'--cap-add' => 'cap_add',
|
||||||
|
'--cap-drop' => 'cap_drop',
|
||||||
|
'--security-opt' => 'security_opt',
|
||||||
|
'--sysctl' => 'sysctls',
|
||||||
|
'--device' => 'devices',
|
||||||
|
'--ulimit' => 'ulimits',
|
||||||
|
'--init' => 'init',
|
||||||
|
'--ulimit' => 'ulimits',
|
||||||
|
'--privileged' => 'privileged',
|
||||||
|
]);
|
||||||
|
foreach ($matches as $match) {
|
||||||
|
$option = $match[1];
|
||||||
|
$value = isset($match[2]) && $match[2] !== '' ? $match[2] : true;
|
||||||
|
if ($list_options->contains($option)) {
|
||||||
|
$value = explode(',', $value);
|
||||||
|
}
|
||||||
|
if (array_key_exists($option, $options)) {
|
||||||
|
if (is_array($options[$option])) {
|
||||||
|
$options[$option][] = $value;
|
||||||
|
} else {
|
||||||
|
$options[$option] = [$options[$option], $value];
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
$options[$option] = $value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
$options = collect($options);
|
||||||
|
// Easily get mappings from https://github.com/composerize/composerize/blob/master/packages/composerize/src/mappings.js
|
||||||
|
foreach ($options as $option => $value) {
|
||||||
|
if (!data_get($mapping, $option)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if ($option === '--ulimit') {
|
||||||
|
$ulimits = collect([]);
|
||||||
|
collect($value)->map(function ($ulimit) use ($ulimits){
|
||||||
|
$ulimit = explode('=', $ulimit);
|
||||||
|
$type = $ulimit[0];
|
||||||
|
$limits = explode(':', $ulimit[1]);
|
||||||
|
if (count($limits) == 2) {
|
||||||
|
$soft_limit = $limits[0];
|
||||||
|
$hard_limit = $limits[1];
|
||||||
|
$ulimits->put($type, [
|
||||||
|
'soft' => $soft_limit,
|
||||||
|
'hard' => $hard_limit
|
||||||
|
]);
|
||||||
|
|
||||||
|
} else {
|
||||||
|
$soft_limit = $ulimit[1];
|
||||||
|
$ulimits->put($type, [
|
||||||
|
'soft' => $soft_limit,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
$compose_options->put($mapping[$option], $ulimits);
|
||||||
|
} else {
|
||||||
|
if ($list_options->contains($option)) {
|
||||||
|
if ($compose_options->has($mapping[$option])) {
|
||||||
|
$compose_options->put($mapping[$option], $options->get($mapping[$option]) . ',' . $value);
|
||||||
|
} else {
|
||||||
|
$compose_options->put($mapping[$option], $value);
|
||||||
|
}
|
||||||
|
continue;
|
||||||
|
} else {
|
||||||
|
$compose_options->put($mapping[$option], $value);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
$compose_options->forget($option);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return $compose_options->toArray();
|
||||||
|
}
|
||||||
|
|||||||
@@ -29,7 +29,7 @@ function generate_github_installation_token(GithubApp $source)
|
|||||||
'Accept' => 'application/vnd.github.machine-man-preview+json'
|
'Accept' => 'application/vnd.github.machine-man-preview+json'
|
||||||
])->post("{$source->api_url}/app/installations/{$source->installation_id}/access_tokens");
|
])->post("{$source->api_url}/app/installations/{$source->installation_id}/access_tokens");
|
||||||
if ($token->failed()) {
|
if ($token->failed()) {
|
||||||
throw new \Exception("Failed to get access token for " . $source->name . " with error: " . $token->json()['message']);
|
throw new RuntimeException("Failed to get access token for " . $source->name . " with error: " . $token->json()['message']);
|
||||||
}
|
}
|
||||||
return $token->json()['token'];
|
return $token->json()['token'];
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -22,6 +22,7 @@ use App\Notifications\Channels\EmailChannel;
|
|||||||
use App\Notifications\Channels\TelegramChannel;
|
use App\Notifications\Channels\TelegramChannel;
|
||||||
use App\Notifications\Internal\GeneralNotification;
|
use App\Notifications\Internal\GeneralNotification;
|
||||||
use DanHarrin\LivewireRateLimiting\Exceptions\TooManyRequestsException;
|
use DanHarrin\LivewireRateLimiting\Exceptions\TooManyRequestsException;
|
||||||
|
use Illuminate\Database\UniqueConstraintViolationException;
|
||||||
use Illuminate\Mail\Message;
|
use Illuminate\Mail\Message;
|
||||||
use Illuminate\Notifications\Messages\MailMessage;
|
use Illuminate\Notifications\Messages\MailMessage;
|
||||||
use Illuminate\Support\Facades\Cache;
|
use Illuminate\Support\Facades\Cache;
|
||||||
@@ -107,6 +108,12 @@ function handleError(?Throwable $error = null, ?Livewire\Component $livewire = n
|
|||||||
}
|
}
|
||||||
return "Too many requests. Please try again in {$error->secondsUntilAvailable} seconds.";
|
return "Too many requests. Please try again in {$error->secondsUntilAvailable} seconds.";
|
||||||
}
|
}
|
||||||
|
if ($error instanceof UniqueConstraintViolationException) {
|
||||||
|
if (isset($livewire)) {
|
||||||
|
return $livewire->dispatch('error', "Duplicate entry found.", "Please use a different name.");
|
||||||
|
}
|
||||||
|
return "Duplicate entry found. Please use a different name.";
|
||||||
|
}
|
||||||
|
|
||||||
if ($error instanceof Throwable) {
|
if ($error instanceof Throwable) {
|
||||||
$message = $error->getMessage();
|
$message = $error->getMessage();
|
||||||
@@ -474,7 +481,14 @@ function queryResourcesByUuid(string $uuid)
|
|||||||
if ($mariadb) return $mariadb;
|
if ($mariadb) return $mariadb;
|
||||||
return $resource;
|
return $resource;
|
||||||
}
|
}
|
||||||
|
function generatTagDeployWebhook($tag_name)
|
||||||
|
{
|
||||||
|
$baseUrl = base_url();
|
||||||
|
$api = Url::fromString($baseUrl) . '/api/v1';
|
||||||
|
$endpoint = "/deploy?tag=$tag_name";
|
||||||
|
$url = $api . $endpoint;
|
||||||
|
return $url;
|
||||||
|
}
|
||||||
function generateDeployWebhook($resource)
|
function generateDeployWebhook($resource)
|
||||||
{
|
{
|
||||||
$baseUrl = base_url();
|
$baseUrl = base_url();
|
||||||
@@ -930,7 +944,7 @@ function parseDockerComposeFile(Service|Application $resource, bool $isNew = fal
|
|||||||
'service_id' => $resource->id,
|
'service_id' => $resource->id,
|
||||||
])->first();
|
])->first();
|
||||||
['command' => $command, 'forService' => $forService, 'generatedValue' => $generatedValue, 'port' => $port] = parseEnvVariable($value);
|
['command' => $command, 'forService' => $forService, 'generatedValue' => $generatedValue, 'port' => $port] = parseEnvVariable($value);
|
||||||
if ($command->value() === 'FQDN' || $command->value() === 'URL') {
|
if ($command?->value() === 'FQDN' || $command?->value() === 'URL') {
|
||||||
if (Str::lower($forService) === $serviceName) {
|
if (Str::lower($forService) === $serviceName) {
|
||||||
$fqdn = generateFqdn($resource->server, $containerName);
|
$fqdn = generateFqdn($resource->server, $containerName);
|
||||||
} else {
|
} else {
|
||||||
@@ -1350,7 +1364,7 @@ function parseDockerComposeFile(Service|Application $resource, bool $isNew = fal
|
|||||||
'application_id' => $resource->id,
|
'application_id' => $resource->id,
|
||||||
])->first();
|
])->first();
|
||||||
['command' => $command, 'forService' => $forService, 'generatedValue' => $generatedValue, 'port' => $port] = parseEnvVariable($value);
|
['command' => $command, 'forService' => $forService, 'generatedValue' => $generatedValue, 'port' => $port] = parseEnvVariable($value);
|
||||||
if ($command->value() === 'FQDN' || $command->value() === 'URL') {
|
if ($command?->value() === 'FQDN' || $command?->value() === 'URL') {
|
||||||
if (Str::lower($forService) === $serviceName) {
|
if (Str::lower($forService) === $serviceName) {
|
||||||
$fqdn = generateFqdn($server, $containerName);
|
$fqdn = generateFqdn($server, $containerName);
|
||||||
} else {
|
} else {
|
||||||
@@ -1660,3 +1674,42 @@ function ip_match($ip, $cidrs, &$match = null)
|
|||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
function check_fqdn_usage(ServiceApplication|Application $own_resource)
|
||||||
|
{
|
||||||
|
$domains = collect($own_resource->fqdns)->map(function ($domain) {
|
||||||
|
if (str($domain)->endsWith('/')) {
|
||||||
|
$domain = str($domain)->beforeLast('/');
|
||||||
|
}
|
||||||
|
return str($domain)->replace('http://', '')->replace('https://', '');
|
||||||
|
});
|
||||||
|
$apps = Application::all();
|
||||||
|
foreach ($apps as $app) {
|
||||||
|
$list_of_domains = collect(explode(',', $app->fqdn))->filter(fn ($fqdn) => $fqdn !== '');
|
||||||
|
foreach ($list_of_domains as $domain) {
|
||||||
|
if (str($domain)->endsWith('/')) {
|
||||||
|
$domain = str($domain)->beforeLast('/');
|
||||||
|
}
|
||||||
|
$naked_domain = str($domain)->replace('http://', '')->replace('https://', '')->value();
|
||||||
|
if ($domains->contains($naked_domain)) {
|
||||||
|
if ($app->uuid !== $own_resource->uuid) {
|
||||||
|
throw new \RuntimeException("Domain $naked_domain is already in use by another resource.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
$apps = ServiceApplication::all();
|
||||||
|
foreach ($apps as $app) {
|
||||||
|
$list_of_domains = collect(explode(',', $app->fqdn))->filter(fn ($fqdn) => $fqdn !== '');
|
||||||
|
foreach ($list_of_domains as $domain) {
|
||||||
|
if (str($domain)->endsWith('/')) {
|
||||||
|
$domain = str($domain)->beforeLast('/');
|
||||||
|
}
|
||||||
|
$naked_domain = str($domain)->replace('http://', '')->replace('https://', '')->value();
|
||||||
|
if ($domains->contains($naked_domain)) {
|
||||||
|
if ($app->uuid !== $own_resource->uuid) {
|
||||||
|
throw new \RuntimeException("Domain $naked_domain is already in use by another resource.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ return [
|
|||||||
|
|
||||||
// The release version of your application
|
// The release version of your application
|
||||||
// Example with dynamic git hash: trim(exec('git --git-dir ' . base_path('.git') . ' log --pretty="%h" -n1 HEAD'))
|
// Example with dynamic git hash: trim(exec('git --git-dir ' . base_path('.git') . ' log --pretty="%h" -n1 HEAD'))
|
||||||
'release' => '4.0.0-beta.201',
|
'release' => '4.0.0-beta.207',
|
||||||
// When left empty or `null` the Laravel environment will be used
|
// When left empty or `null` the Laravel environment will be used
|
||||||
'environment' => config('app.env'),
|
'environment' => config('app.env'),
|
||||||
|
|
||||||
|
|||||||
@@ -1,48 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
declare(strict_types=1);
|
|
||||||
|
|
||||||
return [
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Add an additional second for every 100th word of the toast messages.
|
|
||||||
*
|
|
||||||
* Supported: true | false
|
|
||||||
*/
|
|
||||||
'accessibility' => true,
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The vertical alignment of the toast container.
|
|
||||||
*
|
|
||||||
* Supported: "bottom", "middle" or "top"
|
|
||||||
*/
|
|
||||||
'alignment' => 'top',
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Allow users to close toast messages prematurely.
|
|
||||||
*
|
|
||||||
* Supported: true | false
|
|
||||||
*/
|
|
||||||
'closeable' => true,
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The on-screen duration of each toast.
|
|
||||||
*
|
|
||||||
* Minimum: 3000 (in milliseconds)
|
|
||||||
*/
|
|
||||||
'duration' => 5000,
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The horizontal position of each toast.
|
|
||||||
*
|
|
||||||
* Supported: "center", "left" or "right"
|
|
||||||
*/
|
|
||||||
'position' => 'center',
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Whether messages passed as translation keys should be translated automatically.
|
|
||||||
*
|
|
||||||
* Supported: true | false
|
|
||||||
*/
|
|
||||||
'translate' => true,
|
|
||||||
];
|
|
||||||
@@ -1,3 +1,3 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
return '4.0.0-beta.201';
|
return '4.0.0-beta.207';
|
||||||
|
|||||||
@@ -0,0 +1,28 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
use Illuminate\Database\Migrations\Migration;
|
||||||
|
use Illuminate\Database\Schema\Blueprint;
|
||||||
|
use Illuminate\Support\Facades\Schema;
|
||||||
|
|
||||||
|
return new class extends Migration
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Run the migrations.
|
||||||
|
*/
|
||||||
|
public function up(): void
|
||||||
|
{
|
||||||
|
Schema::table('applications', function (Blueprint $table) {
|
||||||
|
$table->string('custom_docker_run_options')->nullable();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reverse the migrations.
|
||||||
|
*/
|
||||||
|
public function down(): void
|
||||||
|
{
|
||||||
|
Schema::table('applications', function (Blueprint $table) {
|
||||||
|
$table->dropColumn('custom_docker_run_options');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
39
database/migrations/2024_02_01_111228_create_tags_table.php
Normal file
39
database/migrations/2024_02_01_111228_create_tags_table.php
Normal file
@@ -0,0 +1,39 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
use Illuminate\Database\Migrations\Migration;
|
||||||
|
use Illuminate\Database\Schema\Blueprint;
|
||||||
|
use Illuminate\Support\Facades\Schema;
|
||||||
|
|
||||||
|
return new class extends Migration
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Run the migrations.
|
||||||
|
*/
|
||||||
|
public function up(): void
|
||||||
|
{
|
||||||
|
Schema::create('tags', function (Blueprint $table) {
|
||||||
|
$table->id();
|
||||||
|
$table->string('uuid')->unique();
|
||||||
|
$table->string('name')->unique();
|
||||||
|
$table->foreignId('team_id')->nullable()->constrained()->onDelete('cascade');
|
||||||
|
$table->timestamps();
|
||||||
|
});
|
||||||
|
Schema::create('taggables', function (Blueprint $table) {
|
||||||
|
$table->unsignedBigInteger('tag_id');
|
||||||
|
$table->unsignedBigInteger('taggable_id');
|
||||||
|
$table->string('taggable_type');
|
||||||
|
$table->foreign('tag_id')->references('id')->on('tags')->onDelete('cascade');
|
||||||
|
$table->unique(['tag_id', 'taggable_id', 'taggable_type'], 'taggable_unique'); // Composite unique index
|
||||||
|
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reverse the migrations.
|
||||||
|
*/
|
||||||
|
public function down(): void
|
||||||
|
{
|
||||||
|
Schema::dropIfExists('taggables');
|
||||||
|
Schema::dropIfExists('tags');
|
||||||
|
}
|
||||||
|
};
|
||||||
@@ -0,0 +1,28 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
use Illuminate\Database\Migrations\Migration;
|
||||||
|
use Illuminate\Database\Schema\Blueprint;
|
||||||
|
use Illuminate\Support\Facades\Schema;
|
||||||
|
|
||||||
|
return new class extends Migration
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Run the migrations.
|
||||||
|
*/
|
||||||
|
public function up(): void
|
||||||
|
{
|
||||||
|
Schema::table('application_deployment_queues', function (Blueprint $table) {
|
||||||
|
$table->string('destination_id')->nullable();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reverse the migrations.
|
||||||
|
*/
|
||||||
|
public function down(): void
|
||||||
|
{
|
||||||
|
Schema::table('application_deployment_queues', function (Blueprint $table) {
|
||||||
|
$table->dropColumn('destination_id');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
@@ -14,6 +14,10 @@ button[isError] {
|
|||||||
@apply bg-red-600 hover:bg-red-700;
|
@apply bg-red-600 hover:bg-red-700;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
button[isHighlighted] {
|
||||||
|
@apply bg-coollabs hover:bg-coollabs-100;
|
||||||
|
}
|
||||||
|
|
||||||
.scrollbar {
|
.scrollbar {
|
||||||
@apply scrollbar-thumb-coollabs-100 scrollbar-track-coolgray-200 scrollbar-w-2;
|
@apply scrollbar-thumb-coollabs-100 scrollbar-track-coolgray-200 scrollbar-w-2;
|
||||||
}
|
}
|
||||||
@@ -72,11 +76,11 @@ a {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.box {
|
.box {
|
||||||
@apply flex p-2 transition-colors cursor-pointer min-h-[4rem] bg-coolgray-100 hover:bg-coollabs-100 hover:text-white hover:no-underline min-w-[24rem];
|
@apply flex p-2 transition-colors cursor-pointer min-h-[4rem] bg-coolgray-100 hover:bg-coollabs-100 hover:text-white hover:no-underline;
|
||||||
}
|
}
|
||||||
|
|
||||||
.box-without-bg {
|
.box-without-bg {
|
||||||
@apply flex p-2 transition-colors min-h-full hover:text-white hover:no-underline min-h-[4rem];
|
@apply flex p-2 transition-colors hover:text-white hover:no-underline min-h-[4rem];
|
||||||
}
|
}
|
||||||
|
|
||||||
.description {
|
.description {
|
||||||
|
|||||||
@@ -4,7 +4,7 @@
|
|||||||
class="transition rounded w-11 h-11" src="{{ asset('coolify-transparent.png') }}"></a>
|
class="transition rounded w-11 h-11" src="{{ asset('coolify-transparent.png') }}"></a>
|
||||||
<ul class="flex flex-col h-full gap-4 menu flex-nowrap">
|
<ul class="flex flex-col h-full gap-4 menu flex-nowrap">
|
||||||
<li title="Dashboard">
|
<li title="Dashboard">
|
||||||
<a class="hover:bg-transparent" href="/">
|
<a class="hover:bg-transparent" href="/">
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" class="{{ request()->is('/') ? 'text-warning icon' : 'icon' }}"
|
<svg xmlns="http://www.w3.org/2000/svg" class="{{ request()->is('/') ? 'text-warning icon' : 'icon' }}"
|
||||||
fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
|
||||||
@@ -13,7 +13,7 @@
|
|||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
<li title="Servers">
|
<li title="Servers">
|
||||||
<a class="hover:bg-transparent" href="/servers">
|
<a class="hover:bg-transparent" href="/servers">
|
||||||
<svg xmlns="http://www.w3.org/2000/svg"
|
<svg xmlns="http://www.w3.org/2000/svg"
|
||||||
class="{{ request()->is('server/*') || request()->is('servers') ? 'text-warning icon' : 'icon' }}"
|
class="{{ request()->is('server/*') || request()->is('servers') ? 'text-warning icon' : 'icon' }}"
|
||||||
viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" fill="none" stroke-linecap="round"
|
viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" fill="none" stroke-linecap="round"
|
||||||
@@ -28,7 +28,7 @@
|
|||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
<li title="Projects">
|
<li title="Projects">
|
||||||
<a class="hover:bg-transparent" href="/projects">
|
<a class="hover:bg-transparent" href="/projects">
|
||||||
<svg xmlns="http://www.w3.org/2000/svg"
|
<svg xmlns="http://www.w3.org/2000/svg"
|
||||||
class="{{ request()->is('project/*') || request()->is('projects') ? 'text-warning icon' : 'icon' }}"
|
class="{{ request()->is('project/*') || request()->is('projects') ? 'text-warning icon' : 'icon' }}"
|
||||||
viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" fill="none" stroke-linecap="round"
|
viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" fill="none" stroke-linecap="round"
|
||||||
@@ -41,7 +41,7 @@
|
|||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
<li title="Command Center">
|
<li title="Command Center">
|
||||||
<a class="hover:bg-transparent" href="/command-center">
|
<a class="hover:bg-transparent" href="/command-center">
|
||||||
<svg xmlns="http://www.w3.org/2000/svg"
|
<svg xmlns="http://www.w3.org/2000/svg"
|
||||||
class="{{ request()->is('command-center') ? 'text-warning icon' : 'icon' }}" viewBox="0 0 24 24"
|
class="{{ request()->is('command-center') ? 'text-warning icon' : 'icon' }}" viewBox="0 0 24 24"
|
||||||
stroke-width="1.5" stroke="currentColor" fill="none" stroke-linecap="round"
|
stroke-width="1.5" stroke="currentColor" fill="none" stroke-linecap="round"
|
||||||
@@ -53,7 +53,7 @@
|
|||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
<li title="Source">
|
<li title="Source">
|
||||||
<a class="hover:bg-transparent" href="{{ route('source.all') }}">
|
<a class="hover:bg-transparent" href="{{ route('source.all') }}">
|
||||||
<svg class="icon" viewBox="0 0 15 15" xmlns="http://www.w3.org/2000/svg">
|
<svg class="icon" viewBox="0 0 15 15" xmlns="http://www.w3.org/2000/svg">
|
||||||
<path fill="currentColor"
|
<path fill="currentColor"
|
||||||
d="m6.793 1.207l.353.354l-.353-.354ZM1.207 6.793l-.353-.354l.353.354Zm0 1.414l.354-.353l-.354.353Zm5.586 5.586l-.354.353l.354-.353Zm1.414 0l-.353-.354l.353.354Zm5.586-5.586l.353.354l-.353-.354Zm0-1.414l-.354.353l.354-.353ZM8.207 1.207l.354-.353l-.354.353ZM6.44.854L.854 6.439l.707.707l5.585-5.585L6.44.854ZM.854 8.56l5.585 5.585l.707-.707l-5.585-5.585l-.707.707Zm7.707 5.585l5.585-5.585l-.707-.707l-5.585 5.585l.707.707Zm5.585-7.707L8.561.854l-.707.707l5.585 5.585l.707-.707Zm0 2.122a1.5 1.5 0 0 0 0-2.122l-.707.707a.5.5 0 0 1 0 .708l.707.707ZM6.44 14.146a1.5 1.5 0 0 0 2.122 0l-.707-.707a.5.5 0 0 1-.708 0l-.707.707ZM.854 6.44a1.5 1.5 0 0 0 0 2.122l.707-.707a.5.5 0 0 1 0-.708L.854 6.44Zm6.292-4.878a.5.5 0 0 1 .708 0L8.56.854a1.5 1.5 0 0 0-2.122 0l.707.707Zm-2 1.293l1 1l.708-.708l-1-1l-.708.708ZM7.5 5a.5.5 0 0 1-.5-.5H6A1.5 1.5 0 0 0 7.5 6V5Zm.5-.5a.5.5 0 0 1-.5.5v1A1.5 1.5 0 0 0 9 4.5H8ZM7.5 4a.5.5 0 0 1 .5.5h1A1.5 1.5 0 0 0 7.5 3v1Zm0-1A1.5 1.5 0 0 0 6 4.5h1a.5.5 0 0 1 .5-.5V3Zm.646 2.854l1.5 1.5l.707-.708l-1.5-1.5l-.707.708ZM10.5 8a.5.5 0 0 1-.5-.5H9A1.5 1.5 0 0 0 10.5 9V8Zm.5-.5a.5.5 0 0 1-.5.5v1A1.5 1.5 0 0 0 12 7.5h-1Zm-.5-.5a.5.5 0 0 1 .5.5h1A1.5 1.5 0 0 0 10.5 6v1Zm0-1A1.5 1.5 0 0 0 9 7.5h1a.5.5 0 0 1 .5-.5V6ZM7 5.5v4h1v-4H7Zm.5 5.5a.5.5 0 0 1-.5-.5H6A1.5 1.5 0 0 0 7.5 12v-1Zm.5-.5a.5.5 0 0 1-.5.5v1A1.5 1.5 0 0 0 9 10.5H8Zm-.5-.5a.5.5 0 0 1 .5.5h1A1.5 1.5 0 0 0 7.5 9v1Zm0-1A1.5 1.5 0 0 0 6 10.5h1a.5.5 0 0 1 .5-.5V9Z" />
|
d="m6.793 1.207l.353.354l-.353-.354ZM1.207 6.793l-.353-.354l.353.354Zm0 1.414l.354-.353l-.354.353Zm5.586 5.586l-.354.353l.354-.353Zm1.414 0l-.353-.354l.353.354Zm5.586-5.586l.353.354l-.353-.354Zm0-1.414l-.354.353l.354-.353ZM8.207 1.207l.354-.353l-.354.353ZM6.44.854L.854 6.439l.707.707l5.585-5.585L6.44.854ZM.854 8.56l5.585 5.585l.707-.707l-5.585-5.585l-.707.707Zm7.707 5.585l5.585-5.585l-.707-.707l-5.585 5.585l.707.707Zm5.585-7.707L8.561.854l-.707.707l5.585 5.585l.707-.707Zm0 2.122a1.5 1.5 0 0 0 0-2.122l-.707.707a.5.5 0 0 1 0 .708l.707.707ZM6.44 14.146a1.5 1.5 0 0 0 2.122 0l-.707-.707a.5.5 0 0 1-.708 0l-.707.707ZM.854 6.44a1.5 1.5 0 0 0 0 2.122l.707-.707a.5.5 0 0 1 0-.708L.854 6.44Zm6.292-4.878a.5.5 0 0 1 .708 0L8.56.854a1.5 1.5 0 0 0-2.122 0l.707.707Zm-2 1.293l1 1l.708-.708l-1-1l-.708.708ZM7.5 5a.5.5 0 0 1-.5-.5H6A1.5 1.5 0 0 0 7.5 6V5Zm.5-.5a.5.5 0 0 1-.5.5v1A1.5 1.5 0 0 0 9 4.5H8ZM7.5 4a.5.5 0 0 1 .5.5h1A1.5 1.5 0 0 0 7.5 3v1Zm0-1A1.5 1.5 0 0 0 6 4.5h1a.5.5 0 0 1 .5-.5V3Zm.646 2.854l1.5 1.5l.707-.708l-1.5-1.5l-.707.708ZM10.5 8a.5.5 0 0 1-.5-.5H9A1.5 1.5 0 0 0 10.5 9V8Zm.5-.5a.5.5 0 0 1-.5.5v1A1.5 1.5 0 0 0 12 7.5h-1Zm-.5-.5a.5.5 0 0 1 .5.5h1A1.5 1.5 0 0 0 10.5 6v1Zm0-1A1.5 1.5 0 0 0 9 7.5h1a.5.5 0 0 1 .5-.5V6ZM7 5.5v4h1v-4H7Zm.5 5.5a.5.5 0 0 1-.5-.5H6A1.5 1.5 0 0 0 7.5 12v-1Zm.5-.5a.5.5 0 0 1-.5.5v1A1.5 1.5 0 0 0 9 10.5H8Zm-.5-.5a.5.5 0 0 1 .5.5h1A1.5 1.5 0 0 0 7.5 9v1Zm0-1A1.5 1.5 0 0 0 6 10.5h1a.5.5 0 0 1 .5-.5V9Z" />
|
||||||
@@ -61,7 +61,7 @@
|
|||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
<li title="Security">
|
<li title="Security">
|
||||||
<a class="hover:bg-transparent" href="{{ route('security.private-key.index') }}">
|
<a class="hover:bg-transparent" href="{{ route('security.private-key.index') }}">
|
||||||
<svg class="icon" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
|
<svg class="icon" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
|
||||||
<path fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round"
|
<path fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round"
|
||||||
stroke-width="2"
|
stroke-width="2"
|
||||||
@@ -70,7 +70,7 @@
|
|||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
<li title="Teams">
|
<li title="Teams">
|
||||||
<a class="hover:bg-transparent" href="{{ route('team.index') }}">
|
<a class="hover:bg-transparent" href="{{ route('team.index') }}">
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" class="icon" viewBox="0 0 24 24" stroke-width="1.5"
|
<svg xmlns="http://www.w3.org/2000/svg" class="icon" viewBox="0 0 24 24" stroke-width="1.5"
|
||||||
stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round">
|
stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round">
|
||||||
<path stroke="none" d="M0 0h24v24H0z" fill="none" />
|
<path stroke="none" d="M0 0h24v24H0z" fill="none" />
|
||||||
@@ -83,6 +83,18 @@
|
|||||||
</svg>
|
</svg>
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
|
<li title="Tags">
|
||||||
|
<a class="hover:bg-transparent" href="{{ route('tags.index') }}">
|
||||||
|
<svg class="icon" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<g fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round"
|
||||||
|
stroke-width="2">
|
||||||
|
<path
|
||||||
|
d="M3 8v4.172a2 2 0 0 0 .586 1.414l5.71 5.71a2.41 2.41 0 0 0 3.408 0l3.592-3.592a2.41 2.41 0 0 0 0-3.408l-5.71-5.71A2 2 0 0 0 9.172 6H5a2 2 0 0 0-2 2" />
|
||||||
|
<path d="m18 19l1.592-1.592a4.82 4.82 0 0 0 0-6.816L15 6m-8 4h-.01" />
|
||||||
|
</g>
|
||||||
|
</svg>
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
|
||||||
<div class="flex-1"></div>
|
<div class="flex-1"></div>
|
||||||
@if (isInstanceAdmin() && !isCloud())
|
@if (isInstanceAdmin() && !isCloud())
|
||||||
@@ -103,7 +115,7 @@
|
|||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
<li title="Profile">
|
<li title="Profile">
|
||||||
<a class="hover:bg-transparent" href="/profile">
|
<a class="hover:bg-transparent" href="/profile">
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" class="icon" viewBox="0 0 24 24" stroke-width="1.5"
|
<svg xmlns="http://www.w3.org/2000/svg" class="icon" viewBox="0 0 24 24" stroke-width="1.5"
|
||||||
stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round">
|
stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round">
|
||||||
<path stroke="none" d="M0 0h24v24H0z" fill="none" />
|
<path stroke="none" d="M0 0h24v24H0z" fill="none" />
|
||||||
@@ -116,7 +128,7 @@
|
|||||||
|
|
||||||
@if (isInstanceAdmin())
|
@if (isInstanceAdmin())
|
||||||
<li title="Settings" class="mt-auto">
|
<li title="Settings" class="mt-auto">
|
||||||
<a class="hover:bg-transparent" href="/settings">
|
<a class="hover:bg-transparent" href="/settings">
|
||||||
<svg xmlns="http://www.w3.org/2000/svg"
|
<svg xmlns="http://www.w3.org/2000/svg"
|
||||||
class="{{ request()->is('settings*') ? 'text-warning icon' : 'icon' }}" viewBox="0 0 24 24"
|
class="{{ request()->is('settings*') ? 'text-warning icon' : 'icon' }}" viewBox="0 0 24 24"
|
||||||
stroke-width="1.5" stroke="currentColor" fill="none" stroke-linecap="round"
|
stroke-width="1.5" stroke="currentColor" fill="none" stroke-linecap="round"
|
||||||
|
|||||||
69
resources/views/components/new-modal.blade.php
Normal file
69
resources/views/components/new-modal.blade.php
Normal file
@@ -0,0 +1,69 @@
|
|||||||
|
@props([
|
||||||
|
'title' => 'Are you sure?',
|
||||||
|
'buttonTitle' => 'Open Modal',
|
||||||
|
'isErrorButton' => false,
|
||||||
|
'disabled' => false,
|
||||||
|
'action' => 'delete',
|
||||||
|
'content' => null,
|
||||||
|
])
|
||||||
|
<div x-data="{ modalOpen: false }" @keydown.escape.window="modalOpen = false" :class="{ 'z-40': modalOpen }"
|
||||||
|
class="relative w-auto h-auto">
|
||||||
|
@if ($content)
|
||||||
|
<div @click="modalOpen=true">
|
||||||
|
{{ $content }}
|
||||||
|
</div>
|
||||||
|
@else
|
||||||
|
@if ($disabled)
|
||||||
|
<x-forms.button isError disabled>{{ $buttonTitle }}</x-forms.button>
|
||||||
|
@elseif ($isErrorButton)
|
||||||
|
<x-forms.button isError @click="modalOpen=true">{{ $buttonTitle }}</x-forms.button>
|
||||||
|
@else
|
||||||
|
<x-forms.button @click="modalOpen=true">{{ $buttonTitle }}</x-forms.button>
|
||||||
|
@endif
|
||||||
|
@endif
|
||||||
|
<template x-teleport="body">
|
||||||
|
<div x-show="modalOpen" class="fixed top-0 left-0 z-[99] flex items-center justify-center w-screen h-screen"
|
||||||
|
x-cloak>
|
||||||
|
<div x-show="modalOpen" x-transition:enter="ease-out duration-100" x-transition:enter-start="opacity-0"
|
||||||
|
x-transition:enter-end="opacity-100" x-transition:leave="ease-in duration-100"
|
||||||
|
x-transition:leave-start="opacity-100" x-transition:leave-end="opacity-0" @click="modalOpen=false"
|
||||||
|
class="absolute inset-0 w-full h-full bg-black bg-opacity-20 backdrop-blur-sm"></div>
|
||||||
|
<div x-show="modalOpen" x-trap.inert.noscroll="modalOpen" x-transition:enter="ease-out duration-100"
|
||||||
|
x-transition:enter-start="opacity-0 -translate-y-2 sm:scale-95"
|
||||||
|
x-transition:enter-end="opacity-100 translate-y-0 sm:scale-100"
|
||||||
|
x-transition:leave="ease-in duration-100"
|
||||||
|
x-transition:leave-start="opacity-100 translate-y-0 sm:scale-100"
|
||||||
|
x-transition:leave-end="opacity-0 -translate-y-2 sm:scale-95"
|
||||||
|
class="relative w-full py-6 border rounded shadow-lg bg-coolgray-100 px-7 border-coolgray-300 sm:max-w-lg">
|
||||||
|
<div class="flex items-center justify-between pb-3">
|
||||||
|
<h3 class="text-2xl font-bold">{{ $title }}</h3>
|
||||||
|
{{-- <button @click="modalOpen=false"
|
||||||
|
class="absolute top-0 right-0 flex items-center justify-center w-8 h-8 mt-5 mr-5 text-white rounded-full hover:bg-coolgray-300">
|
||||||
|
<svg class="w-5 h-5" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24"
|
||||||
|
stroke-width="1.5" stroke="currentColor">
|
||||||
|
<path stroke-linecap="round" stroke-linejoin="round" d="M6 18L18 6M6 6l12 12" />
|
||||||
|
</svg>
|
||||||
|
</button> --}}
|
||||||
|
</div>
|
||||||
|
<div class="relative w-auto pb-8">
|
||||||
|
{{ $slot }}
|
||||||
|
</div>
|
||||||
|
<div class="flex flex-col-reverse sm:flex-row sm:justify-end sm:space-x-2">
|
||||||
|
<x-forms.button @click="modalOpen=false" class="w-24 bg-coolgray-200 hover:bg-coolgray-300"
|
||||||
|
type="button">Cancel
|
||||||
|
</x-forms.button>
|
||||||
|
<div class="flex-1"></div>
|
||||||
|
@if ($isErrorButton)
|
||||||
|
<x-forms.button @click="modalOpen=false" class="w-24" isError type="button"
|
||||||
|
wire:click.prevent="{{ $action }}">Continue
|
||||||
|
</x-forms.button>
|
||||||
|
@else
|
||||||
|
<x-forms.button @click="modalOpen=false" class="w-24" isHighlighted type="button"
|
||||||
|
wire:click.prevent="{{ $action }}">Continue
|
||||||
|
</x-forms.button>
|
||||||
|
@endif
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</div>
|
||||||
@@ -258,7 +258,8 @@
|
|||||||
<div class="flex items-start gap-4 text-xl tracking-tight">Need official support for
|
<div class="flex items-start gap-4 text-xl tracking-tight">Need official support for
|
||||||
your self-hosted instance?
|
your self-hosted instance?
|
||||||
<x-forms.button>
|
<x-forms.button>
|
||||||
<a class="font-bold text-white hover:no-underline" href="{{ config('coolify.contact') }}">Contact
|
<a class="font-bold text-white hover:no-underline"
|
||||||
|
href="{{ config('coolify.contact') }}">Contact
|
||||||
Us</a>
|
Us</a>
|
||||||
</x-forms.button>
|
</x-forms.button>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,31 +1,37 @@
|
|||||||
|
@props(['closeWithX' => 'false', 'fullScreen' => 'false'])
|
||||||
<div x-data="{
|
<div x-data="{
|
||||||
slideOverOpen: false
|
slideOverOpen: false
|
||||||
}" class="relative w-auto h-auto">
|
}" class="relative w-auto h-auto">
|
||||||
{{ $slot }}
|
{{ $slot }}
|
||||||
<template x-teleport="body">
|
<template x-teleport="body">
|
||||||
<div x-show="slideOverOpen" @keydown.window.escape="slideOverOpen=false" class="relative z-[99]">
|
<div x-show="slideOverOpen" @if (!$closeWithX) @keydown.window.escape="slideOverOpen=false" @endif
|
||||||
<div x-show="slideOverOpen" @click="slideOverOpen = false" class="fixed inset-0 bg-black bg-opacity-60"></div>
|
class="relative z-[99]">
|
||||||
|
<div x-show="slideOverOpen" @if (!$closeWithX) @click="slideOverOpen = false" @endif
|
||||||
|
class="fixed inset-0 bg-black bg-opacity-60"></div>
|
||||||
<div class="fixed inset-0 overflow-hidden">
|
<div class="fixed inset-0 overflow-hidden">
|
||||||
<div class="absolute inset-0 overflow-hidden">
|
<div class="absolute inset-0 overflow-hidden">
|
||||||
<div class="fixed inset-y-0 right-0 flex max-w-full pl-10">
|
<div class="fixed inset-y-0 right-0 flex max-w-full pl-10">
|
||||||
<div x-show="slideOverOpen" @click.away="slideOverOpen = false"
|
<div x-show="slideOverOpen"
|
||||||
|
@if (!$closeWithX) @click.away="slideOverOpen = false" @endif
|
||||||
x-transition:enter="transform transition ease-in-out duration-100 sm:duration-300"
|
x-transition:enter="transform transition ease-in-out duration-100 sm:duration-300"
|
||||||
x-transition:enter-start="translate-x-full" x-transition:enter-end="translate-x-0"
|
x-transition:enter-start="translate-x-full" x-transition:enter-end="translate-x-0"
|
||||||
x-transition:leave="transform transition ease-in-out duration-100 sm:duration-300"
|
x-transition:leave="transform transition ease-in-out duration-100 sm:duration-300"
|
||||||
x-transition:leave-start="translate-x-0" x-transition:leave-end="translate-x-full"
|
x-transition:leave-start="translate-x-0" x-transition:leave-end="translate-x-full"
|
||||||
class="w-screen max-w-md">
|
@class([
|
||||||
|
'max-w-md w-screen' => !$fullScreen,
|
||||||
|
'max-w-7xl w-screen' => $fullScreen,
|
||||||
|
])>
|
||||||
<div
|
<div
|
||||||
class="flex flex-col h-full py-6 overflow-hidden border-l shadow-lg bg-base-100 border-neutral-800">
|
class="flex flex-col h-full py-6 overflow-hidden border-l shadow-lg bg-base-100 border-neutral-800">
|
||||||
<div class="px-4 pb-10 sm:px-5">
|
<div class="px-4 pb-4 sm:px-5">
|
||||||
<div class="flex items-start justify-between pb-1">
|
<div class="flex items-start justify-between pb-1">
|
||||||
<h2 class="text-2xl leading-6" id="slide-over-title">
|
<h2 class="text-3xl leading-6" id="slide-over-title">
|
||||||
{{ $title }}</h2>
|
{{ $title }}</h2>
|
||||||
<div class="flex items-center h-auto ml-3">
|
<div class="flex items-center h-auto ml-3">
|
||||||
<button class="icon" @click="slideOverOpen=false"
|
<button class="icon" @click="slideOverOpen=false"
|
||||||
class="absolute top-0 right-0 z-30 flex items-center justify-center px-3 py-2 mt-4 mr-2 space-x-1 text-xs font-normal border-none rounded">
|
class="absolute top-0 right-0 z-30 flex items-center justify-center px-3 py-2 mt-4 mr-2 space-x-1 text-xs font-normal border-none rounded">
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" class="w-6 h-6" fill="none"
|
<svg xmlns="http://www.w3.org/2000/svg" class="w-6 h-6" fill="none"
|
||||||
viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor"
|
viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor">
|
||||||
>
|
|
||||||
<path stroke-linecap="round" stroke-linejoin="round"
|
<path stroke-linecap="round" stroke-linejoin="round"
|
||||||
d="M6 18L18 6M6 6l12 12"></path>
|
d="M6 18L18 6M6 6l12 12"></path>
|
||||||
</svg>
|
</svg>
|
||||||
|
|||||||
@@ -22,20 +22,7 @@
|
|||||||
if (typeof options.html != 'undefined') html = options.html;
|
if (typeof options.html != 'undefined') html = options.html;
|
||||||
|
|
||||||
window.dispatchEvent(new CustomEvent('toast-show', { detail: { type: type, message: message, description: description, position: position, html: html } }));
|
window.dispatchEvent(new CustomEvent('toast-show', { detail: { type: type, message: message, description: description, position: position, html: html } }));
|
||||||
}
|
}" class="relative space-y-5">
|
||||||
|
|
||||||
window.customToastHTML = `
|
|
||||||
<div class='relative flex items-start justify-center p-4'>
|
|
||||||
<div class='flex flex-col'>
|
|
||||||
<p class='text-sm font-medium text-gray-800'>New Friend Request</p>
|
|
||||||
<p class='mt-1 text-xs leading-none text-gray-800'>Friend request from John Doe.</p>
|
|
||||||
<div class='flex mt-3'>
|
|
||||||
<button type='button' @click='burnToast(toast.id)' class='inline-flex items-center px-2 py-1 text-xs font-semibold text-white bg-indigo-600 rounded shadow-sm hover:bg-indigo-500 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-indigo-600'>Accept</button>
|
|
||||||
<button type='button' @click='burnToast(toast.id)' class='inline-flex items-center px-2 py-1 ml-3 text-xs font-semibold text-gray-900 bg-white rounded shadow-sm ring-1 ring-inset ring-gray-300 hover:bg-gray-50'>Decline</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
`" class="relative space-y-5">
|
|
||||||
<template x-teleport="body">
|
<template x-teleport="body">
|
||||||
<ul x-data="{
|
<ul x-data="{
|
||||||
toasts: [],
|
toasts: [],
|
||||||
@@ -369,14 +356,14 @@ window.customToastHTML = `
|
|||||||
}, 5);
|
}, 5);
|
||||||
}, 4000);"
|
}, 4000);"
|
||||||
@mouseover="toastHovered=true" @mouseout="toastHovered=false"
|
@mouseover="toastHovered=true" @mouseout="toastHovered=false"
|
||||||
class="absolute w-full duration-300 ease-out select-none sm:max-w-xs"
|
class="absolute w-full duration-100 ease-out sm:max-w-xs"
|
||||||
:class="{ 'toast-no-description': !toast.description }">
|
:class="{ 'toast-no-description': !toast.description }">
|
||||||
<span
|
<span
|
||||||
class="relative flex flex-col items-start shadow-[0_5px_15px_-3px_rgb(0_0_0_/_0.08)] w-full transition-all duration-300 ease-out bg-coolgray-200 border border-coolgray-100 sm:rounded-md sm:max-w-xs group"
|
class="relative flex flex-col items-start shadow-[0_5px_15px_-3px_rgb(0_0_0_/_0.08)] w-full transition-all duration-100 ease-out bg-coolgray-100 border border-coolgray-200 rounded sm:max-w-xs group"
|
||||||
:class="{ 'p-4': !toast.html, 'p-0': toast.html }">
|
:class="{ 'p-4': !toast.html, 'p-0': toast.html }">
|
||||||
<template x-if="!toast.html">
|
<template x-if="!toast.html">
|
||||||
<div class="relative">
|
<div class="relative">
|
||||||
<div class="flex items-center"
|
<div class="flex items-start"
|
||||||
:class="{ 'text-green-500': toast.type=='success', 'text-blue-500': toast.type=='info', 'text-orange-400': toast.type=='warning', 'text-red-500': toast.type=='danger', 'text-gray-800': toast.type=='default' }">
|
:class="{ 'text-green-500': toast.type=='success', 'text-blue-500': toast.type=='info', 'text-orange-400': toast.type=='warning', 'text-red-500': toast.type=='danger', 'text-gray-800': toast.type=='default' }">
|
||||||
|
|
||||||
<svg x-show="toast.type=='success'" class="w-[18px] h-[18px] mr-1.5 -ml-1"
|
<svg x-show="toast.type=='success'" class="w-[18px] h-[18px] mr-1.5 -ml-1"
|
||||||
@@ -403,12 +390,12 @@ window.customToastHTML = `
|
|||||||
d="M2 12C2 6.47715 6.47715 2 12 2C17.5228 2 22 6.47715 22 12C22 17.5228 17.5228 22 12 22C6.47715 22 2 17.5228 2 12ZM11.9996 7C12.5519 7 12.9996 7.44772 12.9996 8V12C12.9996 12.5523 12.5519 13 11.9996 13C11.4474 13 10.9996 12.5523 10.9996 12V8C10.9996 7.44772 11.4474 7 11.9996 7ZM12.001 14.99C11.4488 14.9892 11.0004 15.4363 10.9997 15.9886L10.9996 15.9986C10.9989 16.5509 11.446 16.9992 11.9982 17C12.5505 17.0008 12.9989 16.5537 12.9996 16.0014L12.9996 15.9914C13.0004 15.4391 12.5533 14.9908 12.001 14.99Z"
|
d="M2 12C2 6.47715 6.47715 2 12 2C17.5228 2 22 6.47715 22 12C22 17.5228 17.5228 22 12 22C6.47715 22 2 17.5228 2 12ZM11.9996 7C12.5519 7 12.9996 7.44772 12.9996 8V12C12.9996 12.5523 12.5519 13 11.9996 13C11.4474 13 10.9996 12.5523 10.9996 12V8C10.9996 7.44772 11.4474 7 11.9996 7ZM12.001 14.99C11.4488 14.9892 11.0004 15.4363 10.9997 15.9886L10.9996 15.9986C10.9989 16.5509 11.446 16.9992 11.9982 17C12.5505 17.0008 12.9989 16.5537 12.9996 16.0014L12.9996 15.9914C13.0004 15.4391 12.5533 14.9908 12.001 14.99Z"
|
||||||
fill="currentColor"></path>
|
fill="currentColor"></path>
|
||||||
</svg>
|
</svg>
|
||||||
<p class="font-medium leading-none text-neutral-200"
|
<p class="leading-2 text-neutral-200"
|
||||||
x-html="toast.message">
|
x-html="toast.message">
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<p x-show="toast.description" :class="{ 'pl-5': toast.type!='default' }"
|
<p x-show="toast.description" :class="{ 'pl-5': toast.type!='default' }"
|
||||||
class="mt-1.5 text-xs leading-none opacity-70" x-html="toast.description"></p>
|
class="mt-1.5 text-xs leading-2 opacity-90 whitespace-pre-wrap" x-html="toast.description"></p>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
<template x-if="toast.html">
|
<template x-if="toast.html">
|
||||||
|
|||||||
@@ -170,6 +170,7 @@
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
window.Livewire.on('installDocker', () => {
|
window.Livewire.on('installDocker', () => {
|
||||||
|
console.log('Installing Docker...');
|
||||||
installDocker.showModal();
|
installDocker.showModal();
|
||||||
})
|
})
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,15 +1,5 @@
|
|||||||
@extends('layouts.base')
|
@extends('layouts.base')
|
||||||
@section('body')
|
@section('body')
|
||||||
<x-modal noSubmit modalId="installDocker">
|
|
||||||
<x-slot:modalBody>
|
|
||||||
<livewire:activity-monitor header="Docker Installation Logs" />
|
|
||||||
</x-slot:modalBody>
|
|
||||||
<x-slot:modalSubmit>
|
|
||||||
<x-forms.button onclick="installDocker.close()" type="submit">
|
|
||||||
Close
|
|
||||||
</x-forms.button>
|
|
||||||
</x-slot:modalSubmit>
|
|
||||||
</x-modal>
|
|
||||||
@if (isSubscriptionActive() || isDev())
|
@if (isSubscriptionActive() || isDev())
|
||||||
<div title="Send us feedback or get help!" class="fixed top-0 right-0 p-2 px-4 pt-4 mt-auto text-xs">
|
<div title="Send us feedback or get help!" class="fixed top-0 right-0 p-2 px-4 pt-4 mt-auto text-xs">
|
||||||
<button class="flex items-center justify-center gap-2" wire:click="help" onclick="help.showModal()">
|
<button class="flex items-center justify-center gap-2" wire:click="help" onclick="help.showModal()">
|
||||||
|
|||||||
@@ -12,7 +12,7 @@
|
|||||||
<x-navbar-subscription />
|
<x-navbar-subscription />
|
||||||
@endif
|
@endif
|
||||||
|
|
||||||
<main class="main max-w-screen-2xl">
|
<main class="mx-auto main max-w-screen-2xl">
|
||||||
{{ $slot }}
|
{{ $slot }}
|
||||||
</main>
|
</main>
|
||||||
@endsection
|
@endsection
|
||||||
|
|||||||
@@ -5,7 +5,8 @@
|
|||||||
<h1 class="text-5xl font-bold">Welcome to Coolify</h1>
|
<h1 class="text-5xl font-bold">Welcome to Coolify</h1>
|
||||||
<p class="py-6 text-xl text-center">Let me help you to set the basics.</p>
|
<p class="py-6 text-xl text-center">Let me help you to set the basics.</p>
|
||||||
<div class="flex justify-center ">
|
<div class="flex justify-center ">
|
||||||
<x-forms.button class="justify-center box" wire:click="$set('currentState','explanation')">Get Started
|
<x-forms.button class="justify-center w-64 box" wire:click="$set('currentState','explanation')">Get
|
||||||
|
Started
|
||||||
</x-forms.button>
|
</x-forms.button>
|
||||||
</div>
|
</div>
|
||||||
@endif
|
@endif
|
||||||
@@ -31,7 +32,7 @@
|
|||||||
Telegram, Email, etc.) when something goes wrong, or an action needed from your side.</p>
|
Telegram, Email, etc.) when something goes wrong, or an action needed from your side.</p>
|
||||||
</x-slot:explanation>
|
</x-slot:explanation>
|
||||||
<x-slot:actions>
|
<x-slot:actions>
|
||||||
<x-forms.button class="justify-center box" wire:click="explanation">Next
|
<x-forms.button class="justify-center w-64 box" wire:click="explanation">Next
|
||||||
</x-forms.button>
|
</x-forms.button>
|
||||||
</x-slot:actions>
|
</x-slot:actions>
|
||||||
</x-boarding-step>
|
</x-boarding-step>
|
||||||
@@ -43,11 +44,11 @@
|
|||||||
or on a <x-highlighted text="Remote Server" />?
|
or on a <x-highlighted text="Remote Server" />?
|
||||||
</x-slot:question>
|
</x-slot:question>
|
||||||
<x-slot:actions>
|
<x-slot:actions>
|
||||||
<x-forms.button class="justify-center box" wire:target="setServerType('localhost')"
|
<x-forms.button class="justify-center w-64 box" wire:target="setServerType('localhost')"
|
||||||
wire:click="setServerType('localhost')">Localhost
|
wire:click="setServerType('localhost')">Localhost
|
||||||
</x-forms.button>
|
</x-forms.button>
|
||||||
|
|
||||||
<x-forms.button class="justify-center box" wire:target="setServerType('remote')"
|
<x-forms.button class="justify-center w-64 box " wire:target="setServerType('remote')"
|
||||||
wire:click="setServerType('remote')">Remote Server
|
wire:click="setServerType('remote')">Remote Server
|
||||||
</x-forms.button>
|
</x-forms.button>
|
||||||
@if (!$serverReachable)
|
@if (!$serverReachable)
|
||||||
@@ -57,9 +58,10 @@
|
|||||||
'root' or skip the boarding process and add a new private key manually to Coolify and to the
|
'root' or skip the boarding process and add a new private key manually to Coolify and to the
|
||||||
server.
|
server.
|
||||||
<br />
|
<br />
|
||||||
Check this <a target="_blank" class="underline" href="https://coolify.io/docs/server/openssh">documentation</a> for further help.
|
Check this <a target="_blank" class="underline"
|
||||||
|
href="https://coolify.io/docs/server/openssh">documentation</a> for further help.
|
||||||
<x-forms.input readonly id="serverPublicKey"></x-forms.input>
|
<x-forms.input readonly id="serverPublicKey"></x-forms.input>
|
||||||
<x-forms.button class="box" wire:target="setServerType('localhost')"
|
<x-forms.button class="w-64 box" wire:target="setServerType('localhost')"
|
||||||
wire:click="setServerType('localhost')">Check again
|
wire:click="setServerType('localhost')">Check again
|
||||||
</x-forms.button>
|
</x-forms.button>
|
||||||
@endif
|
@endif
|
||||||
@@ -83,10 +85,10 @@
|
|||||||
Do you have your own SSH Private Key?
|
Do you have your own SSH Private Key?
|
||||||
</x-slot:question>
|
</x-slot:question>
|
||||||
<x-slot:actions>
|
<x-slot:actions>
|
||||||
<x-forms.button class="justify-center box" wire:target="setPrivateKey('own')"
|
<x-forms.button class="justify-center w-64 box" wire:target="setPrivateKey('own')"
|
||||||
wire:click="setPrivateKey('own')">Yes
|
wire:click="setPrivateKey('own')">Yes
|
||||||
</x-forms.button>
|
</x-forms.button>
|
||||||
<x-forms.button class="justify-center box" wire:target="setPrivateKey('create')"
|
<x-forms.button class="justify-center w-64 box" wire:target="setPrivateKey('create')"
|
||||||
wire:click="setPrivateKey('create')">No (create one for me)
|
wire:click="setPrivateKey('create')">No (create one for me)
|
||||||
</x-forms.button>
|
</x-forms.button>
|
||||||
@if (count($privateKeys) > 0)
|
@if (count($privateKeys) > 0)
|
||||||
@@ -119,7 +121,7 @@
|
|||||||
There are already servers available for your Team. Do you want to use one of them?
|
There are already servers available for your Team. Do you want to use one of them?
|
||||||
</x-slot:question>
|
</x-slot:question>
|
||||||
<x-slot:actions>
|
<x-slot:actions>
|
||||||
<x-forms.button class="justify-center box" wire:click="createNewServer">No (create one for me)
|
<x-forms.button class="justify-center w-64 box" wire:click="createNewServer">No (create one for me)
|
||||||
</x-forms.button>
|
</x-forms.button>
|
||||||
<div>
|
<div>
|
||||||
<form wire:submit='selectExistingServer' class="flex flex-col w-full gap-4 lg:w-96">
|
<form wire:submit='selectExistingServer' class="flex flex-col w-full gap-4 lg:w-96">
|
||||||
@@ -139,7 +141,7 @@
|
|||||||
'root' or skip the boarding process and add a new private key manually to Coolify and to the
|
'root' or skip the boarding process and add a new private key manually to Coolify and to the
|
||||||
server.
|
server.
|
||||||
<x-forms.input readonly id="serverPublicKey"></x-forms.input>
|
<x-forms.input readonly id="serverPublicKey"></x-forms.input>
|
||||||
<x-forms.button class="box" wire:target="validateServer" wire:click="validateServer">Check
|
<x-forms.button class="w-64 box" wire:target="validateServer" wire:click="validateServer">Check
|
||||||
again
|
again
|
||||||
</x-forms.button>
|
</x-forms.button>
|
||||||
@endif
|
@endif
|
||||||
@@ -231,12 +233,16 @@
|
|||||||
Could not find Docker Engine on your server. Do you want me to install it for you?
|
Could not find Docker Engine on your server. Do you want me to install it for you?
|
||||||
</x-slot:question>
|
</x-slot:question>
|
||||||
<x-slot:actions>
|
<x-slot:actions>
|
||||||
<x-forms.button class="justify-center box" wire:click="installDocker">
|
<x-slide-over closeWithX fullScreen>
|
||||||
Let's do it!</x-forms.button>
|
<x-slot:title>Configuring Server</x-slot:title>
|
||||||
@if ($dockerInstallationStarted)
|
<x-slot:content>
|
||||||
<x-forms.button class="justify-center box" wire:click="dockerInstalledOrSkipped">
|
<livewire:server.validate-and-install :server="$this->createdServer" />
|
||||||
Validate Server & Continue</x-forms.button>
|
</x-slot:content>
|
||||||
@endif
|
<x-forms.button @click="slideOverOpen=true" class="font-bold box w-96"
|
||||||
|
wire:click.prevent='installServer' isHighlighted>
|
||||||
|
Let's do it!
|
||||||
|
</x-forms.button>
|
||||||
|
</x-slide-over>
|
||||||
</x-slot:actions>
|
</x-slot:actions>
|
||||||
<x-slot:explanation>
|
<x-slot:explanation>
|
||||||
<p>This will install the latest Docker Engine on your server, configure a few things to be able
|
<p>This will install the latest Docker Engine on your server, configure a few things to be able
|
||||||
@@ -246,7 +252,6 @@
|
|||||||
documentation</a>.</p>
|
documentation</a>.</p>
|
||||||
</x-slot:explanation>
|
</x-slot:explanation>
|
||||||
</x-boarding-step>
|
</x-boarding-step>
|
||||||
|
|
||||||
@endif
|
@endif
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
@@ -289,7 +294,7 @@
|
|||||||
@endif
|
@endif
|
||||||
</x-slot:question>
|
</x-slot:question>
|
||||||
<x-slot:actions>
|
<x-slot:actions>
|
||||||
<x-forms.button class="justify-center box" wire:click="createNewProject">Let's create a new
|
<x-forms.button class="justify-center w-64 box" wire:click="createNewProject">Let's create a new
|
||||||
one!</x-forms.button>
|
one!</x-forms.button>
|
||||||
<div>
|
<div>
|
||||||
@if (count($projects) > 0)
|
@if (count($projects) > 0)
|
||||||
@@ -322,7 +327,7 @@
|
|||||||
I will redirect you to the new resource page, where you can create your first resource.
|
I will redirect you to the new resource page, where you can create your first resource.
|
||||||
</x-slot:question>
|
</x-slot:question>
|
||||||
<x-slot:actions>
|
<x-slot:actions>
|
||||||
<div class="items-center justify-center box" wire:click="showNewResource">Let's do
|
<div class="items-center justify-center w-64 box" wire:click="showNewResource">Let's do
|
||||||
it!</div>
|
it!</div>
|
||||||
</x-slot:actions>
|
</x-slot:actions>
|
||||||
<x-slot:explanation>
|
<x-slot:explanation>
|
||||||
|
|||||||
@@ -102,41 +102,40 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="flex items-center gap-2">
|
<div class="flex items-center gap-2">
|
||||||
<h3 class="py-4">Deployments </h3>
|
<h3 class="py-4">Deployments</h3>
|
||||||
@if (count($deployments_per_server) > 0)
|
@if (count($deployments_per_server) > 0)
|
||||||
<x-loading />
|
<x-loading />
|
||||||
@endif
|
@endif
|
||||||
</div>
|
</div>
|
||||||
{{-- <div wire:poll.4000ms="get_deployments" class="grid grid-cols-1 gap-2 lg:grid-cols-3"> --}}
|
<div wire:poll.1000ms="get_deployments" class="grid grid-cols-1">
|
||||||
<div class="grid grid-cols-1">
|
|
||||||
@forelse ($deployments_per_server as $server_name => $deployments)
|
@forelse ($deployments_per_server as $server_name => $deployments)
|
||||||
<h4 class="py-4">{{ $server_name }}</h4>
|
<h4 class="py-4">{{ $server_name }}</h4>
|
||||||
<div class="grid grid-cols-1 gap-2 lg:grid-cols-3">
|
<div class="grid grid-cols-1 gap-2 lg:grid-cols-3">
|
||||||
@foreach ($deployments as $deployment)
|
@foreach ($deployments as $deployment)
|
||||||
<a href="{{ data_get($deployment, 'deployment_url') }}" @class([
|
<a href="{{ data_get($deployment, 'deployment_url') }}" @class([
|
||||||
'gap-2 cursor-pointer box group border-l-2 border-dotted',
|
'gap-2 cursor-pointer box group border-l-2 border-dotted',
|
||||||
'border-white' => data_get($deployment, 'status') === 'queued',
|
'border-coolgray-500' => data_get($deployment, 'status') === 'queued',
|
||||||
'border-yellow-500' => data_get($deployment, 'status') === 'in_progress',
|
'border-yellow-500' => data_get($deployment, 'status') === 'in_progress',
|
||||||
])>
|
])>
|
||||||
<div class="flex flex-col mx-6">
|
<div class="flex flex-col mx-6">
|
||||||
<div class="font-bold text-white">
|
<div class="font-bold text-white">
|
||||||
{{ data_get($deployment, 'application_name') }}
|
{{ data_get($deployment, 'application_name') }}
|
||||||
</div>
|
</div>
|
||||||
@if (data_get($deployment, 'pull_request_id') !== 0)
|
@if (data_get($deployment, 'pull_request_id') !== 0)
|
||||||
<div class="description">
|
<div class="description">
|
||||||
PR #{{ data_get($deployment, 'pull_request_id') }}
|
PR #{{ data_get($deployment, 'pull_request_id') }}
|
||||||
|
</div>
|
||||||
|
@endif
|
||||||
|
<div class="description">
|
||||||
|
{{ str(data_get($deployment, 'status'))->headline() }}
|
||||||
</div>
|
</div>
|
||||||
@endif
|
|
||||||
<div class="description">
|
|
||||||
{{ str(data_get($deployment, 'status'))->headline() }}
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
<div class="flex-1"></div>
|
||||||
<div class="flex-1"></div>
|
</a>
|
||||||
</a>
|
@endforeach
|
||||||
@endforeach
|
|
||||||
</div>
|
</div>
|
||||||
@empty
|
@empty
|
||||||
<div>No queued / in progress deployments</div>
|
<div>No deployments running.</div>
|
||||||
@endforelse
|
@endforelse
|
||||||
</div>
|
</div>
|
||||||
<script>
|
<script>
|
||||||
|
|||||||
@@ -1,9 +1,4 @@
|
|||||||
<div>
|
<div>
|
||||||
<x-modal yesOrNo modalId="deleteDestination" modalTitle="Delete Destination">
|
|
||||||
<x-slot:modalBody>
|
|
||||||
<p>This destination will be deleted. It is not reversible. <br>Please think again.</p>
|
|
||||||
</x-slot:modalBody>
|
|
||||||
</x-modal>
|
|
||||||
<form class="flex flex-col">
|
<form class="flex flex-col">
|
||||||
<div class="flex items-center gap-2">
|
<div class="flex items-center gap-2">
|
||||||
<h1>Destination</h1>
|
<h1>Destination</h1>
|
||||||
@@ -11,9 +6,9 @@
|
|||||||
Save
|
Save
|
||||||
</x-forms.button>
|
</x-forms.button>
|
||||||
@if ($destination->network !== 'coolify')
|
@if ($destination->network !== 'coolify')
|
||||||
<x-forms.button isError isModal modalId="deleteDestination">
|
<x-new-modal isErrorButton buttonTitle="Delete Destination">
|
||||||
Delete
|
This destination will be deleted. It is not reversible. <br>Please think again.
|
||||||
</x-forms.button>
|
</x-new-modal>
|
||||||
@endif
|
@endif
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
@@ -14,7 +14,7 @@
|
|||||||
</x-forms.select>
|
</x-forms.select>
|
||||||
{{-- <x-forms.checkbox type="checkbox" id="is_swarm" label="Is it a Swarm network?" /> --}}
|
{{-- <x-forms.checkbox type="checkbox" id="is_swarm" label="Is it a Swarm network?" /> --}}
|
||||||
<x-forms.button type="submit">
|
<x-forms.button type="submit">
|
||||||
Save Destination
|
Continue
|
||||||
</x-forms.button>
|
</x-forms.button>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
18
resources/views/livewire/new-activity-monitor.blade.php
Normal file
18
resources/views/livewire/new-activity-monitor.blade.php
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
@php use App\Actions\CoolifyTask\RunRemoteProcess; @endphp
|
||||||
|
<div>
|
||||||
|
@if ($this->activity)
|
||||||
|
@if (isset($header))
|
||||||
|
<div class="flex gap-2 pb-2">
|
||||||
|
{{ $header }}
|
||||||
|
@if ($isPollingActive)
|
||||||
|
<x-loading />
|
||||||
|
@endif
|
||||||
|
</div>
|
||||||
|
@endif
|
||||||
|
<div
|
||||||
|
class="scrollbar flex flex-col-reverse w-full overflow-y-auto border border-solid rounded border-coolgray-300 max-h-[32rem] p-4 pt-6 text-xs text-white">
|
||||||
|
|
||||||
|
<pre class="font-mono whitespace-pre-wrap" @if ($isPollingActive) wire:poll.1000ms="polling" @endif>{{ RunRemoteProcess::decodeOutput($this->activity) }}</pre>
|
||||||
|
</div>
|
||||||
|
@endif
|
||||||
|
</div>
|
||||||
@@ -63,6 +63,9 @@
|
|||||||
@click.prevent="activeTab = 'resource-operations'; window.location.hash = 'resource-operations'"
|
@click.prevent="activeTab = 'resource-operations'; window.location.hash = 'resource-operations'"
|
||||||
href="#">Resource Operations
|
href="#">Resource Operations
|
||||||
</a>
|
</a>
|
||||||
|
<a :class="activeTab === 'tags' && 'text-white'"
|
||||||
|
@click.prevent="activeTab = 'tags'; window.location.hash = 'tags'" href="#">Tags
|
||||||
|
</a>
|
||||||
<a :class="activeTab === 'danger' && 'text-white'"
|
<a :class="activeTab === 'danger' && 'text-white'"
|
||||||
@click.prevent="activeTab = 'danger'; window.location.hash = 'danger'" href="#">Danger Zone
|
@click.prevent="activeTab = 'danger'; window.location.hash = 'danger'" href="#">Danger Zone
|
||||||
</a>
|
</a>
|
||||||
@@ -112,6 +115,9 @@
|
|||||||
<div x-cloak x-show="activeTab === 'resource-operations'">
|
<div x-cloak x-show="activeTab === 'resource-operations'">
|
||||||
<livewire:project.shared.resource-operations :resource="$application" />
|
<livewire:project.shared.resource-operations :resource="$application" />
|
||||||
</div>
|
</div>
|
||||||
|
<div x-cloak x-show="activeTab === 'tags'">
|
||||||
|
<livewire:project.shared.tags :resource="$application" />
|
||||||
|
</div>
|
||||||
<div x-cloak x-show="activeTab === 'danger'">
|
<div x-cloak x-show="activeTab === 'danger'">
|
||||||
<livewire:project.shared.danger :resource="$application" />
|
<livewire:project.shared.danger :resource="$application" />
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -5,8 +5,12 @@
|
|||||||
@else
|
@else
|
||||||
<x-forms.button wire:click.prevent="show_debug">Show Debug Logs</x-forms.button>
|
<x-forms.button wire:click.prevent="show_debug">Show Debug Logs</x-forms.button>
|
||||||
@endif
|
@endif
|
||||||
|
@if (data_get($application_deployment_queue, 'status') === 'queued')
|
||||||
|
<x-forms.button wire:click.prevent="force_start">Force Start</x-forms.button>
|
||||||
|
@endif
|
||||||
@if (data_get($application_deployment_queue, 'status') === 'in_progress' ||
|
@if (data_get($application_deployment_queue, 'status') === 'in_progress' ||
|
||||||
data_get($application_deployment_queue, 'status') === 'queued')
|
data_get($application_deployment_queue, 'status') === 'queued')
|
||||||
<x-forms.button isError wire:click.prevent="cancel">Cancel Deployment</x-forms.button>
|
<x-forms.button isError wire:click.prevent="cancel">Cancel</x-forms.button>
|
||||||
@endif
|
@endif
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -112,9 +112,7 @@
|
|||||||
helper="If set, it will tag the built image with this tag too. <br><br>Example: If you set it to 'latest', it will push the image with the commit sha tag + with the latest tag."
|
helper="If set, it will tag the built image with this tag too. <br><br>Example: If you set it to 'latest', it will push the image with the commit sha tag + with the latest tag."
|
||||||
label="Docker Image Tag" />
|
label="Docker Image Tag" />
|
||||||
@endif
|
@endif
|
||||||
|
|
||||||
@endif
|
@endif
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
@endif
|
@endif
|
||||||
|
|
||||||
@@ -130,7 +128,6 @@
|
|||||||
@endif
|
@endif
|
||||||
@if ($application->could_set_build_commands())
|
@if ($application->could_set_build_commands())
|
||||||
@if ($application->build_pack === 'nixpacks')
|
@if ($application->build_pack === 'nixpacks')
|
||||||
|
|
||||||
<div class="flex flex-col gap-2 xl:flex-row">
|
<div class="flex flex-col gap-2 xl:flex-row">
|
||||||
<x-forms.input placeholder="If you modify this, you probably need to have a nixpacks.toml"
|
<x-forms.input placeholder="If you modify this, you probably need to have a nixpacks.toml"
|
||||||
id="application.install_command" label="Install Command" />
|
id="application.install_command" label="Install Command" />
|
||||||
@@ -194,6 +191,13 @@
|
|||||||
@endif
|
@endif
|
||||||
@endif
|
@endif
|
||||||
</div>
|
</div>
|
||||||
|
<div>The following options are for advanced use cases. Only modify them if you
|
||||||
|
know what are
|
||||||
|
you doing.</div>
|
||||||
|
<x-forms.input
|
||||||
|
helper="You can add custom docker run options that will be used when your container is started.<br>Note: Not all options are supported, as they could mess up Coolify's automation and could cause bad experience for users.<br><br>Check the <a class='text-white underline' href='https://coolify.io/docs/custom-docker-options'>docs.</a>"
|
||||||
|
placeholder="--cap-add SYS_ADMIN --device=/dev/fuse --security-opt apparmor:unconfined --ulimit nofile=1024:1024 --tmpfs /run:rw,noexec,nosuid,size=65536k"
|
||||||
|
id="application.custom_docker_run_options" label="Custom Docker Options" />
|
||||||
@endif
|
@endif
|
||||||
|
|
||||||
@endif
|
@endif
|
||||||
|
|||||||
@@ -106,18 +106,6 @@
|
|||||||
</svg>
|
</svg>
|
||||||
Deploy
|
Deploy
|
||||||
</button>
|
</button>
|
||||||
{{-- @if (isDev())
|
|
||||||
<button wire:click='deployNew'
|
|
||||||
class="flex items-center gap-2 cursor-pointer hover:text-white text-neutral-400">
|
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" class="w-5 h-5 text-warning" viewBox="0 0 24 24"
|
|
||||||
stroke-width="1.5" stroke="currentColor" fill="none" stroke-linecap="round"
|
|
||||||
stroke-linejoin="round">
|
|
||||||
<path stroke="none" d="M0 0h24v24H0z" fill="none" />
|
|
||||||
<path d="M7 4v16l13 -8z" />
|
|
||||||
</svg>
|
|
||||||
Deploy (new)
|
|
||||||
</button>
|
|
||||||
@endif --}}
|
|
||||||
@endif
|
@endif
|
||||||
@endif
|
@endif
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,18 +1,17 @@
|
|||||||
<form wire:submit='clone'>
|
<form>
|
||||||
<div class="flex flex-col">
|
<div class="flex flex-col">
|
||||||
<h1>Clone</h1>
|
<h1>Clone</h1>
|
||||||
<div class="subtitle ">Quickly clone all resources to a new project</div>
|
<div class="subtitle ">Quickly clone all resources to a new project or environment</div>
|
||||||
</div>
|
|
||||||
<div class="flex items-end gap-2">
|
|
||||||
<x-forms.input required id="newProjectName" label="New Project Name" />
|
|
||||||
<x-forms.button type="submit">Clone</x-forms.button>
|
|
||||||
</div>
|
</div>
|
||||||
|
<x-forms.input required id="newName" label="New Name" />
|
||||||
|
<x-forms.button isHighlighted wire:click="clone('project')" class="mt-4">Clone to a new Project</x-forms.button>
|
||||||
|
<x-forms.button isHighlighted wire:click="clone('environment')" class="mt-4">Clone to a new Environment</x-forms.button>
|
||||||
<h3 class="pt-4 pb-2">Servers</h3>
|
<h3 class="pt-4 pb-2">Servers</h3>
|
||||||
|
<div>Choose the server and network to clone the resources to.</div>
|
||||||
<div class="flex flex-col gap-4">
|
<div class="flex flex-col gap-4">
|
||||||
@foreach ($servers->sortBy('id') as $server)
|
@foreach ($servers->sortBy('id') as $server)
|
||||||
<div class="p-4 border border-coolgray-500">
|
<div class="p-4">
|
||||||
<h3>{{ $server->name }}</h3>
|
<h4>{{ $server->name }}</h4>
|
||||||
<h5>{{ $server->description }}</h5>
|
|
||||||
<div class="pt-4 pb-2">Docker Networks</div>
|
<div class="pt-4 pb-2">Docker Networks</div>
|
||||||
<div class="grid grid-cols-1 gap-2 pb-4 lg:grid-cols-4">
|
<div class="grid grid-cols-1 gap-2 pb-4 lg:grid-cols-4">
|
||||||
@foreach ($server->destinations() as $destination)
|
@foreach ($server->destinations() as $destination)
|
||||||
@@ -28,9 +27,10 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<h3 class="pt-4 pb-2">Resources</h3>
|
<h3 class="pt-4 pb-2">Resources</h3>
|
||||||
<div class="grid grid-cols-1 gap-2 p-4 border border-coolgray-500">
|
<div>These will be cloned to the new project</div>
|
||||||
|
<div class="grid grid-cols-1 gap-2 pt-4 opacity-95 lg:grid-cols-2 xl:grid-cols-3">
|
||||||
@foreach ($environment->applications->sortBy('name') as $application)
|
@foreach ($environment->applications->sortBy('name') as $application)
|
||||||
<div>
|
<div class="cursor-default box-without-bg bg-coolgray-100 group">
|
||||||
<div class="flex flex-col">
|
<div class="flex flex-col">
|
||||||
<div class="font-bold text-white">{{ $application->name }}</div>
|
<div class="font-bold text-white">{{ $application->name }}</div>
|
||||||
<div class="description">{{ $application->description }}</div>
|
<div class="description">{{ $application->description }}</div>
|
||||||
@@ -38,7 +38,7 @@
|
|||||||
</div>
|
</div>
|
||||||
@endforeach
|
@endforeach
|
||||||
@foreach ($environment->databases()->sortBy('name') as $database)
|
@foreach ($environment->databases()->sortBy('name') as $database)
|
||||||
<div>
|
<div class="cursor-default box-without-bg bg-coolgray-100 group">
|
||||||
<div class="flex flex-col">
|
<div class="flex flex-col">
|
||||||
<div class="font-bold text-white">{{ $database->name }}</div>
|
<div class="font-bold text-white">{{ $database->name }}</div>
|
||||||
<div class="description">{{ $database->description }}</div>
|
<div class="description">{{ $database->description }}</div>
|
||||||
@@ -46,7 +46,7 @@
|
|||||||
</div>
|
</div>
|
||||||
@endforeach
|
@endforeach
|
||||||
@foreach ($environment->services->sortBy('name') as $service)
|
@foreach ($environment->services->sortBy('name') as $service)
|
||||||
<div>
|
<div class="cursor-default box-without-bg bg-coolgray-100 group">
|
||||||
<div class="flex flex-col">
|
<div class="flex flex-col">
|
||||||
<div class="font-bold text-white">{{ $service->name }}</div>
|
<div class="font-bold text-white">{{ $service->name }}</div>
|
||||||
<div class="description">{{ $service->description }}</div>
|
<div class="description">{{ $service->description }}</div>
|
||||||
|
|||||||
@@ -48,6 +48,9 @@
|
|||||||
@click.prevent="activeTab = 'resource-operations'; window.location.hash = 'resource-operations'"
|
@click.prevent="activeTab = 'resource-operations'; window.location.hash = 'resource-operations'"
|
||||||
href="#">Resource Operations
|
href="#">Resource Operations
|
||||||
</a>
|
</a>
|
||||||
|
<a :class="activeTab === 'tags' && 'text-white'"
|
||||||
|
@click.prevent="activeTab = 'tags'; window.location.hash = 'tags'" href="#">Tags
|
||||||
|
</a>
|
||||||
<a :class="activeTab === 'danger' && 'text-white'"
|
<a :class="activeTab === 'danger' && 'text-white'"
|
||||||
@click.prevent="activeTab = 'danger';
|
@click.prevent="activeTab = 'danger';
|
||||||
window.location.hash = 'danger'"
|
window.location.hash = 'danger'"
|
||||||
@@ -89,6 +92,9 @@
|
|||||||
<div x-cloak x-show="activeTab === 'resource-operations'">
|
<div x-cloak x-show="activeTab === 'resource-operations'">
|
||||||
<livewire:project.shared.resource-operations :resource="$database" />
|
<livewire:project.shared.resource-operations :resource="$database" />
|
||||||
</div>
|
</div>
|
||||||
|
<div x-cloak x-show="activeTab === 'tags'">
|
||||||
|
<livewire:project.shared.tags :resource="$database" />
|
||||||
|
</div>
|
||||||
<div x-cloak x-show="activeTab === 'danger'">
|
<div x-cloak x-show="activeTab === 'danger'">
|
||||||
<livewire:project.shared.danger :resource="$database" />
|
<livewire:project.shared.danger :resource="$database" />
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,8 +1,3 @@
|
|||||||
<div>
|
<x-new-modal isErrorButton buttonTitle="Delete Environment" disabled="{{ $disabled }}">
|
||||||
<x-modal yesOrNo modalId="deleteEnvironment" modalTitle="Delete Environment">
|
This environment will be deleted. It is not reversible. <br>Please think again.
|
||||||
<x-slot:modalBody>
|
</x-new-modal>
|
||||||
<p>This environment will be deleted. It is not reversible. <br>Please think again.</p>
|
|
||||||
</x-slot:modalBody>
|
|
||||||
</x-modal>
|
|
||||||
<x-forms.button isError isModal modalId="deleteEnvironment"> Delete Environment</x-forms.button>
|
|
||||||
</div>
|
|
||||||
|
|||||||
@@ -1,10 +1,3 @@
|
|||||||
<div>
|
<x-new-modal isErrorButton buttonTitle="Delete Project" disabled="{{ $disabled }}">
|
||||||
<x-modal yesOrNo modalId="deleteProject" modalTitle="Delete Project">
|
This project will be deleted. It is not reversible. <br>Please think again.
|
||||||
<x-slot:modalBody>
|
</x-new-modal>
|
||||||
<p>This project will be deleted. It is not reversible. <br>Please think again.</p>
|
|
||||||
</x-slot:modalBody>
|
|
||||||
</x-modal>
|
|
||||||
<x-forms.button isError isModal modalId="deleteProject">
|
|
||||||
Delete Project
|
|
||||||
</x-forms.button>
|
|
||||||
</div>
|
|
||||||
|
|||||||
@@ -5,6 +5,7 @@
|
|||||||
<div class="flex items-end gap-2">
|
<div class="flex items-end gap-2">
|
||||||
<h2>General</h2>
|
<h2>General</h2>
|
||||||
<x-forms.button type="submit">Save</x-forms.button>
|
<x-forms.button type="submit">Save</x-forms.button>
|
||||||
|
<livewire:project.delete-project :disabled="$project->resource_count() > 0" :project_id="$project->id" />
|
||||||
</div>
|
</div>
|
||||||
<div class="flex gap-2">
|
<div class="flex gap-2">
|
||||||
<x-forms.input label="Name" id="project.name" />
|
<x-forms.input label="Name" id="project.name" />
|
||||||
@@ -23,7 +24,8 @@
|
|||||||
Add</button>
|
Add</button>
|
||||||
</x-slide-over>
|
</x-slide-over>
|
||||||
</div>
|
</div>
|
||||||
<div class="flex items-center gap-2 pb-4">You can use these variables anywhere with <span class="text-warning">@{{project.VARIABLENAME}}</span><x-helper
|
<div class="flex items-center gap-2 pb-4">You can use these variables anywhere with <span
|
||||||
|
class="text-warning">@{{ project.VARIABLENAME }}</span><x-helper
|
||||||
helper="More info <a class='text-white underline' href='https://coolify.io/docs/environment-variables#shared-variables' target='_blank'>here</a>."></x-helper>
|
helper="More info <a class='text-white underline' href='https://coolify.io/docs/environment-variables#shared-variables' target='_blank'>here</a>."></x-helper>
|
||||||
</div>
|
</div>
|
||||||
<div class="flex flex-col gap-2">
|
<div class="flex flex-col gap-2">
|
||||||
|
|||||||
@@ -3,6 +3,7 @@
|
|||||||
<div class="flex items-end gap-2">
|
<div class="flex items-end gap-2">
|
||||||
<h1>Environment: {{ data_get($environment, 'name') }}</h1>
|
<h1>Environment: {{ data_get($environment, 'name') }}</h1>
|
||||||
<x-forms.button type="submit">Save</x-forms.button>
|
<x-forms.button type="submit">Save</x-forms.button>
|
||||||
|
<livewire:project.delete-environment :disabled="!$environment->isEmpty()" :environment_id="$environment->id" />
|
||||||
</div>
|
</div>
|
||||||
<nav class="flex pt-2 pb-10">
|
<nav class="flex pt-2 pb-10">
|
||||||
<ol class="flex items-center">
|
<ol class="flex items-center">
|
||||||
|
|||||||
@@ -8,7 +8,7 @@
|
|||||||
<li class="step">Select a Repository, Branch & Save</li>
|
<li class="step">Select a Repository, Branch & Save</li>
|
||||||
</ul>
|
</ul>
|
||||||
<div class="flex flex-col justify-center gap-2 text-left xl:flex-row">
|
<div class="flex flex-col justify-center gap-2 text-left xl:flex-row">
|
||||||
@foreach ($private_keys as $key)
|
@forelse ($private_keys as $key)
|
||||||
@if ($private_key_id == $key->id)
|
@if ($private_key_id == $key->id)
|
||||||
<div class="gap-2 py-4 cursor-pointer group hover:bg-coollabs bg-coolgray-200"
|
<div class="gap-2 py-4 cursor-pointer group hover:bg-coollabs bg-coolgray-200"
|
||||||
wire:click.defer="setPrivateKey('{{ $key->id }}')" wire:key="{{ $key->id }}">
|
wire:click.defer="setPrivateKey('{{ $key->id }}')" wire:key="{{ $key->id }}">
|
||||||
@@ -32,7 +32,16 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@endif
|
@endif
|
||||||
@endforeach
|
@empty
|
||||||
|
<div class="flex flex-col items-center justify-center gap-2">
|
||||||
|
<div class="text-neutral-500">
|
||||||
|
No private keys found.
|
||||||
|
</div>
|
||||||
|
<a href="{{ route('security.private-key.index') }}">
|
||||||
|
<x-forms.button>Create a new private key</x-forms.button>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
@endforelse
|
||||||
</div>
|
</div>
|
||||||
@endif
|
@endif
|
||||||
@if ($current_step === 'repository')
|
@if ($current_step === 'repository')
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user