mirror of
https://github.com/ershisan99/coolify.git
synced 2026-01-04 05:02:12 +00:00
Compare commits
91 Commits
v4.0.0-bet
...
v4.0.0-bet
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b528a0f4ec | ||
|
|
7f265a6692 | ||
|
|
55e00e35c1 | ||
|
|
d94e1ba55b | ||
|
|
00e7167174 | ||
|
|
9d7b69fc0e | ||
|
|
db9a68e9c9 | ||
|
|
b5d9d6e268 | ||
|
|
5ff0c563ec | ||
|
|
e2131523ec | ||
|
|
1f5f51e3e5 | ||
|
|
1bb0d54dce | ||
|
|
094bb37049 | ||
|
|
58601db5ef | ||
|
|
b5bef98a9b | ||
|
|
1026f1efa5 | ||
|
|
0c673fb524 | ||
|
|
7ee2c9478d | ||
|
|
4b51f8251b | ||
|
|
e91a64b1cc | ||
|
|
8920762fc5 | ||
|
|
ba40f93386 | ||
|
|
781bf52a8e | ||
|
|
cca1a9832e | ||
|
|
1a152a5597 | ||
|
|
76a5290351 | ||
|
|
829e17ef2b | ||
|
|
bc5d3bea14 | ||
|
|
edf2a2e68d | ||
|
|
5df443a016 | ||
|
|
f6396f2e74 | ||
|
|
c618e58a11 | ||
|
|
2ea27acdde | ||
|
|
331cad276e | ||
|
|
b74eab8377 | ||
|
|
fb80318553 | ||
|
|
3eb4aed867 | ||
|
|
f6f959a897 | ||
|
|
2b422a542a | ||
|
|
d0e9d58a43 | ||
|
|
8a1933b9b2 | ||
|
|
96a587f343 | ||
|
|
2bb6a71874 | ||
|
|
94acd12f1c | ||
|
|
5e44a61068 | ||
|
|
a54f0ed94d | ||
|
|
662c6f3cc2 | ||
|
|
d93c635a0a | ||
|
|
17b73aaf91 | ||
|
|
c194911458 | ||
|
|
92e99e3fb4 | ||
|
|
848e6102a1 | ||
|
|
ef37bf9b1a | ||
|
|
eb41e023c7 | ||
|
|
6b4987bf39 | ||
|
|
d46ff76887 | ||
|
|
d53a9e672c | ||
|
|
80ef99a24b | ||
|
|
6062a1f8c7 | ||
|
|
d974bd9a07 | ||
|
|
fb8c9566d5 | ||
|
|
7431bd69e9 | ||
|
|
5d7f393e94 | ||
|
|
ef25d100e1 | ||
|
|
17f82c8972 | ||
|
|
05c937743c | ||
|
|
bf2e7ff130 | ||
|
|
1b30ee606f | ||
|
|
3235907266 | ||
|
|
99c7e417d6 | ||
|
|
d81906d348 | ||
|
|
296872d2e4 | ||
|
|
7cd02b4916 | ||
|
|
0e217f48be | ||
|
|
61fdf4b6c7 | ||
|
|
91dbf1f01a | ||
|
|
d52aac76c0 | ||
|
|
6102e441d6 | ||
|
|
1a5fec39c0 | ||
|
|
efa5091b98 | ||
|
|
be4386658a | ||
|
|
0cddce7a37 | ||
|
|
a86d13632e | ||
|
|
d71682a3f7 | ||
|
|
c901ace21a | ||
|
|
418398a870 | ||
|
|
baca57062e | ||
|
|
52df8e6e8b | ||
|
|
b51747378a | ||
|
|
424a6b0428 | ||
|
|
692047e4c8 |
@@ -3,7 +3,7 @@ tasks:
|
|||||||
# Fix because of https://github.com/gitpod-io/gitpod/issues/16614
|
# Fix because of https://github.com/gitpod-io/gitpod/issues/16614
|
||||||
before: sudo curl -o /usr/local/bin/docker-compose -fsSL https://github.com/docker/compose/releases/download/v2.16.0/docker-compose-linux-$(uname -m)
|
before: sudo curl -o /usr/local/bin/docker-compose -fsSL https://github.com/docker/compose/releases/download/v2.16.0/docker-compose-linux-$(uname -m)
|
||||||
init: |
|
init: |
|
||||||
cp .env.example .env &&
|
cp .env.development.example .env &&
|
||||||
sed -i "s#APP_URL=http://localhost#APP_URL=$(gp url 8000)#g" .env
|
sed -i "s#APP_URL=http://localhost#APP_URL=$(gp url 8000)#g" .env
|
||||||
sed -i "s#USERID=#USERID=33333#g" .env
|
sed -i "s#USERID=#USERID=33333#g" .env
|
||||||
sed -i "s#GROUPID=#GROUPID=33333#g" .env
|
sed -i "s#GROUPID=#GROUPID=33333#g" .env
|
||||||
@@ -20,7 +20,7 @@ tasks:
|
|||||||
echo "Waiting for Sail environment to boot up."
|
echo "Waiting for Sail environment to boot up."
|
||||||
gp sync-await spin-is-ready
|
gp sync-await spin-is-ready
|
||||||
./vendor/bin/spin exec vite npm install
|
./vendor/bin/spin exec vite npm install
|
||||||
./vendor/bin/spin exec vite npm run dev
|
./vendor/bin/spin exec vite npm run dev -- --host
|
||||||
|
|
||||||
- name: Laravel Queue Worker, listening to code changes
|
- name: Laravel Queue Worker, listening to code changes
|
||||||
command: |
|
command: |
|
||||||
|
|||||||
@@ -33,7 +33,6 @@ class StartClickhouse
|
|||||||
$environment_variables = $this->generate_environment_variables();
|
$environment_variables = $this->generate_environment_variables();
|
||||||
|
|
||||||
$docker_compose = [
|
$docker_compose = [
|
||||||
'version' => '3.8',
|
|
||||||
'services' => [
|
'services' => [
|
||||||
$container_name => [
|
$container_name => [
|
||||||
'image' => $this->database->image,
|
'image' => $this->database->image,
|
||||||
|
|||||||
@@ -107,7 +107,6 @@ class StartDatabaseProxy
|
|||||||
COPY nginx.conf /etc/nginx/nginx.conf
|
COPY nginx.conf /etc/nginx/nginx.conf
|
||||||
EOF;
|
EOF;
|
||||||
$docker_compose = [
|
$docker_compose = [
|
||||||
'version' => '3.8',
|
|
||||||
'services' => [
|
'services' => [
|
||||||
$proxyContainerName => [
|
$proxyContainerName => [
|
||||||
'build' => [
|
'build' => [
|
||||||
|
|||||||
@@ -36,7 +36,6 @@ class StartDragonfly
|
|||||||
$environment_variables = $this->generate_environment_variables();
|
$environment_variables = $this->generate_environment_variables();
|
||||||
|
|
||||||
$docker_compose = [
|
$docker_compose = [
|
||||||
'version' => '3.8',
|
|
||||||
'services' => [
|
'services' => [
|
||||||
$container_name => [
|
$container_name => [
|
||||||
'image' => $this->database->image,
|
'image' => $this->database->image,
|
||||||
|
|||||||
@@ -37,7 +37,6 @@ class StartKeydb
|
|||||||
$this->add_custom_keydb();
|
$this->add_custom_keydb();
|
||||||
|
|
||||||
$docker_compose = [
|
$docker_compose = [
|
||||||
'version' => '3.8',
|
|
||||||
'services' => [
|
'services' => [
|
||||||
$container_name => [
|
$container_name => [
|
||||||
'image' => $this->database->image,
|
'image' => $this->database->image,
|
||||||
@@ -96,7 +95,7 @@ class StartKeydb
|
|||||||
if (count($volume_names) > 0) {
|
if (count($volume_names) > 0) {
|
||||||
$docker_compose['volumes'] = $volume_names;
|
$docker_compose['volumes'] = $volume_names;
|
||||||
}
|
}
|
||||||
if (!is_null($this->database->keydb_conf)) {
|
if (!is_null($this->database->keydb_conf) || !empty($this->database->keydb_conf)) {
|
||||||
$docker_compose['services'][$container_name]['volumes'][] = [
|
$docker_compose['services'][$container_name]['volumes'][] = [
|
||||||
'type' => 'bind',
|
'type' => 'bind',
|
||||||
'source' => $this->configuration_dir . '/keydb.conf',
|
'source' => $this->configuration_dir . '/keydb.conf',
|
||||||
@@ -162,7 +161,7 @@ class StartKeydb
|
|||||||
}
|
}
|
||||||
private function add_custom_keydb()
|
private function add_custom_keydb()
|
||||||
{
|
{
|
||||||
if (is_null($this->database->keydb_conf)) {
|
if (is_null($this->database->keydb_conf) || empty($this->database->keydb_conf)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
$filename = 'keydb.conf';
|
$filename = 'keydb.conf';
|
||||||
|
|||||||
@@ -32,7 +32,6 @@ class StartMariadb
|
|||||||
$environment_variables = $this->generate_environment_variables();
|
$environment_variables = $this->generate_environment_variables();
|
||||||
$this->add_custom_mysql();
|
$this->add_custom_mysql();
|
||||||
$docker_compose = [
|
$docker_compose = [
|
||||||
'version' => '3.8',
|
|
||||||
'services' => [
|
'services' => [
|
||||||
$container_name => [
|
$container_name => [
|
||||||
'image' => $this->database->image,
|
'image' => $this->database->image,
|
||||||
@@ -90,7 +89,7 @@ class StartMariadb
|
|||||||
if (count($volume_names) > 0) {
|
if (count($volume_names) > 0) {
|
||||||
$docker_compose['volumes'] = $volume_names;
|
$docker_compose['volumes'] = $volume_names;
|
||||||
}
|
}
|
||||||
if (!is_null($this->database->mariadb_conf)) {
|
if (!is_null($this->database->mariadb_conf) || !empty($this->database->mariadb_conf)) {
|
||||||
$docker_compose['services'][$container_name]['volumes'][] = [
|
$docker_compose['services'][$container_name]['volumes'][] = [
|
||||||
'type' => 'bind',
|
'type' => 'bind',
|
||||||
'source' => $this->configuration_dir . '/custom-config.cnf',
|
'source' => $this->configuration_dir . '/custom-config.cnf',
|
||||||
@@ -165,7 +164,7 @@ class StartMariadb
|
|||||||
}
|
}
|
||||||
private function add_custom_mysql()
|
private function add_custom_mysql()
|
||||||
{
|
{
|
||||||
if (is_null($this->database->mariadb_conf)) {
|
if (is_null($this->database->mariadb_conf) || empty($this->database->mariadb_conf)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
$filename = 'custom-config.cnf';
|
$filename = 'custom-config.cnf';
|
||||||
|
|||||||
@@ -35,7 +35,6 @@ class StartMongodb
|
|||||||
$this->add_custom_mongo_conf();
|
$this->add_custom_mongo_conf();
|
||||||
|
|
||||||
$docker_compose = [
|
$docker_compose = [
|
||||||
'version' => '3.8',
|
|
||||||
'services' => [
|
'services' => [
|
||||||
$container_name => [
|
$container_name => [
|
||||||
'image' => $this->database->image,
|
'image' => $this->database->image,
|
||||||
@@ -97,7 +96,7 @@ class StartMongodb
|
|||||||
if (count($volume_names) > 0) {
|
if (count($volume_names) > 0) {
|
||||||
$docker_compose['volumes'] = $volume_names;
|
$docker_compose['volumes'] = $volume_names;
|
||||||
}
|
}
|
||||||
if (!is_null($this->database->mongo_conf)) {
|
if (!is_null($this->database->mongo_conf) || !empty($this->database->mongo_conf)) {
|
||||||
$docker_compose['services'][$container_name]['volumes'][] = [
|
$docker_compose['services'][$container_name]['volumes'][] = [
|
||||||
'type' => 'bind',
|
'type' => 'bind',
|
||||||
'source' => $this->configuration_dir . '/mongod.conf',
|
'source' => $this->configuration_dir . '/mongod.conf',
|
||||||
@@ -178,7 +177,7 @@ class StartMongodb
|
|||||||
}
|
}
|
||||||
private function add_custom_mongo_conf()
|
private function add_custom_mongo_conf()
|
||||||
{
|
{
|
||||||
if (is_null($this->database->mongo_conf)) {
|
if (is_null($this->database->mongo_conf) || empty($this->database->mongo_conf)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
$filename = 'mongod.conf';
|
$filename = 'mongod.conf';
|
||||||
|
|||||||
@@ -32,7 +32,6 @@ class StartMysql
|
|||||||
$environment_variables = $this->generate_environment_variables();
|
$environment_variables = $this->generate_environment_variables();
|
||||||
$this->add_custom_mysql();
|
$this->add_custom_mysql();
|
||||||
$docker_compose = [
|
$docker_compose = [
|
||||||
'version' => '3.8',
|
|
||||||
'services' => [
|
'services' => [
|
||||||
$container_name => [
|
$container_name => [
|
||||||
'image' => $this->database->image,
|
'image' => $this->database->image,
|
||||||
@@ -90,7 +89,7 @@ class StartMysql
|
|||||||
if (count($volume_names) > 0) {
|
if (count($volume_names) > 0) {
|
||||||
$docker_compose['volumes'] = $volume_names;
|
$docker_compose['volumes'] = $volume_names;
|
||||||
}
|
}
|
||||||
if (!is_null($this->database->mysql_conf)) {
|
if (!is_null($this->database->mysql_conf) || !empty($this->database->mysql_conf)) {
|
||||||
$docker_compose['services'][$container_name]['volumes'][] = [
|
$docker_compose['services'][$container_name]['volumes'][] = [
|
||||||
'type' => 'bind',
|
'type' => 'bind',
|
||||||
'source' => $this->configuration_dir . '/custom-config.cnf',
|
'source' => $this->configuration_dir . '/custom-config.cnf',
|
||||||
@@ -165,7 +164,7 @@ class StartMysql
|
|||||||
}
|
}
|
||||||
private function add_custom_mysql()
|
private function add_custom_mysql()
|
||||||
{
|
{
|
||||||
if (is_null($this->database->mysql_conf)) {
|
if (is_null($this->database->mysql_conf) || empty($this->database->mysql_conf)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
$filename = 'custom-config.cnf';
|
$filename = 'custom-config.cnf';
|
||||||
|
|||||||
@@ -35,7 +35,6 @@ class StartPostgresql
|
|||||||
$this->add_custom_conf();
|
$this->add_custom_conf();
|
||||||
|
|
||||||
$docker_compose = [
|
$docker_compose = [
|
||||||
'version' => '3.8',
|
|
||||||
'services' => [
|
'services' => [
|
||||||
$container_name => [
|
$container_name => [
|
||||||
'image' => $this->database->image,
|
'image' => $this->database->image,
|
||||||
@@ -78,7 +77,6 @@ class StartPostgresql
|
|||||||
data_set($docker_compose, "services.{$container_name}.cpuset", $this->database->limits_cpuset);
|
data_set($docker_compose, "services.{$container_name}.cpuset", $this->database->limits_cpuset);
|
||||||
}
|
}
|
||||||
if ($this->database->destination->server->isLogDrainEnabled() && $this->database->isLogDrainEnabled()) {
|
if ($this->database->destination->server->isLogDrainEnabled() && $this->database->isLogDrainEnabled()) {
|
||||||
ray('Log Drain Enabled');
|
|
||||||
$docker_compose['services'][$container_name]['logging'] = [
|
$docker_compose['services'][$container_name]['logging'] = [
|
||||||
'driver' => 'fluentd',
|
'driver' => 'fluentd',
|
||||||
'options' => [
|
'options' => [
|
||||||
@@ -107,7 +105,7 @@ class StartPostgresql
|
|||||||
];
|
];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (!is_null($this->database->postgres_conf)) {
|
if (!is_null($this->database->postgres_conf) && !empty($this->database->postgres_conf)) {
|
||||||
$docker_compose['services'][$container_name]['volumes'][] = [
|
$docker_compose['services'][$container_name]['volumes'][] = [
|
||||||
'type' => 'bind',
|
'type' => 'bind',
|
||||||
'source' => $this->configuration_dir . '/custom-postgres.conf',
|
'source' => $this->configuration_dir . '/custom-postgres.conf',
|
||||||
@@ -165,8 +163,6 @@ class StartPostgresql
|
|||||||
private function generate_environment_variables()
|
private function generate_environment_variables()
|
||||||
{
|
{
|
||||||
$environment_variables = collect();
|
$environment_variables = collect();
|
||||||
ray('Generate Environment Variables')->green();
|
|
||||||
ray($this->database->runtime_environment_variables)->green();
|
|
||||||
foreach ($this->database->runtime_environment_variables as $env) {
|
foreach ($this->database->runtime_environment_variables as $env) {
|
||||||
$environment_variables->push("$env->key=$env->real_value");
|
$environment_variables->push("$env->key=$env->real_value");
|
||||||
}
|
}
|
||||||
@@ -203,11 +199,16 @@ class StartPostgresql
|
|||||||
}
|
}
|
||||||
private function add_custom_conf()
|
private function add_custom_conf()
|
||||||
{
|
{
|
||||||
if (is_null($this->database->postgres_conf)) {
|
if (is_null($this->database->postgres_conf) || empty($this->database->postgres_conf)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
$filename = 'custom-postgres.conf';
|
$filename = 'custom-postgres.conf';
|
||||||
$content = $this->database->postgres_conf;
|
$content = $this->database->postgres_conf;
|
||||||
|
if (!str($content)->contains('listen_addresses')) {
|
||||||
|
$content .= "\nlisten_addresses = '*'";
|
||||||
|
$this->database->postgres_conf = $content;
|
||||||
|
$this->database->save();
|
||||||
|
}
|
||||||
$content_base64 = base64_encode($content);
|
$content_base64 = base64_encode($content);
|
||||||
$this->commands[] = "echo '{$content_base64}' | base64 -d | tee $this->configuration_dir/{$filename} > /dev/null";
|
$this->commands[] = "echo '{$content_base64}' | base64 -d | tee $this->configuration_dir/{$filename} > /dev/null";
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -37,7 +37,6 @@ class StartRedis
|
|||||||
$this->add_custom_redis();
|
$this->add_custom_redis();
|
||||||
|
|
||||||
$docker_compose = [
|
$docker_compose = [
|
||||||
'version' => '3.8',
|
|
||||||
'services' => [
|
'services' => [
|
||||||
$container_name => [
|
$container_name => [
|
||||||
'image' => $this->database->image,
|
'image' => $this->database->image,
|
||||||
@@ -100,7 +99,7 @@ class StartRedis
|
|||||||
if (count($volume_names) > 0) {
|
if (count($volume_names) > 0) {
|
||||||
$docker_compose['volumes'] = $volume_names;
|
$docker_compose['volumes'] = $volume_names;
|
||||||
}
|
}
|
||||||
if (!is_null($this->database->redis_conf)) {
|
if (!is_null($this->database->redis_conf) || !empty($this->database->redis_conf)) {
|
||||||
$docker_compose['services'][$container_name]['volumes'][] = [
|
$docker_compose['services'][$container_name]['volumes'][] = [
|
||||||
'type' => 'bind',
|
'type' => 'bind',
|
||||||
'source' => $this->configuration_dir . '/redis.conf',
|
'source' => $this->configuration_dir . '/redis.conf',
|
||||||
@@ -166,7 +165,7 @@ class StartRedis
|
|||||||
}
|
}
|
||||||
private function add_custom_redis()
|
private function add_custom_redis()
|
||||||
{
|
{
|
||||||
if (is_null($this->database->redis_conf)) {
|
if (is_null($this->database->redis_conf) || empty($this->database->redis_conf)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
$filename = 'redis.conf';
|
$filename = 'redis.conf';
|
||||||
|
|||||||
657
app/Actions/Docker/GetContainersStatus.php
Normal file
657
app/Actions/Docker/GetContainersStatus.php
Normal file
@@ -0,0 +1,657 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Actions\Docker;
|
||||||
|
|
||||||
|
use App\Actions\Database\StartDatabaseProxy;
|
||||||
|
use App\Actions\Proxy\CheckProxy;
|
||||||
|
use App\Actions\Proxy\StartProxy;
|
||||||
|
use App\Actions\Shared\ComplexStatusCheck;
|
||||||
|
use App\Models\Application;
|
||||||
|
use App\Models\ApplicationPreview;
|
||||||
|
use App\Models\Server;
|
||||||
|
use App\Models\ServiceDatabase;
|
||||||
|
use App\Notifications\Container\ContainerRestarted;
|
||||||
|
use App\Notifications\Container\ContainerStopped;
|
||||||
|
use Illuminate\Support\Arr;
|
||||||
|
use Illuminate\Support\Facades\Log;
|
||||||
|
use Lorisleiva\Actions\Concerns\AsAction;
|
||||||
|
|
||||||
|
class GetContainersStatus
|
||||||
|
{
|
||||||
|
use AsAction;
|
||||||
|
public $applications;
|
||||||
|
public $server;
|
||||||
|
|
||||||
|
public function handle(Server $server)
|
||||||
|
{
|
||||||
|
if (isDev()) {
|
||||||
|
$server = Server::find(0);
|
||||||
|
}
|
||||||
|
$this->server = $server;
|
||||||
|
if (!$this->server->isFunctional()) {
|
||||||
|
return 'Server is not ready.';
|
||||||
|
};
|
||||||
|
$this->applications = $this->server->applications();
|
||||||
|
$skip_these_applications = collect([]);
|
||||||
|
foreach ($this->applications as $application) {
|
||||||
|
if ($application->additional_servers->count() > 0) {
|
||||||
|
$skip_these_applications->push($application);
|
||||||
|
ComplexStatusCheck::run($application);
|
||||||
|
$this->applications = $this->applications->filter(function ($value, $key) use ($application) {
|
||||||
|
return $value->id !== $application->id;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
$this->applications = $this->applications->filter(function ($value, $key) use ($skip_these_applications) {
|
||||||
|
return !$skip_these_applications->pluck('id')->contains($value->id);
|
||||||
|
});
|
||||||
|
$this->old_way();
|
||||||
|
// if ($this->server->isSwarm()) {
|
||||||
|
// $this->old_way();
|
||||||
|
// } else {
|
||||||
|
// if (!$this->server->is_metrics_enabled) {
|
||||||
|
// $this->old_way();
|
||||||
|
// return;
|
||||||
|
// }
|
||||||
|
// $sentinel_found = instant_remote_process(["docker inspect coolify-sentinel"], $this->server, false);
|
||||||
|
// $sentinel_found = json_decode($sentinel_found, true);
|
||||||
|
// $status = data_get($sentinel_found, '0.State.Status', 'exited');
|
||||||
|
// if ($status === 'running') {
|
||||||
|
// ray('Checking with Sentinel');
|
||||||
|
// $this->sentinel();
|
||||||
|
// } else {
|
||||||
|
// ray('Checking the Old way');
|
||||||
|
// $this->old_way();
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
}
|
||||||
|
|
||||||
|
private function sentinel()
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
$containers = $this->server->getContainers();
|
||||||
|
if ($containers->count() === 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
$databases = $this->server->databases();
|
||||||
|
$services = $this->server->services()->get();
|
||||||
|
$previews = $this->server->previews();
|
||||||
|
$foundApplications = [];
|
||||||
|
$foundApplicationPreviews = [];
|
||||||
|
$foundDatabases = [];
|
||||||
|
$foundServices = [];
|
||||||
|
|
||||||
|
foreach ($containers as $container) {
|
||||||
|
$labels = Arr::undot(data_get($container, 'labels'));
|
||||||
|
$containerStatus = data_get($container, 'state');
|
||||||
|
$containerHealth = data_get($container, 'health_status', 'unhealthy');
|
||||||
|
$containerStatus = "$containerStatus ($containerHealth)";
|
||||||
|
$applicationId = data_get($labels, 'coolify.applicationId');
|
||||||
|
if ($applicationId) {
|
||||||
|
$pullRequestId = data_get($labels, 'coolify.pullRequestId');
|
||||||
|
if ($pullRequestId) {
|
||||||
|
if (str($applicationId)->contains('-')) {
|
||||||
|
$applicationId = str($applicationId)->before('-');
|
||||||
|
}
|
||||||
|
$preview = ApplicationPreview::where('application_id', $applicationId)->where('pull_request_id', $pullRequestId)->first();
|
||||||
|
if ($preview) {
|
||||||
|
$foundApplicationPreviews[] = $preview->id;
|
||||||
|
$statusFromDb = $preview->status;
|
||||||
|
if ($statusFromDb !== $containerStatus) {
|
||||||
|
$preview->update(['status' => $containerStatus]);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
//Notify user that this container should not be there.
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
$application = $this->applications->where('id', $applicationId)->first();
|
||||||
|
if ($application) {
|
||||||
|
$foundApplications[] = $application->id;
|
||||||
|
$statusFromDb = $application->status;
|
||||||
|
if ($statusFromDb !== $containerStatus) {
|
||||||
|
$application->update(['status' => $containerStatus]);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
//Notify user that this container should not be there.
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
$uuid = data_get($labels, 'com.docker.compose.service');
|
||||||
|
$type = data_get($labels, 'coolify.type');
|
||||||
|
if ($uuid) {
|
||||||
|
if ($type === 'service') {
|
||||||
|
$database_id = data_get($labels, 'coolify.service.subId');
|
||||||
|
if ($database_id) {
|
||||||
|
$service_db = ServiceDatabase::where('id', $database_id)->first();
|
||||||
|
if ($service_db) {
|
||||||
|
$uuid = $service_db->service->uuid;
|
||||||
|
$isPublic = data_get($service_db, 'is_public');
|
||||||
|
if ($isPublic) {
|
||||||
|
$foundTcpProxy = $containers->filter(function ($value, $key) use ($uuid) {
|
||||||
|
if ($this->server->isSwarm()) {
|
||||||
|
// TODO: fix this with sentinel
|
||||||
|
return data_get($value, 'Spec.Name') === "coolify-proxy_$uuid";
|
||||||
|
} else {
|
||||||
|
return data_get($value, 'name') === "$uuid-proxy";
|
||||||
|
}
|
||||||
|
})->first();
|
||||||
|
if (!$foundTcpProxy) {
|
||||||
|
StartDatabaseProxy::run($service_db);
|
||||||
|
// $this->server->team?->notify(new ContainerRestarted("TCP Proxy for {$service_db->service->name}", $this->server));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
$database = $databases->where('uuid', $uuid)->first();
|
||||||
|
if ($database) {
|
||||||
|
$isPublic = data_get($database, 'is_public');
|
||||||
|
$foundDatabases[] = $database->id;
|
||||||
|
$statusFromDb = $database->status;
|
||||||
|
if ($statusFromDb !== $containerStatus) {
|
||||||
|
$database->update(['status' => $containerStatus]);
|
||||||
|
}
|
||||||
|
if ($isPublic) {
|
||||||
|
$foundTcpProxy = $containers->filter(function ($value, $key) use ($uuid) {
|
||||||
|
if ($this->server->isSwarm()) {
|
||||||
|
// TODO: fix this with sentinel
|
||||||
|
return data_get($value, 'Spec.Name') === "coolify-proxy_$uuid";
|
||||||
|
} else {
|
||||||
|
return data_get($value, 'name') === "$uuid-proxy";
|
||||||
|
}
|
||||||
|
})->first();
|
||||||
|
if (!$foundTcpProxy) {
|
||||||
|
StartDatabaseProxy::run($database);
|
||||||
|
$this->server->team?->notify(new ContainerRestarted("TCP Proxy for {$database->name}", $this->server));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Notify user that this container should not be there.
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (data_get($container, 'name') === 'coolify-db') {
|
||||||
|
$foundDatabases[] = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
$serviceLabelId = data_get($labels, 'coolify.serviceId');
|
||||||
|
if ($serviceLabelId) {
|
||||||
|
$subType = data_get($labels, 'coolify.service.subType');
|
||||||
|
$subId = data_get($labels, 'coolify.service.subId');
|
||||||
|
$service = $services->where('id', $serviceLabelId)->first();
|
||||||
|
if (!$service) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if ($subType === 'application') {
|
||||||
|
$service = $service->applications()->where('id', $subId)->first();
|
||||||
|
} else {
|
||||||
|
$service = $service->databases()->where('id', $subId)->first();
|
||||||
|
}
|
||||||
|
if ($service) {
|
||||||
|
$foundServices[] = "$service->id-$service->name";
|
||||||
|
$statusFromDb = $service->status;
|
||||||
|
if ($statusFromDb !== $containerStatus) {
|
||||||
|
// ray('Updating status: ' . $containerStatus);
|
||||||
|
$service->update(['status' => $containerStatus]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
$exitedServices = collect([]);
|
||||||
|
foreach ($services as $service) {
|
||||||
|
$apps = $service->applications()->get();
|
||||||
|
$dbs = $service->databases()->get();
|
||||||
|
foreach ($apps as $app) {
|
||||||
|
if (in_array("$app->id-$app->name", $foundServices)) {
|
||||||
|
continue;
|
||||||
|
} else {
|
||||||
|
$exitedServices->push($app);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
foreach ($dbs as $db) {
|
||||||
|
if (in_array("$db->id-$db->name", $foundServices)) {
|
||||||
|
continue;
|
||||||
|
} else {
|
||||||
|
$exitedServices->push($db);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
$exitedServices = $exitedServices->unique('id');
|
||||||
|
foreach ($exitedServices as $exitedService) {
|
||||||
|
if (str($exitedService->status)->startsWith('exited')) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
$name = data_get($exitedService, 'name');
|
||||||
|
$fqdn = data_get($exitedService, 'fqdn');
|
||||||
|
$containerName = $name ? "$name, available at $fqdn" : $fqdn;
|
||||||
|
$projectUuid = data_get($service, 'environment.project.uuid');
|
||||||
|
$serviceUuid = data_get($service, 'uuid');
|
||||||
|
$environmentName = data_get($service, 'environment.name');
|
||||||
|
|
||||||
|
if ($projectUuid && $serviceUuid && $environmentName) {
|
||||||
|
$url = base_url() . '/project/' . $projectUuid . "/" . $environmentName . "/service/" . $serviceUuid;
|
||||||
|
} else {
|
||||||
|
$url = null;
|
||||||
|
}
|
||||||
|
$this->server->team?->notify(new ContainerStopped($containerName, $this->server, $url));
|
||||||
|
$exitedService->update(['status' => 'exited']);
|
||||||
|
}
|
||||||
|
|
||||||
|
$notRunningApplications = $this->applications->pluck('id')->diff($foundApplications);
|
||||||
|
foreach ($notRunningApplications as $applicationId) {
|
||||||
|
$application = $this->applications->where('id', $applicationId)->first();
|
||||||
|
if (str($application->status)->startsWith('exited')) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
$application->update(['status' => 'exited']);
|
||||||
|
|
||||||
|
$name = data_get($application, 'name');
|
||||||
|
$fqdn = data_get($application, 'fqdn');
|
||||||
|
|
||||||
|
$containerName = $name ? "$name ($fqdn)" : $fqdn;
|
||||||
|
|
||||||
|
$projectUuid = data_get($application, 'environment.project.uuid');
|
||||||
|
$applicationUuid = data_get($application, 'uuid');
|
||||||
|
$environment = data_get($application, 'environment.name');
|
||||||
|
|
||||||
|
if ($projectUuid && $applicationUuid && $environment) {
|
||||||
|
$url = base_url() . '/project/' . $projectUuid . "/" . $environment . "/application/" . $applicationUuid;
|
||||||
|
} else {
|
||||||
|
$url = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->server->team?->notify(new ContainerStopped($containerName, $this->server, $url));
|
||||||
|
}
|
||||||
|
$notRunningApplicationPreviews = $previews->pluck('id')->diff($foundApplicationPreviews);
|
||||||
|
foreach ($notRunningApplicationPreviews as $previewId) {
|
||||||
|
$preview = $previews->where('id', $previewId)->first();
|
||||||
|
if (str($preview->status)->startsWith('exited')) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
$preview->update(['status' => 'exited']);
|
||||||
|
|
||||||
|
$name = data_get($preview, 'name');
|
||||||
|
$fqdn = data_get($preview, 'fqdn');
|
||||||
|
|
||||||
|
$containerName = $name ? "$name ($fqdn)" : $fqdn;
|
||||||
|
|
||||||
|
$projectUuid = data_get($preview, 'application.environment.project.uuid');
|
||||||
|
$environmentName = data_get($preview, 'application.environment.name');
|
||||||
|
$applicationUuid = data_get($preview, 'application.uuid');
|
||||||
|
|
||||||
|
if ($projectUuid && $applicationUuid && $environmentName) {
|
||||||
|
$url = base_url() . '/project/' . $projectUuid . "/" . $environmentName . "/application/" . $applicationUuid;
|
||||||
|
} else {
|
||||||
|
$url = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->server->team?->notify(new ContainerStopped($containerName, $this->server, $url));
|
||||||
|
}
|
||||||
|
$notRunningDatabases = $databases->pluck('id')->diff($foundDatabases);
|
||||||
|
foreach ($notRunningDatabases as $database) {
|
||||||
|
$database = $databases->where('id', $database)->first();
|
||||||
|
if (str($database->status)->startsWith('exited')) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
$database->update(['status' => 'exited']);
|
||||||
|
|
||||||
|
$name = data_get($database, 'name');
|
||||||
|
$fqdn = data_get($database, 'fqdn');
|
||||||
|
|
||||||
|
$containerName = $name;
|
||||||
|
|
||||||
|
$projectUuid = data_get($database, 'environment.project.uuid');
|
||||||
|
$environmentName = data_get($database, 'environment.name');
|
||||||
|
$databaseUuid = data_get($database, 'uuid');
|
||||||
|
|
||||||
|
if ($projectUuid && $databaseUuid && $environmentName) {
|
||||||
|
$url = base_url() . '/project/' . $projectUuid . "/" . $environmentName . "/database/" . $databaseUuid;
|
||||||
|
} else {
|
||||||
|
$url = null;
|
||||||
|
}
|
||||||
|
$this->server->team?->notify(new ContainerStopped($containerName, $this->server, $url));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if proxy is running
|
||||||
|
$this->server->proxyType();
|
||||||
|
$foundProxyContainer = $containers->filter(function ($value, $key) {
|
||||||
|
if ($this->server->isSwarm()) {
|
||||||
|
// TODO: fix this with sentinel
|
||||||
|
return data_get($value, 'Spec.Name') === 'coolify-proxy_traefik';
|
||||||
|
} else {
|
||||||
|
return data_get($value, 'name') === 'coolify-proxy';
|
||||||
|
}
|
||||||
|
})->first();
|
||||||
|
if (!$foundProxyContainer) {
|
||||||
|
try {
|
||||||
|
$shouldStart = CheckProxy::run($this->server);
|
||||||
|
if ($shouldStart) {
|
||||||
|
StartProxy::run($this->server, false);
|
||||||
|
$this->server->team?->notify(new ContainerRestarted('coolify-proxy', $this->server));
|
||||||
|
}
|
||||||
|
} catch (\Throwable $e) {
|
||||||
|
ray($e);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
$this->server->proxy->status = data_get($foundProxyContainer, 'state');
|
||||||
|
$this->server->save();
|
||||||
|
$connectProxyToDockerNetworks = connectProxyToNetworks($this->server);
|
||||||
|
instant_remote_process($connectProxyToDockerNetworks, $this->server, false);
|
||||||
|
}
|
||||||
|
} catch (\Exception $e) {
|
||||||
|
send_internal_notification("ContainerStatusJob failed on ({$this->server->id}) with: " . $e->getMessage());
|
||||||
|
ray($e->getMessage());
|
||||||
|
return handleError($e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
private function old_way()
|
||||||
|
{
|
||||||
|
if ($this->server->isSwarm()) {
|
||||||
|
$containers = instant_remote_process(["docker service inspect $(docker service ls -q) --format '{{json .}}'"], $this->server, false);
|
||||||
|
$containerReplicates = instant_remote_process(["docker service ls --format '{{json .}}'"], $this->server, false);
|
||||||
|
} else {
|
||||||
|
// Precheck for containers
|
||||||
|
$containers = instant_remote_process(["docker container ls -q"], $this->server, false);
|
||||||
|
if (!$containers) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
$containers = instant_remote_process(["docker container inspect $(docker container ls -q) --format '{{json .}}'"], $this->server, false);
|
||||||
|
$containerReplicates = null;
|
||||||
|
}
|
||||||
|
if (is_null($containers)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$containers = format_docker_command_output_to_json($containers);
|
||||||
|
if ($containerReplicates) {
|
||||||
|
$containerReplicates = format_docker_command_output_to_json($containerReplicates);
|
||||||
|
foreach ($containerReplicates as $containerReplica) {
|
||||||
|
$name = data_get($containerReplica, 'Name');
|
||||||
|
$containers = $containers->map(function ($container) use ($name, $containerReplica) {
|
||||||
|
if (data_get($container, 'Spec.Name') === $name) {
|
||||||
|
$replicas = data_get($containerReplica, 'Replicas');
|
||||||
|
$running = str($replicas)->explode('/')[0];
|
||||||
|
$total = str($replicas)->explode('/')[1];
|
||||||
|
if ($running === $total) {
|
||||||
|
data_set($container, 'State.Status', 'running');
|
||||||
|
data_set($container, 'State.Health.Status', 'healthy');
|
||||||
|
} else {
|
||||||
|
data_set($container, 'State.Status', 'starting');
|
||||||
|
data_set($container, 'State.Health.Status', 'unhealthy');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return $container;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
$databases = $this->server->databases();
|
||||||
|
$services = $this->server->services()->get();
|
||||||
|
$previews = $this->server->previews();
|
||||||
|
$foundApplications = [];
|
||||||
|
$foundApplicationPreviews = [];
|
||||||
|
$foundDatabases = [];
|
||||||
|
$foundServices = [];
|
||||||
|
|
||||||
|
foreach ($containers as $container) {
|
||||||
|
if ($this->server->isSwarm()) {
|
||||||
|
$labels = data_get($container, 'Spec.Labels');
|
||||||
|
$uuid = data_get($labels, 'coolify.name');
|
||||||
|
} else {
|
||||||
|
$labels = data_get($container, 'Config.Labels');
|
||||||
|
}
|
||||||
|
$containerStatus = data_get($container, 'State.Status');
|
||||||
|
$containerHealth = data_get($container, 'State.Health.Status', 'unhealthy');
|
||||||
|
$containerStatus = "$containerStatus ($containerHealth)";
|
||||||
|
$labels = Arr::undot(format_docker_labels_to_json($labels));
|
||||||
|
$applicationId = data_get($labels, 'coolify.applicationId');
|
||||||
|
if ($applicationId) {
|
||||||
|
$pullRequestId = data_get($labels, 'coolify.pullRequestId');
|
||||||
|
if ($pullRequestId) {
|
||||||
|
if (str($applicationId)->contains('-')) {
|
||||||
|
$applicationId = str($applicationId)->before('-');
|
||||||
|
}
|
||||||
|
$preview = ApplicationPreview::where('application_id', $applicationId)->where('pull_request_id', $pullRequestId)->first();
|
||||||
|
if ($preview) {
|
||||||
|
$foundApplicationPreviews[] = $preview->id;
|
||||||
|
$statusFromDb = $preview->status;
|
||||||
|
if ($statusFromDb !== $containerStatus) {
|
||||||
|
$preview->update(['status' => $containerStatus]);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
//Notify user that this container should not be there.
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
$application = $this->applications->where('id', $applicationId)->first();
|
||||||
|
if ($application) {
|
||||||
|
$foundApplications[] = $application->id;
|
||||||
|
$statusFromDb = $application->status;
|
||||||
|
if ($statusFromDb !== $containerStatus) {
|
||||||
|
$application->update(['status' => $containerStatus]);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
//Notify user that this container should not be there.
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
$uuid = data_get($labels, 'com.docker.compose.service');
|
||||||
|
$type = data_get($labels, 'coolify.type');
|
||||||
|
|
||||||
|
if ($uuid) {
|
||||||
|
if ($type === 'service') {
|
||||||
|
$database_id = data_get($labels, 'coolify.service.subId');
|
||||||
|
if ($database_id) {
|
||||||
|
$service_db = ServiceDatabase::where('id', $database_id)->first();
|
||||||
|
if ($service_db) {
|
||||||
|
$uuid = $service_db->service->uuid;
|
||||||
|
$isPublic = data_get($service_db, 'is_public');
|
||||||
|
if ($isPublic) {
|
||||||
|
$foundTcpProxy = $containers->filter(function ($value, $key) use ($uuid) {
|
||||||
|
if ($this->server->isSwarm()) {
|
||||||
|
return data_get($value, 'Spec.Name') === "coolify-proxy_$uuid";
|
||||||
|
} else {
|
||||||
|
return data_get($value, 'Name') === "/$uuid-proxy";
|
||||||
|
}
|
||||||
|
})->first();
|
||||||
|
if (!$foundTcpProxy) {
|
||||||
|
StartDatabaseProxy::run($service_db);
|
||||||
|
// $this->server->team?->notify(new ContainerRestarted("TCP Proxy for {$service_db->service->name}", $this->server));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
$database = $databases->where('uuid', $uuid)->first();
|
||||||
|
if ($database) {
|
||||||
|
$isPublic = data_get($database, 'is_public');
|
||||||
|
$foundDatabases[] = $database->id;
|
||||||
|
$statusFromDb = $database->status;
|
||||||
|
if ($statusFromDb !== $containerStatus) {
|
||||||
|
$database->update(['status' => $containerStatus]);
|
||||||
|
}
|
||||||
|
if ($isPublic) {
|
||||||
|
$foundTcpProxy = $containers->filter(function ($value, $key) use ($uuid) {
|
||||||
|
if ($this->server->isSwarm()) {
|
||||||
|
return data_get($value, 'Spec.Name') === "coolify-proxy_$uuid";
|
||||||
|
} else {
|
||||||
|
return data_get($value, 'Name') === "/$uuid-proxy";
|
||||||
|
}
|
||||||
|
})->first();
|
||||||
|
if (!$foundTcpProxy) {
|
||||||
|
StartDatabaseProxy::run($database);
|
||||||
|
$this->server->team?->notify(new ContainerRestarted("TCP Proxy for {$database->name}", $this->server));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Notify user that this container should not be there.
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (data_get($container, 'Name') === '/coolify-db') {
|
||||||
|
$foundDatabases[] = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
$serviceLabelId = data_get($labels, 'coolify.serviceId');
|
||||||
|
if ($serviceLabelId) {
|
||||||
|
$subType = data_get($labels, 'coolify.service.subType');
|
||||||
|
$subId = data_get($labels, 'coolify.service.subId');
|
||||||
|
$service = $services->where('id', $serviceLabelId)->first();
|
||||||
|
if (!$service) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if ($subType === 'application') {
|
||||||
|
$service = $service->applications()->where('id', $subId)->first();
|
||||||
|
} else {
|
||||||
|
$service = $service->databases()->where('id', $subId)->first();
|
||||||
|
}
|
||||||
|
if ($service) {
|
||||||
|
$foundServices[] = "$service->id-$service->name";
|
||||||
|
$statusFromDb = $service->status;
|
||||||
|
if ($statusFromDb !== $containerStatus) {
|
||||||
|
// ray('Updating status: ' . $containerStatus);
|
||||||
|
$service->update(['status' => $containerStatus]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
$exitedServices = collect([]);
|
||||||
|
foreach ($services as $service) {
|
||||||
|
$apps = $service->applications()->get();
|
||||||
|
$dbs = $service->databases()->get();
|
||||||
|
foreach ($apps as $app) {
|
||||||
|
if (in_array("$app->id-$app->name", $foundServices)) {
|
||||||
|
continue;
|
||||||
|
} else {
|
||||||
|
$exitedServices->push($app);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
foreach ($dbs as $db) {
|
||||||
|
if (in_array("$db->id-$db->name", $foundServices)) {
|
||||||
|
continue;
|
||||||
|
} else {
|
||||||
|
$exitedServices->push($db);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
$exitedServices = $exitedServices->unique('id');
|
||||||
|
foreach ($exitedServices as $exitedService) {
|
||||||
|
if (str($exitedService->status)->startsWith('exited')) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
$name = data_get($exitedService, 'name');
|
||||||
|
$fqdn = data_get($exitedService, 'fqdn');
|
||||||
|
$containerName = $name ? "$name, available at $fqdn" : $fqdn;
|
||||||
|
$projectUuid = data_get($service, 'environment.project.uuid');
|
||||||
|
$serviceUuid = data_get($service, 'uuid');
|
||||||
|
$environmentName = data_get($service, 'environment.name');
|
||||||
|
|
||||||
|
if ($projectUuid && $serviceUuid && $environmentName) {
|
||||||
|
$url = base_url() . '/project/' . $projectUuid . "/" . $environmentName . "/service/" . $serviceUuid;
|
||||||
|
} else {
|
||||||
|
$url = null;
|
||||||
|
}
|
||||||
|
$this->server->team?->notify(new ContainerStopped($containerName, $this->server, $url));
|
||||||
|
$exitedService->update(['status' => 'exited']);
|
||||||
|
}
|
||||||
|
|
||||||
|
$notRunningApplications = $this->applications->pluck('id')->diff($foundApplications);
|
||||||
|
foreach ($notRunningApplications as $applicationId) {
|
||||||
|
$application = $this->applications->where('id', $applicationId)->first();
|
||||||
|
if (str($application->status)->startsWith('exited')) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
$application->update(['status' => 'exited']);
|
||||||
|
|
||||||
|
$name = data_get($application, 'name');
|
||||||
|
$fqdn = data_get($application, 'fqdn');
|
||||||
|
|
||||||
|
$containerName = $name ? "$name ($fqdn)" : $fqdn;
|
||||||
|
|
||||||
|
$projectUuid = data_get($application, 'environment.project.uuid');
|
||||||
|
$applicationUuid = data_get($application, 'uuid');
|
||||||
|
$environment = data_get($application, 'environment.name');
|
||||||
|
|
||||||
|
if ($projectUuid && $applicationUuid && $environment) {
|
||||||
|
$url = base_url() . '/project/' . $projectUuid . "/" . $environment . "/application/" . $applicationUuid;
|
||||||
|
} else {
|
||||||
|
$url = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->server->team?->notify(new ContainerStopped($containerName, $this->server, $url));
|
||||||
|
}
|
||||||
|
$notRunningApplicationPreviews = $previews->pluck('id')->diff($foundApplicationPreviews);
|
||||||
|
foreach ($notRunningApplicationPreviews as $previewId) {
|
||||||
|
$preview = $previews->where('id', $previewId)->first();
|
||||||
|
if (str($preview->status)->startsWith('exited')) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
$preview->update(['status' => 'exited']);
|
||||||
|
|
||||||
|
$name = data_get($preview, 'name');
|
||||||
|
$fqdn = data_get($preview, 'fqdn');
|
||||||
|
|
||||||
|
$containerName = $name ? "$name ($fqdn)" : $fqdn;
|
||||||
|
|
||||||
|
$projectUuid = data_get($preview, 'application.environment.project.uuid');
|
||||||
|
$environmentName = data_get($preview, 'application.environment.name');
|
||||||
|
$applicationUuid = data_get($preview, 'application.uuid');
|
||||||
|
|
||||||
|
if ($projectUuid && $applicationUuid && $environmentName) {
|
||||||
|
$url = base_url() . '/project/' . $projectUuid . "/" . $environmentName . "/application/" . $applicationUuid;
|
||||||
|
} else {
|
||||||
|
$url = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->server->team?->notify(new ContainerStopped($containerName, $this->server, $url));
|
||||||
|
}
|
||||||
|
$notRunningDatabases = $databases->pluck('id')->diff($foundDatabases);
|
||||||
|
foreach ($notRunningDatabases as $database) {
|
||||||
|
$database = $databases->where('id', $database)->first();
|
||||||
|
if (str($database->status)->startsWith('exited')) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
$database->update(['status' => 'exited']);
|
||||||
|
|
||||||
|
$name = data_get($database, 'name');
|
||||||
|
$fqdn = data_get($database, 'fqdn');
|
||||||
|
|
||||||
|
$containerName = $name;
|
||||||
|
|
||||||
|
$projectUuid = data_get($database, 'environment.project.uuid');
|
||||||
|
$environmentName = data_get($database, 'environment.name');
|
||||||
|
$databaseUuid = data_get($database, 'uuid');
|
||||||
|
|
||||||
|
if ($projectUuid && $databaseUuid && $environmentName) {
|
||||||
|
$url = base_url() . '/project/' . $projectUuid . "/" . $environmentName . "/database/" . $databaseUuid;
|
||||||
|
} else {
|
||||||
|
$url = null;
|
||||||
|
}
|
||||||
|
$this->server->team?->notify(new ContainerStopped($containerName, $this->server, $url));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if proxy is running
|
||||||
|
$this->server->proxyType();
|
||||||
|
$foundProxyContainer = $containers->filter(function ($value, $key) {
|
||||||
|
if ($this->server->isSwarm()) {
|
||||||
|
return data_get($value, 'Spec.Name') === 'coolify-proxy_traefik';
|
||||||
|
} else {
|
||||||
|
return data_get($value, 'Name') === '/coolify-proxy';
|
||||||
|
}
|
||||||
|
})->first();
|
||||||
|
if (!$foundProxyContainer) {
|
||||||
|
try {
|
||||||
|
$shouldStart = CheckProxy::run($this->server);
|
||||||
|
if ($shouldStart) {
|
||||||
|
StartProxy::run($this->server, false);
|
||||||
|
$this->server->team?->notify(new ContainerRestarted('coolify-proxy', $this->server));
|
||||||
|
}
|
||||||
|
} catch (\Throwable $e) {
|
||||||
|
ray($e);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
$this->server->proxy->status = data_get($foundProxyContainer, 'State.Status');
|
||||||
|
$this->server->save();
|
||||||
|
$connectProxyToDockerNetworks = connectProxyToNetworks($this->server);
|
||||||
|
instant_remote_process($connectProxyToDockerNetworks, $this->server, false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
22
app/Actions/Server/StartSentinel.php
Normal file
22
app/Actions/Server/StartSentinel.php
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Actions\Server;
|
||||||
|
|
||||||
|
use Lorisleiva\Actions\Concerns\AsAction;
|
||||||
|
use App\Models\Server;
|
||||||
|
|
||||||
|
class StartSentinel
|
||||||
|
{
|
||||||
|
use AsAction;
|
||||||
|
public function handle(Server $server, $version = 'latest', bool $restart = false)
|
||||||
|
{
|
||||||
|
if ($restart) {
|
||||||
|
instant_remote_process(['docker rm -f coolify-sentinel'], $server, false);
|
||||||
|
}
|
||||||
|
instant_remote_process([
|
||||||
|
"docker run --rm --pull always -d -e \"SCHEDULER=true\" --name coolify-sentinel -v /var/run/docker.sock:/var/run/docker.sock -v /data/coolify/metrics:/app/metrics -v /data/coolify/logs:/app/logs --pid host --health-cmd \"curl --fail http://127.0.0.1:8888/api/health || exit 1\" --health-interval 10s --health-retries 3 ghcr.io/coollabsio/sentinel:$version",
|
||||||
|
"chown -R 9999:root /data/coolify/metrics /data/coolify/logs",
|
||||||
|
"chmod -R 700 /data/coolify/metrics /data/coolify/logs"
|
||||||
|
], $server, false);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -9,6 +9,7 @@ use App\Jobs\ScheduledTaskJob;
|
|||||||
use App\Jobs\InstanceAutoUpdateJob;
|
use App\Jobs\InstanceAutoUpdateJob;
|
||||||
use App\Jobs\ContainerStatusJob;
|
use App\Jobs\ContainerStatusJob;
|
||||||
use App\Jobs\PullHelperImageJob;
|
use App\Jobs\PullHelperImageJob;
|
||||||
|
use App\Jobs\PullSentinelImageJob;
|
||||||
use App\Jobs\ServerStatusJob;
|
use App\Jobs\ServerStatusJob;
|
||||||
use App\Models\InstanceSettings;
|
use App\Models\InstanceSettings;
|
||||||
use App\Models\ScheduledDatabaseBackup;
|
use App\Models\ScheduledDatabaseBackup;
|
||||||
@@ -57,7 +58,10 @@ class Kernel extends ConsoleKernel
|
|||||||
{
|
{
|
||||||
$servers = Server::all()->where('settings.is_usable', true)->where('settings.is_reachable', true)->where('ip', '!=', '1.2.3.4');
|
$servers = Server::all()->where('settings.is_usable', true)->where('settings.is_reachable', true)->where('ip', '!=', '1.2.3.4');
|
||||||
foreach ($servers as $server) {
|
foreach ($servers as $server) {
|
||||||
$schedule->job(new PullHelperImageJob($server))->everyTenMinutes()->onOneServer();
|
if (config('coolify.is_sentinel_enabled')) {
|
||||||
|
$schedule->job(new PullSentinelImageJob($server))->everyFiveMinutes()->onOneServer();
|
||||||
|
}
|
||||||
|
$schedule->job(new PullHelperImageJob($server))->everyFiveMinutes()->onOneServer();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
private function check_resources($schedule)
|
private function check_resources($schedule)
|
||||||
|
|||||||
@@ -37,7 +37,7 @@ class Controller extends BaseController
|
|||||||
public function email_verify(EmailVerificationRequest $request) {
|
public function email_verify(EmailVerificationRequest $request) {
|
||||||
$request->fulfill();
|
$request->fulfill();
|
||||||
$name = request()->user()?->name;
|
$name = request()->user()?->name;
|
||||||
send_internal_notification("User {$name} verified their email address.");
|
// send_internal_notification("User {$name} verified their email address.");
|
||||||
return redirect(RouteServiceProvider::HOME);
|
return redirect(RouteServiceProvider::HOME);
|
||||||
}
|
}
|
||||||
public function forgot_password(Request $request) {
|
public function forgot_password(Request $request) {
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
namespace App\Jobs;
|
namespace App\Jobs;
|
||||||
|
|
||||||
|
use App\Actions\Docker\GetContainersStatus;
|
||||||
use App\Enums\ApplicationDeploymentStatus;
|
use App\Enums\ApplicationDeploymentStatus;
|
||||||
use App\Enums\ProcessStatus;
|
use App\Enums\ProcessStatus;
|
||||||
use App\Events\ApplicationStatusChanged;
|
use App\Events\ApplicationStatusChanged;
|
||||||
@@ -302,7 +303,8 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
|
|||||||
{
|
{
|
||||||
|
|
||||||
if ($this->server->isProxyShouldRun()) {
|
if ($this->server->isProxyShouldRun()) {
|
||||||
dispatch(new ContainerStatusJob($this->server));
|
GetContainersStatus::dispatch($this->server);
|
||||||
|
// dispatch(new ContainerStatusJob($this->server));
|
||||||
}
|
}
|
||||||
$this->next(ApplicationDeploymentStatus::FINISHED->value);
|
$this->next(ApplicationDeploymentStatus::FINISHED->value);
|
||||||
if ($this->pull_request_id !== 0) {
|
if ($this->pull_request_id !== 0) {
|
||||||
@@ -869,7 +871,7 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
|
|||||||
$this->write_deployment_configurations();
|
$this->write_deployment_configurations();
|
||||||
$this->server = $this->original_server;
|
$this->server = $this->original_server;
|
||||||
}
|
}
|
||||||
if (count($this->application->ports_mappings_array) > 0 || (bool) $this->application->settings->is_consistent_container_name_enabled || $this->pull_request_id !== 0 || str($this->application->custom_docker_run_options)->contains('--ip') || str($this->application->custom_docker_run_options)->contains('--ip6')) {
|
if (count($this->application->ports_mappings_array) > 0 || (bool) $this->application->settings->is_consistent_container_name_enabled || isset($this->application->settings->custom_internal_name) || $this->pull_request_id !== 0 || str($this->application->custom_docker_run_options)->contains('--ip') || str($this->application->custom_docker_run_options)->contains('--ip6')) {
|
||||||
$this->application_deployment_queue->addLogEntry("----------------------------------------");
|
$this->application_deployment_queue->addLogEntry("----------------------------------------");
|
||||||
if (count($this->application->ports_mappings_array) > 0) {
|
if (count($this->application->ports_mappings_array) > 0) {
|
||||||
$this->application_deployment_queue->addLogEntry("Application has ports mapped to the host system, rolling update is not supported.");
|
$this->application_deployment_queue->addLogEntry("Application has ports mapped to the host system, rolling update is not supported.");
|
||||||
@@ -877,6 +879,9 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
|
|||||||
if ((bool) $this->application->settings->is_consistent_container_name_enabled) {
|
if ((bool) $this->application->settings->is_consistent_container_name_enabled) {
|
||||||
$this->application_deployment_queue->addLogEntry("Consistent container name feature enabled, rolling update is not supported.");
|
$this->application_deployment_queue->addLogEntry("Consistent container name feature enabled, rolling update is not supported.");
|
||||||
}
|
}
|
||||||
|
if (isset($this->application->settings->custom_internal_name)) {
|
||||||
|
$this->application_deployment_queue->addLogEntry("Custom internal name is set, rolling update is not supported.");
|
||||||
|
}
|
||||||
if ($this->pull_request_id !== 0) {
|
if ($this->pull_request_id !== 0) {
|
||||||
$this->application->settings->is_consistent_container_name_enabled = true;
|
$this->application->settings->is_consistent_container_name_enabled = true;
|
||||||
$this->application_deployment_queue->addLogEntry("Pull request deployment, rolling update is not supported.");
|
$this->application_deployment_queue->addLogEntry("Pull request deployment, rolling update is not supported.");
|
||||||
@@ -1017,7 +1022,9 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
|
|||||||
"command" => "docker rm -f {$this->deployment_uuid}",
|
"command" => "docker rm -f {$this->deployment_uuid}",
|
||||||
"ignore_errors" => true,
|
"ignore_errors" => true,
|
||||||
"hidden" => true
|
"hidden" => true
|
||||||
],
|
]
|
||||||
|
);
|
||||||
|
$this->execute_remote_command(
|
||||||
[
|
[
|
||||||
$runCommand,
|
$runCommand,
|
||||||
"hidden" => true,
|
"hidden" => true,
|
||||||
@@ -1284,7 +1291,6 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
|
|||||||
$this->application->parseHealthcheckFromDockerfile($dockerfile);
|
$this->application->parseHealthcheckFromDockerfile($dockerfile);
|
||||||
}
|
}
|
||||||
$docker_compose = [
|
$docker_compose = [
|
||||||
'version' => '3.8',
|
|
||||||
'services' => [
|
'services' => [
|
||||||
$this->container_name => [
|
$this->container_name => [
|
||||||
'image' => $this->production_image_name,
|
'image' => $this->production_image_name,
|
||||||
@@ -1292,7 +1298,11 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
|
|||||||
'restart' => RESTART_MODE,
|
'restart' => RESTART_MODE,
|
||||||
'expose' => $ports,
|
'expose' => $ports,
|
||||||
'networks' => [
|
'networks' => [
|
||||||
$this->destination->network,
|
$this->destination->network => [
|
||||||
|
'aliases' => [
|
||||||
|
$this->container_name
|
||||||
|
]
|
||||||
|
]
|
||||||
],
|
],
|
||||||
'mem_limit' => $this->application->limits_memory,
|
'mem_limit' => $this->application->limits_memory,
|
||||||
'memswap_limit' => $this->application->limits_memory_swap,
|
'memswap_limit' => $this->application->limits_memory_swap,
|
||||||
@@ -1310,6 +1320,9 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
|
|||||||
]
|
]
|
||||||
]
|
]
|
||||||
];
|
];
|
||||||
|
if (isset($this->application->settings->custom_internal_name)) {
|
||||||
|
$docker_compose['services'][$this->container_name]['networks'][$this->destination->network]['aliases'][] = $this->application->settings->custom_internal_name;
|
||||||
|
}
|
||||||
// if (str($this->saved_outputs->get('dotenv'))->isNotEmpty()) {
|
// if (str($this->saved_outputs->get('dotenv'))->isNotEmpty()) {
|
||||||
// if (data_get($docker_compose, "services.{$this->container_name}.env_file")) {
|
// if (data_get($docker_compose, "services.{$this->container_name}.env_file")) {
|
||||||
// $docker_compose['services'][$this->container_name]['env_file'][] = '.env';
|
// $docker_compose['services'][$this->container_name]['env_file'][] = '.env';
|
||||||
@@ -1519,95 +1532,8 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
|
|||||||
return $local_persistent_volumes_names;
|
return $local_persistent_volumes_names;
|
||||||
}
|
}
|
||||||
|
|
||||||
/*private function generate_environment_variables($ports)
|
|
||||||
{
|
|
||||||
$environment_variables = collect();
|
|
||||||
if ($this->pull_request_id === 0) {
|
|
||||||
foreach ($this->application->runtime_environment_variables as $env) {
|
|
||||||
// This is necessary because we have to escape the value of the environment variable
|
|
||||||
// but only if the environment variable is created after 4.0.0-beta.240
|
|
||||||
// when I implemented the escaping feature.
|
|
||||||
|
|
||||||
// Old environment variables are not escaped, because it could break the application
|
|
||||||
// as the application could expect the unescaped value.
|
|
||||||
if ($env->version === '4.0.0-beta.239') {
|
|
||||||
$real_value = $env->real_value;
|
|
||||||
} else {
|
|
||||||
$real_value = escapeEnvVariables($env->real_value);
|
|
||||||
}
|
|
||||||
if ($env->is_literal) {
|
|
||||||
$real_value = escapeDollarSign($real_value);
|
|
||||||
$environment_variables->push("$env->key='$real_value'");
|
|
||||||
} else {
|
|
||||||
$environment_variables->push("$env->key=$real_value");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
foreach ($this->application->nixpacks_environment_variables as $env) {
|
|
||||||
if ($env->version === '4.0.0-beta.239') {
|
|
||||||
$real_value = $env->real_value;
|
|
||||||
} else {
|
|
||||||
$real_value = escapeEnvVariables($env->real_value);
|
|
||||||
}
|
|
||||||
if ($env->is_literal) {
|
|
||||||
$real_value = escapeDollarSign($real_value);
|
|
||||||
$environment_variables->push("$env->key='$real_value'");
|
|
||||||
} else {
|
|
||||||
$environment_variables->push("$env->key=$real_value");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
foreach ($this->application->runtime_environment_variables_preview as $env) {
|
|
||||||
if ($env->version === '4.0.0-beta.239') {
|
|
||||||
$real_value = $env->real_value;
|
|
||||||
} else {
|
|
||||||
$real_value = escapeEnvVariables($env->real_value);
|
|
||||||
}
|
|
||||||
if ($env->is_literal) {
|
|
||||||
$real_value = escapeDollarSign($real_value);
|
|
||||||
$environment_variables->push("$env->key='$real_value'");
|
|
||||||
} else {
|
|
||||||
$environment_variables->push("$env->key=$real_value");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
foreach ($this->application->nixpacks_environment_variables_preview as $env) {
|
|
||||||
if ($env->version === '4.0.0-beta.239') {
|
|
||||||
$real_value = $env->real_value;
|
|
||||||
} else {
|
|
||||||
$real_value = escapeEnvVariables($env->real_value);
|
|
||||||
}
|
|
||||||
if ($env->is_literal) {
|
|
||||||
$real_value = escapeDollarSign($real_value);
|
|
||||||
$environment_variables->push("$env->key='$real_value'");
|
|
||||||
} else {
|
|
||||||
$environment_variables->push("$env->key=$real_value");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// Add PORT if not exists, use the first port as default
|
|
||||||
if ($environment_variables->filter(fn ($env) => Str::of($env)->startsWith('PORT'))->isEmpty()) {
|
|
||||||
$environment_variables->push("PORT={$ports[0]}");
|
|
||||||
}
|
|
||||||
// Add HOST if not exists
|
|
||||||
if ($environment_variables->filter(fn ($env) => Str::of($env)->startsWith('HOST'))->isEmpty()) {
|
|
||||||
$environment_variables->push("HOST=0.0.0.0");
|
|
||||||
}
|
|
||||||
if ($environment_variables->filter(fn ($env) => Str::of($env)->startsWith('SOURCE_COMMIT'))->isEmpty()) {
|
|
||||||
if (!is_null($this->commit)) {
|
|
||||||
$environment_variables->push("SOURCE_COMMIT={$this->commit}");
|
|
||||||
} else {
|
|
||||||
$environment_variables->push("SOURCE_COMMIT=unknown");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
ray($environment_variables->all());
|
|
||||||
return $environment_variables->all();
|
|
||||||
}*/
|
|
||||||
|
|
||||||
private function generate_healthcheck_commands()
|
private function generate_healthcheck_commands()
|
||||||
{
|
{
|
||||||
// if ($this->application->dockerfile || $this->application->build_pack === 'dockerfile' || $this->application->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->application->health_check_port) {
|
if (!$this->application->health_check_port) {
|
||||||
$health_check_port = $this->application->ports_exposes_array[0];
|
$health_check_port = $this->application->ports_exposes_array[0];
|
||||||
} else {
|
} else {
|
||||||
@@ -1619,12 +1545,12 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
|
|||||||
if ($this->application->health_check_path) {
|
if ($this->application->health_check_path) {
|
||||||
$this->full_healthcheck_url = "{$this->application->health_check_method}: {$this->application->health_check_scheme}://{$this->application->health_check_host}:{$health_check_port}{$this->application->health_check_path}";
|
$this->full_healthcheck_url = "{$this->application->health_check_method}: {$this->application->health_check_scheme}://{$this->application->health_check_host}:{$health_check_port}{$this->application->health_check_path}";
|
||||||
$generated_healthchecks_commands = [
|
$generated_healthchecks_commands = [
|
||||||
"curl -s -X {$this->application->health_check_method} -f {$this->application->health_check_scheme}://{$this->application->health_check_host}:{$health_check_port}{$this->application->health_check_path} > /dev/null"
|
"curl -s -X {$this->application->health_check_method} -f {$this->application->health_check_scheme}://{$this->application->health_check_host}:{$health_check_port}{$this->application->health_check_path} > /dev/null || wget -q -O- {$this->application->health_check_scheme}://{$this->application->health_check_host}:{$health_check_port}{$this->application->health_check_path} > /dev/null || exit 1"
|
||||||
];
|
];
|
||||||
} else {
|
} else {
|
||||||
$this->full_healthcheck_url = "{$this->application->health_check_method}: {$this->application->health_check_scheme}://{$this->application->health_check_host}:{$health_check_port}/";
|
$this->full_healthcheck_url = "{$this->application->health_check_method}: {$this->application->health_check_scheme}://{$this->application->health_check_host}:{$health_check_port}/";
|
||||||
$generated_healthchecks_commands = [
|
$generated_healthchecks_commands = [
|
||||||
"curl -s -X {$this->application->health_check_method} -f {$this->application->health_check_scheme}://{$this->application->health_check_host}:{$health_check_port}/"
|
"curl -s -X {$this->application->health_check_method} -f {$this->application->health_check_scheme}://{$this->application->health_check_host}:{$health_check_port}/ > /dev/null || wget -q -O- {$this->application->health_check_scheme}://{$this->application->health_check_host}:{$health_check_port}/ > /dev/null || exit 1"
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
return implode(' ', $generated_healthchecks_commands);
|
return implode(' ', $generated_healthchecks_commands);
|
||||||
@@ -1813,12 +1739,17 @@ COPY ./nginx.conf /etc/nginx/conf.d/default.conf");
|
|||||||
["docker rm -f $containerName >/dev/null 2>&1", "hidden" => true, "ignore_errors" => true],
|
["docker rm -f $containerName >/dev/null 2>&1", "hidden" => true, "ignore_errors" => true],
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
if ($this->application->settings->is_consistent_container_name_enabled) {
|
if ($this->application->settings->is_consistent_container_name_enabled || isset($this->application->settings->custom_internal_name)) {
|
||||||
$this->execute_remote_command(
|
$this->execute_remote_command(
|
||||||
["docker rm -f $this->container_name >/dev/null 2>&1", "hidden" => true, "ignore_errors" => true],
|
["docker rm -f $this->container_name >/dev/null 2>&1", "hidden" => true, "ignore_errors" => true],
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
if ($this->application->dockerfile || $this->application->build_pack === 'dockerfile' || $this->application->build_pack === 'dockerimage') {
|
||||||
|
$this->application_deployment_queue->addLogEntry("----------------------------------------");
|
||||||
|
$this->application_deployment_queue->addLogEntry("WARNING: Dockerfile or Docker Image based deployment detected. The healthcheck needs a curl or wget command to check the health of the application. Please make sure that it is available in the image or turn off healthcheck on Coolify's UI.");
|
||||||
|
$this->application_deployment_queue->addLogEntry("----------------------------------------");
|
||||||
|
}
|
||||||
$this->application_deployment_queue->addLogEntry("New container is not healthy, rolling back to the old container.");
|
$this->application_deployment_queue->addLogEntry("New container is not healthy, rolling back to the old container.");
|
||||||
$this->application_deployment_queue->update([
|
$this->application_deployment_queue->update([
|
||||||
'status' => ApplicationDeploymentStatus::FAILED->value,
|
'status' => ApplicationDeploymentStatus::FAILED->value,
|
||||||
|
|||||||
@@ -2,15 +2,8 @@
|
|||||||
|
|
||||||
namespace App\Jobs;
|
namespace App\Jobs;
|
||||||
|
|
||||||
use App\Actions\Database\StartDatabaseProxy;
|
use App\Actions\Docker\GetContainersStatus;
|
||||||
use App\Actions\Proxy\CheckProxy;
|
|
||||||
use App\Actions\Proxy\StartProxy;
|
|
||||||
use App\Actions\Shared\ComplexStatusCheck;
|
|
||||||
use App\Models\ApplicationPreview;
|
|
||||||
use App\Models\Server;
|
use App\Models\Server;
|
||||||
use App\Models\ServiceDatabase;
|
|
||||||
use App\Notifications\Container\ContainerRestarted;
|
|
||||||
use App\Notifications\Container\ContainerStopped;
|
|
||||||
use Illuminate\Bus\Queueable;
|
use Illuminate\Bus\Queueable;
|
||||||
use Illuminate\Contracts\Queue\ShouldBeEncrypted;
|
use Illuminate\Contracts\Queue\ShouldBeEncrypted;
|
||||||
use Illuminate\Contracts\Queue\ShouldQueue;
|
use Illuminate\Contracts\Queue\ShouldQueue;
|
||||||
@@ -18,7 +11,6 @@ use Illuminate\Foundation\Bus\Dispatchable;
|
|||||||
use Illuminate\Queue\InteractsWithQueue;
|
use Illuminate\Queue\InteractsWithQueue;
|
||||||
use Illuminate\Queue\Middleware\WithoutOverlapping;
|
use Illuminate\Queue\Middleware\WithoutOverlapping;
|
||||||
use Illuminate\Queue\SerializesModels;
|
use Illuminate\Queue\SerializesModels;
|
||||||
use Illuminate\Support\Arr;
|
|
||||||
|
|
||||||
class ContainerStatusJob implements ShouldQueue, ShouldBeEncrypted
|
class ContainerStatusJob implements ShouldQueue, ShouldBeEncrypted
|
||||||
{
|
{
|
||||||
@@ -44,335 +36,337 @@ class ContainerStatusJob implements ShouldQueue, ShouldBeEncrypted
|
|||||||
|
|
||||||
public function handle()
|
public function handle()
|
||||||
{
|
{
|
||||||
if (!$this->server->isFunctional()) {
|
GetContainersStatus::run($this->server);
|
||||||
return 'Server is not ready.';
|
return;
|
||||||
};
|
// if (!$this->server->isFunctional()) {
|
||||||
$applications = $this->server->applications();
|
// return 'Server is not ready.';
|
||||||
$skip_these_applications = collect([]);
|
// };
|
||||||
foreach ($applications as $application) {
|
// $applications = $this->server->applications();
|
||||||
if ($application->additional_servers->count() > 0) {
|
// $skip_these_applications = collect([]);
|
||||||
$skip_these_applications->push($application);
|
// foreach ($applications as $application) {
|
||||||
ComplexStatusCheck::run($application);
|
// if ($application->additional_servers->count() > 0) {
|
||||||
$applications = $applications->filter(function ($value, $key) use ($application) {
|
// $skip_these_applications->push($application);
|
||||||
return $value->id !== $application->id;
|
// ComplexStatusCheck::run($application);
|
||||||
});
|
// $applications = $applications->filter(function ($value, $key) use ($application) {
|
||||||
}
|
// return $value->id !== $application->id;
|
||||||
}
|
// });
|
||||||
$applications = $applications->filter(function ($value, $key) use ($skip_these_applications) {
|
// }
|
||||||
return !$skip_these_applications->pluck('id')->contains($value->id);
|
// }
|
||||||
});
|
// $applications = $applications->filter(function ($value, $key) use ($skip_these_applications) {
|
||||||
try {
|
// return !$skip_these_applications->pluck('id')->contains($value->id);
|
||||||
if ($this->server->isSwarm()) {
|
// });
|
||||||
$containers = instant_remote_process(["docker service inspect $(docker service ls -q) --format '{{json .}}'"], $this->server, false);
|
// try {
|
||||||
$containerReplicates = instant_remote_process(["docker service ls --format '{{json .}}'"], $this->server, false);
|
// if ($this->server->isSwarm()) {
|
||||||
} else {
|
// $containers = instant_remote_process(["docker service inspect $(docker service ls -q) --format '{{json .}}'"], $this->server, false);
|
||||||
// Precheck for containers
|
// $containerReplicates = instant_remote_process(["docker service ls --format '{{json .}}'"], $this->server, false);
|
||||||
$containers = instant_remote_process(["docker container ls -q"], $this->server, false);
|
// } else {
|
||||||
if (!$containers) {
|
// // Precheck for containers
|
||||||
return;
|
// $containers = instant_remote_process(["docker container ls -q"], $this->server, false);
|
||||||
}
|
// if (!$containers) {
|
||||||
$containers = instant_remote_process(["docker container inspect $(docker container ls -q) --format '{{json .}}'"], $this->server, false);
|
// return;
|
||||||
$containerReplicates = null;
|
// }
|
||||||
}
|
// $containers = instant_remote_process(["docker container inspect $(docker container ls -q) --format '{{json .}}'"], $this->server, false);
|
||||||
if (is_null($containers)) {
|
// $containerReplicates = null;
|
||||||
return;
|
// }
|
||||||
}
|
// if (is_null($containers)) {
|
||||||
|
// return;
|
||||||
|
// }
|
||||||
|
|
||||||
$containers = format_docker_command_output_to_json($containers);
|
// $containers = format_docker_command_output_to_json($containers);
|
||||||
if ($containerReplicates) {
|
// if ($containerReplicates) {
|
||||||
$containerReplicates = format_docker_command_output_to_json($containerReplicates);
|
// $containerReplicates = format_docker_command_output_to_json($containerReplicates);
|
||||||
foreach ($containerReplicates as $containerReplica) {
|
// foreach ($containerReplicates as $containerReplica) {
|
||||||
$name = data_get($containerReplica, 'Name');
|
// $name = data_get($containerReplica, 'Name');
|
||||||
$containers = $containers->map(function ($container) use ($name, $containerReplica) {
|
// $containers = $containers->map(function ($container) use ($name, $containerReplica) {
|
||||||
if (data_get($container, 'Spec.Name') === $name) {
|
// if (data_get($container, 'Spec.Name') === $name) {
|
||||||
$replicas = data_get($containerReplica, 'Replicas');
|
// $replicas = data_get($containerReplica, 'Replicas');
|
||||||
$running = str($replicas)->explode('/')[0];
|
// $running = str($replicas)->explode('/')[0];
|
||||||
$total = str($replicas)->explode('/')[1];
|
// $total = str($replicas)->explode('/')[1];
|
||||||
if ($running === $total) {
|
// if ($running === $total) {
|
||||||
data_set($container, 'State.Status', 'running');
|
// data_set($container, 'State.Status', 'running');
|
||||||
data_set($container, 'State.Health.Status', 'healthy');
|
// data_set($container, 'State.Health.Status', 'healthy');
|
||||||
} else {
|
// } else {
|
||||||
data_set($container, 'State.Status', 'starting');
|
// data_set($container, 'State.Status', 'starting');
|
||||||
data_set($container, 'State.Health.Status', 'unhealthy');
|
// data_set($container, 'State.Health.Status', 'unhealthy');
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
return $container;
|
// return $container;
|
||||||
});
|
// });
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
$databases = $this->server->databases();
|
// $databases = $this->server->databases();
|
||||||
$services = $this->server->services()->get();
|
// $services = $this->server->services()->get();
|
||||||
$previews = $this->server->previews();
|
// $previews = $this->server->previews();
|
||||||
$foundApplications = [];
|
// $foundApplications = [];
|
||||||
$foundApplicationPreviews = [];
|
// $foundApplicationPreviews = [];
|
||||||
$foundDatabases = [];
|
// $foundDatabases = [];
|
||||||
$foundServices = [];
|
// $foundServices = [];
|
||||||
|
|
||||||
foreach ($containers as $container) {
|
// foreach ($containers as $container) {
|
||||||
if ($this->server->isSwarm()) {
|
// if ($this->server->isSwarm()) {
|
||||||
$labels = data_get($container, 'Spec.Labels');
|
// $labels = data_get($container, 'Spec.Labels');
|
||||||
$uuid = data_get($labels, 'coolify.name');
|
// $uuid = data_get($labels, 'coolify.name');
|
||||||
} else {
|
// } else {
|
||||||
$labels = data_get($container, 'Config.Labels');
|
// $labels = data_get($container, 'Config.Labels');
|
||||||
}
|
// }
|
||||||
$containerStatus = data_get($container, 'State.Status');
|
// $containerStatus = data_get($container, 'State.Status');
|
||||||
$containerHealth = data_get($container, 'State.Health.Status', 'unhealthy');
|
// $containerHealth = data_get($container, 'State.Health.Status', 'unhealthy');
|
||||||
$containerStatus = "$containerStatus ($containerHealth)";
|
// $containerStatus = "$containerStatus ($containerHealth)";
|
||||||
$labels = Arr::undot(format_docker_labels_to_json($labels));
|
// $labels = Arr::undot(format_docker_labels_to_json($labels));
|
||||||
$applicationId = data_get($labels, 'coolify.applicationId');
|
// $applicationId = data_get($labels, 'coolify.applicationId');
|
||||||
if ($applicationId) {
|
// if ($applicationId) {
|
||||||
$pullRequestId = data_get($labels, 'coolify.pullRequestId');
|
// $pullRequestId = data_get($labels, 'coolify.pullRequestId');
|
||||||
if ($pullRequestId) {
|
// if ($pullRequestId) {
|
||||||
if (str($applicationId)->contains('-')) {
|
// if (str($applicationId)->contains('-')) {
|
||||||
$applicationId = str($applicationId)->before('-');
|
// $applicationId = str($applicationId)->before('-');
|
||||||
}
|
// }
|
||||||
$preview = ApplicationPreview::where('application_id', $applicationId)->where('pull_request_id', $pullRequestId)->first();
|
// $preview = ApplicationPreview::where('application_id', $applicationId)->where('pull_request_id', $pullRequestId)->first();
|
||||||
if ($preview) {
|
// if ($preview) {
|
||||||
$foundApplicationPreviews[] = $preview->id;
|
// $foundApplicationPreviews[] = $preview->id;
|
||||||
$statusFromDb = $preview->status;
|
// $statusFromDb = $preview->status;
|
||||||
if ($statusFromDb !== $containerStatus) {
|
// if ($statusFromDb !== $containerStatus) {
|
||||||
$preview->update(['status' => $containerStatus]);
|
// $preview->update(['status' => $containerStatus]);
|
||||||
}
|
// }
|
||||||
} else {
|
// } else {
|
||||||
//Notify user that this container should not be there.
|
// //Notify user that this container should not be there.
|
||||||
}
|
// }
|
||||||
} else {
|
// } else {
|
||||||
$application = $applications->where('id', $applicationId)->first();
|
// $application = $applications->where('id', $applicationId)->first();
|
||||||
if ($application) {
|
// if ($application) {
|
||||||
$foundApplications[] = $application->id;
|
// $foundApplications[] = $application->id;
|
||||||
$statusFromDb = $application->status;
|
// $statusFromDb = $application->status;
|
||||||
if ($statusFromDb !== $containerStatus) {
|
// if ($statusFromDb !== $containerStatus) {
|
||||||
$application->update(['status' => $containerStatus]);
|
// $application->update(['status' => $containerStatus]);
|
||||||
}
|
// }
|
||||||
} else {
|
// } else {
|
||||||
//Notify user that this container should not be there.
|
// //Notify user that this container should not be there.
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
} else {
|
// } else {
|
||||||
$uuid = data_get($labels, 'com.docker.compose.service');
|
// $uuid = data_get($labels, 'com.docker.compose.service');
|
||||||
$type = data_get($labels, 'coolify.type');
|
// $type = data_get($labels, 'coolify.type');
|
||||||
|
|
||||||
if ($uuid) {
|
// if ($uuid) {
|
||||||
if ($type === 'service') {
|
// if ($type === 'service') {
|
||||||
$database_id = data_get($labels, 'coolify.service.subId');
|
// $database_id = data_get($labels, 'coolify.service.subId');
|
||||||
if ($database_id) {
|
// if ($database_id) {
|
||||||
$service_db = ServiceDatabase::where('id', $database_id)->first();
|
// $service_db = ServiceDatabase::where('id', $database_id)->first();
|
||||||
if ($service_db) {
|
// if ($service_db) {
|
||||||
$uuid = $service_db->service->uuid;
|
// $uuid = $service_db->service->uuid;
|
||||||
$isPublic = data_get($service_db, 'is_public');
|
// $isPublic = data_get($service_db, 'is_public');
|
||||||
if ($isPublic) {
|
// if ($isPublic) {
|
||||||
$foundTcpProxy = $containers->filter(function ($value, $key) use ($uuid) {
|
// $foundTcpProxy = $containers->filter(function ($value, $key) use ($uuid) {
|
||||||
if ($this->server->isSwarm()) {
|
// if ($this->server->isSwarm()) {
|
||||||
return data_get($value, 'Spec.Name') === "coolify-proxy_$uuid";
|
// return data_get($value, 'Spec.Name') === "coolify-proxy_$uuid";
|
||||||
} else {
|
// } else {
|
||||||
return data_get($value, 'Name') === "/$uuid-proxy";
|
// return data_get($value, 'Name') === "/$uuid-proxy";
|
||||||
}
|
// }
|
||||||
})->first();
|
// })->first();
|
||||||
if (!$foundTcpProxy) {
|
// if (!$foundTcpProxy) {
|
||||||
StartDatabaseProxy::run($service_db);
|
// StartDatabaseProxy::run($service_db);
|
||||||
// $this->server->team?->notify(new ContainerRestarted("TCP Proxy for {$service_db->service->name}", $this->server));
|
// // $this->server->team?->notify(new ContainerRestarted("TCP Proxy for {$service_db->service->name}", $this->server));
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
} else {
|
// } else {
|
||||||
$database = $databases->where('uuid', $uuid)->first();
|
// $database = $databases->where('uuid', $uuid)->first();
|
||||||
if ($database) {
|
// if ($database) {
|
||||||
$isPublic = data_get($database, 'is_public');
|
// $isPublic = data_get($database, 'is_public');
|
||||||
$foundDatabases[] = $database->id;
|
// $foundDatabases[] = $database->id;
|
||||||
$statusFromDb = $database->status;
|
// $statusFromDb = $database->status;
|
||||||
if ($statusFromDb !== $containerStatus) {
|
// if ($statusFromDb !== $containerStatus) {
|
||||||
$database->update(['status' => $containerStatus]);
|
// $database->update(['status' => $containerStatus]);
|
||||||
}
|
// }
|
||||||
if ($isPublic) {
|
// if ($isPublic) {
|
||||||
$foundTcpProxy = $containers->filter(function ($value, $key) use ($uuid) {
|
// $foundTcpProxy = $containers->filter(function ($value, $key) use ($uuid) {
|
||||||
if ($this->server->isSwarm()) {
|
// if ($this->server->isSwarm()) {
|
||||||
return data_get($value, 'Spec.Name') === "coolify-proxy_$uuid";
|
// return data_get($value, 'Spec.Name') === "coolify-proxy_$uuid";
|
||||||
} else {
|
// } else {
|
||||||
return data_get($value, 'Name') === "/$uuid-proxy";
|
// return data_get($value, 'Name') === "/$uuid-proxy";
|
||||||
}
|
// }
|
||||||
})->first();
|
// })->first();
|
||||||
if (!$foundTcpProxy) {
|
// if (!$foundTcpProxy) {
|
||||||
StartDatabaseProxy::run($database);
|
// StartDatabaseProxy::run($database);
|
||||||
$this->server->team?->notify(new ContainerRestarted("TCP Proxy for {$database->name}", $this->server));
|
// $this->server->team?->notify(new ContainerRestarted("TCP Proxy for {$database->name}", $this->server));
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
} else {
|
// } else {
|
||||||
// Notify user that this container should not be there.
|
// // Notify user that this container should not be there.
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
if (data_get($container, 'Name') === '/coolify-db') {
|
// if (data_get($container, 'Name') === '/coolify-db') {
|
||||||
$foundDatabases[] = 0;
|
// $foundDatabases[] = 0;
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
$serviceLabelId = data_get($labels, 'coolify.serviceId');
|
// $serviceLabelId = data_get($labels, 'coolify.serviceId');
|
||||||
if ($serviceLabelId) {
|
// if ($serviceLabelId) {
|
||||||
$subType = data_get($labels, 'coolify.service.subType');
|
// $subType = data_get($labels, 'coolify.service.subType');
|
||||||
$subId = data_get($labels, 'coolify.service.subId');
|
// $subId = data_get($labels, 'coolify.service.subId');
|
||||||
$service = $services->where('id', $serviceLabelId)->first();
|
// $service = $services->where('id', $serviceLabelId)->first();
|
||||||
if (!$service) {
|
// if (!$service) {
|
||||||
continue;
|
// continue;
|
||||||
}
|
// }
|
||||||
if ($subType === 'application') {
|
// if ($subType === 'application') {
|
||||||
$service = $service->applications()->where('id', $subId)->first();
|
// $service = $service->applications()->where('id', $subId)->first();
|
||||||
} else {
|
// } else {
|
||||||
$service = $service->databases()->where('id', $subId)->first();
|
// $service = $service->databases()->where('id', $subId)->first();
|
||||||
}
|
// }
|
||||||
if ($service) {
|
// if ($service) {
|
||||||
$foundServices[] = "$service->id-$service->name";
|
// $foundServices[] = "$service->id-$service->name";
|
||||||
$statusFromDb = $service->status;
|
// $statusFromDb = $service->status;
|
||||||
if ($statusFromDb !== $containerStatus) {
|
// if ($statusFromDb !== $containerStatus) {
|
||||||
// ray('Updating status: ' . $containerStatus);
|
// // ray('Updating status: ' . $containerStatus);
|
||||||
$service->update(['status' => $containerStatus]);
|
// $service->update(['status' => $containerStatus]);
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
$exitedServices = collect([]);
|
// $exitedServices = collect([]);
|
||||||
foreach ($services as $service) {
|
// foreach ($services as $service) {
|
||||||
$apps = $service->applications()->get();
|
// $apps = $service->applications()->get();
|
||||||
$dbs = $service->databases()->get();
|
// $dbs = $service->databases()->get();
|
||||||
foreach ($apps as $app) {
|
// foreach ($apps as $app) {
|
||||||
if (in_array("$app->id-$app->name", $foundServices)) {
|
// if (in_array("$app->id-$app->name", $foundServices)) {
|
||||||
continue;
|
// continue;
|
||||||
} else {
|
// } else {
|
||||||
$exitedServices->push($app);
|
// $exitedServices->push($app);
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
foreach ($dbs as $db) {
|
// foreach ($dbs as $db) {
|
||||||
if (in_array("$db->id-$db->name", $foundServices)) {
|
// if (in_array("$db->id-$db->name", $foundServices)) {
|
||||||
continue;
|
// continue;
|
||||||
} else {
|
// } else {
|
||||||
$exitedServices->push($db);
|
// $exitedServices->push($db);
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
$exitedServices = $exitedServices->unique('id');
|
// $exitedServices = $exitedServices->unique('id');
|
||||||
foreach ($exitedServices as $exitedService) {
|
// foreach ($exitedServices as $exitedService) {
|
||||||
if (str($exitedService->status)->startsWith('exited')) {
|
// if (str($exitedService->status)->startsWith('exited')) {
|
||||||
continue;
|
// continue;
|
||||||
}
|
// }
|
||||||
$name = data_get($exitedService, 'name');
|
// $name = data_get($exitedService, 'name');
|
||||||
$fqdn = data_get($exitedService, 'fqdn');
|
// $fqdn = data_get($exitedService, 'fqdn');
|
||||||
$containerName = $name ? "$name, available at $fqdn" : $fqdn;
|
// $containerName = $name ? "$name, available at $fqdn" : $fqdn;
|
||||||
$projectUuid = data_get($service, 'environment.project.uuid');
|
// $projectUuid = data_get($service, 'environment.project.uuid');
|
||||||
$serviceUuid = data_get($service, 'uuid');
|
// $serviceUuid = data_get($service, 'uuid');
|
||||||
$environmentName = data_get($service, 'environment.name');
|
// $environmentName = data_get($service, 'environment.name');
|
||||||
|
|
||||||
if ($projectUuid && $serviceUuid && $environmentName) {
|
// if ($projectUuid && $serviceUuid && $environmentName) {
|
||||||
$url = base_url() . '/project/' . $projectUuid . "/" . $environmentName . "/service/" . $serviceUuid;
|
// $url = base_url() . '/project/' . $projectUuid . "/" . $environmentName . "/service/" . $serviceUuid;
|
||||||
} else {
|
// } else {
|
||||||
$url = null;
|
// $url = null;
|
||||||
}
|
// }
|
||||||
$this->server->team?->notify(new ContainerStopped($containerName, $this->server, $url));
|
// $this->server->team?->notify(new ContainerStopped($containerName, $this->server, $url));
|
||||||
$exitedService->update(['status' => 'exited']);
|
// $exitedService->update(['status' => 'exited']);
|
||||||
}
|
// }
|
||||||
|
|
||||||
$notRunningApplications = $applications->pluck('id')->diff($foundApplications);
|
// $notRunningApplications = $applications->pluck('id')->diff($foundApplications);
|
||||||
foreach ($notRunningApplications as $applicationId) {
|
// foreach ($notRunningApplications as $applicationId) {
|
||||||
$application = $applications->where('id', $applicationId)->first();
|
// $application = $applications->where('id', $applicationId)->first();
|
||||||
if (str($application->status)->startsWith('exited')) {
|
// if (str($application->status)->startsWith('exited')) {
|
||||||
continue;
|
// continue;
|
||||||
}
|
// }
|
||||||
$application->update(['status' => 'exited']);
|
// $application->update(['status' => 'exited']);
|
||||||
|
|
||||||
$name = data_get($application, 'name');
|
// $name = data_get($application, 'name');
|
||||||
$fqdn = data_get($application, 'fqdn');
|
// $fqdn = data_get($application, 'fqdn');
|
||||||
|
|
||||||
$containerName = $name ? "$name ($fqdn)" : $fqdn;
|
// $containerName = $name ? "$name ($fqdn)" : $fqdn;
|
||||||
|
|
||||||
$projectUuid = data_get($application, 'environment.project.uuid');
|
// $projectUuid = data_get($application, 'environment.project.uuid');
|
||||||
$applicationUuid = data_get($application, 'uuid');
|
// $applicationUuid = data_get($application, 'uuid');
|
||||||
$environment = data_get($application, 'environment.name');
|
// $environment = data_get($application, 'environment.name');
|
||||||
|
|
||||||
if ($projectUuid && $applicationUuid && $environment) {
|
// if ($projectUuid && $applicationUuid && $environment) {
|
||||||
$url = base_url() . '/project/' . $projectUuid . "/" . $environment . "/application/" . $applicationUuid;
|
// $url = base_url() . '/project/' . $projectUuid . "/" . $environment . "/application/" . $applicationUuid;
|
||||||
} else {
|
// } else {
|
||||||
$url = null;
|
// $url = null;
|
||||||
}
|
// }
|
||||||
|
|
||||||
$this->server->team?->notify(new ContainerStopped($containerName, $this->server, $url));
|
// $this->server->team?->notify(new ContainerStopped($containerName, $this->server, $url));
|
||||||
}
|
// }
|
||||||
$notRunningApplicationPreviews = $previews->pluck('id')->diff($foundApplicationPreviews);
|
// $notRunningApplicationPreviews = $previews->pluck('id')->diff($foundApplicationPreviews);
|
||||||
foreach ($notRunningApplicationPreviews as $previewId) {
|
// foreach ($notRunningApplicationPreviews as $previewId) {
|
||||||
$preview = $previews->where('id', $previewId)->first();
|
// $preview = $previews->where('id', $previewId)->first();
|
||||||
if (str($preview->status)->startsWith('exited')) {
|
// if (str($preview->status)->startsWith('exited')) {
|
||||||
continue;
|
// continue;
|
||||||
}
|
// }
|
||||||
$preview->update(['status' => 'exited']);
|
// $preview->update(['status' => 'exited']);
|
||||||
|
|
||||||
$name = data_get($preview, 'name');
|
// $name = data_get($preview, 'name');
|
||||||
$fqdn = data_get($preview, 'fqdn');
|
// $fqdn = data_get($preview, 'fqdn');
|
||||||
|
|
||||||
$containerName = $name ? "$name ($fqdn)" : $fqdn;
|
// $containerName = $name ? "$name ($fqdn)" : $fqdn;
|
||||||
|
|
||||||
$projectUuid = data_get($preview, 'application.environment.project.uuid');
|
// $projectUuid = data_get($preview, 'application.environment.project.uuid');
|
||||||
$environmentName = data_get($preview, 'application.environment.name');
|
// $environmentName = data_get($preview, 'application.environment.name');
|
||||||
$applicationUuid = data_get($preview, 'application.uuid');
|
// $applicationUuid = data_get($preview, 'application.uuid');
|
||||||
|
|
||||||
if ($projectUuid && $applicationUuid && $environmentName) {
|
// if ($projectUuid && $applicationUuid && $environmentName) {
|
||||||
$url = base_url() . '/project/' . $projectUuid . "/" . $environmentName . "/application/" . $applicationUuid;
|
// $url = base_url() . '/project/' . $projectUuid . "/" . $environmentName . "/application/" . $applicationUuid;
|
||||||
} else {
|
// } else {
|
||||||
$url = null;
|
// $url = null;
|
||||||
}
|
// }
|
||||||
|
|
||||||
$this->server->team?->notify(new ContainerStopped($containerName, $this->server, $url));
|
// $this->server->team?->notify(new ContainerStopped($containerName, $this->server, $url));
|
||||||
}
|
// }
|
||||||
$notRunningDatabases = $databases->pluck('id')->diff($foundDatabases);
|
// $notRunningDatabases = $databases->pluck('id')->diff($foundDatabases);
|
||||||
foreach ($notRunningDatabases as $database) {
|
// foreach ($notRunningDatabases as $database) {
|
||||||
$database = $databases->where('id', $database)->first();
|
// $database = $databases->where('id', $database)->first();
|
||||||
if (str($database->status)->startsWith('exited')) {
|
// if (str($database->status)->startsWith('exited')) {
|
||||||
continue;
|
// continue;
|
||||||
}
|
// }
|
||||||
$database->update(['status' => 'exited']);
|
// $database->update(['status' => 'exited']);
|
||||||
|
|
||||||
$name = data_get($database, 'name');
|
// $name = data_get($database, 'name');
|
||||||
$fqdn = data_get($database, 'fqdn');
|
// $fqdn = data_get($database, 'fqdn');
|
||||||
|
|
||||||
$containerName = $name;
|
// $containerName = $name;
|
||||||
|
|
||||||
$projectUuid = data_get($database, 'environment.project.uuid');
|
// $projectUuid = data_get($database, 'environment.project.uuid');
|
||||||
$environmentName = data_get($database, 'environment.name');
|
// $environmentName = data_get($database, 'environment.name');
|
||||||
$databaseUuid = data_get($database, 'uuid');
|
// $databaseUuid = data_get($database, 'uuid');
|
||||||
|
|
||||||
if ($projectUuid && $databaseUuid && $environmentName) {
|
// if ($projectUuid && $databaseUuid && $environmentName) {
|
||||||
$url = base_url() . '/project/' . $projectUuid . "/" . $environmentName . "/database/" . $databaseUuid;
|
// $url = base_url() . '/project/' . $projectUuid . "/" . $environmentName . "/database/" . $databaseUuid;
|
||||||
} else {
|
// } else {
|
||||||
$url = null;
|
// $url = null;
|
||||||
}
|
// }
|
||||||
$this->server->team?->notify(new ContainerStopped($containerName, $this->server, $url));
|
// $this->server->team?->notify(new ContainerStopped($containerName, $this->server, $url));
|
||||||
}
|
// }
|
||||||
|
|
||||||
// Check if proxy is running
|
// // Check if proxy is running
|
||||||
$this->server->proxyType();
|
// $this->server->proxyType();
|
||||||
$foundProxyContainer = $containers->filter(function ($value, $key) {
|
// $foundProxyContainer = $containers->filter(function ($value, $key) {
|
||||||
if ($this->server->isSwarm()) {
|
// if ($this->server->isSwarm()) {
|
||||||
return data_get($value, 'Spec.Name') === 'coolify-proxy_traefik';
|
// return data_get($value, 'Spec.Name') === 'coolify-proxy_traefik';
|
||||||
} else {
|
// } else {
|
||||||
return data_get($value, 'Name') === '/coolify-proxy';
|
// return data_get($value, 'Name') === '/coolify-proxy';
|
||||||
}
|
// }
|
||||||
})->first();
|
// })->first();
|
||||||
if (!$foundProxyContainer) {
|
// if (!$foundProxyContainer) {
|
||||||
try {
|
// try {
|
||||||
$shouldStart = CheckProxy::run($this->server);
|
// $shouldStart = CheckProxy::run($this->server);
|
||||||
if ($shouldStart) {
|
// if ($shouldStart) {
|
||||||
StartProxy::run($this->server, false);
|
// StartProxy::run($this->server, false);
|
||||||
$this->server->team?->notify(new ContainerRestarted('coolify-proxy', $this->server));
|
// $this->server->team?->notify(new ContainerRestarted('coolify-proxy', $this->server));
|
||||||
}
|
// }
|
||||||
} catch (\Throwable $e) {
|
// } catch (\Throwable $e) {
|
||||||
ray($e);
|
// ray($e);
|
||||||
}
|
// }
|
||||||
} else {
|
// } else {
|
||||||
$this->server->proxy->status = data_get($foundProxyContainer, 'State.Status');
|
// $this->server->proxy->status = data_get($foundProxyContainer, 'State.Status');
|
||||||
$this->server->save();
|
// $this->server->save();
|
||||||
$connectProxyToDockerNetworks = connectProxyToNetworks($this->server);
|
// $connectProxyToDockerNetworks = connectProxyToNetworks($this->server);
|
||||||
instant_remote_process($connectProxyToDockerNetworks, $this->server, false);
|
// instant_remote_process($connectProxyToDockerNetworks, $this->server, false);
|
||||||
}
|
// }
|
||||||
} catch (\Throwable $e) {
|
// } catch (\Throwable $e) {
|
||||||
send_internal_notification("ContainerStatusJob failed on ({$this->server->id}) with: " . $e->getMessage());
|
// send_internal_notification("ContainerStatusJob failed on ({$this->server->id}) with: " . $e->getMessage());
|
||||||
ray($e->getMessage());
|
// ray($e->getMessage());
|
||||||
return handleError($e);
|
// return handleError($e);
|
||||||
}
|
// }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
56
app/Jobs/PullSentinelImageJob.php
Normal file
56
app/Jobs/PullSentinelImageJob.php
Normal file
@@ -0,0 +1,56 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Jobs;
|
||||||
|
|
||||||
|
use App\Actions\Server\StartSentinel;
|
||||||
|
use App\Models\Server;
|
||||||
|
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\Middleware\WithoutOverlapping;
|
||||||
|
use Illuminate\Queue\SerializesModels;
|
||||||
|
|
||||||
|
class PullSentinelImageJob implements ShouldQueue, ShouldBeEncrypted
|
||||||
|
{
|
||||||
|
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
|
||||||
|
|
||||||
|
public $timeout = 1000;
|
||||||
|
|
||||||
|
public function middleware(): array
|
||||||
|
{
|
||||||
|
return [(new WithoutOverlapping($this->server->uuid))];
|
||||||
|
}
|
||||||
|
|
||||||
|
public function uniqueId(): string
|
||||||
|
{
|
||||||
|
return $this->server->uuid;
|
||||||
|
}
|
||||||
|
public function __construct(public Server $server)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
public function handle(): void
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
$version = get_latest_sentinel_version();
|
||||||
|
if (!$version) {
|
||||||
|
ray('Failed to get latest Sentinel version');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
$local_version = instant_remote_process(['docker exec coolify-sentinel sh -c "curl http://127.0.0.1:8888/api/version"'], $this->server, false);
|
||||||
|
if (empty($local_version)) {
|
||||||
|
$local_version = '0.0.0';
|
||||||
|
}
|
||||||
|
if (version_compare($local_version, $version, '<')) {
|
||||||
|
StartSentinel::run($this->server, $version, true);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
ray('Sentinel image is up to date');
|
||||||
|
} catch (\Throwable $e) {
|
||||||
|
send_internal_notification('PullSentinelImageJob failed with: ' . $e->getMessage());
|
||||||
|
ray($e->getMessage());
|
||||||
|
throw $e;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -17,7 +17,7 @@ class ServerStatusJob implements ShouldQueue, ShouldBeEncrypted
|
|||||||
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
|
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
|
||||||
|
|
||||||
public int|string|null $disk_usage = null;
|
public int|string|null $disk_usage = null;
|
||||||
public $tries = 4;
|
public $tries = 3;
|
||||||
public function backoff(): int
|
public function backoff(): int
|
||||||
{
|
{
|
||||||
return isDev() ? 1 : 3;
|
return isDev() ? 1 : 3;
|
||||||
@@ -38,12 +38,15 @@ class ServerStatusJob implements ShouldQueue, ShouldBeEncrypted
|
|||||||
public function handle()
|
public function handle()
|
||||||
{
|
{
|
||||||
if (!$this->server->isServerReady($this->tries)) {
|
if (!$this->server->isServerReady($this->tries)) {
|
||||||
return "Server is not ready yet.";
|
throw new \RuntimeException('Server is not ready.');
|
||||||
};
|
};
|
||||||
try {
|
try {
|
||||||
if ($this->server->isFunctional()) {
|
if ($this->server->isFunctional()) {
|
||||||
$this->cleanup(notify: false);
|
$this->cleanup(notify: false);
|
||||||
$this->removeCoolifyYaml();
|
$this->removeCoolifyYaml();
|
||||||
|
if (config('coolify.is_sentinel_enabled')) {
|
||||||
|
$this->server->checkSentinel();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} catch (\Throwable $e) {
|
} catch (\Throwable $e) {
|
||||||
send_internal_notification('ServerStatusJob failed with: ' . $e->getMessage());
|
send_internal_notification('ServerStatusJob failed with: ' . $e->getMessage());
|
||||||
|
|||||||
@@ -21,6 +21,7 @@ class Advanced extends Component
|
|||||||
'application.settings.is_gpu_enabled' => 'boolean|required',
|
'application.settings.is_gpu_enabled' => 'boolean|required',
|
||||||
'application.settings.is_build_server_enabled' => 'boolean|required',
|
'application.settings.is_build_server_enabled' => 'boolean|required',
|
||||||
'application.settings.is_consistent_container_name_enabled' => 'boolean|required',
|
'application.settings.is_consistent_container_name_enabled' => 'boolean|required',
|
||||||
|
'application.settings.custom_internal_name' => 'string|nullable',
|
||||||
'application.settings.is_gzip_enabled' => 'boolean|required',
|
'application.settings.is_gzip_enabled' => 'boolean|required',
|
||||||
'application.settings.is_stripprefix_enabled' => 'boolean|required',
|
'application.settings.is_stripprefix_enabled' => 'boolean|required',
|
||||||
'application.settings.gpu_driver' => 'string|required',
|
'application.settings.gpu_driver' => 'string|required',
|
||||||
@@ -30,7 +31,8 @@ class Advanced extends Component
|
|||||||
'application.settings.is_raw_compose_deployment_enabled' => 'boolean|required',
|
'application.settings.is_raw_compose_deployment_enabled' => 'boolean|required',
|
||||||
'application.settings.connect_to_docker_network' => 'boolean|required',
|
'application.settings.connect_to_docker_network' => 'boolean|required',
|
||||||
];
|
];
|
||||||
public function mount() {
|
public function mount()
|
||||||
|
{
|
||||||
$this->is_force_https_enabled = $this->application->isForceHttpsEnabled();
|
$this->is_force_https_enabled = $this->application->isForceHttpsEnabled();
|
||||||
$this->is_gzip_enabled = $this->application->isGzipEnabled();
|
$this->is_gzip_enabled = $this->application->isGzipEnabled();
|
||||||
$this->is_stripprefix_enabled = $this->application->isStripprefixEnabled();
|
$this->is_stripprefix_enabled = $this->application->isStripprefixEnabled();
|
||||||
@@ -65,7 +67,8 @@ class Advanced extends Component
|
|||||||
$this->dispatch('success', 'Settings saved.');
|
$this->dispatch('success', 'Settings saved.');
|
||||||
$this->dispatch('configurationChanged');
|
$this->dispatch('configurationChanged');
|
||||||
}
|
}
|
||||||
public function submit() {
|
public function submit()
|
||||||
|
{
|
||||||
if ($this->application->settings->gpu_count && $this->application->settings->gpu_device_ids) {
|
if ($this->application->settings->gpu_count && $this->application->settings->gpu_device_ids) {
|
||||||
$this->dispatch('error', 'You cannot set both GPU count and GPU device IDs.');
|
$this->dispatch('error', 'You cannot set both GPU count and GPU device IDs.');
|
||||||
$this->application->settings->gpu_count = null;
|
$this->application->settings->gpu_count = null;
|
||||||
@@ -76,6 +79,16 @@ class Advanced extends Component
|
|||||||
$this->application->settings->save();
|
$this->application->settings->save();
|
||||||
$this->dispatch('success', 'Settings saved.');
|
$this->dispatch('success', 'Settings saved.');
|
||||||
}
|
}
|
||||||
|
public function saveCustomName()
|
||||||
|
{
|
||||||
|
if (isset($this->application->settings->custom_internal_name)) {
|
||||||
|
$this->application->settings->custom_internal_name = str($this->application->settings->custom_internal_name)->slug()->value();
|
||||||
|
} else {
|
||||||
|
$this->application->settings->custom_internal_name = null;
|
||||||
|
}
|
||||||
|
$this->application->settings->save();
|
||||||
|
$this->dispatch('success', 'Custom name saved.');
|
||||||
|
}
|
||||||
public function render()
|
public function render()
|
||||||
{
|
{
|
||||||
return view('livewire.project.application.advanced');
|
return view('livewire.project.application.advanced');
|
||||||
|
|||||||
@@ -255,7 +255,6 @@ class General extends Component
|
|||||||
}
|
}
|
||||||
public function resetDefaultLabels()
|
public function resetDefaultLabels()
|
||||||
{
|
{
|
||||||
ray('resetDefaultLabels');
|
|
||||||
$this->customLabels = str(implode("|", generateLabelsApplication($this->application)))->replace("|", "\n");
|
$this->customLabels = str(implode("|", generateLabelsApplication($this->application)))->replace("|", "\n");
|
||||||
$this->ports_exposes = $this->application->ports_exposes;
|
$this->ports_exposes = $this->application->ports_exposes;
|
||||||
|
|
||||||
@@ -299,7 +298,10 @@ class General extends Component
|
|||||||
}
|
}
|
||||||
|
|
||||||
if ($this->application->build_pack === 'dockercompose' && $this->initialDockerComposeLocation !== $this->application->docker_compose_location) {
|
if ($this->application->build_pack === 'dockercompose' && $this->initialDockerComposeLocation !== $this->application->docker_compose_location) {
|
||||||
$this->loadComposeFile();
|
$compose_return = $this->loadComposeFile();
|
||||||
|
if ($compose_return instanceof \Livewire\Features\SupportEvents\Event) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
$this->validate();
|
$this->validate();
|
||||||
if ($this->ports_exposes !== $this->application->ports_exposes) {
|
if ($this->ports_exposes !== $this->application->ports_exposes) {
|
||||||
|
|||||||
@@ -3,9 +3,9 @@
|
|||||||
namespace App\Livewire\Project\Application;
|
namespace App\Livewire\Project\Application;
|
||||||
|
|
||||||
use App\Actions\Application\StopApplication;
|
use App\Actions\Application\StopApplication;
|
||||||
|
use App\Actions\Docker\GetContainersStatus;
|
||||||
use App\Events\ApplicationStatusChanged;
|
use App\Events\ApplicationStatusChanged;
|
||||||
use App\Jobs\ComplexContainerStatusJob;
|
use App\Jobs\ContainerStatusJob;
|
||||||
use App\Jobs\ContainerStatusJob;
|
|
||||||
use App\Jobs\ServerStatusJob;
|
use App\Jobs\ServerStatusJob;
|
||||||
use App\Models\Application;
|
use App\Models\Application;
|
||||||
use Livewire\Component;
|
use Livewire\Component;
|
||||||
@@ -33,7 +33,8 @@ class Heading extends Component
|
|||||||
public function check_status($showNotification = false)
|
public function check_status($showNotification = false)
|
||||||
{
|
{
|
||||||
if ($this->application->destination->server->isFunctional()) {
|
if ($this->application->destination->server->isFunctional()) {
|
||||||
dispatch(new ContainerStatusJob($this->application->destination->server));
|
GetContainersStatus::dispatch($this->application->destination->server);
|
||||||
|
// dispatch(new ContainerStatusJob($this->application->destination->server));
|
||||||
} else {
|
} else {
|
||||||
dispatch(new ServerStatusJob($this->application->destination->server));
|
dispatch(new ServerStatusJob($this->application->destination->server));
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ use App\Actions\Database\StartMysql;
|
|||||||
use App\Actions\Database\StartPostgresql;
|
use App\Actions\Database\StartPostgresql;
|
||||||
use App\Actions\Database\StartRedis;
|
use App\Actions\Database\StartRedis;
|
||||||
use App\Actions\Database\StopDatabase;
|
use App\Actions\Database\StopDatabase;
|
||||||
|
use App\Actions\Docker\GetContainersStatus;
|
||||||
use App\Jobs\ContainerStatusJob;
|
use App\Jobs\ContainerStatusJob;
|
||||||
use Livewire\Component;
|
use Livewire\Component;
|
||||||
|
|
||||||
@@ -44,7 +45,8 @@ class Heading extends Component
|
|||||||
|
|
||||||
public function check_status($showNotification = false)
|
public function check_status($showNotification = false)
|
||||||
{
|
{
|
||||||
dispatch_sync(new ContainerStatusJob($this->database->destination->server));
|
GetContainersStatus::run($this->database->destination->server);
|
||||||
|
// dispatch_sync(new ContainerStatusJob($this->database->destination->server));
|
||||||
$this->database->refresh();
|
$this->database->refresh();
|
||||||
if ($showNotification) $this->dispatch('success', 'Database status updated.');
|
if ($showNotification) $this->dispatch('success', 'Database status updated.');
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -150,7 +150,7 @@ class GithubPrivateRepository extends Component
|
|||||||
'repository_project_id' => $this->selected_repository_id,
|
'repository_project_id' => $this->selected_repository_id,
|
||||||
'git_repository' => "{$this->selected_repository_owner}/{$this->selected_repository_repo}",
|
'git_repository' => "{$this->selected_repository_owner}/{$this->selected_repository_repo}",
|
||||||
'git_branch' => $this->selected_branch_name,
|
'git_branch' => $this->selected_branch_name,
|
||||||
'build_pack' => 'nixpacks',
|
'build_pack' => $this->build_pack,
|
||||||
'ports_exposes' => $this->port,
|
'ports_exposes' => $this->port,
|
||||||
'publish_directory' => $this->publish_directory,
|
'publish_directory' => $this->publish_directory,
|
||||||
'environment_id' => $environment->id,
|
'environment_id' => $environment->id,
|
||||||
@@ -162,6 +162,9 @@ class GithubPrivateRepository extends Component
|
|||||||
$application->settings->is_static = $this->is_static;
|
$application->settings->is_static = $this->is_static;
|
||||||
$application->settings->save();
|
$application->settings->save();
|
||||||
|
|
||||||
|
if ($this->build_pack === 'dockerfile' || $this->build_pack === 'dockerimage') {
|
||||||
|
$application->health_check_enabled = false;
|
||||||
|
}
|
||||||
$fqdn = generateFqdn($destination->server, $application->uuid);
|
$fqdn = generateFqdn($destination->server, $application->uuid);
|
||||||
$application->fqdn = $fqdn;
|
$application->fqdn = $fqdn;
|
||||||
|
|
||||||
|
|||||||
@@ -19,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;
|
||||||
@@ -125,7 +125,7 @@ class GithubPrivateRepositoryDeployKey extends Component
|
|||||||
'name' => generate_random_name(),
|
'name' => generate_random_name(),
|
||||||
'git_repository' => $this->git_repository,
|
'git_repository' => $this->git_repository,
|
||||||
'git_branch' => $this->branch,
|
'git_branch' => $this->branch,
|
||||||
'build_pack' => 'nixpacks',
|
'build_pack' => $this->build_pack,
|
||||||
'ports_exposes' => $this->port,
|
'ports_exposes' => $this->port,
|
||||||
'publish_directory' => $this->publish_directory,
|
'publish_directory' => $this->publish_directory,
|
||||||
'environment_id' => $environment->id,
|
'environment_id' => $environment->id,
|
||||||
@@ -138,7 +138,7 @@ class GithubPrivateRepositoryDeployKey extends Component
|
|||||||
'name' => generate_random_name(),
|
'name' => generate_random_name(),
|
||||||
'git_repository' => $this->git_repository,
|
'git_repository' => $this->git_repository,
|
||||||
'git_branch' => $this->branch,
|
'git_branch' => $this->branch,
|
||||||
'build_pack' => 'nixpacks',
|
'build_pack' => $this->build_pack,
|
||||||
'ports_exposes' => $this->port,
|
'ports_exposes' => $this->port,
|
||||||
'publish_directory' => $this->publish_directory,
|
'publish_directory' => $this->publish_directory,
|
||||||
'environment_id' => $environment->id,
|
'environment_id' => $environment->id,
|
||||||
@@ -149,7 +149,9 @@ class GithubPrivateRepositoryDeployKey extends Component
|
|||||||
'source_type' => $this->git_source->getMorphClass()
|
'source_type' => $this->git_source->getMorphClass()
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
if ($this->build_pack === 'dockerfile' || $this->build_pack === 'dockerimage') {
|
||||||
|
$application_init['health_check_enabled'] = false;
|
||||||
|
}
|
||||||
$application = Application::create($application_init);
|
$application = Application::create($application_init);
|
||||||
$application->settings->is_static = $this->is_static;
|
$application->settings->is_static = $this->is_static;
|
||||||
$application->settings->save();
|
$application->settings->save();
|
||||||
|
|||||||
@@ -98,7 +98,8 @@ class PublicGitRepository extends Component
|
|||||||
(str($this->repository_url)->startsWith('https://') ||
|
(str($this->repository_url)->startsWith('https://') ||
|
||||||
str($this->repository_url)->startsWith('http://')) &&
|
str($this->repository_url)->startsWith('http://')) &&
|
||||||
!str($this->repository_url)->endsWith('.git') &&
|
!str($this->repository_url)->endsWith('.git') &&
|
||||||
!str($this->repository_url)->contains('github.com')
|
(!str($this->repository_url)->contains('github.com') ||
|
||||||
|
!str($this->repository_url)->contains('git.sr.ht'))
|
||||||
) {
|
) {
|
||||||
$this->repository_url = $this->repository_url . '.git';
|
$this->repository_url = $this->repository_url . '.git';
|
||||||
}
|
}
|
||||||
@@ -204,6 +205,9 @@ class PublicGitRepository extends Component
|
|||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if ($this->build_pack === 'dockerfile' || $this->build_pack === 'dockerimage') {
|
||||||
|
$application_init['health_check_enabled'] = false;
|
||||||
|
}
|
||||||
$application = Application::create($application_init);
|
$application = Application::create($application_init);
|
||||||
|
|
||||||
$application->settings->is_static = $this->is_static;
|
$application->settings->is_static = $this->is_static;
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
namespace App\Livewire\Project\Service;
|
namespace App\Livewire\Project\Service;
|
||||||
|
|
||||||
|
use App\Actions\Docker\GetContainersStatus;
|
||||||
use App\Jobs\ContainerStatusJob;
|
use App\Jobs\ContainerStatusJob;
|
||||||
use App\Models\Service;
|
use App\Models\Service;
|
||||||
use Livewire\Component;
|
use Livewire\Component;
|
||||||
@@ -64,7 +65,8 @@ class Configuration extends Component
|
|||||||
public function check_status()
|
public function check_status()
|
||||||
{
|
{
|
||||||
try {
|
try {
|
||||||
dispatch_sync(new ContainerStatusJob($this->service->server));
|
GetContainersStatus::run($this->service->server);
|
||||||
|
// dispatch_sync(new ContainerStatusJob($this->service->server));
|
||||||
$this->dispatch('refresh')->self();
|
$this->dispatch('refresh')->self();
|
||||||
$this->dispatch('updateStatus');
|
$this->dispatch('updateStatus');
|
||||||
} catch (\Exception $e) {
|
} catch (\Exception $e) {
|
||||||
|
|||||||
@@ -3,6 +3,7 @@
|
|||||||
namespace App\Livewire\Project\Shared;
|
namespace App\Livewire\Project\Shared;
|
||||||
|
|
||||||
use App\Actions\Application\StopApplicationOneServer;
|
use App\Actions\Application\StopApplicationOneServer;
|
||||||
|
use App\Actions\Docker\GetContainersStatus;
|
||||||
use App\Events\ApplicationStatusChanged;
|
use App\Events\ApplicationStatusChanged;
|
||||||
use App\Jobs\ContainerStatusJob;
|
use App\Jobs\ContainerStatusJob;
|
||||||
use App\Models\Server;
|
use App\Models\Server;
|
||||||
@@ -90,7 +91,8 @@ class Destination extends Component
|
|||||||
}
|
}
|
||||||
public function refreshServers()
|
public function refreshServers()
|
||||||
{
|
{
|
||||||
ContainerStatusJob::dispatchSync($this->resource->destination->server);
|
GetContainersStatus::run($this->resource->destination->server);
|
||||||
|
// ContainerStatusJob::dispatchSync($this->resource->destination->server);
|
||||||
$this->loadData();
|
$this->loadData();
|
||||||
$this->dispatch('refresh');
|
$this->dispatch('refresh');
|
||||||
ApplicationStatusChanged::dispatch(data_get($this->resource, 'environment.project.team.id'));
|
ApplicationStatusChanged::dispatch(data_get($this->resource, 'environment.project.team.id'));
|
||||||
|
|||||||
@@ -122,7 +122,7 @@ class ExecuteContainerCommand extends Component
|
|||||||
if ($server->isForceDisabled()) {
|
if ($server->isForceDisabled()) {
|
||||||
throw new \RuntimeException('Server is disabled.');
|
throw new \RuntimeException('Server is disabled.');
|
||||||
}
|
}
|
||||||
$cmd = 'sh -c "if [ -f ~/.profile ]; then . ~/.profile; fi; ' . str_replace('"', '\"', $this->command) . '"';
|
$cmd = "sh -c 'if [ -f ~/.profile ]; then . ~/.profile; fi; " . str_replace("'", "'\''", $this->command) . "'";
|
||||||
if (!empty($this->workDir)) {
|
if (!empty($this->workDir)) {
|
||||||
$exec = "docker exec -w {$this->workDir} {$container_name} {$cmd}";
|
$exec = "docker exec -w {$this->workDir} {$container_name} {$cmd}";
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -27,7 +27,7 @@ class Logs extends Component
|
|||||||
public $query;
|
public $query;
|
||||||
public $status;
|
public $status;
|
||||||
public $serviceSubType;
|
public $serviceSubType;
|
||||||
|
public $cpu;
|
||||||
public function loadContainers($server_id)
|
public function loadContainers($server_id)
|
||||||
{
|
{
|
||||||
try {
|
try {
|
||||||
@@ -49,6 +49,14 @@ class Logs extends Component
|
|||||||
return handleError($e, $this);
|
return handleError($e, $this);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
public function loadMetrics()
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
$server = data_get($this->resource, 'destination.server');
|
||||||
|
if ($server->isFunctional()) {
|
||||||
|
$this->cpu = $server->getMetrics();
|
||||||
|
}
|
||||||
|
}
|
||||||
public function mount()
|
public function mount()
|
||||||
{
|
{
|
||||||
try {
|
try {
|
||||||
@@ -95,6 +103,7 @@ class Logs extends Component
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
$this->containers = $this->containers->sort();
|
$this->containers = $this->containers->sort();
|
||||||
|
$this->loadMetrics();
|
||||||
} catch (\Exception $e) {
|
} catch (\Exception $e) {
|
||||||
return handleError($e, $this);
|
return handleError($e, $this);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
namespace App\Livewire\Server\Proxy;
|
namespace App\Livewire\Server\Proxy;
|
||||||
|
|
||||||
|
use App\Actions\Docker\GetContainersStatus;
|
||||||
use App\Actions\Proxy\CheckProxy;
|
use App\Actions\Proxy\CheckProxy;
|
||||||
use App\Jobs\ContainerStatusJob;
|
use App\Jobs\ContainerStatusJob;
|
||||||
use App\Models\Server;
|
use App\Models\Server;
|
||||||
@@ -49,7 +50,8 @@ class Status extends Component
|
|||||||
public function getProxyStatus()
|
public function getProxyStatus()
|
||||||
{
|
{
|
||||||
try {
|
try {
|
||||||
dispatch_sync(new ContainerStatusJob($this->server));
|
GetContainersStatus::run($this->server);
|
||||||
|
// dispatch_sync(new ContainerStatusJob($this->server));
|
||||||
$this->dispatch('proxyStatusUpdated');
|
$this->dispatch('proxyStatusUpdated');
|
||||||
} catch (\Throwable $e) {
|
} catch (\Throwable $e) {
|
||||||
return handleError($e, $this);
|
return handleError($e, $this);
|
||||||
|
|||||||
@@ -26,6 +26,7 @@ class Deployments extends Component
|
|||||||
"server_id",
|
"server_id",
|
||||||
"status"
|
"status"
|
||||||
])->sortBy('id')->groupBy('server_name')->toArray();
|
])->sortBy('id')->groupBy('server_name')->toArray();
|
||||||
|
$this->dispatch('deployments', $this->deployments_per_tag_per_server);
|
||||||
} catch (\Exception $e) {
|
} catch (\Exception $e) {
|
||||||
return handleError($e, $this);
|
return handleError($e, $this);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -20,6 +20,12 @@ class Index extends Component
|
|||||||
public $webhook = null;
|
public $webhook = null;
|
||||||
public $deployments_per_tag_per_server = [];
|
public $deployments_per_tag_per_server = [];
|
||||||
|
|
||||||
|
protected $listeners = ['deployments' => 'update_deployments'];
|
||||||
|
|
||||||
|
public function update_deployments($deployments)
|
||||||
|
{
|
||||||
|
$this->deployments_per_tag_per_server = $deployments;
|
||||||
|
}
|
||||||
public function tag_updated()
|
public function tag_updated()
|
||||||
{
|
{
|
||||||
if ($this->tag == "") {
|
if ($this->tag == "") {
|
||||||
@@ -39,14 +45,13 @@ class Index extends Component
|
|||||||
public function redeploy_all()
|
public function redeploy_all()
|
||||||
{
|
{
|
||||||
try {
|
try {
|
||||||
$message = collect([]);
|
$this->applications->each(function ($resource){
|
||||||
$this->applications->each(function ($resource) use ($message) {
|
|
||||||
$deploy = new Deploy();
|
$deploy = new Deploy();
|
||||||
$message->push($deploy->deploy_resource($resource));
|
$deploy->deploy_resource($resource);
|
||||||
});
|
});
|
||||||
$this->services->each(function ($resource) use ($message) {
|
$this->services->each(function ($resource) {
|
||||||
$deploy = new Deploy();
|
$deploy = new Deploy();
|
||||||
$message->push($deploy->deploy_resource($resource));
|
$deploy->deploy_resource($resource);
|
||||||
});
|
});
|
||||||
$this->dispatch('success', 'Mass deployment started.');
|
$this->dispatch('success', 'Mass deployment started.');
|
||||||
} catch (\Exception $e) {
|
} catch (\Exception $e) {
|
||||||
|
|||||||
@@ -847,7 +847,7 @@ class Application extends BaseModel
|
|||||||
if (!$composeFileContent) {
|
if (!$composeFileContent) {
|
||||||
$this->docker_compose_location = $initialDockerComposeLocation;
|
$this->docker_compose_location = $initialDockerComposeLocation;
|
||||||
$this->save();
|
$this->save();
|
||||||
throw new \RuntimeException("Could not load base compose file from $workdir$composeFile");
|
throw new \RuntimeException("Docker Compose file not found at: $workdir$composeFile");
|
||||||
} else {
|
} else {
|
||||||
$this->docker_compose_raw = $composeFileContent;
|
$this->docker_compose_raw = $composeFileContent;
|
||||||
$this->save();
|
$this->save();
|
||||||
|
|||||||
@@ -3,14 +3,16 @@
|
|||||||
namespace App\Models;
|
namespace App\Models;
|
||||||
|
|
||||||
use App\Actions\Server\InstallDocker;
|
use App\Actions\Server\InstallDocker;
|
||||||
|
use App\Actions\Server\StartSentinel;
|
||||||
use App\Enums\ProxyTypes;
|
use App\Enums\ProxyTypes;
|
||||||
|
use App\Jobs\PullSentinelImageJob;
|
||||||
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\Collection;
|
||||||
use Illuminate\Support\Facades\DB;
|
use Illuminate\Support\Facades\DB;
|
||||||
use Illuminate\Support\Facades\Storage;
|
use Illuminate\Support\Facades\Storage;
|
||||||
use Illuminate\Support\Sleep;
|
|
||||||
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;
|
||||||
@@ -463,58 +465,98 @@ $schema://$host {
|
|||||||
Storage::disk('ssh-keys')->delete($sshKeyFileLocation);
|
Storage::disk('ssh-keys')->delete($sshKeyFileLocation);
|
||||||
Storage::disk('ssh-mux')->delete($this->muxFilename());
|
Storage::disk('ssh-mux')->delete($this->muxFilename());
|
||||||
}
|
}
|
||||||
|
public function checkSentinel()
|
||||||
|
{
|
||||||
|
ray("Checking sentinel on server: {$this->name}");
|
||||||
|
if ($this->is_metrics_enabled) {
|
||||||
|
$sentinel_found = instant_remote_process(["docker inspect coolify-sentinel"], $this, false);
|
||||||
|
$sentinel_found = json_decode($sentinel_found, true);
|
||||||
|
$status = data_get($sentinel_found, '0.State.Status', 'exited');
|
||||||
|
if ($status !== 'running') {
|
||||||
|
ray('Sentinel is not running, starting it...');
|
||||||
|
PullSentinelImageJob::dispatch($this);
|
||||||
|
} else {
|
||||||
|
ray('Sentinel is running');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
public function getMetrics()
|
||||||
|
{
|
||||||
|
if ($this->is_metrics_enabled) {
|
||||||
|
$from = now()->subMinutes(5)->toIso8601ZuluString();
|
||||||
|
$cpu = instant_remote_process(["docker exec coolify-sentinel sh -c 'curl http://localhost:8888/api/cpu/history?from=$from'"], $this, false);
|
||||||
|
$cpu = str($cpu)->explode("\n")->skip(1)->all();
|
||||||
|
$parsedCollection = collect($cpu)->flatMap(function ($item) {
|
||||||
|
return collect(explode("\n", trim($item)))->map(function ($line) {
|
||||||
|
list($time, $value) = explode(',', trim($line));
|
||||||
|
return [(int) $time, (float) $value];
|
||||||
|
});
|
||||||
|
})->toArray();
|
||||||
|
return $parsedCollection;
|
||||||
|
}
|
||||||
|
}
|
||||||
public function isServerReady(int $tries = 3)
|
public function isServerReady(int $tries = 3)
|
||||||
{
|
{
|
||||||
if ($this->skipServer()) {
|
if ($this->skipServer()) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
$checkIteration = 1;
|
$serverUptimeCheckNumber = $this->unreachable_count;
|
||||||
$isServerReady = false;
|
if ($this->unreachable_count < $tries) {
|
||||||
while ($checkIteration < $tries) {
|
$serverUptimeCheckNumber = $this->unreachable_count + 1;
|
||||||
['uptime' => $uptime] = $this->validateConnection();
|
}
|
||||||
if ($uptime) {
|
if ($this->unreachable_count > $tries) {
|
||||||
if ($this->unreachable_notification_sent === true) {
|
$serverUptimeCheckNumber = $tries;
|
||||||
$this->update(['unreachable_notification_sent' => false]);
|
}
|
||||||
|
|
||||||
|
$serverUptimeCheckNumberMax = $tries;
|
||||||
|
|
||||||
|
// ray('server: ' . $this->name);
|
||||||
|
// ray('serverUptimeCheckNumber: ' . $serverUptimeCheckNumber);
|
||||||
|
// ray('serverUptimeCheckNumberMax: ' . $serverUptimeCheckNumberMax);
|
||||||
|
|
||||||
|
['uptime' => $uptime] = $this->validateConnection();
|
||||||
|
if ($uptime) {
|
||||||
|
if ($this->unreachable_notification_sent === true) {
|
||||||
|
$this->update(['unreachable_notification_sent' => false]);
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
} else {
|
||||||
|
if ($serverUptimeCheckNumber >= $serverUptimeCheckNumberMax) {
|
||||||
|
// Reached max number of retries
|
||||||
|
if ($this->unreachable_notification_sent === false) {
|
||||||
|
ray('Server unreachable, sending notification...');
|
||||||
|
$this->team?->notify(new Unreachable($this));
|
||||||
|
$this->update(['unreachable_notification_sent' => true]);
|
||||||
|
}
|
||||||
|
if ($this->settings->is_reachable === true) {
|
||||||
|
$this->settings()->update([
|
||||||
|
'is_reachable' => false,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach ($this->applications() as $application) {
|
||||||
|
$application->update(['status' => 'exited']);
|
||||||
|
}
|
||||||
|
foreach ($this->databases() as $database) {
|
||||||
|
$database->update(['status' => 'exited']);
|
||||||
|
}
|
||||||
|
foreach ($this->services()->get() as $service) {
|
||||||
|
$apps = $service->applications()->get();
|
||||||
|
$dbs = $service->databases()->get();
|
||||||
|
foreach ($apps as $app) {
|
||||||
|
$app->update(['status' => 'exited']);
|
||||||
|
}
|
||||||
|
foreach ($dbs as $db) {
|
||||||
|
$db->update(['status' => 'exited']);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
$this->settings()->update([
|
|
||||||
'is_reachable' => true,
|
|
||||||
]);
|
|
||||||
$isServerReady = true;
|
|
||||||
break;
|
|
||||||
} else {
|
} else {
|
||||||
ray('Server is not ready yet.');
|
$this->update([
|
||||||
$checkIteration++;
|
'unreachable_count' => $this->unreachable_count + 1,
|
||||||
Sleep::for(10)->seconds();
|
]);
|
||||||
}
|
}
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
if ($isServerReady) {
|
|
||||||
return $isServerReady;
|
|
||||||
}
|
|
||||||
if ($this->unreachable_notification_sent === false) {
|
|
||||||
ray('Server unreachable, sending notification...');
|
|
||||||
$this->team?->notify(new Unreachable($this));
|
|
||||||
$this->update(['unreachable_notification_sent' => true]);
|
|
||||||
}
|
|
||||||
$this->settings()->update([
|
|
||||||
'is_reachable' => false,
|
|
||||||
]);
|
|
||||||
foreach ($this->applications() as $application) {
|
|
||||||
$application->update(['status' => 'exited']);
|
|
||||||
}
|
|
||||||
foreach ($this->databases() as $database) {
|
|
||||||
$database->update(['status' => 'exited']);
|
|
||||||
}
|
|
||||||
foreach ($this->services()->get() as $service) {
|
|
||||||
$apps = $service->applications()->get();
|
|
||||||
$dbs = $service->databases()->get();
|
|
||||||
foreach ($apps as $app) {
|
|
||||||
$app->update(['status' => 'exited']);
|
|
||||||
}
|
|
||||||
foreach ($dbs as $db) {
|
|
||||||
$db->update(['status' => 'exited']);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
public function getDiskUsage()
|
public function getDiskUsage()
|
||||||
{
|
{
|
||||||
@@ -539,7 +581,36 @@ $schema://$host {
|
|||||||
{
|
{
|
||||||
return instant_remote_process(["docker start $id"], $this);
|
return instant_remote_process(["docker start $id"], $this);
|
||||||
}
|
}
|
||||||
public function loadUnmanagedContainers()
|
public function getContainers(): Collection
|
||||||
|
{
|
||||||
|
$sentinel_found = instant_remote_process(["docker inspect coolify-sentinel"], $this, false);
|
||||||
|
$sentinel_found = json_decode($sentinel_found, true);
|
||||||
|
$status = data_get($sentinel_found, '0.State.Status', 'exited');
|
||||||
|
if ($status === 'running') {
|
||||||
|
$containers = instant_remote_process(['docker exec coolify-sentinel sh -c "curl http://127.0.0.1:8888/api/containers"'], $this, false);
|
||||||
|
if (is_null($containers)) {
|
||||||
|
return collect([]);
|
||||||
|
}
|
||||||
|
$containers = data_get(json_decode($containers, true), 'containers', []);
|
||||||
|
return collect($containers);
|
||||||
|
} else {
|
||||||
|
if ($this->isSwarm()) {
|
||||||
|
$containers = instant_remote_process(["docker service inspect $(docker service ls -q) --format '{{json .}}'"], $this, false);
|
||||||
|
} else {
|
||||||
|
$containers = instant_remote_process(["docker container ls -q"], $this, false);
|
||||||
|
if (!$containers) {
|
||||||
|
return collect([]);
|
||||||
|
}
|
||||||
|
$containers = instant_remote_process(["docker container inspect $(docker container ls -q) --format '{{json .}}'"], $this, false);
|
||||||
|
}
|
||||||
|
if (is_null($containers)) {
|
||||||
|
return collect([]);
|
||||||
|
}
|
||||||
|
|
||||||
|
return format_docker_command_output_to_json($containers);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
public function loadUnmanagedContainers(): Collection
|
||||||
{
|
{
|
||||||
if ($this->isFunctional()) {
|
if ($this->isFunctional()) {
|
||||||
$containers = instant_remote_process(["docker ps -a --format '{{json .}}'"], $this);
|
$containers = instant_remote_process(["docker ps -a --format '{{json .}}'"], $this);
|
||||||
@@ -762,11 +833,6 @@ $schema://$host {
|
|||||||
$server->settings()->update([
|
$server->settings()->update([
|
||||||
'is_reachable' => false,
|
'is_reachable' => false,
|
||||||
]);
|
]);
|
||||||
if (data_get($server, 'unreachable_notification_sent') === false) {
|
|
||||||
ray('Server unreachable, sending notification...');
|
|
||||||
$this->team?->notify(new Unreachable($this));
|
|
||||||
$this->update(['unreachable_notification_sent' => true]);
|
|
||||||
}
|
|
||||||
return ['uptime' => false, 'error' => $e->getMessage()];
|
return ['uptime' => false, 'error' => $e->getMessage()];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -171,7 +171,7 @@ class Service extends BaseModel
|
|||||||
],
|
],
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
$fields->put('Tolgee', $data);
|
$fields->put('Tolgee', $data->toArray());
|
||||||
break;
|
break;
|
||||||
case str($image)?->contains('logto'):
|
case str($image)?->contains('logto'):
|
||||||
$data = collect([]);
|
$data = collect([]);
|
||||||
@@ -195,7 +195,7 @@ class Service extends BaseModel
|
|||||||
],
|
],
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
$fields->put('Logto', $data);
|
$fields->put('Logto', $data->toArray());
|
||||||
break;
|
break;
|
||||||
case str($image)?->contains('unleash-server'):
|
case str($image)?->contains('unleash-server'):
|
||||||
$data = collect([]);
|
$data = collect([]);
|
||||||
@@ -218,7 +218,7 @@ class Service extends BaseModel
|
|||||||
],
|
],
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
$fields->put('Unleash', $data);
|
$fields->put('Unleash', $data->toArray());
|
||||||
break;
|
break;
|
||||||
case str($image)?->contains('grafana'):
|
case str($image)?->contains('grafana'):
|
||||||
$data = collect([]);
|
$data = collect([]);
|
||||||
@@ -241,7 +241,7 @@ class Service extends BaseModel
|
|||||||
],
|
],
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
$fields->put('Grafana', $data);
|
$fields->put('Grafana', $data->toArray());
|
||||||
break;
|
break;
|
||||||
case str($image)?->contains('directus'):
|
case str($image)?->contains('directus'):
|
||||||
$data = collect([]);
|
$data = collect([]);
|
||||||
@@ -267,7 +267,7 @@ class Service extends BaseModel
|
|||||||
],
|
],
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
$fields->put('Directus', $data);
|
$fields->put('Directus', $data->toArray());
|
||||||
break;
|
break;
|
||||||
case str($image)?->contains('kong'):
|
case str($image)?->contains('kong'):
|
||||||
$data = collect([]);
|
$data = collect([]);
|
||||||
@@ -370,7 +370,7 @@ class Service extends BaseModel
|
|||||||
],
|
],
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
$fields->put('Weblate', $data);
|
$fields->put('Weblate', $data->toArray());
|
||||||
break;
|
break;
|
||||||
case str($image)?->contains('meilisearch'):
|
case str($image)?->contains('meilisearch'):
|
||||||
$data = collect([]);
|
$data = collect([]);
|
||||||
@@ -384,7 +384,7 @@ class Service extends BaseModel
|
|||||||
],
|
],
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
$fields->put('Meilisearch', $data);
|
$fields->put('Meilisearch', $data->toArray());
|
||||||
break;
|
break;
|
||||||
case str($image)?->contains('ghost'):
|
case str($image)?->contains('ghost'):
|
||||||
$data = collect([]);
|
$data = collect([]);
|
||||||
@@ -444,7 +444,31 @@ class Service extends BaseModel
|
|||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
$fields->put('Ghost', $data);
|
$fields->put('Ghost', $data->toArray());
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
$data = collect([]);
|
||||||
|
$admin_user = $this->environment_variables()->where('key', 'SERVICE_USER_ADMIN')->first();
|
||||||
|
$admin_password = $this->environment_variables()->where('key', 'SERVICE_PASSWORD_ADMIN')->first();
|
||||||
|
$data = $data->merge([
|
||||||
|
'User' => [
|
||||||
|
'key' => 'SERVICE_USER_ADMIN',
|
||||||
|
'value' => data_get($admin_user, 'value', 'admin'),
|
||||||
|
'readonly' => true,
|
||||||
|
'rules' => 'required',
|
||||||
|
],
|
||||||
|
]);
|
||||||
|
if ($admin_password) {
|
||||||
|
$data = $data->merge([
|
||||||
|
'Password' => [
|
||||||
|
'key' => 'SERVICE_PASSWORD_ADMIN',
|
||||||
|
'value' => data_get($admin_password, 'value'),
|
||||||
|
'rules' => 'required',
|
||||||
|
'isPassword' => true,
|
||||||
|
],
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
$fields->put('Admin', $data->toArray());
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
namespace App\Notifications\Server;
|
namespace App\Notifications\Server;
|
||||||
|
|
||||||
|
use App\Actions\Docker\GetContainersStatus;
|
||||||
use App\Jobs\ContainerStatusJob;
|
use App\Jobs\ContainerStatusJob;
|
||||||
use App\Models\Server;
|
use App\Models\Server;
|
||||||
use Illuminate\Bus\Queueable;
|
use Illuminate\Bus\Queueable;
|
||||||
@@ -22,7 +23,8 @@ class Revived extends Notification implements ShouldQueue
|
|||||||
if ($this->server->unreachable_notification_sent === false) {
|
if ($this->server->unreachable_notification_sent === false) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
dispatch(new ContainerStatusJob($server));
|
GetContainersStatus::dispatch($server);
|
||||||
|
// dispatch(new ContainerStatusJob($server));
|
||||||
}
|
}
|
||||||
|
|
||||||
public function via(object $notifiable): array
|
public function via(object $notifiable): array
|
||||||
|
|||||||
@@ -147,6 +147,18 @@ function get_route_parameters(): array
|
|||||||
return Route::current()->parameters();
|
return Route::current()->parameters();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function get_latest_sentinel_version(): string
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
$response = Http::get('https://cdn.coollabs.io/coolify/versions.json');
|
||||||
|
$versions = $response->json();
|
||||||
|
return data_get($versions, 'coolify.sentinel.version');
|
||||||
|
} catch (\Throwable $e) {
|
||||||
|
//throw $e;
|
||||||
|
ray($e->getMessage());
|
||||||
|
return '0.0.0';
|
||||||
|
}
|
||||||
|
}
|
||||||
function get_latest_version_of_coolify(): string
|
function get_latest_version_of_coolify(): string
|
||||||
{
|
{
|
||||||
try {
|
try {
|
||||||
@@ -637,7 +649,6 @@ function parseDockerComposeFile(Service|Application $resource, bool $isNew = fal
|
|||||||
$allServices = getServiceTemplates();
|
$allServices = getServiceTemplates();
|
||||||
$topLevelVolumes = collect(data_get($yaml, 'volumes', []));
|
$topLevelVolumes = collect(data_get($yaml, 'volumes', []));
|
||||||
$topLevelNetworks = collect(data_get($yaml, 'networks', []));
|
$topLevelNetworks = collect(data_get($yaml, 'networks', []));
|
||||||
$dockerComposeVersion = data_get($yaml, 'version') ?? '3.8';
|
|
||||||
$services = data_get($yaml, 'services');
|
$services = data_get($yaml, 'services');
|
||||||
|
|
||||||
$generatedServiceFQDNS = collect([]);
|
$generatedServiceFQDNS = collect([]);
|
||||||
@@ -1192,7 +1203,6 @@ function parseDockerComposeFile(Service|Application $resource, bool $isNew = fal
|
|||||||
return $service;
|
return $service;
|
||||||
});
|
});
|
||||||
$finalServices = [
|
$finalServices = [
|
||||||
'version' => $dockerComposeVersion,
|
|
||||||
'services' => $services->toArray(),
|
'services' => $services->toArray(),
|
||||||
'volumes' => $topLevelVolumes->toArray(),
|
'volumes' => $topLevelVolumes->toArray(),
|
||||||
'networks' => $topLevelNetworks->toArray(),
|
'networks' => $topLevelNetworks->toArray(),
|
||||||
@@ -1230,7 +1240,6 @@ function parseDockerComposeFile(Service|Application $resource, bool $isNew = fal
|
|||||||
$topLevelVolumes = collect([]);
|
$topLevelVolumes = collect([]);
|
||||||
}
|
}
|
||||||
$topLevelNetworks = collect(data_get($yaml, 'networks', []));
|
$topLevelNetworks = collect(data_get($yaml, 'networks', []));
|
||||||
$dockerComposeVersion = data_get($yaml, 'version') ?? '3.8';
|
|
||||||
$services = data_get($yaml, 'services');
|
$services = data_get($yaml, 'services');
|
||||||
|
|
||||||
$generatedServiceFQDNS = collect([]);
|
$generatedServiceFQDNS = collect([]);
|
||||||
@@ -1661,7 +1670,6 @@ function parseDockerComposeFile(Service|Application $resource, bool $isNew = fal
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
$finalServices = [
|
$finalServices = [
|
||||||
'version' => $dockerComposeVersion,
|
|
||||||
'services' => $services->toArray(),
|
'services' => $services->toArray(),
|
||||||
'volumes' => $topLevelVolumes->toArray(),
|
'volumes' => $topLevelVolumes->toArray(),
|
||||||
'networks' => $topLevelNetworks->toArray(),
|
'networks' => $topLevelNetworks->toArray(),
|
||||||
@@ -1841,7 +1849,7 @@ function validate_dns_entry(string $fqdn, Server $server)
|
|||||||
$dns_servers = data_get($settings, 'custom_dns_servers');
|
$dns_servers = data_get($settings, 'custom_dns_servers');
|
||||||
$dns_servers = str($dns_servers)->explode(',');
|
$dns_servers = str($dns_servers)->explode(',');
|
||||||
if ($server->id === 0) {
|
if ($server->id === 0) {
|
||||||
$ip = data_get($settings, 'public_ipv4') || data_get($settings, 'public_ipv6') || $server->ip;
|
$ip = data_get($settings, 'public_ipv4', data_get($settings, 'public_ipv6', $server->ip));
|
||||||
} else {
|
} else {
|
||||||
$ip = $server->ip;
|
$ip = $server->ip;
|
||||||
}
|
}
|
||||||
@@ -1920,7 +1928,6 @@ function check_domain_usage(ServiceApplication|Application|null $resource = null
|
|||||||
$naked_domain = str($domain)->value();
|
$naked_domain = str($domain)->value();
|
||||||
if ($domains->contains($naked_domain)) {
|
if ($domains->contains($naked_domain)) {
|
||||||
if (data_get($resource, 'uuid')) {
|
if (data_get($resource, 'uuid')) {
|
||||||
ray($resource->uuid, $app->uuid);
|
|
||||||
if ($resource->uuid !== $app->uuid) {
|
if ($resource->uuid !== $app->uuid) {
|
||||||
throw new \RuntimeException("Domain $naked_domain is already in use by another resource called: <br><br>{$app->name}.");
|
throw new \RuntimeException("Domain $naked_domain is already in use by another resource called: <br><br>{$app->name}.");
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,4 +14,5 @@ return [
|
|||||||
'helper_image' => env('HELPER_IMAGE', 'ghcr.io/coollabsio/coolify-helper:latest'),
|
'helper_image' => env('HELPER_IMAGE', 'ghcr.io/coollabsio/coolify-helper:latest'),
|
||||||
'is_horizon_enabled' => env('HORIZON_ENABLED', true),
|
'is_horizon_enabled' => env('HORIZON_ENABLED', true),
|
||||||
'is_scheduler_enabled' => env('SCHEDULER_ENABLED', true),
|
'is_scheduler_enabled' => env('SCHEDULER_ENABLED', true),
|
||||||
|
'is_sentinel_enabled' => env('SENTINEL_ENABLED', false),
|
||||||
];
|
];
|
||||||
|
|||||||
@@ -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.273',
|
'release' => '4.0.0-beta.277',
|
||||||
// When left empty or `null` the Laravel environment will be used
|
// When left empty or `null` the Laravel environment will be used
|
||||||
'environment' => config('app.env'),
|
'environment' => config('app.env'),
|
||||||
|
|
||||||
|
|||||||
@@ -1,3 +1,3 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
return '4.0.0-beta.273';
|
return '4.0.0-beta.277';
|
||||||
|
|||||||
@@ -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_settings', function (Blueprint $table) {
|
||||||
|
$table->string('custom_internal_name')->nullable();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reverse the migrations.
|
||||||
|
*/
|
||||||
|
public function down(): void
|
||||||
|
{
|
||||||
|
Schema::table('application_settings', function (Blueprint $table) {
|
||||||
|
$table->dropColumn('custom_internal_name');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
28
database/migrations/2024_05_07_124019_add_server_metrics.php
Normal file
28
database/migrations/2024_05_07_124019_add_server_metrics.php
Normal file
@@ -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('servers', function (Blueprint $table) {
|
||||||
|
$table->boolean('is_metrics_enabled')->default(true);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reverse the migrations.
|
||||||
|
*/
|
||||||
|
public function down(): void
|
||||||
|
{
|
||||||
|
Schema::table('servers', function (Blueprint $table) {
|
||||||
|
$table->dropColumn('is_metrics_enabled');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
@@ -1,5 +1,3 @@
|
|||||||
version: "3.8"
|
|
||||||
|
|
||||||
services:
|
services:
|
||||||
coolify:
|
coolify:
|
||||||
build:
|
build:
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
version: '3.8'
|
|
||||||
services:
|
services:
|
||||||
coolify:
|
coolify:
|
||||||
image: "ghcr.io/coollabsio/coolify:${LATEST_IMAGE:-latest}"
|
image: "ghcr.io/coollabsio/coolify:${LATEST_IMAGE:-latest}"
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
version: '3.8'
|
|
||||||
services:
|
services:
|
||||||
coolify-testing-host:
|
coolify-testing-host:
|
||||||
init: true
|
init: true
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
version: '3.8'
|
|
||||||
services:
|
services:
|
||||||
coolify:
|
coolify:
|
||||||
container_name: coolify
|
container_name: coolify
|
||||||
@@ -11,7 +10,6 @@ services:
|
|||||||
depends_on:
|
depends_on:
|
||||||
- postgres
|
- postgres
|
||||||
- redis
|
- redis
|
||||||
|
|
||||||
postgres:
|
postgres:
|
||||||
image: postgres:15-alpine
|
image: postgres:15-alpine
|
||||||
container_name: coolify-db
|
container_name: coolify-db
|
||||||
|
|||||||
30
lang/zh-cn.json
Normal file
30
lang/zh-cn.json
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
{
|
||||||
|
"auth.login": "登录",
|
||||||
|
"auth.login.azure": "使用 Microsoft 登录",
|
||||||
|
"auth.login.bitbucket": "使用 Bitbucket 登录",
|
||||||
|
"auth.login.github": "使用 GitHub 登录",
|
||||||
|
"auth.login.gitlab": "使用 Gitlab 登录",
|
||||||
|
"auth.login.google": "使用 Google 登录",
|
||||||
|
"auth.already_registered": "已经注册?",
|
||||||
|
"auth.confirm_password": "确认密码",
|
||||||
|
"auth.forgot_password": "忘记密码",
|
||||||
|
"auth.forgot_password_send_email": "发送密码重置邮件",
|
||||||
|
"auth.register_now": "注册",
|
||||||
|
"auth.logout": "退出登录",
|
||||||
|
"auth.register": "注册",
|
||||||
|
"auth.registration_disabled": "注册已禁用,请联系管理员",
|
||||||
|
"auth.reset_password": "重置密码",
|
||||||
|
"auth.failed": "这些凭据与我们的记录不符",
|
||||||
|
"auth.failed.callback": "处理第三方登录的回调时出错",
|
||||||
|
"auth.failed.password": "密码错误",
|
||||||
|
"auth.failed.email": "该账户未注册",
|
||||||
|
"auth.throttle": "登录次数过多,请在 :seconds 秒后重试",
|
||||||
|
"input.name": "用户名",
|
||||||
|
"input.email": "邮箱",
|
||||||
|
"input.password": "密码",
|
||||||
|
"input.password.again": "确认密码",
|
||||||
|
"input.code": "验证码",
|
||||||
|
"input.recovery_code": "恢复码",
|
||||||
|
"button.save": "保存",
|
||||||
|
"repository.url": "<span class='text-helper'>示例</span><br>对于公共代码仓库,请使用 <span class='text-helper'>https://...</span>。<br>对于私有代码仓库,请使用 <span class='text-helper'>git@...</span>。<br><br>https://github.com/coollabsio/coolify-examples <span class='text-helper'>main</span> 分支将被选择<br>https://github.com/coollabsio/coolify-examples/tree/nodejs-fastify <span class='text-helper'>nodejs-fastify</span> 分支将被选择。<br>https://gitea.com/sedlav/expressjs.git <span class='text-helper'>main</span> 分支将被选择。<br>https://gitlab.com/andrasbacsai/nodejs-example.git <span class='text-helper'>main</span> 分支将被选择"
|
||||||
|
}
|
||||||
11
other/scripts/get-subs.php
Normal file
11
other/scripts/get-subs.php
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
$handle = fopen("/tmp/export.csv", "w");
|
||||||
|
App\Models\Team::chunk(100, function ($teams) use ($handle) {
|
||||||
|
foreach ($teams as $team) {
|
||||||
|
if ($team->subscription->stripe_invoice_paid == true) {
|
||||||
|
foreach ($team->members as $member) {
|
||||||
|
fputcsv($handle, [$member->email, $member->name], ",");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
fclose($handle);
|
||||||
2
public/svgs/listmonk.svg
Normal file
2
public/svgs/listmonk.svg
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="163.03" height="30.38" viewBox="0 0 43.135 8.038" xmlns:v="https://vecta.io/nano"><circle cx="4.019" cy="4.019" r="3.149" fill="#fff" stroke="#0055d4" stroke-width="1.74"/><path d="M11.457 7.303q-.566 0-.879-.322-.313-.331-.313-.932V.712L11.5.572v5.442q0 .305.253.305.139 0 .244-.052l.253.879q-.357.157-.792.157zm2.619-4.754v4.615H12.84V2.549zM13.449.172q.331 0 .54.209.218.2.218.514 0 .313-.218.522-.209.2-.54.2-.331 0-.54-.2-.209-.209-.209-.522 0-.313.209-.514.209-.209.54-.209zm3.319 2.238q.975 0 1.672.557l-.47.705q-.583-.366-1.149-.366-.305 0-.47.113-.165.113-.165.305 0 .139.07.235.078.096.279.183.209.087.618.209.731.2 1.088.54.357.331.357.914 0 .462-.27.801-.261.34-.714.522-.453.174-1.01.174-.583 0-1.062-.174-.479-.183-.819-.496l.61-.679q.583.453 1.237.453.348 0 .549-.131.209-.139.209-.374 0-.183-.078-.287-.078-.104-.287-.192-.209-.096-.653-.218-.697-.192-1.036-.54-.331-.357-.331-.879 0-.392.226-.705.226-.313.636-.488.418-.183.967-.183zm5.342 4.536q-.253.174-.575.261-.313.096-.627.096-.714-.009-1.08-.409-.366-.401-.366-1.176V3.42h-.688v-.871h.688v-1.01l1.237-.148v1.158h1.062l-.122.871h-.94v2.273q0 .331.113.479.113.148.348.148.235 0 .522-.157zm5.493-4.536q.549 0 .879.374.34.374.34 1.019v3.361h-1.237V4.012q0-.679-.453-.679-.244 0-.427.157-.183.157-.374.488v3.187h-1.237V4.012q0-.679-.453-.679-.244 0-.427.165-.183.157-.366.479v3.187h-1.237V2.549h1.071l.096.575q.261-.348.583-.531.331-.183.758-.183.392 0 .679.2.287.192.418.549.287-.374.618-.557.34-.192.766-.192zm4.148 0q1.036 0 1.62.653.583.644.583 1.794 0 .731-.27 1.289-.261.549-.766.853-.496.305-1.176.305-1.036 0-1.628-.644-.583-.653-.583-1.803 0-.731.261-1.28.27-.557.766-.862.505-.305 1.193-.305zm0 .923q-.47 0-.705.374-.226.366-.226 1.149 0 .784.226 1.158.235.366.697.366.462 0 .688-.366.235-.374.235-1.158 0-.784-.226-1.149-.226-.374-.688-.374zm5.271-.923q.61 0 .949.374.34.366.34 1.019v3.361h-1.237V4.012q0-.374-.131-.522-.122-.157-.374-.157-.261 0-.479.165-.209.157-.409.479v3.187h-1.237V2.549h1.071l.096.583q.287-.357.627-.54.348-.183.784-.183zM40.2.572v6.592h-1.237V.712zm2.804 1.977l-1.472 2.029 1.602 2.586h-1.402l-1.489-2.525 1.48-2.09z"/></svg>
|
||||||
|
After Width: | Height: | Size: 2.1 KiB |
@@ -32,7 +32,7 @@ body {
|
|||||||
@apply block w-full py-1.5 rounded border-0 text-sm ring-1 ring-inset;
|
@apply block w-full py-1.5 rounded border-0 text-sm ring-1 ring-inset;
|
||||||
}
|
}
|
||||||
|
|
||||||
.input[type='password'] {
|
.input[type="password"] {
|
||||||
@apply pr-10;
|
@apply pr-10;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -52,7 +52,6 @@ button[isHighlighted]:not(:disabled) {
|
|||||||
@apply text-white bg-coollabs hover:bg-coollabs-100;
|
@apply text-white bg-coollabs hover:bg-coollabs-100;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
h1 {
|
h1 {
|
||||||
@apply text-2xl font-bold dark:text-white;
|
@apply text-2xl font-bold dark:text-white;
|
||||||
}
|
}
|
||||||
@@ -78,7 +77,7 @@ label {
|
|||||||
}
|
}
|
||||||
|
|
||||||
table {
|
table {
|
||||||
@apply min-w-full divide-y dark:divide-coolgray-200 divide-neutral-300 ;
|
@apply min-w-full divide-y dark:divide-coolgray-200 divide-neutral-300;
|
||||||
}
|
}
|
||||||
|
|
||||||
thead {
|
thead {
|
||||||
@@ -117,7 +116,7 @@ tr td:first-child {
|
|||||||
@apply flex items-center gap-2 text-error;
|
@apply flex items-center gap-2 text-error;
|
||||||
}
|
}
|
||||||
.tag {
|
.tag {
|
||||||
@apply px-2 py-1 cursor-pointer box-description dark:bg-coolgray-100 dark:hover:bg-coolgray-300 bg-neutral-100 hover:bg-neutral-200
|
@apply px-2 py-1 cursor-pointer box-description dark:bg-coolgray-100 dark:hover:bg-coolgray-300 bg-neutral-100 hover:bg-neutral-200;
|
||||||
}
|
}
|
||||||
.add-tag {
|
.add-tag {
|
||||||
@apply flex items-center px-2 text-xs cursor-pointer dark:text-neutral-500/20 text-neutral-500 group-hover:text-neutral-700 group-hover:dark:text-white dark:hover:bg-coolgray-300 hover:bg-neutral-200;
|
@apply flex items-center px-2 text-xs cursor-pointer dark:text-neutral-500/20 text-neutral-500 group-hover:text-neutral-700 group-hover:dark:text-white dark:hover:bg-coolgray-300 hover:bg-neutral-200;
|
||||||
@@ -135,7 +134,6 @@ tr td:first-child {
|
|||||||
|
|
||||||
.badge-absolute {
|
.badge-absolute {
|
||||||
@apply absolute top-0 right-0 w-2 h-2 border-none rounded-t-none rounded-r-none;
|
@apply absolute top-0 right-0 w-2 h-2 border-none rounded-t-none rounded-r-none;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.badge-success {
|
.badge-success {
|
||||||
@@ -159,7 +157,7 @@ tr td:first-child {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.menu-item {
|
.menu-item {
|
||||||
@apply flex items-center w-full gap-3 py-1 pl-2 dark:hover:bg-coolgray-100 dark:hover:text-white hover:bg-neutral-300;
|
@apply flex items-center w-full gap-3 px-2 py-1 text-sm sm:pr-0 dark:hover:bg-coolgray-100 dark:hover:text-white hover:bg-neutral-300 min-w-fit sm:min-w-64;
|
||||||
}
|
}
|
||||||
|
|
||||||
.menu-item-active {
|
.menu-item-active {
|
||||||
@@ -174,7 +172,6 @@ tr td:first-child {
|
|||||||
@apply w-6 h-6 dark:hover:text-white;
|
@apply w-6 h-6 dark:hover:text-white;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
.scrollbar {
|
.scrollbar {
|
||||||
@apply scrollbar-thumb-coollabs-100 dark:scrollbar-track-coolgray-200 scrollbar-track-neutral-200 scrollbar-w-2;
|
@apply scrollbar-thumb-coollabs-100 dark:scrollbar-track-coolgray-200 scrollbar-track-neutral-200 scrollbar-w-2;
|
||||||
}
|
}
|
||||||
@@ -188,7 +185,7 @@ tr td:first-child {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.navbar-main {
|
.navbar-main {
|
||||||
@apply flex items-center h-10 gap-6 pb-2 border-b-2 border-solid dark:border-coolgray-200;
|
@apply flex flex-col gap-4 pb-2 border-b-2 border-solid h-fit md:flex-row justify-items-start sm:justify-between dark:border-coolgray-200 md:items-center;
|
||||||
}
|
}
|
||||||
|
|
||||||
.loading {
|
.loading {
|
||||||
@@ -203,20 +200,19 @@ tr td:first-child {
|
|||||||
@apply relative flex lg:flex-row flex-col p-2 transition-colors cursor-pointer min-h-[4rem] dark:bg-coolgray-100 bg-white border text-black dark:text-white hover:text-black border-neutral-200 dark:border-black hover:bg-neutral-100 dark:hover:bg-coollabs-100 dark:hover:text-white hover:no-underline;
|
@apply relative flex lg:flex-row flex-col p-2 transition-colors cursor-pointer min-h-[4rem] dark:bg-coolgray-100 bg-white border text-black dark:text-white hover:text-black border-neutral-200 dark:border-black hover:bg-neutral-100 dark:hover:bg-coollabs-100 dark:hover:text-white hover:no-underline;
|
||||||
}
|
}
|
||||||
.box-boarding {
|
.box-boarding {
|
||||||
@apply flex lg:flex-row flex-col p-2 transition-colors cursor-pointer min-h-[4rem] dark:bg-coolgray-100 dark:text-white bg-neutral-50 border border-neutral-200 dark:border-black hover:bg-neutral-100 dark:hover:bg-coollabs-100 dark:hover:text-white hover:text-black hover:no-underline text-black ;
|
@apply flex lg:flex-row flex-col p-2 transition-colors cursor-pointer min-h-[4rem] dark:bg-coolgray-100 dark:text-white bg-neutral-50 border border-neutral-200 dark:border-black hover:bg-neutral-100 dark:hover:bg-coollabs-100 dark:hover:text-white hover:text-black hover:no-underline text-black;
|
||||||
}
|
}
|
||||||
.box-without-bg {
|
.box-without-bg {
|
||||||
@apply flex p-2 transition-colors dark:hover:text-white hover:no-underline min-h-[4rem] border border-neutral-200 dark:border-black;
|
@apply flex p-2 transition-colors dark:hover:text-white hover:no-underline min-h-[4rem] border border-neutral-200 dark:border-black;
|
||||||
}
|
}
|
||||||
.box-without-bg-without-border {
|
.box-without-bg-without-border {
|
||||||
@apply flex p-2 transition-colors dark:hover:text-white hover:no-underline min-h-[4rem] ;
|
@apply flex p-2 transition-colors dark:hover:text-white hover:no-underline min-h-[4rem];
|
||||||
}
|
}
|
||||||
|
|
||||||
.on-box {
|
.on-box {
|
||||||
@apply rounded hover:bg-neutral-300 dark:hover:bg-coolgray-500/20;
|
@apply rounded hover:bg-neutral-300 dark:hover:bg-coolgray-500/20;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
.box-title {
|
.box-title {
|
||||||
@apply font-bold text-black dark:text-white group-hover:dark:text-white;
|
@apply font-bold text-black dark:text-white group-hover:dark:text-white;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,8 +4,7 @@
|
|||||||
<a class="flex items-center mb-6 text-5xl font-extrabold tracking-tight text-gray-900 dark:text-white">
|
<a class="flex items-center mb-6 text-5xl font-extrabold tracking-tight text-gray-900 dark:text-white">
|
||||||
Coolify
|
Coolify
|
||||||
</a>
|
</a>
|
||||||
<div
|
<div class="w-full bg-white rounded-lg shadow md:mt-0 sm:max-w-md xl:p-0 dark:bg-base">
|
||||||
class="w-full bg-white rounded-lg shadow md:mt-0 sm:max-w-md xl:p-0 dark:bg-base">
|
|
||||||
<div class="p-6 space-y-4 md:space-y-6 sm:p-8">
|
<div class="p-6 space-y-4 md:space-y-6 sm:p-8">
|
||||||
<h1 class="text-xl font-bold leading-tight tracking-tight text-gray-900 md:text-2xl dark:text-white">
|
<h1 class="text-xl font-bold leading-tight tracking-tight text-gray-900 md:text-2xl dark:text-white">
|
||||||
Create an account
|
Create an account
|
||||||
|
|||||||
@@ -84,7 +84,7 @@
|
|||||||
<livewire:switch-team />
|
<livewire:switch-team />
|
||||||
</div>
|
</div>
|
||||||
<ul role="list" class="flex flex-col flex-1 gap-y-7">
|
<ul role="list" class="flex flex-col flex-1 gap-y-7">
|
||||||
<li class="flex-1 ">
|
<li class="flex-1 overflow-x-hidden">
|
||||||
<ul role="list" class="flex flex-col h-full space-y-1.5">
|
<ul role="list" class="flex flex-col h-full space-y-1.5">
|
||||||
@if (isSubscribed() || !isCloud())
|
@if (isSubscribed() || !isCloud())
|
||||||
<li>
|
<li>
|
||||||
|
|||||||
@@ -1,18 +1,20 @@
|
|||||||
<div class="pb-6">
|
<div class="pb-6">
|
||||||
<h1>Notifications</h1>
|
<h1>Notifications</h1>
|
||||||
<div class="subtitle">Get notified about your infrastructure.</div>
|
<div class="subtitle">Get notified about your infrastructure.</div>
|
||||||
<nav class="navbar-main">
|
<div class="navbar-main">
|
||||||
<a class="{{ request()->routeIs('notifications.email') ? 'dark:text-white' : '' }}"
|
<nav class="flex items-center gap-6 min-h-10">
|
||||||
href="{{ route('notifications.email') }}">
|
<a class="{{ request()->routeIs('notifications.email') ? 'dark:text-white' : '' }}"
|
||||||
<button>Email</button>
|
href="{{ route('notifications.email') }}">
|
||||||
</a>
|
<button>Email</button>
|
||||||
<a class="{{ request()->routeIs('notifications.telegram') ? 'dark:text-white' : '' }}"
|
</a>
|
||||||
href="{{ route('notifications.telegram') }}">
|
<a class="{{ request()->routeIs('notifications.telegram') ? 'dark:text-white' : '' }}"
|
||||||
<button>Telegram</button>
|
href="{{ route('notifications.telegram') }}">
|
||||||
</a>
|
<button>Telegram</button>
|
||||||
<a class="{{ request()->routeIs('notifications.discord') ? 'dark:text-white' : '' }}"
|
</a>
|
||||||
href="{{ route('notifications.discord') }}">
|
<a class="{{ request()->routeIs('notifications.discord') ? 'dark:text-white' : '' }}"
|
||||||
<button>Discord</button>
|
href="{{ route('notifications.discord') }}">
|
||||||
</a>
|
<button>Discord</button>
|
||||||
</nav>
|
</a>
|
||||||
|
</nav>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -8,7 +8,7 @@
|
|||||||
x-transition:leave-end="translate-y-full" x-init="setTimeout(() => { bannerVisible = true }, bannerVisibleAfter);"
|
x-transition:leave-end="translate-y-full" x-init="setTimeout(() => { bannerVisible = true }, bannerVisibleAfter);"
|
||||||
class="fixed bottom-0 right-0 h-auto duration-300 ease-out px-5 pb-5 max-w-[46rem] z-[999]" x-cloak>
|
class="fixed bottom-0 right-0 h-auto duration-300 ease-out px-5 pb-5 max-w-[46rem] z-[999]" x-cloak>
|
||||||
<div
|
<div
|
||||||
class="flex flex-col items-center justify-between w-full h-full max-w-4xl p-6 mx-auto bg-white border shadow-lg lg:border-t dark:border-coolgray-300 dark:bg-coolgray-100/40 hover:dark:bg-coolgray-100/100 lg:p-8 lg:flex-row sm:rounded">
|
class="flex flex-col items-center justify-between w-full h-full max-w-4xl p-6 mx-auto bg-white border shadow-lg lg:border-t dark:border-coolgray-300 dark:bg-coolgray-100 hover:dark:bg-coolgray-100 lg:p-8 lg:flex-row sm:rounded">
|
||||||
<div
|
<div
|
||||||
class="flex flex-col items-start h-full pb-0 text-xs lg:items-center lg:flex-row lg:pr-6 lg:space-x-5 dark:text-neutral-300 ">
|
class="flex flex-col items-start h-full pb-0 text-xs lg:items-center lg:flex-row lg:pr-6 lg:space-x-5 dark:text-neutral-300 ">
|
||||||
@if (isset($icon))
|
@if (isset($icon))
|
||||||
|
|||||||
@@ -1,35 +1,34 @@
|
|||||||
<nav class="flex pt-2 pb-10">
|
<nav class="flex pt-2 pb-10">
|
||||||
<ol class="flex items-center">
|
<ol class="flex items-center flex-wrap gap-y-1">
|
||||||
<li class="inline-flex items-center">
|
<li class="inline-flex items-center">
|
||||||
<a wire:navigate class="text-xs truncate lg:text-sm"
|
|
||||||
href="{{ route('project.show', ['project_uuid' => $this->parameters['project_uuid']]) }}">
|
|
||||||
{{ data_get($resource, 'environment.project.name', 'Undefined Name') }}</a>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<div class="flex items-center">
|
|
||||||
<svg aria-hidden="true" class="w-4 h-4 mx-1 font-bold dark:text-warning" fill="currentColor" viewBox="0 0 20 20"
|
|
||||||
xmlns="http://www.w3.org/2000/svg">
|
|
||||||
<path fill-rule="evenodd"
|
|
||||||
d="M7.293 14.707a1 1 0 010-1.414L10.586 10 7.293 6.707a1 1 0 011.414-1.414l4 4a1 1 0 010 1.414l-4 4a1 1 0 01-1.414 0z"
|
|
||||||
clip-rule="evenodd"></path>
|
|
||||||
</svg>
|
|
||||||
<a class="text-xs truncate lg:text-sm"
|
|
||||||
href="{{ route('project.resource.index', ['environment_name' => $this->parameters['environment_name'], 'project_uuid' => $this->parameters['project_uuid']]) }}">{{ $this->parameters['environment_name'] }}</a>
|
|
||||||
</div>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<div class="flex items-center">
|
<div class="flex items-center">
|
||||||
|
<a wire:navigate class="text-xs truncate lg:text-sm"
|
||||||
|
href="{{ route('project.show', ['project_uuid' => $this->parameters['project_uuid']]) }}">
|
||||||
|
{{ data_get($resource, 'environment.project.name', 'Undefined Name') }}</a>
|
||||||
<svg aria-hidden="true" class="w-4 h-4 mx-1 font-bold dark:text-warning" fill="currentColor"
|
<svg aria-hidden="true" class="w-4 h-4 mx-1 font-bold dark:text-warning" fill="currentColor"
|
||||||
viewBox="0 0 20 20" xmlns="http://www.w3.org/2000/svg">
|
viewBox="0 0 20 20" xmlns="http://www.w3.org/2000/svg">
|
||||||
<path fill-rule="evenodd"
|
<path fill-rule="evenodd"
|
||||||
d="M7.293 14.707a1 1 0 010-1.414L10.586 10 7.293 6.707a1 1 0 011.414-1.414l4 4a1 1 0 010 1.414l-4 4a1 1 0 01-1.414 0z"
|
d="M7.293 14.707a1 1 0 010-1.414L10.586 10 7.293 6.707a1 1 0 011.414-1.414l4 4a1 1 0 010 1.414l-4 4a1 1 0 01-1.414 0z"
|
||||||
clip-rule="evenodd"></path>
|
clip-rule="evenodd"></path>
|
||||||
</svg>
|
</svg>
|
||||||
<span class="text-xs truncate lg:text-sm">{{ data_get($resource, 'name') }}</span>
|
|
||||||
</div>
|
</div>
|
||||||
</li>
|
</li>
|
||||||
<li>
|
<li>
|
||||||
<div class="flex items-center">
|
<div class="flex items-center">
|
||||||
|
|
||||||
|
<a class="text-xs truncate lg:text-sm"
|
||||||
|
href="{{ route('project.resource.index', ['environment_name' => $this->parameters['environment_name'], 'project_uuid' => $this->parameters['project_uuid']]) }}">{{ $this->parameters['environment_name'] }}</a>
|
||||||
|
<svg aria-hidden="true" class="w-4 h-4 mx-1 font-bold dark:text-warning" fill="currentColor"
|
||||||
|
viewBox="0 0 20 20" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path fill-rule="evenodd"
|
||||||
|
d="M7.293 14.707a1 1 0 010-1.414L10.586 10 7.293 6.707a1 1 0 011.414-1.414l4 4a1 1 0 010 1.414l-4 4a1 1 0 01-1.414 0z"
|
||||||
|
clip-rule="evenodd"></path>
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<div class="flex items-center">
|
||||||
|
<span class="text-xs truncate lg:text-sm">{{ data_get($resource, 'name') }}</span>
|
||||||
<svg aria-hidden="true" class="w-4 h-4 mx-1 font-bold dark:text-warning" fill="currentColor"
|
<svg aria-hidden="true" class="w-4 h-4 mx-1 font-bold dark:text-warning" fill="currentColor"
|
||||||
viewBox="0 0 20 20" xmlns="http://www.w3.org/2000/svg">
|
viewBox="0 0 20 20" xmlns="http://www.w3.org/2000/svg">
|
||||||
<path fill-rule="evenodd"
|
<path fill-rule="evenodd"
|
||||||
|
|||||||
@@ -1,12 +1,14 @@
|
|||||||
<div class="pb-6">
|
<div class="pb-6">
|
||||||
<h1>Security</h1>
|
<h1>Security</h1>
|
||||||
<div class="subtitle">Security related settings.</div>
|
<div class="subtitle">Security related settings.</div>
|
||||||
<nav class="navbar-main">
|
<div class="navbar-main">
|
||||||
<a href="{{ route('security.private-key.index') }}">
|
<nav class="flex items-center gap-6 scrollbar min-h-10">
|
||||||
<button>Private Keys</button>
|
<a href="{{ route('security.private-key.index') }}">
|
||||||
</a>
|
<button>Private Keys</button>
|
||||||
<a href="{{ route('security.api-tokens') }}">
|
</a>
|
||||||
<button>API tokens</button>
|
<a href="{{ route('security.api-tokens') }}">
|
||||||
</a>
|
<button>API tokens</button>
|
||||||
</nav>
|
</a>
|
||||||
|
</nav>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -5,47 +5,49 @@
|
|||||||
<livewire:server.proxy.status :server="$server" />
|
<livewire:server.proxy.status :server="$server" />
|
||||||
</div>
|
</div>
|
||||||
<div class="subtitle">{{ data_get($server, 'name') }}.</div>
|
<div class="subtitle">{{ data_get($server, 'name') }}.</div>
|
||||||
<nav class="navbar-main">
|
<div class="navbar-main">
|
||||||
<a class="{{ request()->routeIs('server.show') ? 'dark:text-white' : '' }}"
|
<nav class="flex items-center gap-6 overflow-x-scroll sm:overflow-x-hidden scrollbar min-h-10 whitespace-nowrap">
|
||||||
href="{{ route('server.show', [
|
<a class="{{ request()->routeIs('server.show') ? 'dark:text-white' : '' }}"
|
||||||
'server_uuid' => data_get($parameters, 'server_uuid'),
|
href="{{ route('server.show', [
|
||||||
]) }}">
|
|
||||||
<button>General</button>
|
|
||||||
</a>
|
|
||||||
<a class="{{ request()->routeIs('server.private-key') ? 'dark:text-white' : '' }}"
|
|
||||||
href="{{ route('server.private-key', [
|
|
||||||
'server_uuid' => data_get($parameters, 'server_uuid'),
|
|
||||||
]) }}">
|
|
||||||
<button>Private Key</button>
|
|
||||||
</a>
|
|
||||||
<a class="{{ request()->routeIs('server.resources') ? 'dark:text-white' : '' }}"
|
|
||||||
href="{{ route('server.resources', [
|
|
||||||
'server_uuid' => data_get($parameters, 'server_uuid'),
|
|
||||||
]) }}">
|
|
||||||
<button>Resources</button>
|
|
||||||
</a>
|
|
||||||
@if (!$server->isSwarmWorker() && !$server->settings->is_build_server)
|
|
||||||
<a class="{{ request()->routeIs('server.proxy') ? 'dark:text-white' : '' }}"
|
|
||||||
href="{{ route('server.proxy', [
|
|
||||||
'server_uuid' => data_get($parameters, 'server_uuid'),
|
'server_uuid' => data_get($parameters, 'server_uuid'),
|
||||||
]) }}">
|
]) }}">
|
||||||
<button>Proxy</button>
|
<button>General</button>
|
||||||
</a>
|
</a>
|
||||||
<a class="{{ request()->routeIs('server.destinations') ? 'dark:text-white' : '' }}"
|
<a class="{{ request()->routeIs('server.private-key') ? 'dark:text-white' : '' }}"
|
||||||
href="{{ route('server.destinations', [
|
href="{{ route('server.private-key', [
|
||||||
'server_uuid' => data_get($parameters, 'server_uuid'),
|
'server_uuid' => data_get($parameters, 'server_uuid'),
|
||||||
]) }}">
|
]) }}">
|
||||||
<button>Destinations</button>
|
<button>Private Key</button>
|
||||||
</a>
|
</a>
|
||||||
<a class="{{ request()->routeIs('server.log-drains') ? 'dark:text-white' : '' }}"
|
<a class="{{ request()->routeIs('server.resources') ? 'dark:text-white' : '' }}"
|
||||||
href="{{ route('server.log-drains', [
|
href="{{ route('server.resources', [
|
||||||
'server_uuid' => data_get($parameters, 'server_uuid'),
|
'server_uuid' => data_get($parameters, 'server_uuid'),
|
||||||
]) }}">
|
]) }}">
|
||||||
<button>Log Drains</button>
|
<button>Resources</button>
|
||||||
</a>
|
</a>
|
||||||
@endif
|
@if (!$server->isSwarmWorker() && !$server->settings->is_build_server)
|
||||||
|
<a class="{{ request()->routeIs('server.proxy') ? 'dark:text-white' : '' }}"
|
||||||
<div class="flex-1"></div>
|
href="{{ route('server.proxy', [
|
||||||
<livewire:server.proxy.deploy :server="$server" />
|
'server_uuid' => data_get($parameters, 'server_uuid'),
|
||||||
</nav>
|
]) }}">
|
||||||
|
<button>Proxy</button>
|
||||||
|
</a>
|
||||||
|
<a class="{{ request()->routeIs('server.destinations') ? 'dark:text-white' : '' }}"
|
||||||
|
href="{{ route('server.destinations', [
|
||||||
|
'server_uuid' => data_get($parameters, 'server_uuid'),
|
||||||
|
]) }}">
|
||||||
|
<button>Destinations</button>
|
||||||
|
</a>
|
||||||
|
<a class="{{ request()->routeIs('server.log-drains') ? 'dark:text-white' : '' }}"
|
||||||
|
href="{{ route('server.log-drains', [
|
||||||
|
'server_uuid' => data_get($parameters, 'server_uuid'),
|
||||||
|
]) }}">
|
||||||
|
<button>Log Drains</button>
|
||||||
|
</a>
|
||||||
|
@endif
|
||||||
|
</nav>
|
||||||
|
<div class="order-first sm:order-last">
|
||||||
|
<livewire:server.proxy.deploy :server="$server" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,17 +1,19 @@
|
|||||||
<div class="pb-5">
|
<div class="pb-5">
|
||||||
<h1>Settings</h1>
|
<h1>Settings</h1>
|
||||||
<div class="subtitle">Instance wide settings for Coolify.</div>
|
<div class="subtitle">Instance wide settings for Coolify.</div>
|
||||||
<nav class="navbar-main">
|
<div class="navbar-main">
|
||||||
<a class="{{ request()->routeIs('settings.index') ? 'dark:text-white' : '' }}"
|
<nav class="flex items-center gap-6 min-h-10 whitespace-nowrap">
|
||||||
href="{{ route('settings.index') }}">
|
<a class="{{ request()->routeIs('settings.index') ? 'dark:text-white' : '' }}"
|
||||||
<button>Configuration</button>
|
href="{{ route('settings.index') }}">
|
||||||
</a>
|
<button>Configuration</button>
|
||||||
@if (isCloud())
|
|
||||||
<a class="{{ request()->routeIs('settings.license') ? 'dark:text-white' : '' }}"
|
|
||||||
href="{{ route('settings.license') }}">
|
|
||||||
<button>Resale License</button>
|
|
||||||
</a>
|
</a>
|
||||||
@endif
|
@if (isCloud())
|
||||||
<div class="flex-1"></div>
|
<a class="{{ request()->routeIs('settings.license') ? 'dark:text-white' : '' }}"
|
||||||
</nav>
|
href="{{ route('settings.license') }}">
|
||||||
|
<button>Resale License</button>
|
||||||
|
</a>
|
||||||
|
@endif
|
||||||
|
<div class="flex-1"></div>
|
||||||
|
</nav>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -6,14 +6,16 @@
|
|||||||
</x-modal-input>
|
</x-modal-input>
|
||||||
</div>
|
</div>
|
||||||
<div class="subtitle">Team wide configurations.</div>
|
<div class="subtitle">Team wide configurations.</div>
|
||||||
<nav class="navbar-main">
|
<div class="navbar-main">
|
||||||
<a class="{{ request()->routeIs('team.index') ? 'dark:text-white' : '' }}" href="{{ route('team.index') }}">
|
<nav class="flex items-center gap-6 min-h-10">
|
||||||
<button>General</button>
|
<a class="{{ request()->routeIs('team.index') ? 'dark:text-white' : '' }}" href="{{ route('team.index') }}">
|
||||||
</a>
|
<button>General</button>
|
||||||
<a class="{{ request()->routeIs('team.member.index') ? 'dark:text-white' : '' }}"
|
</a>
|
||||||
href="{{ route('team.member.index') }}">
|
<a class="{{ request()->routeIs('team.member.index') ? 'dark:text-white' : '' }}"
|
||||||
<button>Members</button>
|
href="{{ route('team.member.index') }}">
|
||||||
</a>
|
<button>Members</button>
|
||||||
<div class="flex-1"></div>
|
</a>
|
||||||
</nav>
|
<div class="flex-1"></div>
|
||||||
|
</nav>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -9,10 +9,10 @@
|
|||||||
<svg xmlns="http://www.w3.org/2000/svg" class="w-6 h-6 stroke-current shrink-0" fill="none"
|
<svg xmlns="http://www.w3.org/2000/svg" class="w-6 h-6 stroke-current shrink-0" fill="none"
|
||||||
viewBox="0 0 24 24">
|
viewBox="0 0 24 24">
|
||||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
|
||||||
d="M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z" />
|
d="M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z" />
|
||||||
</svg>
|
</svg>
|
||||||
Your subscription has been activated! Welcome onboard! <br>It could take a few seconds before your
|
Your subscription has been activated! Welcome onboard! <br>It could take a few seconds before your
|
||||||
subscription is activated.<br> Please be patient.
|
subscription is activated.<br> Please be patient.
|
||||||
</div>
|
</div>
|
||||||
@endif
|
@endif
|
||||||
<h3 class="pb-4">Projects</h3>
|
<h3 class="pb-4">Projects</h3>
|
||||||
@@ -23,23 +23,24 @@
|
|||||||
@if (data_get($project, 'environments')->count() === 1) onclick="gotoProject('{{ data_get($project, 'uuid') }}', '{{ data_get($project, 'environments.0.name', 'production') }}')"
|
@if (data_get($project, 'environments')->count() === 1) onclick="gotoProject('{{ data_get($project, 'uuid') }}', '{{ data_get($project, 'environments.0.name', 'production') }}')"
|
||||||
@else
|
@else
|
||||||
onclick="window.location.href = '{{ route('project.show', ['project_uuid' => data_get($project, 'uuid')]) }}'" @endif>
|
onclick="window.location.href = '{{ route('project.show', ['project_uuid' => data_get($project, 'uuid')]) }}'" @endif>
|
||||||
<div class="flex flex-col justify-center flex-1 mx-6">
|
<div class="flex flex-1 mx-6">
|
||||||
<div class="box-title">{{ $project->name }}</div>
|
<div class="flex flex-col justify-center flex-1">
|
||||||
<div class="box-description">
|
<div class="box-title">{{ $project->name }}</div>
|
||||||
{{ $project->description }}</div>
|
<div class="box-description">
|
||||||
</div>
|
{{ $project->description }}</div>
|
||||||
<span
|
</div>
|
||||||
class="flex items-center justify-center gap-2 pt-4 pb-2 mr-4 text-xs lg:py-0 lg:justify-normal">
|
<div class="flex items-center justify-center gap-2 text-xs font-bold ">
|
||||||
<a class="hover:underline"
|
<a class="hover:underline"
|
||||||
href="{{ route('project.resource.create', ['project_uuid' => data_get($project, 'uuid'), 'environment_name' => data_get($project, 'environments.0.name', 'production')]) }}">
|
href="{{ route('project.resource.create', ['project_uuid' => data_get($project, 'uuid'), 'environment_name' => data_get($project, 'environments.0.name', 'production')]) }}">
|
||||||
<span class="p-2 font-bold">+
|
<span class="p-2 font-bold">+
|
||||||
Add Resource</span>
|
Add Resource</span>
|
||||||
</a>
|
</a>
|
||||||
<a class="font-bold hover:underline"
|
<a class="hover:underline"
|
||||||
href="{{ route('project.edit', ['project_uuid' => data_get($project, 'uuid')]) }}">
|
href="{{ route('project.edit', ['project_uuid' => data_get($project, 'uuid')]) }}">
|
||||||
Settings
|
Settings
|
||||||
</a>
|
</a>
|
||||||
</span>
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@endforeach
|
@endforeach
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,10 +1,10 @@
|
|||||||
<div>
|
<div>
|
||||||
<div class="flex flex-col">
|
<div class="flex flex-col md:w-96">
|
||||||
<div class="flex items-center gap-2">
|
<div class="flex items-center gap-2">
|
||||||
<h2>Advanced</h2>
|
<h2>Advanced</h2>
|
||||||
</div>
|
</div>
|
||||||
<div>Advanced configuration for your application.</div>
|
<div>Advanced configuration for your application.</div>
|
||||||
<div class="flex flex-col gap-1 pt-4 md:w-96">
|
<div class="flex flex-col gap-1 pt-4">
|
||||||
<h3>General</h3>
|
<h3>General</h3>
|
||||||
@if ($application->git_based())
|
@if ($application->git_based())
|
||||||
<x-forms.checkbox helper="Automatically deploy new commits based on Git webhooks." instantSave
|
<x-forms.checkbox helper="Automatically deploy new commits based on Git webhooks." instantSave
|
||||||
@@ -16,10 +16,6 @@
|
|||||||
<x-forms.checkbox
|
<x-forms.checkbox
|
||||||
helper="Your application will be available only on https if your domain starts with https://..."
|
helper="Your application will be available only on https if your domain starts with https://..."
|
||||||
instantSave id="is_force_https_enabled" label="Force Https" />
|
instantSave id="is_force_https_enabled" label="Force Https" />
|
||||||
<x-forms.checkbox
|
|
||||||
helper="The deployed container will have the same name ({{ $application->uuid }}). <span class='font-bold dark:text-warning'>You will lose the rolling update feature!</span>"
|
|
||||||
instantSave id="application.settings.is_consistent_container_name_enabled"
|
|
||||||
label="Consistent Container Names" />
|
|
||||||
<x-forms.checkbox label="Enable gzip compression"
|
<x-forms.checkbox label="Enable gzip compression"
|
||||||
helper="You can disable gzip compression if you want. Some services are compressing data by default. In this case, you do not need this."
|
helper="You can disable gzip compression if you want. Some services are compressing data by default. In this case, you do not need this."
|
||||||
instantSave id="is_gzip_enabled" />
|
instantSave id="is_gzip_enabled" />
|
||||||
@@ -30,13 +26,22 @@
|
|||||||
label="Raw Compose Deployment"
|
label="Raw Compose Deployment"
|
||||||
helper="WARNING: Advanced use cases only. Your docker compose file will be deployed as-is. Nothing is modified by Coolify. You need to configure the proxy parts. More info in the <a class='underline dark:text-white' href='https://coolify.io/docs/knowledge-base/docker/compose#raw-docker-compose-deployment'>documentation.</a>" />
|
helper="WARNING: Advanced use cases only. Your docker compose file will be deployed as-is. Nothing is modified by Coolify. You need to configure the proxy parts. More info in the <a class='underline dark:text-white' href='https://coolify.io/docs/knowledge-base/docker/compose#raw-docker-compose-deployment'>documentation.</a>" />
|
||||||
@endif
|
@endif
|
||||||
|
<h3>Container Names</h3>
|
||||||
|
<x-forms.checkbox
|
||||||
|
helper="The deployed container will have the same name ({{ $application->uuid }}). <span class='font-bold dark:text-warning'>You will lose the rolling update feature!</span>"
|
||||||
|
instantSave id="application.settings.is_consistent_container_name_enabled"
|
||||||
|
label="Consistent Container Names" />
|
||||||
|
<form class="flex items-end gap-2 pl-2" wire:submit.prevent='saveCustomName'>
|
||||||
|
<x-forms.input
|
||||||
|
helper="You can add a custom internal name for your container. This name will be used in the internal network. <br><br>The name will be converted to slug format when you save it. <span class='font-bold dark:text-warning'>You will lose the rolling update feature!</span>"
|
||||||
|
instantSave id="application.settings.custom_internal_name" label="Add Custom Internal Name" />
|
||||||
|
<x-forms.button type="submit">Save</x-forms.button>
|
||||||
|
</form>
|
||||||
@if ($application->build_pack === 'dockercompose')
|
@if ($application->build_pack === 'dockercompose')
|
||||||
<h3>Network</h3>
|
<h3>Network</h3>
|
||||||
<div class="w-96">
|
<x-forms.checkbox instantSave id="application.settings.connect_to_docker_network"
|
||||||
<x-forms.checkbox instantSave id="application.settings.connect_to_docker_network"
|
label="Connect To Predefined Network"
|
||||||
label="Connect To Predefined Network"
|
helper="By default, you do not reach the Coolify defined networks.<br>Starting a docker compose based resource will have an internal network. <br>If you connect to a Coolify defined network, you maybe need to use different internal DNS names to connect to a resource.<br><br>For more information, check <a class='underline dark:text-white' target='_blank' href='https://coolify.io/docs/knowledge-base/docker/compose#connect-to-predefined-networks'>this</a>." />
|
||||||
helper="By default, you do not reach the Coolify defined networks.<br>Starting a docker compose based resource will have an internal network. <br>If you connect to a Coolify defined network, you maybe need to use different internal DNS names to connect to a resource.<br><br>For more information, check <a class='underline dark:text-white' target='_blank' href='https://coolify.io/docs/knowledge-base/docker/compose#connect-to-predefined-networks'>this</a>." />
|
|
||||||
</div>
|
|
||||||
@endif
|
@endif
|
||||||
@if (!$application->settings->is_raw_compose_deployment_enabled)
|
@if (!$application->settings->is_raw_compose_deployment_enabled)
|
||||||
<h3>Logs</h3>
|
<h3>Logs</h3>
|
||||||
@@ -60,16 +65,14 @@
|
|||||||
@endif
|
@endif
|
||||||
<form wire:submit="submit">
|
<form wire:submit="submit">
|
||||||
@if ($application->build_pack !== 'dockercompose')
|
@if ($application->build_pack !== 'dockercompose')
|
||||||
<div class="w-96">
|
<x-forms.checkbox
|
||||||
<x-forms.checkbox
|
helper="Enable GPU usage for this application. More info <a href='https://docs.docker.com/compose/gpu-support/' class='underline dark:text-white' target='_blank'>here</a>."
|
||||||
helper="Enable GPU usage for this application. More info <a href='https://docs.docker.com/compose/gpu-support/' class='underline dark:text-white' target='_blank'>here</a>."
|
instantSave id="application.settings.is_gpu_enabled" label="Attach GPU" />
|
||||||
instantSave id="application.settings.is_gpu_enabled" label="Attach GPU" />
|
@if ($application->settings->is_gpu_enabled)
|
||||||
@if ($application->settings->is_gpu_enabled)
|
<h5>GPU Settings</h5>
|
||||||
<h5>GPU Settings</h5>
|
|
||||||
|
|
||||||
<x-forms.button type="submit">Save</x-forms.button>
|
<x-forms.button type="submit">Save</x-forms.button>
|
||||||
@endif
|
@endif
|
||||||
</div>
|
|
||||||
@endif
|
@endif
|
||||||
@if ($application->settings->is_gpu_enabled)
|
@if ($application->settings->is_gpu_enabled)
|
||||||
<div class="flex flex-col w-full gap-2 p-2 xl:flex-row">
|
<div class="flex flex-col w-full gap-2 p-2 xl:flex-row">
|
||||||
|
|||||||
@@ -2,8 +2,8 @@
|
|||||||
<h1>Configuration</h1>
|
<h1>Configuration</h1>
|
||||||
<livewire:project.shared.configuration-checker :resource="$application" />
|
<livewire:project.shared.configuration-checker :resource="$application" />
|
||||||
<livewire:project.application.heading :application="$application" />
|
<livewire:project.application.heading :application="$application" />
|
||||||
<div x-data="{ activeTab: window.location.hash ? window.location.hash.substring(1) : 'general' }" class="flex h-full pt-6">
|
<div x-data="{ activeTab: window.location.hash ? window.location.hash.substring(1) : 'general' }" class="flex flex-col h-full gap-8 pt-6 sm:flex-row">
|
||||||
<div class="flex flex-col gap-2 xl:w-48">
|
<div class="flex flex-col items-start gap-2 min-w-fit">
|
||||||
<a class="menu-item" :class="activeTab === 'general' && 'menu-item-active'"
|
<a class="menu-item" :class="activeTab === 'general' && 'menu-item-active'"
|
||||||
@click.prevent="activeTab = 'general'; window.location.hash = 'general'" href="#">General</a>
|
@click.prevent="activeTab = 'general'; window.location.hash = 'general'" href="#">General</a>
|
||||||
@if ($application->destination->server->isSwarm())
|
@if ($application->destination->server->isSwarm())
|
||||||
@@ -78,7 +78,7 @@
|
|||||||
@click.prevent="activeTab = 'danger'; window.location.hash = 'danger'" href="#">Danger Zone
|
@click.prevent="activeTab = 'danger'; window.location.hash = 'danger'" href="#">Danger Zone
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
<div class="w-full pl-8">
|
<div class="w-full">
|
||||||
<div x-cloak x-show="activeTab === 'general'" class="h-full">
|
<div x-cloak x-show="activeTab === 'general'" class="h-full">
|
||||||
<livewire:project.application.general :application="$application" />
|
<livewire:project.application.general :application="$application" />
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -116,7 +116,7 @@
|
|||||||
@if ($application->build_pack !== 'dockerimage')
|
@if ($application->build_pack !== 'dockerimage')
|
||||||
<h3 class="pt-8">Build</h3>
|
<h3 class="pt-8">Build</h3>
|
||||||
@if ($application->build_pack !== 'dockercompose')
|
@if ($application->build_pack !== 'dockercompose')
|
||||||
<div class="w-96">
|
<div class="max-w-96">
|
||||||
<x-forms.checkbox
|
<x-forms.checkbox
|
||||||
helper="Use a build server to build your application. You can configure your build server in the Server settings. This is experimental. For more info, check the <a href='https://coolify.io/docs/knowledge-base/server/build-server' class='underline' target='_blank'>documentation</a>."
|
helper="Use a build server to build your application. You can configure your build server in the Server settings. This is experimental. For more info, check the <a href='https://coolify.io/docs/knowledge-base/server/build-server' class='underline' target='_blank'>documentation</a>."
|
||||||
instantSave id="application.settings.is_build_server_enabled"
|
instantSave id="application.settings.is_build_server_enabled"
|
||||||
@@ -242,7 +242,10 @@
|
|||||||
@endif
|
@endif
|
||||||
</div>
|
</div>
|
||||||
<x-forms.textarea label="Container Labels" rows="15" id="customLabels"></x-forms.textarea>
|
<x-forms.textarea label="Container Labels" rows="15" id="customLabels"></x-forms.textarea>
|
||||||
<x-forms.button wire:click="resetDefaultLabels">Reset to Coolify Generated Labels</x-forms.button>
|
<x-modal-confirmation buttonFullWidth action="resetDefaultLabels" buttonTitle="Reset to Coolify Generated Labels">
|
||||||
|
Are you sure you want to reset the labels to Coolify generated labels? <br>It could break the proxy configuration after you restart the container.
|
||||||
|
</x-modal-confirmation>
|
||||||
|
|
||||||
@endif
|
@endif
|
||||||
|
|
||||||
<h3 class="pt-8">Pre/Post Deployment Commands</h3>
|
<h3 class="pt-8">Pre/Post Deployment Commands</h3>
|
||||||
|
|||||||
@@ -1,30 +1,33 @@
|
|||||||
<nav wire:poll.5000ms="check_status">
|
<nav wire:poll.5000ms="check_status">
|
||||||
<x-resources.breadcrumbs :resource="$application" :parameters="$parameters" />
|
<x-resources.breadcrumbs :resource="$application" :parameters="$parameters" />
|
||||||
<div class="navbar-main">
|
<div class="navbar-main">
|
||||||
<a href="{{ route('project.application.configuration', $parameters) }}">
|
<nav class="flex items-center flex-shrink-0 gap-6 scrollbar min-h-10 whitespace-nowrap">
|
||||||
Configuration
|
<a href="{{ route('project.application.configuration', $parameters) }}">
|
||||||
</a>
|
Configuration
|
||||||
<a href="{{ route('project.application.deployment.index', $parameters) }}">
|
|
||||||
<button>Deployments</button>
|
|
||||||
</a>
|
|
||||||
<a href="{{ route('project.application.logs', $parameters) }}">
|
|
||||||
<button>Logs</button>
|
|
||||||
</a>
|
|
||||||
@if (!$application->destination->server->isSwarm())
|
|
||||||
<a href="{{ route('project.application.command', $parameters) }}">
|
|
||||||
<button>Command</button>
|
|
||||||
</a>
|
</a>
|
||||||
@endif
|
<a href="{{ route('project.application.deployment.index', $parameters) }}">
|
||||||
<x-applications.links :application="$application" />
|
<button>Deployments</button>
|
||||||
<div class="flex-1"></div>
|
</a>
|
||||||
<div class="flex items-center gap-2">
|
<a href="{{ route('project.application.logs', $parameters) }}">
|
||||||
|
<button>Logs</button>
|
||||||
|
</a>
|
||||||
|
@if (!$application->destination->server->isSwarm())
|
||||||
|
<a href="{{ route('project.application.command', $parameters) }}">
|
||||||
|
<button>Command</button>
|
||||||
|
</a>
|
||||||
|
@endif
|
||||||
|
<x-applications.links :application="$application" />
|
||||||
|
</nav>
|
||||||
|
<div class="flex flex-wrap items-center gap-2">
|
||||||
@if ($application->build_pack === 'dockercompose' && is_null($application->docker_compose_raw))
|
@if ($application->build_pack === 'dockercompose' && is_null($application->docker_compose_raw))
|
||||||
<div>Please load a Compose file.</div>
|
<div>Please load a Compose file.</div>
|
||||||
@else
|
@else
|
||||||
@if (!$application->destination->server->isSwarm())
|
@if (!$application->destination->server->isSwarm())
|
||||||
|
<div>
|
||||||
<x-applications.advanced :application="$application" />
|
<x-applications.advanced :application="$application" />
|
||||||
|
</div>
|
||||||
@endif
|
@endif
|
||||||
<div class="flex gap-2">
|
<div class="flex flex-wrap gap-2">
|
||||||
@if (!str($application->status)->startsWith('exited'))
|
@if (!str($application->status)->startsWith('exited'))
|
||||||
@if (!$application->destination->server->isSwarm())
|
@if (!$application->destination->server->isSwarm())
|
||||||
<x-forms.button title="With rolling update if possible" wire:click='deploy'>
|
<x-forms.button title="With rolling update if possible" wire:click='deploy'>
|
||||||
@@ -100,6 +103,7 @@
|
|||||||
</div>
|
</div>
|
||||||
@endif
|
@endif
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
@script
|
@script
|
||||||
<script>
|
<script>
|
||||||
|
|||||||
@@ -2,53 +2,53 @@
|
|||||||
<h1>Configuration</h1>
|
<h1>Configuration</h1>
|
||||||
<livewire:project.shared.configuration-checker :resource="$database" />
|
<livewire:project.shared.configuration-checker :resource="$database" />
|
||||||
<livewire:project.database.heading :database="$database" />
|
<livewire:project.database.heading :database="$database" />
|
||||||
<div x-data="{ activeTab: window.location.hash ? window.location.hash.substring(1) : 'general' }" class="flex h-full pt-6">
|
<div x-data="{ activeTab: window.location.hash ? window.location.hash.substring(1) : 'general' }" class="flex flex-col h-full gap-8 pt-6 sm:flex-row">
|
||||||
<div class="flex flex-col gap-4 min-w-fit">
|
<div class="flex flex-col items-start gap-2 min-w-fit">
|
||||||
<a :class="activeTab === 'general' && 'dark:text-white'"
|
<a class="menu-item" :class="activeTab === 'general' && 'menu-item-active'"
|
||||||
@click.prevent="activeTab = 'general';
|
@click.prevent="activeTab = 'general';
|
||||||
window.location.hash = 'general'"
|
window.location.hash = 'general'"
|
||||||
href="#">General</a>
|
href="#">General</a>
|
||||||
<a :class="activeTab === 'environment-variables' && 'dark:text-white'"
|
<a class="menu-item" :class="activeTab === 'environment-variables' && 'menu-item-active'"
|
||||||
@click.prevent="activeTab = 'environment-variables'; window.location.hash = 'environment-variables'"
|
@click.prevent="activeTab = 'environment-variables'; window.location.hash = 'environment-variables'"
|
||||||
href="#">Environment
|
href="#">Environment
|
||||||
Variables</a>
|
Variables</a>
|
||||||
<a :class="activeTab === 'servers' && 'dark:text-white'"
|
<a class="menu-item" :class="activeTab === 'servers' && 'menu-item-active'"
|
||||||
@click.prevent="activeTab = 'servers';
|
@click.prevent="activeTab = 'servers';
|
||||||
window.location.hash = 'servers'"
|
window.location.hash = 'servers'"
|
||||||
href="#">Servers
|
href="#">Servers
|
||||||
</a>
|
</a>
|
||||||
<a :class="activeTab === 'storages' && 'dark:text-white'"
|
<a class="menu-item" :class="activeTab === 'storages' && 'menu-item-active'"
|
||||||
@click.prevent="activeTab = 'storages';
|
@click.prevent="activeTab = 'storages';
|
||||||
window.location.hash = 'storages'"
|
window.location.hash = 'storages'"
|
||||||
href="#">Storages
|
href="#">Storages
|
||||||
</a>
|
</a>
|
||||||
<a :class="activeTab === 'import' && 'dark:text-white'"
|
<a class="menu-item" :class="activeTab === 'import' && 'menu-item-active'"
|
||||||
@click.prevent="activeTab = 'import';
|
@click.prevent="activeTab = 'import';
|
||||||
window.location.hash = 'import'" href="#">Import
|
window.location.hash = 'import'" href="#">Import
|
||||||
Backup
|
Backup
|
||||||
</a>
|
</a>
|
||||||
<a :class="activeTab === 'webhooks' && 'dark:text-white'"
|
<a class="menu-item" :class="activeTab === 'webhooks' && 'menu-item-active'"
|
||||||
@click.prevent="activeTab = 'webhooks'; window.location.hash = 'webhooks'" href="#">Webhooks
|
@click.prevent="activeTab = 'webhooks'; window.location.hash = 'webhooks'" href="#">Webhooks
|
||||||
</a>
|
</a>
|
||||||
<a :class="activeTab === 'resource-limits' && 'dark:text-white'"
|
<a class="menu-item" :class="activeTab === 'resource-limits' && 'menu-item-active'"
|
||||||
@click.prevent="activeTab = 'resource-limits';
|
@click.prevent="activeTab = 'resource-limits';
|
||||||
window.location.hash = 'resource-limits'"
|
window.location.hash = 'resource-limits'"
|
||||||
href="#">Resource Limits
|
href="#">Resource Limits
|
||||||
</a>
|
</a>
|
||||||
<a :class="activeTab === 'resource-operations' && 'dark:text-white'"
|
<a class="menu-item" :class="activeTab === 'resource-operations' && 'menu-item-active'"
|
||||||
@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' && 'dark:text-white'"
|
<a class="menu-item" :class="activeTab === 'tags' && 'menu-item-active'"
|
||||||
@click.prevent="activeTab = 'tags'; window.location.hash = 'tags'" href="#">Tags
|
@click.prevent="activeTab = 'tags'; window.location.hash = 'tags'" href="#">Tags
|
||||||
</a>
|
</a>
|
||||||
<a :class="activeTab === 'danger' && 'dark:text-white'"
|
<a class="menu-item" :class="activeTab === 'danger' && 'menu-item-active'"
|
||||||
@click.prevent="activeTab = 'danger';
|
@click.prevent="activeTab = 'danger';
|
||||||
window.location.hash = 'danger'"
|
window.location.hash = 'danger'"
|
||||||
href="#">Danger Zone
|
href="#">Danger Zone
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
<div class="w-full pl-8">
|
<div class="w-full">
|
||||||
<div x-cloak x-show="activeTab === 'general'" class="h-full">
|
<div x-cloak x-show="activeTab === 'general'" class="h-full">
|
||||||
@if ($database->type() === 'standalone-postgresql')
|
@if ($database->type() === 'standalone-postgresql')
|
||||||
<livewire:project.database.postgresql.general :database="$database" />
|
<livewire:project.database.postgresql.general :database="$database" />
|
||||||
|
|||||||
@@ -7,67 +7,70 @@
|
|||||||
</x-slot:content>
|
</x-slot:content>
|
||||||
</x-slide-over>
|
</x-slide-over>
|
||||||
<div class="navbar-main">
|
<div class="navbar-main">
|
||||||
<a class="{{ request()->routeIs('project.database.configuration') ? 'dark:text-white' : '' }}"
|
<nav class="flex items-center flex-shrink-0 gap-6 overflow-x-scroll sm:overflow-x-hidden scrollbar min-h-10 whitespace-nowrap">
|
||||||
href="{{ route('project.database.configuration', $parameters) }}">
|
<a class="{{ request()->routeIs('project.database.configuration') ? 'dark:text-white' : '' }}"
|
||||||
<button>Configuration</button>
|
href="{{ route('project.database.configuration', $parameters) }}">
|
||||||
</a>
|
<button>Configuration</button>
|
||||||
<a class="{{ request()->routeIs('project.database.command') ? 'dark:text-white' : '' }}"
|
|
||||||
href="{{ route('project.database.command', $parameters) }}">
|
|
||||||
<button>Execute Command</button>
|
|
||||||
</a>
|
|
||||||
<a class="{{ request()->routeIs('project.database.logs') ? 'dark:text-white' : '' }}"
|
|
||||||
href="{{ route('project.database.logs', $parameters) }}">
|
|
||||||
<button>Logs</button>
|
|
||||||
</a>
|
|
||||||
@if (
|
|
||||||
$database->getMorphClass() === 'App\Models\StandalonePostgresql' ||
|
|
||||||
$database->getMorphClass() === 'App\Models\StandaloneMongodb' ||
|
|
||||||
$database->getMorphClass() === 'App\Models\StandaloneMysql' ||
|
|
||||||
$database->getMorphClass() === 'App\Models\StandaloneMariadb')
|
|
||||||
<a class="{{ request()->routeIs('project.database.backup.index') ? 'dark:text-white' : '' }}"
|
|
||||||
href="{{ route('project.database.backup.index', $parameters) }}">
|
|
||||||
<button>Backups</button>
|
|
||||||
</a>
|
</a>
|
||||||
@endif
|
<a class="{{ request()->routeIs('project.database.command') ? 'dark:text-white' : '' }}"
|
||||||
<div class="flex-1"></div>
|
href="{{ route('project.database.command', $parameters) }}">
|
||||||
@if (!str($database->status)->startsWith('exited'))
|
<button>Execute Command</button>
|
||||||
<x-modal-confirmation @click="$wire.dispatch('stopEvent')">
|
</a>
|
||||||
<x-slot:button-title>
|
<a class="{{ request()->routeIs('project.database.logs') ? 'dark:text-white' : '' }}"
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" class="w-5 h-5 text-error" viewBox="0 0 24 24"
|
href="{{ route('project.database.logs', $parameters) }}">
|
||||||
stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round"
|
<button>Logs</button>
|
||||||
|
</a>
|
||||||
|
@if (
|
||||||
|
$database->getMorphClass() === 'App\Models\StandalonePostgresql' ||
|
||||||
|
$database->getMorphClass() === 'App\Models\StandaloneMongodb' ||
|
||||||
|
$database->getMorphClass() === 'App\Models\StandaloneMysql' ||
|
||||||
|
$database->getMorphClass() === 'App\Models\StandaloneMariadb')
|
||||||
|
<a class="{{ request()->routeIs('project.database.backup.index') ? 'dark:text-white' : '' }}"
|
||||||
|
href="{{ route('project.database.backup.index', $parameters) }}">
|
||||||
|
<button>Backups</button>
|
||||||
|
</a>
|
||||||
|
@endif
|
||||||
|
</nav>
|
||||||
|
<div class="flex flex-wrap items-center gap-2">
|
||||||
|
@if (!str($database->status)->startsWith('exited'))
|
||||||
|
<x-modal-confirmation @click="$wire.dispatch('stopEvent')">
|
||||||
|
<x-slot:button-title>
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" class="w-5 h-5 text-error" viewBox="0 0 24 24"
|
||||||
|
stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round"
|
||||||
|
stroke-linejoin="round">
|
||||||
|
<path stroke="none" d="M0 0h24v24H0z" fill="none"></path>
|
||||||
|
<path d="M6 5m0 1a1 1 0 0 1 1 -1h2a1 1 0 0 1 1 1v12a1 1 0 0 1 -1 1h-2a1 1 0 0 1 -1 -1z">
|
||||||
|
</path>
|
||||||
|
<path d="M14 5m0 1a1 1 0 0 1 1 -1h2a1 1 0 0 1 1 1v12a1 1 0 0 1 -1 1h-2a1 1 0 0 1 -1 -1z">
|
||||||
|
</path>
|
||||||
|
</svg>
|
||||||
|
Stop
|
||||||
|
</x-slot:button-title>
|
||||||
|
This database will be stopped. <br>Please think again.
|
||||||
|
</x-modal-confirmation>
|
||||||
|
@else
|
||||||
|
<button @click="$wire.dispatch('startEvent')" class="gap-2 button">
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" class="w-5 h-5 dark:text-warning" viewBox="0 0 24 24"
|
||||||
|
stroke-width="1.5" stroke="currentColor" fill="none" stroke-linecap="round"
|
||||||
stroke-linejoin="round">
|
stroke-linejoin="round">
|
||||||
<path stroke="none" d="M0 0h24v24H0z" fill="none"></path>
|
<path stroke="none" d="M0 0h24v24H0z" fill="none" />
|
||||||
<path d="M6 5m0 1a1 1 0 0 1 1 -1h2a1 1 0 0 1 1 1v12a1 1 0 0 1 -1 1h-2a1 1 0 0 1 -1 -1z">
|
<path d="M7 4v16l13 -8z" />
|
||||||
</path>
|
|
||||||
<path d="M14 5m0 1a1 1 0 0 1 1 -1h2a1 1 0 0 1 1 1v12a1 1 0 0 1 -1 1h-2a1 1 0 0 1 -1 -1z">
|
|
||||||
</path>
|
|
||||||
</svg>
|
</svg>
|
||||||
Stop
|
Start
|
||||||
</x-slot:button-title>
|
</button>
|
||||||
This database will be stopped. <br>Please think again.
|
@endif
|
||||||
</x-modal-confirmation>
|
@script
|
||||||
@else
|
<script>
|
||||||
<button @click="$wire.dispatch('startEvent')" class="gap-2 button">
|
$wire.$on('startEvent', () => {
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" class="w-5 h-5 dark:text-warning" viewBox="0 0 24 24"
|
window.dispatchEvent(new CustomEvent('startdatabase'));
|
||||||
stroke-width="1.5" stroke="currentColor" fill="none" stroke-linecap="round"
|
$wire.$call('start');
|
||||||
stroke-linejoin="round">
|
});
|
||||||
<path stroke="none" d="M0 0h24v24H0z" fill="none" />
|
$wire.$on('stopEvent', () => {
|
||||||
<path d="M7 4v16l13 -8z" />
|
$wire.$dispatch('info', 'Stopping database.');
|
||||||
</svg>
|
$wire.$call('stop');
|
||||||
Start
|
});
|
||||||
</button>
|
</script>
|
||||||
@endif
|
@endscript
|
||||||
@script
|
</div>
|
||||||
<script>
|
|
||||||
$wire.$on('startEvent', () => {
|
|
||||||
window.dispatchEvent(new CustomEvent('startdatabase'));
|
|
||||||
$wire.$call('start');
|
|
||||||
});
|
|
||||||
$wire.$on('stopEvent', () => {
|
|
||||||
$wire.$dispatch('info', 'Stopping database.');
|
|
||||||
$wire.$call('stop');
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
@endscript
|
|
||||||
</div>
|
</div>
|
||||||
</nav>
|
</nav>
|
||||||
|
|||||||
@@ -20,7 +20,7 @@
|
|||||||
Save
|
Save
|
||||||
</x-forms.button>
|
</x-forms.button>
|
||||||
</div>
|
</div>
|
||||||
<div class="flex gap-2">
|
<div class="flex gap-2 flex-wrap sm:flex-nowrap">
|
||||||
<x-forms.input label="Name" id="database.name" />
|
<x-forms.input label="Name" id="database.name" />
|
||||||
<x-forms.input label="Description" id="database.description" />
|
<x-forms.input label="Description" id="database.description" />
|
||||||
<x-forms.input label="Image" id="database.image" required
|
<x-forms.input label="Image" id="database.image" required
|
||||||
@@ -28,7 +28,7 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
@if ($database->started_at)
|
@if ($database->started_at)
|
||||||
<div class="flex gap-2">
|
<div class="flex gap-2 flex-wrap sm:flex-nowrap">
|
||||||
<x-forms.input label="Initial Username" id="database.postgres_user" placeholder="If empty: postgres"
|
<x-forms.input label="Initial Username" id="database.postgres_user" placeholder="If empty: postgres"
|
||||||
readonly helper="You can only change this in the database." />
|
readonly helper="You can only change this in the database." />
|
||||||
<x-forms.input label="Initial Password" id="database.postgres_password" type="password" required
|
<x-forms.input label="Initial Password" id="database.postgres_password" type="password" required
|
||||||
|
|||||||
@@ -6,22 +6,30 @@
|
|||||||
<livewire:project.delete-environment :disabled="!$environment->isEmpty()" :environment_id="$environment->id" />
|
<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 flex-wrap gap-y-1">
|
||||||
<li class="inline-flex items-center">
|
<li class="inline-flex items-center">
|
||||||
<a class="text-xs truncate lg:text-sm"
|
|
||||||
href="{{ route('project.show', ['project_uuid' => data_get($parameters, 'project_uuid')]) }}">
|
|
||||||
{{ $project->name }}</a>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<div class="flex items-center">
|
<div class="flex items-center">
|
||||||
|
<a class="text-xs truncate lg:text-sm"
|
||||||
|
href="{{ route('project.show', ['project_uuid' => data_get($parameters, 'project_uuid')]) }}">
|
||||||
|
{{ $project->name }}</a>
|
||||||
<svg aria-hidden="true" class="w-4 h-4 mx-1 font-bold dark:text-warning" fill="currentColor"
|
<svg aria-hidden="true" class="w-4 h-4 mx-1 font-bold dark:text-warning" fill="currentColor"
|
||||||
viewBox="0 0 20 20" xmlns="http://www.w3.org/2000/svg">
|
viewBox="0 0 20 20" xmlns="http://www.w3.org/2000/svg">
|
||||||
<path fill-rule="evenodd"
|
<path fill-rule="evenodd"
|
||||||
d="M7.293 14.707a1 1 0 010-1.414L10.586 10 7.293 6.707a1 1 0 011.414-1.414l4 4a1 1 0 010 1.414l-4 4a1 1 0 01-1.414 0z"
|
d="M7.293 14.707a1 1 0 010-1.414L10.586 10 7.293 6.707a1 1 0 011.414-1.414l4 4a1 1 0 010 1.414l-4 4a1 1 0 01-1.414 0z"
|
||||||
clip-rule="evenodd"></path>
|
clip-rule="evenodd"></path>
|
||||||
</svg>
|
</svg>
|
||||||
|
</div>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<div class="flex items-center">
|
||||||
<a class="text-xs truncate lg:text-sm"
|
<a class="text-xs truncate lg:text-sm"
|
||||||
href="{{ route('project.resource.index', ['environment_name' => data_get($parameters, 'environment_name'), 'project_uuid' => data_get($parameters, 'project_uuid')]) }}">{{ data_get($parameters, 'environment_name') }}</a>
|
href="{{ route('project.resource.index', ['environment_name' => data_get($parameters, 'environment_name'), 'project_uuid' => data_get($parameters, 'project_uuid')]) }}">{{ data_get($parameters, 'environment_name') }}</a>
|
||||||
|
<svg aria-hidden="true" class="w-4 h-4 mx-1 font-bold dark:text-warning" fill="currentColor"
|
||||||
|
viewBox="0 0 20 20" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path fill-rule="evenodd"
|
||||||
|
d="M7.293 14.707a1 1 0 010-1.414L10.586 10 7.293 6.707a1 1 0 011.414-1.414l4 4a1 1 0 010 1.414l-4 4a1 1 0 01-1.414 0z"
|
||||||
|
clip-rule="evenodd"></path>
|
||||||
|
</svg>
|
||||||
</div>
|
</div>
|
||||||
</li>
|
</li>
|
||||||
<li>
|
<li>
|
||||||
|
|||||||
@@ -8,15 +8,14 @@
|
|||||||
<div class="subtitle">All your projects are here.</div>
|
<div class="subtitle">All your projects are here.</div>
|
||||||
<div class="grid gap-2 lg:grid-cols-2">
|
<div class="grid gap-2 lg:grid-cols-2">
|
||||||
@forelse ($projects as $project)
|
@forelse ($projects as $project)
|
||||||
<div class="box group" x-data
|
<div class="box group" x-data x-on:click="goto('{{ $project->uuid }}')">
|
||||||
x-on:click="goto('{{ $project->uuid }}')">
|
|
||||||
<a class="flex flex-col justify-center flex-1 mx-6"
|
<a class="flex flex-col justify-center flex-1 mx-6"
|
||||||
href="{{ route('project.show', ['project_uuid' => data_get($project, 'uuid')]) }}">
|
href="{{ route('project.show', ['project_uuid' => data_get($project, 'uuid')]) }}">
|
||||||
<div class="box-title">{{ $project->name }}</div>
|
<div class="box-title">{{ $project->name }}</div>
|
||||||
<div class="box-description ">
|
<div class="box-description ">
|
||||||
{{ $project->description }}</div>
|
{{ $project->description }}</div>
|
||||||
</a>
|
</a>
|
||||||
<div class="flex items-center text-xs">
|
<div class="flex items-center justify-center gap-2 pt-4 pb-2 mr-4 text-xs lg:py-0 lg:justify-normal">
|
||||||
<a class="mx-4 font-bold hover:underline"
|
<a class="mx-4 font-bold hover:underline"
|
||||||
href="{{ route('project.edit', ['project_uuid' => data_get($project, 'uuid')]) }}">
|
href="{{ route('project.edit', ['project_uuid' => data_get($project, 'uuid')]) }}">
|
||||||
Settings
|
Settings
|
||||||
|
|||||||
@@ -33,14 +33,16 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="flex flex-col items-center justify-center">
|
||||||
|
<x-loading wire:loading wire:target="loadRepositories({{ $ghapp->id }})" />
|
||||||
|
</div>
|
||||||
@endforeach
|
@endforeach
|
||||||
</div>
|
</div>
|
||||||
@endif
|
@endif
|
||||||
@if ($current_step === 'repository')
|
@if ($current_step === 'repository')
|
||||||
@if ($repositories->count() > 0)
|
@if ($repositories->count() > 0)
|
||||||
<div class="flex items-end gap-2">
|
<div class="flex items-end gap-2">
|
||||||
<x-forms.select class="w-full" label="Repository"
|
<x-forms.select class="w-full" label="Repository" wire:model="selected_repository_id">
|
||||||
wire:model="selected_repository_id">
|
|
||||||
@foreach ($repositories as $repo)
|
@foreach ($repositories as $repo)
|
||||||
@if ($loop->first)
|
@if ($loop->first)
|
||||||
<option selected value="{{ data_get($repo, 'id') }}">
|
<option selected value="{{ data_get($repo, 'id') }}">
|
||||||
|
|||||||
@@ -71,7 +71,8 @@
|
|||||||
<div class="max-w-full px-4 truncate box-description" x-text="item.fqdn"></div>
|
<div class="max-w-full px-4 truncate box-description" x-text="item.fqdn"></div>
|
||||||
</div>
|
</div>
|
||||||
</a>
|
</a>
|
||||||
<div class="flex flex-wrap gap-1 pt-1 group-hover:dark:text-white group-hover:text-black group min-h-6">
|
<div
|
||||||
|
class="flex flex-wrap gap-1 pt-1 group-hover:dark:text-white group-hover:text-black group min-h-6">
|
||||||
<template x-for="tag in item.tags">
|
<template x-for="tag in item.tags">
|
||||||
<div class="tag" @click.prevent="gotoTag(tag.name)" x-text="tag.name"></div>
|
<div class="tag" @click.prevent="gotoTag(tag.name)" x-text="tag.name"></div>
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
@@ -1,45 +1,46 @@
|
|||||||
<div x-data="{ activeTab: window.location.hash ? window.location.hash.substring(1) : 'service-stack' }" x-init="$wire.check_status" wire:poll.5000ms="check_status">
|
<div x-data="{ activeTab: window.location.hash ? window.location.hash.substring(1) : 'service-stack' }" x-init="$wire.check_status" wire:poll.5000ms="check_status">
|
||||||
<livewire:project.service.navbar :service="$service" :parameters="$parameters" :query="$query" />
|
<livewire:project.service.navbar :service="$service" :parameters="$parameters" :query="$query" />
|
||||||
<div class="flex h-full pt-6">
|
<div class="flex flex-col h-full gap-8 pt-6 sm:flex-row">
|
||||||
<div class="flex flex-col items-start gap-4 min-w-fit">
|
<div class="flex flex-col items-start gap-2 min-w-fit">
|
||||||
<a target="_blank" href="{{ $service->documentation() }}">Documentation <x-external-link /></a>
|
<a class="menu-item sm:min-w-fit" target="_blank" href="{{ $service->documentation() }}">Documentation
|
||||||
<a :class="activeTab === 'service-stack' && 'dark:text-white'"
|
<x-external-link /></a>
|
||||||
|
<a class="menu-item sm:min-w-fit" :class="activeTab === 'service-stack' && 'menu-item-active'"
|
||||||
@click.prevent="activeTab = 'service-stack';
|
@click.prevent="activeTab = 'service-stack';
|
||||||
window.location.hash = 'service-stack'"
|
window.location.hash = 'service-stack'"
|
||||||
href="#">Service Stack</a>
|
href="#">Service Stack</a>
|
||||||
<a :class="activeTab === 'environment-variables' && 'dark:text-white'"
|
<a class="menu-item sm:min-w-fit" :class="activeTab === 'environment-variables' && 'menu-item-active'"
|
||||||
@click.prevent="activeTab = 'environment-variables'; window.location.hash = 'environment-variables'"
|
@click.prevent="activeTab = 'environment-variables'; window.location.hash = 'environment-variables'"
|
||||||
href="#">Environment
|
href="#">Environment
|
||||||
Variables</a>
|
Variables</a>
|
||||||
<a :class="activeTab === 'storages' && 'dark:text-white'"
|
<a class="menu-item sm:min-w-fit" :class="activeTab === 'storages' && 'menu-item-active'"
|
||||||
@click.prevent="activeTab = 'storages';
|
@click.prevent="activeTab = 'storages';
|
||||||
window.location.hash = 'storages'"
|
window.location.hash = 'storages'"
|
||||||
href="#">Storages</a>
|
href="#">Storages</a>
|
||||||
<a :class="activeTab === 'execute-command' && 'dark:text-white'"
|
<a class="menu-item sm:min-w-fit" :class="activeTab === 'execute-command' && 'menu-item-active'"
|
||||||
@click.prevent="activeTab = 'execute-command';
|
@click.prevent="activeTab = 'execute-command';
|
||||||
window.location.hash = 'execute-command'"
|
window.location.hash = 'execute-command'"
|
||||||
href="#">Execute Command</a>
|
href="#">Execute Command</a>
|
||||||
<a :class="activeTab === 'logs' && 'dark:text-white'"
|
<a class="menu-item sm:min-w-fit" :class="activeTab === 'logs' && 'menu-item-active'"
|
||||||
@click.prevent="activeTab = 'logs';
|
@click.prevent="activeTab = 'logs';
|
||||||
window.location.hash = 'logs'"
|
window.location.hash = 'logs'"
|
||||||
href="#">Logs</a>
|
href="#">Logs</a>
|
||||||
<a :class="activeTab === 'webhooks' && 'dark:text-white'"
|
<a class="menu-item sm:min-w-fit" :class="activeTab === 'webhooks' && 'menu-item-active'"
|
||||||
@click.prevent="activeTab = 'webhooks'; window.location.hash = 'webhooks'" href="#">Webhooks
|
@click.prevent="activeTab = 'webhooks'; window.location.hash = 'webhooks'" href="#">Webhooks
|
||||||
</a>
|
</a>
|
||||||
<a :class="activeTab === 'resource-operations' && 'dark:text-white'"
|
<a class="menu-item sm:min-w-fit" :class="activeTab === 'resource-operations' && 'menu-item-active'"
|
||||||
@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' && 'dark:text-white'"
|
<a class="menu-item sm:min-w-fit" :class="activeTab === 'tags' && 'menu-item-active'"
|
||||||
@click.prevent="activeTab = 'tags'; window.location.hash = 'tags'" href="#">Tags
|
@click.prevent="activeTab = 'tags'; window.location.hash = 'tags'" href="#">Tags
|
||||||
</a>
|
</a>
|
||||||
<a :class="activeTab === 'danger' && 'dark:text-white'"
|
<a class="menu-item sm:min-w-fit" :class="activeTab === 'danger' && 'menu-item-active'"
|
||||||
@click.prevent="activeTab = 'danger';
|
@click.prevent="activeTab = 'danger';
|
||||||
window.location.hash = 'danger'"
|
window.location.hash = 'danger'"
|
||||||
href="#">Danger Zone
|
href="#">Danger Zone
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
<div class="w-full pl-8">
|
<div class="w-full">
|
||||||
<div x-cloak x-show="activeTab === 'service-stack'">
|
<div x-cloak x-show="activeTab === 'service-stack'">
|
||||||
<livewire:project.service.stack-form :service="$service" />
|
<livewire:project.service.stack-form :service="$service" />
|
||||||
<h3>Services</h3>
|
<h3>Services</h3>
|
||||||
|
|||||||
@@ -1,30 +1,31 @@
|
|||||||
<div x-data="{ activeTab: window.location.hash ? window.location.hash.substring(1) : 'general' }">
|
<div x-data="{ activeTab: window.location.hash ? window.location.hash.substring(1) : 'general' }">
|
||||||
<livewire:project.service.navbar :service="$service" :parameters="$parameters" :query="$query" />
|
<livewire:project.service.navbar :service="$service" :parameters="$parameters" :query="$query" />
|
||||||
<div class="flex h-full pt-6">
|
<div class="flex flex-col h-full gap-8 pt-6 sm:flex-row">
|
||||||
<div class="flex flex-col gap-4 min-w-fit">
|
<div class="flex flex-col items-start gap-2 min-w-fit">
|
||||||
<a class="{{ request()->routeIs('project.service.configuration') ? 'dark:text-white' : '' }}"
|
<a class="menu-item"
|
||||||
|
class="{{ request()->routeIs('project.service.configuration') ? 'menu-item-active' : '' }}"
|
||||||
href="{{ route('project.service.configuration', [...$parameters, 'stack_service_uuid' => null]) }}">
|
href="{{ route('project.service.configuration', [...$parameters, 'stack_service_uuid' => null]) }}">
|
||||||
<button><- Back</button>
|
<button><- Back</button>
|
||||||
</a>
|
</a>
|
||||||
<a :class="activeTab === 'general' && 'dark:text-white'"
|
<a class="menu-item" :class="activeTab === 'general' && 'menu-item-active'"
|
||||||
@click.prevent="activeTab = 'general'; window.location.hash = 'general'; if(window.location.search) window.location.search = ''"
|
@click.prevent="activeTab = 'general'; window.location.hash = 'general'; if(window.location.search) window.location.search = ''"
|
||||||
href="#">General</a>
|
href="#">General</a>
|
||||||
<a :class="activeTab === 'storages' && 'dark:text-white'"
|
<a class="menu-item" :class="activeTab === 'storages' && 'menu-item-active'"
|
||||||
@click.prevent="activeTab = 'storages'; window.location.hash = 'storages'; if(window.location.search) window.location.search = ''"
|
@click.prevent="activeTab = 'storages'; window.location.hash = 'storages'; if(window.location.search) window.location.search = ''"
|
||||||
href="#">Storages
|
href="#">Storages
|
||||||
</a>
|
</a>
|
||||||
<a :class="activeTab === 'scheduled-tasks' && 'dark:text-white'"
|
<a class="menu-item" :class="activeTab === 'scheduled-tasks' && 'menu-item-active'"
|
||||||
@click.prevent="activeTab = 'scheduled-tasks'; window.location.hash = 'scheduled-tasks'"
|
@click.prevent="activeTab = 'scheduled-tasks'; window.location.hash = 'scheduled-tasks'"
|
||||||
href="#">Scheduled Tasks
|
href="#">Scheduled Tasks
|
||||||
</a>
|
</a>
|
||||||
@if (str($serviceDatabase?->databaseType())->contains('mysql') ||
|
@if (str($serviceDatabase?->databaseType())->contains('mysql') ||
|
||||||
str($serviceDatabase?->databaseType())->contains('postgres') ||
|
str($serviceDatabase?->databaseType())->contains('postgres') ||
|
||||||
str($serviceDatabase?->databaseType())->contains('mariadb'))
|
str($serviceDatabase?->databaseType())->contains('mariadb'))
|
||||||
<a :class="activeTab === 'backups' && 'dark:text-white'"
|
<a :class="activeTab === 'backups' && 'menu-item-active'" class="menu-item"
|
||||||
@click.prevent="activeTab = 'backups'; window.location.hash = 'backups'" href="#">Backups</a>
|
@click.prevent="activeTab = 'backups'; window.location.hash = 'backups'" href="#">Backups</a>
|
||||||
@endif
|
@endif
|
||||||
</div>
|
</div>
|
||||||
<div class="w-full pl-8">
|
<div class="w-full">
|
||||||
@isset($serviceApplication)
|
@isset($serviceApplication)
|
||||||
<div x-cloak x-show="activeTab === 'general'" class="h-full">
|
<div x-cloak x-show="activeTab === 'general'" class="h-full">
|
||||||
<livewire:project.service.service-application-view :application="$serviceApplication" />
|
<livewire:project.service.service-application-view :application="$serviceApplication" />
|
||||||
|
|||||||
@@ -9,13 +9,14 @@
|
|||||||
<h1>Configuration</h1>
|
<h1>Configuration</h1>
|
||||||
<x-resources.breadcrumbs :resource="$service" :parameters="$parameters" />
|
<x-resources.breadcrumbs :resource="$service" :parameters="$parameters" />
|
||||||
<div class="navbar-main" x-data>
|
<div class="navbar-main" x-data>
|
||||||
<a class="{{ request()->routeIs('project.service.configuration') ? 'dark:text-white' : '' }}"
|
<nav class="flex items-center flex-shrink-0 gap-6 scrollbar min-h-10 whitespace-nowrap">
|
||||||
href="{{ route('project.service.configuration', $parameters) }}">
|
<a class="{{ request()->routeIs('project.service.configuration') ? 'dark:text-white' : '' }}"
|
||||||
<button>Configuration</button>
|
href="{{ route('project.service.configuration', $parameters) }}">
|
||||||
</a>
|
<button>Configuration</button>
|
||||||
<x-services.links :service="$service" />
|
</a>
|
||||||
<div class="flex-1"></div>
|
<x-services.links :service="$service" />
|
||||||
<div class="flex gap-2">
|
</nav>
|
||||||
|
<div class="flex flex-wrap items-center order-first gap-2 sm:order-last">
|
||||||
@if (str($service->status())->contains('running'))
|
@if (str($service->status())->contains('running'))
|
||||||
<button @click="$wire.dispatch('restartEvent')" class="gap-2 button">
|
<button @click="$wire.dispatch('restartEvent')" class="gap-2 button">
|
||||||
<svg class="w-5 h-5 dark:text-warning" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
|
<svg class="w-5 h-5 dark:text-warning" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
|||||||
@@ -54,4 +54,78 @@
|
|||||||
@endforelse
|
@endforelse
|
||||||
</div>
|
</div>
|
||||||
@endif
|
@endif
|
||||||
|
{{-- <section x-data="apex_app" class="container p-5 mx-auto my-20 bg-white drop-shadow-xl rounded-xl">
|
||||||
|
<div class="w-full" x-ref="chart"></div>
|
||||||
|
</section>
|
||||||
|
<script src="https://cdn.jsdelivr.net/npm/apexcharts"></script>
|
||||||
|
<script>
|
||||||
|
document.addEventListener("alpine:init", () => {
|
||||||
|
Alpine.data("apex_app", () => ({
|
||||||
|
data: @js($cpu),
|
||||||
|
init() {
|
||||||
|
let chart = new ApexCharts(this.$refs.chart, this.options);
|
||||||
|
chart.render();
|
||||||
|
this.$watch("data", () => {
|
||||||
|
chart.updateOptions(this.options);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
get options() {
|
||||||
|
return {
|
||||||
|
colors: [function({
|
||||||
|
value,
|
||||||
|
seriesIndex,
|
||||||
|
w
|
||||||
|
}) {
|
||||||
|
if (value < 55) {
|
||||||
|
return '#7E36AF'
|
||||||
|
} else {
|
||||||
|
return '#D9534F'
|
||||||
|
}
|
||||||
|
}, function({
|
||||||
|
value,
|
||||||
|
seriesIndex,
|
||||||
|
w
|
||||||
|
}) {
|
||||||
|
if (value < 111) {
|
||||||
|
return '#7E36AF'
|
||||||
|
} else {
|
||||||
|
return '#D9534F'
|
||||||
|
}
|
||||||
|
}],
|
||||||
|
|
||||||
|
xaxis: {
|
||||||
|
type: 'datetime'
|
||||||
|
},
|
||||||
|
dataLabels: {
|
||||||
|
enabled: false
|
||||||
|
},
|
||||||
|
series: [{
|
||||||
|
name: "Series name",
|
||||||
|
data: this.data
|
||||||
|
}],
|
||||||
|
tooltip: {
|
||||||
|
enabled: true
|
||||||
|
},
|
||||||
|
chart: {
|
||||||
|
stroke: {
|
||||||
|
curve: 'smooth',
|
||||||
|
},
|
||||||
|
height: 500,
|
||||||
|
width: "100%",
|
||||||
|
type: "line",
|
||||||
|
toolbar: {
|
||||||
|
show: true
|
||||||
|
},
|
||||||
|
animations: {
|
||||||
|
initialAnimation: {
|
||||||
|
enabled: false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
});
|
||||||
|
</script> --}}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -6,7 +6,7 @@
|
|||||||
<div class="pb-4">
|
<div class="pb-4">
|
||||||
<div class="flex flex-col flex-wrap gap-2">
|
<div class="flex flex-col flex-wrap gap-2">
|
||||||
@foreach ($servers->sortBy('id') as $server)
|
@foreach ($servers->sortBy('id') as $server)
|
||||||
<h5>Server: <span class="font-bold text-white">{{ $server->name }}</span></h5>
|
<h5>Server: <span class="font-bold text-dark dark:text-white">{{ $server->name }}</span></h5>
|
||||||
@foreach ($server->destinations() as $destination)
|
@foreach ($server->destinations() as $destination)
|
||||||
<x-modal-confirmation action="cloneTo({{ data_get($destination, 'id') }})">
|
<x-modal-confirmation action="cloneTo({{ data_get($destination, 'id') }})">
|
||||||
<x:slot name="content">
|
<x:slot name="content">
|
||||||
@@ -33,7 +33,7 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="flex flex-col flex-wrap gap-2">
|
<div class="flex flex-col flex-wrap gap-2">
|
||||||
@forelse ($projects as $project)
|
@forelse ($projects as $project)
|
||||||
<h5>Project: <span class="font-bold text-white">{{ $project->name }}</span></h5>
|
<h5>Project: <span class="font-bold text-dark dark:text-white">{{ $project->name }}</span></h5>
|
||||||
|
|
||||||
@foreach ($project->environments as $environment)
|
@foreach ($project->environments as $environment)
|
||||||
<x-modal-confirmation action="moveTo({{ data_get($environment, 'id') }})">
|
<x-modal-confirmation action="moveTo({{ data_get($environment, 'id') }})">
|
||||||
|
|||||||
@@ -21,7 +21,7 @@
|
|||||||
<form wire:submit='submit' class="flex items-end gap-2 pt-4">
|
<form wire:submit='submit' class="flex items-end gap-2 pt-4">
|
||||||
<div class="w-64">
|
<div class="w-64">
|
||||||
<x-forms.input label="Create new or assign existing tags"
|
<x-forms.input label="Create new or assign existing tags"
|
||||||
helper="You add more at once with space seperated list: web api something<br><br>If the tag does not exists, it will be created."
|
helper="You add more at once with space separated list: web api something<br><br>If the tag does not exists, it will be created."
|
||||||
wire:model="new_tag" />
|
wire:model="new_tag" />
|
||||||
</div>
|
</div>
|
||||||
<x-forms.button type="submit">Add</x-forms.button>
|
<x-forms.button type="submit">Add</x-forms.button>
|
||||||
|
|||||||
@@ -11,18 +11,26 @@
|
|||||||
@forelse ($project->environments as $environment)
|
@forelse ($project->environments as $environment)
|
||||||
<div class="gap-2 border border-transparent cursor-pointer box group" x-data
|
<div class="gap-2 border border-transparent cursor-pointer box group" x-data
|
||||||
x-on:click="goto('{{ $project->uuid }}','{{ $environment->name }}')">
|
x-on:click="goto('{{ $project->uuid }}','{{ $environment->name }}')">
|
||||||
<a class="flex flex-col justify-center flex-1 mx-6 hover:no-underline"
|
<div class="flex flex-1 mx-6">
|
||||||
href="{{ route('project.resource.index', [$project->uuid, $environment->name]) }}">
|
<a class="flex flex-col justify-center flex-1"
|
||||||
<div class="font-bold dark:text-white"> {{ $environment->name }}</div>
|
href="{{ route('project.resource.index', [$project->uuid, $environment->name]) }}">
|
||||||
<div class="description">
|
<div class="font-bold dark:text-white"> {{ $environment->name }}</div>
|
||||||
{{ $environment->description }}</div>
|
<div class="description">
|
||||||
</a>
|
{{ $environment->description }}</div>
|
||||||
<div class="flex items-center text-xs">
|
</a>
|
||||||
|
<div class="flex items-center justify-center gap-2 text-xs">
|
||||||
|
<a class="font-bold hover:underline"
|
||||||
|
href="{{ route('project.environment.edit', ['project_uuid' => data_get($project, 'uuid'), 'environment_name' => $environment->name]) }}">
|
||||||
|
Settings
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{{-- <div class="flex items-center justify-center gap-2 pt-4 pb-2 mr-4 text-xs lg:py-0 lg:justify-normal">
|
||||||
<a class="mx-4 font-bold hover:underline"
|
<a class="mx-4 font-bold hover:underline"
|
||||||
href="{{ route('project.environment.edit', ['project_uuid' => data_get($project, 'uuid'), 'environment_name' => $environment->name]) }}">
|
href="{{ route('project.environment.edit', ['project_uuid' => data_get($project, 'uuid'), 'environment_name' => $environment->name]) }}">
|
||||||
Settings
|
Settings
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div> --}}
|
||||||
</div>
|
</div>
|
||||||
@empty
|
@empty
|
||||||
<p>No environments found.</p>
|
<p>No environments found.</p>
|
||||||
|
|||||||
@@ -132,7 +132,7 @@
|
|||||||
|
|
||||||
@if ($server->isFunctional())
|
@if ($server->isFunctional())
|
||||||
<h3 class="py-4">Settings</h3>
|
<h3 class="py-4">Settings</h3>
|
||||||
<div class="flex gap-2">
|
<div class="flex gap-2 flex-wrap sm:flex-nowrap">
|
||||||
<x-forms.input id="cleanup_after_percentage" label="Disk cleanup threshold (%)" required
|
<x-forms.input id="cleanup_after_percentage" label="Disk cleanup threshold (%)" required
|
||||||
helper="The disk cleanup task will run when the disk usage exceeds this threshold." />
|
helper="The disk cleanup task will run when the disk usage exceeds this threshold." />
|
||||||
<x-forms.input id="server.settings.concurrent_builds" label="Number of concurrent builds" required
|
<x-forms.input id="server.settings.concurrent_builds" label="Number of concurrent builds" required
|
||||||
|
|||||||
@@ -3,11 +3,11 @@
|
|||||||
<x-limit-reached name="servers" />
|
<x-limit-reached name="servers" />
|
||||||
@else
|
@else
|
||||||
<form class="flex flex-col w-full gap-2" wire:submit='submit'>
|
<form class="flex flex-col w-full gap-2" wire:submit='submit'>
|
||||||
<div class="flex w-full gap-2">
|
<div class="flex w-full gap-2 flex-wrap sm:flex-nowrap">
|
||||||
<x-forms.input autofocus id="name" label="Name" required />
|
<x-forms.input autofocus id="name" label="Name" required />
|
||||||
<x-forms.input id="description" label="Description" />
|
<x-forms.input id="description" label="Description" />
|
||||||
</div>
|
</div>
|
||||||
<div class="flex gap-2">
|
<div class="flex gap-2 flex-wrap sm:flex-nowrap">
|
||||||
<x-forms.input id="ip" label="IP Address/Domain" required
|
<x-forms.input id="ip" label="IP Address/Domain" required
|
||||||
helper="An IP Address (127.0.0.1) or domain (example.com)." />
|
helper="An IP Address (127.0.0.1) or domain (example.com)." />
|
||||||
<x-forms.input type="number" id="port" label="Port" required />
|
<x-forms.input type="number" id="port" label="Port" required />
|
||||||
|
|||||||
@@ -1,13 +1,13 @@
|
|||||||
<div>
|
<div>
|
||||||
<x-server.navbar :server="$server" :parameters="$parameters" />
|
<x-server.navbar :server="$server" :parameters="$parameters" />
|
||||||
<div x-data="{ activeTab: window.location.hash ? window.location.hash.substring(1) : 'managed' }" class="flex h-full">
|
<div x-data="{ activeTab: window.location.hash ? window.location.hash.substring(1) : 'managed' }" class="flex flex-col h-full gap-8 md:flex-row">
|
||||||
<div class="flex flex-col gap-4">
|
<div class="flex flex-row gap-4 md:flex-col">
|
||||||
<a :class="activeTab === 'managed' && 'dark:text-white'"
|
<a :class="activeTab === 'managed' && 'dark:text-white'"
|
||||||
@click.prevent="activeTab = 'managed'; window.location.hash = 'managed'" href="#">Managed</a>
|
@click.prevent="activeTab = 'managed'; window.location.hash = 'managed'" href="#">Managed</a>
|
||||||
<a :class="activeTab === 'unmanaged' && 'dark:text-white'"
|
<a :class="activeTab === 'unmanaged' && 'dark:text-white'"
|
||||||
@click.prevent="activeTab = 'unmanaged'; window.location.hash = 'unmanaged'" href="#">Unmanaged</a>
|
@click.prevent="activeTab = 'unmanaged'; window.location.hash = 'unmanaged'" href="#">Unmanaged</a>
|
||||||
</div>
|
</div>
|
||||||
<div class="w-full pl-8">
|
<div class="w-full">
|
||||||
<div x-cloak x-show="activeTab === 'managed'" class="h-full">
|
<div x-cloak x-show="activeTab === 'managed'" class="h-full">
|
||||||
<div class="flex flex-col">
|
<div class="flex flex-col">
|
||||||
<div class="flex gap-2">
|
<div class="flex gap-2">
|
||||||
|
|||||||
@@ -9,12 +9,14 @@
|
|||||||
<div>General configuration for your Coolify instance.</div>
|
<div>General configuration for your Coolify instance.</div>
|
||||||
|
|
||||||
<div class="flex flex-col gap-2 pt-4">
|
<div class="flex flex-col gap-2 pt-4">
|
||||||
<div class="flex items-end gap-2">
|
<div class="flex items-end gap-2 flex-wrap">
|
||||||
<x-forms.input id="settings.fqdn" label="Instance's Domain" placeholder="https://coolify.io" />
|
<x-forms.input id="settings.fqdn" label="Instance's Domain" placeholder="https://coolify.io" />
|
||||||
<x-forms.input id="settings.custom_dns_servers" label="DNS Servers"
|
<x-forms.input id="settings.custom_dns_servers" label="DNS Servers"
|
||||||
helper="DNS servers for validation FQDNS againts. A comma separated list of DNS servers."
|
helper="DNS servers for validation FQDNs againts. A comma separated list of DNS servers."
|
||||||
placeholder="1.1.1.1,8.8.8.8" />
|
placeholder="1.1.1.1,8.8.8.8" />
|
||||||
<x-forms.checkbox instantSave id="is_dns_validation_enabled" label="Validate DNS settings?" />
|
<div class="md:w-96">
|
||||||
|
<x-forms.checkbox instantSave id="is_dns_validation_enabled" label="Validate DNS settings?" />
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{{-- <div class="flex gap-2 ">
|
{{-- <div class="flex gap-2 ">
|
||||||
@@ -24,12 +26,12 @@
|
|||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
<h2 class="pt-6">Advanced</h2>
|
<h2 class="pt-6">Advanced</h2>
|
||||||
<div class="text-right w-80">
|
<div class="text-right md:w-96">
|
||||||
@if (!is_null(env('AUTOUPDATE', null)))
|
@if (!is_null(env('AUTOUPDATE', null)))
|
||||||
<x-forms.checkbox instantSave helper="AUTOUPDATE is set in .env file, you need to modify it there." disabled
|
<x-forms.checkbox instantSave helper="AUTOUPDATE is set in .env file, you need to modify it there." disabled
|
||||||
id="is_auto_update_enabled" label="Auto Update Coolify" />
|
id="is_auto_update_enabled" label="Auto Update Coolify" />
|
||||||
@else
|
@else
|
||||||
<x-forms.checkbox instantSave id="is_auto_update_enabled" label="Auto Update Coolify" />
|
<x-forms.checkbox instantSave id="is_auto_update_enabled" label="Auto Update Coolify" />
|
||||||
@endif
|
@endif
|
||||||
<x-forms.checkbox instantSave id="is_registration_enabled" label="Registration Allowed" />
|
<x-forms.checkbox instantSave id="is_registration_enabled" label="Registration Allowed" />
|
||||||
<x-forms.checkbox instantSave id="do_not_track" label="Do Not Track" />
|
<x-forms.checkbox instantSave id="do_not_track" label="Do Not Track" />
|
||||||
@@ -38,7 +40,7 @@
|
|||||||
id="next_channel" label="Enable pre-release (early) updates" />
|
id="next_channel" label="Enable pre-release (early) updates" />
|
||||||
@else
|
@else
|
||||||
<x-forms.checkbox disabled instantSave
|
<x-forms.checkbox disabled instantSave
|
||||||
helper="Currently disabled. Do not recommended, only if you like to live on the edge." id="next_channel"
|
helper="Currently disabled. Not recommended. Only if you like to live on the edge." id="next_channel"
|
||||||
label="Enable pre-release (early) updates" />
|
label="Enable pre-release (early) updates" />
|
||||||
@endif
|
@endif
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,18 +1,19 @@
|
|||||||
<div>
|
<div>
|
||||||
<x-settings.navbar />
|
<x-settings.navbar />
|
||||||
<div x-data="{ activeTab: window.location.hash ? window.location.hash.substring(1) : 'general' }" class="flex h-full pt-1">
|
<div x-data="{ activeTab: window.location.hash ? window.location.hash.substring(1) : 'general' }" class="flex flex-col h-full gap-8 pt-1 sm:flex-row">
|
||||||
<div class="flex flex-col gap-4 min-w-fit">
|
<div class="flex gap-6 overflow-x-scroll sm:gap-2 sm:overflow-x-hidden scrollbar sm:flex-col whitespace-nowrap">
|
||||||
<a :class="activeTab === 'general' && 'dark:text-white'"
|
<a class="menu-item" :class="activeTab === 'general' && 'menu-item-active'"
|
||||||
@click.prevent="activeTab = 'general'; window.location.hash = 'general'" href="#">General</a>
|
@click.prevent="activeTab = 'general'; window.location.hash = 'general'" href="#">General</a>
|
||||||
<a :class="activeTab === 'backup' && 'dark:text-white'"
|
<a class="menu-item" :class="activeTab === 'backup' && 'menu-item-active'"
|
||||||
@click.prevent="activeTab = 'backup'; window.location.hash = 'backup'" href="#">Instance Backup</a>
|
@click.prevent="activeTab = 'backup'; window.location.hash = 'backup'" href="#">Instance Backup</a>
|
||||||
<a :class="activeTab === 'smtp' && 'dark:text-white'"
|
<a class="menu-item" :class="activeTab === 'smtp' && 'menu-item-active'"
|
||||||
@click.prevent="activeTab = 'smtp'; window.location.hash = 'smtp'" href="#">Transactional
|
@click.prevent="activeTab = 'smtp'; window.location.hash = 'smtp'" href="#">Transactional
|
||||||
Email</a>
|
Email</a>
|
||||||
<a :class="activeTab === 'auth' && 'dark:text-white'"
|
<a class="menu-item" :class="activeTab === 'auth' && 'menu-item-active'"
|
||||||
@click.prevent="activeTab = 'auth'; window.location.hash = 'auth'" href="#">Authentication (OAuth)</a>
|
@click.prevent="activeTab = 'auth'; window.location.hash = 'auth'" href="#">Authentication
|
||||||
|
(OAuth)</a>
|
||||||
</div>
|
</div>
|
||||||
<div class="w-full pl-8">
|
<div class="w-full">
|
||||||
<div x-cloak x-show="activeTab === 'general'" class="h-full">
|
<div x-cloak x-show="activeTab === 'general'" class="h-full">
|
||||||
<livewire:settings.configuration :settings="$settings" />
|
<livewire:settings.configuration :settings="$settings" />
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,25 +1,24 @@
|
|||||||
<div wire:poll.1000ms="get_deployments" class="grid grid-cols-1">
|
<div wire:poll.2000ms="get_deployments" wire:init='get_deployments'>
|
||||||
@forelse ($deployments_per_tag_per_server as $server_name => $deployments)
|
@forelse ($deployments_per_tag_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">
|
||||||
@foreach ($deployments as $deployment)
|
@foreach ($deployments as $deployment)
|
||||||
<div @class([
|
<a href="{{ data_get($deployment, 'deployment_url') }}" @class([
|
||||||
'box-without-bg dark:bg-coolgray-100 bg-white gap-2 cursor-pointer group border-l-2 border-dotted',
|
'box-without-bg-without-border dark:bg-coolgray-100 bg-white gap-2 cursor-pointer group border-l-2',
|
||||||
'dark:border-coolgray-300' => data_get($deployment, 'status') === 'queued',
|
'dark:border-coolgray-300' => data_get($deployment, 'status') === 'queued',
|
||||||
'border-yellow-500' => data_get($deployment, 'status') === 'in_progress',
|
'dark:border-yellow-500' =>
|
||||||
|
data_get($deployment, 'status') === 'in_progress',
|
||||||
])>
|
])>
|
||||||
<a href="{{ data_get($deployment, 'deployment_url') }}">
|
<div class="flex flex-col mx-6">
|
||||||
<div class="flex flex-col mx-6">
|
<div class="box-title">
|
||||||
<div class="box-title">
|
{{ data_get($deployment, 'application_name') }}
|
||||||
{{ data_get($deployment, 'application_name') }}
|
|
||||||
</div>
|
|
||||||
<div class="box-description">
|
|
||||||
{{ str(data_get($deployment, 'status'))->headline() }}
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
<div class="flex-1"></div>
|
<div class="box-description">
|
||||||
</a>
|
{{ str(data_get($deployment, 'status'))->headline() }}
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="flex-1"></div>
|
||||||
|
</a>
|
||||||
@endforeach
|
@endforeach
|
||||||
</div>
|
</div>
|
||||||
@empty
|
@empty
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
<h1>Tags</h1>
|
<h1>Tags</h1>
|
||||||
<div class="flex flex-col pb-6 ">
|
<div class="flex flex-col pb-6 ">
|
||||||
<div class="subtitle">Tags help you to perform actions on multiple resources.</div>
|
<div class="subtitle">Tags help you to perform actions on multiple resources.</div>
|
||||||
<div class="flex flex-wrap gap-2">
|
<div class="">
|
||||||
@if ($tags->count() === 0)
|
@if ($tags->count() === 0)
|
||||||
<div>No tags yet defined yet. Go to a resource and add a tag there.</div>
|
<div>No tags yet defined yet. Go to a resource and add a tag there.</div>
|
||||||
@else
|
@else
|
||||||
|
|||||||
@@ -10,7 +10,7 @@
|
|||||||
<path stroke="none" d="M0 0h24v24H0z" fill="none" />
|
<path stroke="none" d="M0 0h24v24H0z" fill="none" />
|
||||||
<path d="M19.5 13.572l-7.5 7.428l-7.5 -7.428m0 0a5 5 0 1 1 7.5 -6.566a5 5 0 1 1 7.5 6.572" />
|
<path d="M19.5 13.572l-7.5 7.428l-7.5 -7.428m0 0a5 5 0 1 1 7.5 -6.566a5 5 0 1 1 7.5 6.572" />
|
||||||
</svg>
|
</svg>
|
||||||
Inprogress
|
In progress
|
||||||
@else
|
@else
|
||||||
<svg xmlns="http://www.w3.org/2000/svg"
|
<svg xmlns="http://www.w3.org/2000/svg"
|
||||||
class="w-6 h-6 text-pink-500 transition-colors hover:text-pink-300" viewBox="0 0 24 24"
|
class="w-6 h-6 text-pink-500 transition-colors hover:text-pink-300" viewBox="0 0 24 24"
|
||||||
|
|||||||
@@ -12,6 +12,11 @@ DOCKER_VERSION="24.0"
|
|||||||
CDN="https://cdn.coollabs.io/coolify"
|
CDN="https://cdn.coollabs.io/coolify"
|
||||||
OS_TYPE=$(grep -w "ID" /etc/os-release | cut -d "=" -f 2 | tr -d '"')
|
OS_TYPE=$(grep -w "ID" /etc/os-release | cut -d "=" -f 2 | tr -d '"')
|
||||||
|
|
||||||
|
# Check if the OS is manjaro, if so, change it to arch
|
||||||
|
if [ "$OS_TYPE" = "manjaro" ]; then
|
||||||
|
OS_TYPE="arch"
|
||||||
|
fi
|
||||||
|
|
||||||
if [ "$OS_TYPE" = "arch" ]; then
|
if [ "$OS_TYPE" = "arch" ]; then
|
||||||
OS_VERSION="rolling"
|
OS_VERSION="rolling"
|
||||||
else
|
else
|
||||||
@@ -73,6 +78,9 @@ centos | fedora | rhel | ol | rocky | almalinux | amzn)
|
|||||||
if [ "$OS_TYPE" = "amzn" ]; then
|
if [ "$OS_TYPE" = "amzn" ]; then
|
||||||
dnf install -y wget git jq >/dev/null 2>&1
|
dnf install -y wget git jq >/dev/null 2>&1
|
||||||
else
|
else
|
||||||
|
if ! command -v dnf >/dev/null 2>&1; then
|
||||||
|
yum install -y dnf >/dev/null 2>&1
|
||||||
|
fi
|
||||||
dnf install -y curl wget git jq >/dev/null 2>&1
|
dnf install -y curl wget git jq >/dev/null 2>&1
|
||||||
fi
|
fi
|
||||||
;;
|
;;
|
||||||
@@ -252,7 +260,7 @@ fi
|
|||||||
|
|
||||||
echo -e "-------------"
|
echo -e "-------------"
|
||||||
|
|
||||||
mkdir -p /data/coolify/{source,ssh,applications,databases,backups,services,proxy,webhooks-during-maintenance}
|
mkdir -p /data/coolify/{source,ssh,applications,databases,backups,services,proxy,webhooks-during-maintenance,metrics,logs}
|
||||||
mkdir -p /data/coolify/ssh/{keys,mux}
|
mkdir -p /data/coolify/ssh/{keys,mux}
|
||||||
mkdir -p /data/coolify/proxy/dynamic
|
mkdir -p /data/coolify/proxy/dynamic
|
||||||
|
|
||||||
|
|||||||
@@ -11,7 +11,6 @@ x-logging: &x-logging
|
|||||||
options:
|
options:
|
||||||
max-file: '5'
|
max-file: '5'
|
||||||
max-size: '10m'
|
max-size: '10m'
|
||||||
version: '3'
|
|
||||||
|
|
||||||
services:
|
services:
|
||||||
appwrite:
|
appwrite:
|
||||||
|
|||||||
@@ -4,8 +4,6 @@
|
|||||||
# logo: svgs/authentik.png
|
# logo: svgs/authentik.png
|
||||||
# port: 9000
|
# port: 9000
|
||||||
|
|
||||||
version: "3.4"
|
|
||||||
|
|
||||||
services:
|
services:
|
||||||
postgresql:
|
postgresql:
|
||||||
image: docker.io/library/postgres:12-alpine
|
image: docker.io/library/postgres:12-alpine
|
||||||
|
|||||||
@@ -4,7 +4,6 @@
|
|||||||
# logo: svgs/glitchtip.png
|
# logo: svgs/glitchtip.png
|
||||||
# port: 8080
|
# port: 8080
|
||||||
|
|
||||||
version: "3.8"
|
|
||||||
services:
|
services:
|
||||||
postgres:
|
postgres:
|
||||||
image: postgres:16-alpine
|
image: postgres:16-alpine
|
||||||
|
|||||||
56
templates/compose/listmonk.yaml
Normal file
56
templates/compose/listmonk.yaml
Normal file
@@ -0,0 +1,56 @@
|
|||||||
|
# documentation: https://listmonk.app/
|
||||||
|
# slogan: Self-hosted newsletter and mailing list manager
|
||||||
|
# tags: newsletter, mailing list, self-hosted, open source
|
||||||
|
# logo: svgs/listmonk.svg
|
||||||
|
# port: 9000
|
||||||
|
|
||||||
|
services:
|
||||||
|
listmonk:
|
||||||
|
image: listmonk/listmonk:latest
|
||||||
|
environment:
|
||||||
|
- SERVICE_FQDN_LISTMONK_9000
|
||||||
|
- LISTMONK_app__address=0.0.0.0:9000
|
||||||
|
- LISTMONK_db__host=postgres
|
||||||
|
- LISTMONK_db__name=listmonk
|
||||||
|
- LISTMONK_db__user=$SERVICE_USER_POSTGRES
|
||||||
|
- LISTMONK_db__password=$SERVICE_PASSWORD_POSTGRES
|
||||||
|
- LISTMONK_db__port=5432
|
||||||
|
- LISTMONK_app__admin_username=admin
|
||||||
|
- LISTMONK_app__admin_password=$SERVICE_PASSWORD_ADMIN
|
||||||
|
- TZ=Etc/UTC
|
||||||
|
volumes:
|
||||||
|
- "listmonk-data:/listmonk/uploads"
|
||||||
|
depends_on:
|
||||||
|
postgres:
|
||||||
|
condition: service_healthy
|
||||||
|
healthcheck:
|
||||||
|
test: ["CMD", "wget", "-q", "--spider", "http://127.0.0.1:9000"]
|
||||||
|
interval: 5s
|
||||||
|
timeout: 20s
|
||||||
|
retries: 10
|
||||||
|
listmonk-initial-database-setup:
|
||||||
|
image: listmonk/listmonk:latest
|
||||||
|
command: "./listmonk --install --yes --idempotent"
|
||||||
|
restart: "no"
|
||||||
|
depends_on:
|
||||||
|
postgres:
|
||||||
|
condition: service_healthy
|
||||||
|
environment:
|
||||||
|
- LISTMONK_db__host=postgres
|
||||||
|
- LISTMONK_db__name=listmonk
|
||||||
|
- LISTMONK_db__user=$SERVICE_USER_POSTGRES
|
||||||
|
- LISTMONK_db__password=$SERVICE_PASSWORD_POSTGRES
|
||||||
|
- LISTMONK_db__port=5432
|
||||||
|
postgres:
|
||||||
|
image: "postgres:latest"
|
||||||
|
environment:
|
||||||
|
- POSTGRES_DB=listmonk
|
||||||
|
- POSTGRES_PASSWORD=$SERVICE_PASSWORD_POSTGRES
|
||||||
|
- POSTGRES_USER=$SERVICE_USER_POSTGRES
|
||||||
|
volumes:
|
||||||
|
- "pg-data:/var/lib/postgresql/data"
|
||||||
|
healthcheck:
|
||||||
|
test: ["CMD-SHELL", "pg_isready -U $${POSTGRES_USER} -d $${POSTGRES_DB}"]
|
||||||
|
interval: 5s
|
||||||
|
timeout: 20s
|
||||||
|
retries: 10
|
||||||
@@ -2,7 +2,6 @@
|
|||||||
# slogan: Penpot is the first Open Source design and prototyping platform for product teams.
|
# slogan: Penpot is the first Open Source design and prototyping platform for product teams.
|
||||||
# tags: penpot,design,prototyping,figma,open,source
|
# tags: penpot,design,prototyping,figma,open,source
|
||||||
|
|
||||||
version: "3.5"
|
|
||||||
services:
|
services:
|
||||||
frontend:
|
frontend:
|
||||||
image: "penpotapp/frontend:latest"
|
image: "penpotapp/frontend:latest"
|
||||||
|
|||||||
@@ -4,7 +4,6 @@
|
|||||||
# tags: analytics, privacy, google, alternative
|
# tags: analytics, privacy, google, alternative
|
||||||
# port: 8000
|
# port: 8000
|
||||||
|
|
||||||
version: "3.3"
|
|
||||||
services:
|
services:
|
||||||
plausible:
|
plausible:
|
||||||
image: plausible/analytics:v2.0
|
image: plausible/analytics:v2.0
|
||||||
|
|||||||
File diff suppressed because one or more lines are too long
@@ -1,7 +1,10 @@
|
|||||||
{
|
{
|
||||||
"coolify": {
|
"coolify": {
|
||||||
"v4": {
|
"v4": {
|
||||||
"version": "4.0.0-beta.273"
|
"version": "4.0.0-beta.277"
|
||||||
|
},
|
||||||
|
"sentinel": {
|
||||||
|
"version": "0.0.4"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user