Merge branch 'next' into feat/disable-default-redirect
This commit is contained in:
15
.env.dusk.ci
Normal file
15
.env.dusk.ci
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
APP_ENV=production
|
||||||
|
APP_NAME="Coolify Staging"
|
||||||
|
APP_ID=development
|
||||||
|
APP_KEY=
|
||||||
|
APP_URL=http://localhost
|
||||||
|
APP_PORT=8000
|
||||||
|
SSH_MUX_ENABLED=true
|
||||||
|
|
||||||
|
# PostgreSQL Database Configuration
|
||||||
|
DB_DATABASE=coolify
|
||||||
|
DB_USERNAME=coolify
|
||||||
|
DB_PASSWORD=password
|
||||||
|
DB_HOST=localhost
|
||||||
|
DB_PORT=5432
|
||||||
|
|
@@ -4,6 +4,7 @@ APP_ID=coolify-windows-docker-desktop
|
|||||||
APP_NAME=Coolify
|
APP_NAME=Coolify
|
||||||
APP_KEY=base64:ssTlCmrIE/q7whnKMvT6DwURikg69COzGsAwFVROm80=
|
APP_KEY=base64:ssTlCmrIE/q7whnKMvT6DwURikg69COzGsAwFVROm80=
|
||||||
|
|
||||||
|
DB_USERNAME=coolify
|
||||||
DB_PASSWORD=coolify
|
DB_PASSWORD=coolify
|
||||||
REDIS_PASSWORD=coolify
|
REDIS_PASSWORD=coolify
|
||||||
|
|
||||||
|
65
.github/workflows/browser-tests.yml
vendored
Normal file
65
.github/workflows/browser-tests.yml
vendored
Normal file
@@ -0,0 +1,65 @@
|
|||||||
|
name: Dusk
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches: [ "not-existing" ]
|
||||||
|
jobs:
|
||||||
|
dusk:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
|
services:
|
||||||
|
redis:
|
||||||
|
image: redis
|
||||||
|
env:
|
||||||
|
REDIS_HOST: localhost
|
||||||
|
REDIS_PORT: 6379
|
||||||
|
ports:
|
||||||
|
- 6379:6379
|
||||||
|
options: >-
|
||||||
|
--health-cmd "redis-cli ping"
|
||||||
|
--health-interval 10s
|
||||||
|
--health-timeout 5s
|
||||||
|
--health-retries 5
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
- name: Set up PostgreSQL
|
||||||
|
run: |
|
||||||
|
sudo systemctl start postgresql
|
||||||
|
sudo -u postgres psql -c "CREATE DATABASE coolify;"
|
||||||
|
sudo -u postgres psql -c "CREATE USER coolify WITH PASSWORD 'password';"
|
||||||
|
sudo -u postgres psql -c "ALTER ROLE coolify SET client_encoding TO 'utf8';"
|
||||||
|
sudo -u postgres psql -c "ALTER ROLE coolify SET default_transaction_isolation TO 'read committed';"
|
||||||
|
sudo -u postgres psql -c "ALTER ROLE coolify SET timezone TO 'UTC';"
|
||||||
|
sudo -u postgres psql -c "GRANT ALL PRIVILEGES ON DATABASE coolify TO coolify;"
|
||||||
|
- name: Setup PHP
|
||||||
|
uses: shivammathur/setup-php@v2
|
||||||
|
with:
|
||||||
|
php-version: '8.2'
|
||||||
|
- name: Copy .env
|
||||||
|
run: cp .env.dusk.ci .env
|
||||||
|
- name: Install Dependencies
|
||||||
|
run: composer install --no-progress --prefer-dist --optimize-autoloader
|
||||||
|
- name: Generate key
|
||||||
|
run: php artisan key:generate
|
||||||
|
- name: Install Chrome binaries
|
||||||
|
run: php artisan dusk:chrome-driver --detect
|
||||||
|
- name: Start Chrome Driver
|
||||||
|
run: ./vendor/laravel/dusk/bin/chromedriver-linux --port=4444 &
|
||||||
|
- name: Build assets
|
||||||
|
run: npm install && npm run build
|
||||||
|
- name: Run Laravel Server
|
||||||
|
run: php artisan serve --no-reload &
|
||||||
|
- name: Execute tests
|
||||||
|
run: php artisan dusk
|
||||||
|
- name: Upload Screenshots
|
||||||
|
if: failure()
|
||||||
|
uses: actions/upload-artifact@v4
|
||||||
|
with:
|
||||||
|
name: screenshots
|
||||||
|
path: tests/Browser/screenshots
|
||||||
|
- name: Upload Console Logs
|
||||||
|
if: failure()
|
||||||
|
uses: actions/upload-artifact@v4
|
||||||
|
with:
|
||||||
|
name: console
|
||||||
|
path: tests/Browser/console
|
@@ -12,6 +12,7 @@ class GenerateConfig
|
|||||||
public function handle(Application $application, bool $is_json = false)
|
public function handle(Application $application, bool $is_json = false)
|
||||||
{
|
{
|
||||||
ray()->clearAll();
|
ray()->clearAll();
|
||||||
|
|
||||||
return $application->generateConfig(is_json: $is_json);
|
return $application->generateConfig(is_json: $is_json);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -21,8 +21,6 @@ class StartRedis
|
|||||||
{
|
{
|
||||||
$this->database = $database;
|
$this->database = $database;
|
||||||
|
|
||||||
$startCommand = "redis-server --requirepass {$this->database->redis_password} --appendonly yes";
|
|
||||||
|
|
||||||
$container_name = $this->database->uuid;
|
$container_name = $this->database->uuid;
|
||||||
$this->configuration_dir = database_configuration_dir().'/'.$container_name;
|
$this->configuration_dir = database_configuration_dir().'/'.$container_name;
|
||||||
|
|
||||||
@@ -37,6 +35,8 @@ class StartRedis
|
|||||||
$environment_variables = $this->generate_environment_variables();
|
$environment_variables = $this->generate_environment_variables();
|
||||||
$this->add_custom_redis();
|
$this->add_custom_redis();
|
||||||
|
|
||||||
|
$startCommand = $this->buildStartCommand();
|
||||||
|
|
||||||
$docker_compose = [
|
$docker_compose = [
|
||||||
'services' => [
|
'services' => [
|
||||||
$container_name => [
|
$container_name => [
|
||||||
@@ -105,7 +105,6 @@ class StartRedis
|
|||||||
'target' => '/usr/local/etc/redis/redis.conf',
|
'target' => '/usr/local/etc/redis/redis.conf',
|
||||||
'read_only' => true,
|
'read_only' => true,
|
||||||
];
|
];
|
||||||
$docker_compose['services'][$container_name]['command'] = "redis-server /usr/local/etc/redis/redis.conf --requirepass {$this->database->redis_password} --appendonly yes";
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add custom docker run options
|
// Add custom docker run options
|
||||||
@@ -160,12 +159,26 @@ class StartRedis
|
|||||||
private function generate_environment_variables()
|
private function generate_environment_variables()
|
||||||
{
|
{
|
||||||
$environment_variables = collect();
|
$environment_variables = collect();
|
||||||
foreach ($this->database->runtime_environment_variables as $env) {
|
|
||||||
$environment_variables->push("$env->key=$env->real_value");
|
|
||||||
}
|
|
||||||
|
|
||||||
if ($environment_variables->filter(fn ($env) => str($env)->contains('REDIS_PASSWORD'))->isEmpty()) {
|
foreach ($this->database->runtime_environment_variables as $env) {
|
||||||
$environment_variables->push("REDIS_PASSWORD={$this->database->redis_password}");
|
if ($env->is_shared) {
|
||||||
|
$environment_variables->push("$env->key=$env->real_value");
|
||||||
|
|
||||||
|
if ($env->key === 'REDIS_PASSWORD') {
|
||||||
|
$this->database->update(['redis_password' => $env->real_value]);
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($env->key === 'REDIS_USERNAME') {
|
||||||
|
$this->database->update(['redis_username' => $env->real_value]);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if ($env->key === 'REDIS_PASSWORD') {
|
||||||
|
$env->update(['value' => $this->database->redis_password]);
|
||||||
|
} elseif ($env->key === 'REDIS_USERNAME') {
|
||||||
|
$env->update(['value' => $this->database->redis_username]);
|
||||||
|
}
|
||||||
|
$environment_variables->push("$env->key=$env->real_value");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
add_coolify_default_environment_variables($this->database, $environment_variables, $environment_variables);
|
add_coolify_default_environment_variables($this->database, $environment_variables, $environment_variables);
|
||||||
@@ -173,6 +186,27 @@ class StartRedis
|
|||||||
return $environment_variables->all();
|
return $environment_variables->all();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private function buildStartCommand(): string
|
||||||
|
{
|
||||||
|
$hasRedisConf = ! is_null($this->database->redis_conf) && ! empty($this->database->redis_conf);
|
||||||
|
$redisConfPath = '/usr/local/etc/redis/redis.conf';
|
||||||
|
|
||||||
|
if ($hasRedisConf) {
|
||||||
|
$confContent = $this->database->redis_conf;
|
||||||
|
$hasRequirePass = str_contains($confContent, 'requirepass');
|
||||||
|
|
||||||
|
if ($hasRequirePass) {
|
||||||
|
$command = "redis-server $redisConfPath";
|
||||||
|
} else {
|
||||||
|
$command = "redis-server $redisConfPath --requirepass {$this->database->redis_password}";
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
$command = "redis-server --requirepass {$this->database->redis_password} --appendonly yes";
|
||||||
|
}
|
||||||
|
|
||||||
|
return $command;
|
||||||
|
}
|
||||||
|
|
||||||
private function add_custom_redis()
|
private function add_custom_redis()
|
||||||
{
|
{
|
||||||
if (is_null($this->database->redis_conf) || empty($this->database->redis_conf)) {
|
if (is_null($this->database->redis_conf) || empty($this->database->redis_conf)) {
|
||||||
|
@@ -651,31 +651,5 @@ class GetContainersStatus
|
|||||||
// $this->server->team?->notify(new ContainerStopped($containerName, $this->server, $url));
|
// $this->server->team?->notify(new ContainerStopped($containerName, $this->server, $url));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (! $this->server->proxySet() || $this->server->proxy->force_stop) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
$foundProxyContainer = $this->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);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
17
app/Actions/Server/DeleteServer.php
Normal file
17
app/Actions/Server/DeleteServer.php
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Actions\Server;
|
||||||
|
|
||||||
|
use App\Models\Server;
|
||||||
|
use Lorisleiva\Actions\Concerns\AsAction;
|
||||||
|
|
||||||
|
class DeleteServer
|
||||||
|
{
|
||||||
|
use AsAction;
|
||||||
|
|
||||||
|
public function handle(Server $server)
|
||||||
|
{
|
||||||
|
StopSentinel::run($server);
|
||||||
|
$server->forceDelete();
|
||||||
|
}
|
||||||
|
}
|
@@ -2,7 +2,6 @@
|
|||||||
|
|
||||||
namespace App\Actions\Server;
|
namespace App\Actions\Server;
|
||||||
|
|
||||||
use App\Models\InstanceSettings;
|
|
||||||
use App\Models\Server;
|
use App\Models\Server;
|
||||||
use Lorisleiva\Actions\Concerns\AsAction;
|
use Lorisleiva\Actions\Concerns\AsAction;
|
||||||
|
|
||||||
@@ -10,32 +9,48 @@ class StartSentinel
|
|||||||
{
|
{
|
||||||
use AsAction;
|
use AsAction;
|
||||||
|
|
||||||
public function handle(Server $server, $version = 'latest', bool $restart = false)
|
public function handle(Server $server, $version = 'next', bool $restart = false)
|
||||||
{
|
{
|
||||||
if ($restart) {
|
if ($restart) {
|
||||||
StopSentinel::run($server);
|
StopSentinel::run($server);
|
||||||
}
|
}
|
||||||
$metrics_history = $server->settings->metrics_history_days;
|
$metrics_history = data_get($server, 'settings.sentinel_metrics_history_days');
|
||||||
$refresh_rate = $server->settings->metrics_refresh_rate_seconds;
|
$refresh_rate = data_get($server, 'settings.sentinel_metrics_refresh_rate_seconds');
|
||||||
$token = $server->settings->sentinel_token;
|
$push_interval = data_get($server, 'settings.sentinel_push_interval_seconds');
|
||||||
$fqdn = InstanceSettings::get()->fqdn;
|
$token = data_get($server, 'settings.sentinel_token');
|
||||||
if (str($fqdn)->startsWith('http')) {
|
$endpoint = data_get($server, 'settings.sentinel_custom_url');
|
||||||
throw new \Exception('You should use https to run Sentinel.');
|
$mount_dir = '/data/coolify/sentinel';
|
||||||
|
$image = "ghcr.io/coollabsio/sentinel:$version";
|
||||||
|
if (! $endpoint) {
|
||||||
|
throw new \Exception('You should set FQDN in Instance Settings.');
|
||||||
}
|
}
|
||||||
$environments = [
|
$environments = [
|
||||||
'TOKEN' => $token,
|
'TOKEN' => $token,
|
||||||
'ENDPOINT' => InstanceSettings::get()->fqdn,
|
'PUSH_ENDPOINT' => $endpoint,
|
||||||
'COLLECTOR_ENABLED' => 'true',
|
'PUSH_INTERVAL_SECONDS' => $push_interval,
|
||||||
|
'COLLECTOR_ENABLED' => $server->isMetricsEnabled() ? 'true' : 'false',
|
||||||
'COLLECTOR_REFRESH_RATE_SECONDS' => $refresh_rate,
|
'COLLECTOR_REFRESH_RATE_SECONDS' => $refresh_rate,
|
||||||
'COLLECTOR_RETENTION_PERIOD_DAYS' => $metrics_history
|
'COLLECTOR_RETENTION_PERIOD_DAYS' => $metrics_history,
|
||||||
];
|
];
|
||||||
$docker_environments = "-e \"" . implode("\" -e \"", array_map(fn($key, $value) => "$key=$value", array_keys($environments), $environments)) . "\"";
|
if (isDev()) {
|
||||||
ray($docker_environments);
|
// data_set($environments, 'DEBUG', 'true');
|
||||||
return true;
|
$mount_dir = '/var/lib/docker/volumes/coolify_dev_coolify_data/_data/sentinel';
|
||||||
// instant_remote_process([
|
// $image = 'sentinel';
|
||||||
// "docker run --rm --pull always -d $docker_environments --name coolify-sentinel -v /var/run/docker.sock:/var/run/docker.sock -v /data/coolify/sentinel:/app/sentinel --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/sentinel',
|
$docker_environments = '-e "'.implode('" -e "', array_map(fn ($key, $value) => "$key=$value", array_keys($environments), $environments)).'"';
|
||||||
// 'chmod -R 700 /data/coolify/sentinel',
|
|
||||||
// ], $server, true);
|
$docker_command = "docker run -d $docker_environments --name coolify-sentinel -v /var/run/docker.sock:/var/run/docker.sock -v $mount_dir:/app/db --pid host --health-cmd \"curl --fail http://127.0.0.1:8888/api/health || exit 1\" --health-interval 10s --health-retries 3 --add-host=host.docker.internal:host-gateway $image";
|
||||||
|
|
||||||
|
instant_remote_process([
|
||||||
|
'docker rm -f coolify-sentinel || true',
|
||||||
|
"mkdir -p $mount_dir",
|
||||||
|
$docker_command,
|
||||||
|
"chown -R 9999:root $mount_dir",
|
||||||
|
"chmod -R 700 $mount_dir",
|
||||||
|
], $server);
|
||||||
|
|
||||||
|
$server->settings->is_sentinel_enabled = true;
|
||||||
|
$server->settings->save();
|
||||||
|
$server->sentinelHeartbeat();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -12,5 +12,6 @@ class StopSentinel
|
|||||||
public function handle(Server $server)
|
public function handle(Server $server)
|
||||||
{
|
{
|
||||||
instant_remote_process(['docker rm -f coolify-sentinel'], $server, false);
|
instant_remote_process(['docker rm -f coolify-sentinel'], $server, false);
|
||||||
|
$server->sentinelHeartbeat(isReset: true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -3,16 +3,15 @@
|
|||||||
namespace App\Console;
|
namespace App\Console;
|
||||||
|
|
||||||
use App\Jobs\CheckForUpdatesJob;
|
use App\Jobs\CheckForUpdatesJob;
|
||||||
|
use App\Jobs\CheckHelperImageJob;
|
||||||
use App\Jobs\CleanupInstanceStuffsJob;
|
use App\Jobs\CleanupInstanceStuffsJob;
|
||||||
use App\Jobs\CleanupStaleMultiplexedConnections;
|
use App\Jobs\CleanupStaleMultiplexedConnections;
|
||||||
use App\Jobs\DatabaseBackupJob;
|
use App\Jobs\DatabaseBackupJob;
|
||||||
use App\Jobs\DockerCleanupJob;
|
use App\Jobs\DockerCleanupJob;
|
||||||
use App\Jobs\PullHelperImageJob;
|
|
||||||
use App\Jobs\PullSentinelImageJob;
|
use App\Jobs\PullSentinelImageJob;
|
||||||
use App\Jobs\PullTemplatesFromCDN;
|
use App\Jobs\PullTemplatesFromCDN;
|
||||||
use App\Jobs\ScheduledTaskJob;
|
use App\Jobs\ScheduledTaskJob;
|
||||||
use App\Jobs\ServerCheckJob;
|
use App\Jobs\ServerCheckJob;
|
||||||
use App\Jobs\ServerStorageCheckJob;
|
|
||||||
use App\Jobs\UpdateCoolifyJob;
|
use App\Jobs\UpdateCoolifyJob;
|
||||||
use App\Models\ScheduledDatabaseBackup;
|
use App\Models\ScheduledDatabaseBackup;
|
||||||
use App\Models\ScheduledTask;
|
use App\Models\ScheduledTask;
|
||||||
@@ -20,6 +19,7 @@ use App\Models\Server;
|
|||||||
use App\Models\Team;
|
use App\Models\Team;
|
||||||
use Illuminate\Console\Scheduling\Schedule;
|
use Illuminate\Console\Scheduling\Schedule;
|
||||||
use Illuminate\Foundation\Console\Kernel as ConsoleKernel;
|
use Illuminate\Foundation\Console\Kernel as ConsoleKernel;
|
||||||
|
use Illuminate\Support\Carbon;
|
||||||
|
|
||||||
class Kernel extends ConsoleKernel
|
class Kernel extends ConsoleKernel
|
||||||
{
|
{
|
||||||
@@ -38,13 +38,13 @@ class Kernel extends ConsoleKernel
|
|||||||
$schedule->job(new CleanupInstanceStuffsJob)->everyMinute()->onOneServer();
|
$schedule->job(new CleanupInstanceStuffsJob)->everyMinute()->onOneServer();
|
||||||
// Server Jobs
|
// Server Jobs
|
||||||
$this->check_scheduled_backups($schedule);
|
$this->check_scheduled_backups($schedule);
|
||||||
// $this->check_resources($schedule);
|
$this->check_resources($schedule);
|
||||||
$this->check_scheduled_tasks($schedule);
|
$this->check_scheduled_tasks($schedule);
|
||||||
$schedule->command('uploads:clear')->everyTwoMinutes();
|
$schedule->command('uploads:clear')->everyTwoMinutes();
|
||||||
|
|
||||||
$schedule->command('telescope:prune')->daily();
|
$schedule->command('telescope:prune')->daily();
|
||||||
|
|
||||||
$schedule->job(new PullHelperImageJob)->everyFiveMinutes()->onOneServer();
|
$schedule->job(new CheckHelperImageJob)->everyFiveMinutes()->onOneServer();
|
||||||
} else {
|
} else {
|
||||||
// Instance Jobs
|
// Instance Jobs
|
||||||
$schedule->command('horizon:snapshot')->everyFiveMinutes();
|
$schedule->command('horizon:snapshot')->everyFiveMinutes();
|
||||||
@@ -80,7 +80,7 @@ class Kernel extends ConsoleKernel
|
|||||||
})->cron($settings->update_check_frequency)->timezone($settings->instance_timezone)->onOneServer();
|
})->cron($settings->update_check_frequency)->timezone($settings->instance_timezone)->onOneServer();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
$schedule->job(new PullHelperImageJob)
|
$schedule->job(new CheckHelperImageJob)
|
||||||
->cron($settings->update_check_frequency)
|
->cron($settings->update_check_frequency)
|
||||||
->timezone($settings->instance_timezone)
|
->timezone($settings->instance_timezone)
|
||||||
->onOneServer();
|
->onOneServer();
|
||||||
@@ -115,7 +115,10 @@ class Kernel extends ConsoleKernel
|
|||||||
$servers = $this->all_servers->where('ip', '!=', '1.2.3.4');
|
$servers = $this->all_servers->where('ip', '!=', '1.2.3.4');
|
||||||
}
|
}
|
||||||
foreach ($servers as $server) {
|
foreach ($servers as $server) {
|
||||||
$schedule->job(new ServerCheckJob($server))->everyMinute()->onOneServer();
|
$last_sentinel_update = $server->sentinel_updated_at;
|
||||||
|
if (Carbon::parse($last_sentinel_update)->isBefore(now()->subMinutes(4))) {
|
||||||
|
$schedule->job(new ServerCheckJob($server))->everyMinute()->onOneServer();
|
||||||
|
}
|
||||||
// $schedule->job(new ServerStorageCheckJob($server))->everyMinute()->onOneServer();
|
// $schedule->job(new ServerStorageCheckJob($server))->everyMinute()->onOneServer();
|
||||||
$serverTimezone = $server->settings->server_timezone;
|
$serverTimezone = $server->settings->server_timezone;
|
||||||
if ($server->settings->force_docker_cleanup) {
|
if ($server->settings->force_docker_cleanup) {
|
||||||
|
@@ -1579,11 +1579,16 @@ class ApplicationsController extends Controller
|
|||||||
$request->offsetUnset('docker_compose_domains');
|
$request->offsetUnset('docker_compose_domains');
|
||||||
}
|
}
|
||||||
$instantDeploy = $request->instant_deploy;
|
$instantDeploy = $request->instant_deploy;
|
||||||
|
$isStatic = $request->is_static;
|
||||||
|
$useBuildServer = $request->use_build_server;
|
||||||
|
|
||||||
$use_build_server = $request->use_build_server;
|
if (isset($useBuildServer)) {
|
||||||
|
$application->settings->is_build_server_enabled = $useBuildServer;
|
||||||
|
$application->settings->save();
|
||||||
|
}
|
||||||
|
|
||||||
if (isset($use_build_server)) {
|
if (isset($isStatic)) {
|
||||||
$application->settings->is_build_server_enabled = $use_build_server;
|
$application->settings->is_static = $isStatic;
|
||||||
$application->settings->save();
|
$application->settings->save();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -160,7 +160,7 @@ class OtherController extends Controller
|
|||||||
#[OA\Get(
|
#[OA\Get(
|
||||||
summary: 'Healthcheck',
|
summary: 'Healthcheck',
|
||||||
description: 'Healthcheck endpoint.',
|
description: 'Healthcheck endpoint.',
|
||||||
path: '/healthcheck',
|
path: '/health',
|
||||||
operationId: 'healthcheck',
|
operationId: 'healthcheck',
|
||||||
responses: [
|
responses: [
|
||||||
new OA\Response(
|
new OA\Response(
|
||||||
|
@@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
namespace App\Http\Controllers\Api;
|
namespace App\Http\Controllers\Api;
|
||||||
|
|
||||||
|
use App\Actions\Server\DeleteServer;
|
||||||
use App\Actions\Server\ValidateServer;
|
use App\Actions\Server\ValidateServer;
|
||||||
use App\Enums\ProxyStatus;
|
use App\Enums\ProxyStatus;
|
||||||
use App\Enums\ProxyTypes;
|
use App\Enums\ProxyTypes;
|
||||||
@@ -726,6 +727,7 @@ class ServersController extends Controller
|
|||||||
return response()->json(['message' => 'Server has resources, so you need to delete them before.'], 400);
|
return response()->json(['message' => 'Server has resources, so you need to delete them before.'], 400);
|
||||||
}
|
}
|
||||||
$server->delete();
|
$server->delete();
|
||||||
|
DeleteServer::dispatch($server);
|
||||||
|
|
||||||
return response()->json(['message' => 'Server deleted.']);
|
return response()->json(['message' => 'Server deleted.']);
|
||||||
}
|
}
|
||||||
|
40
app/Jobs/CheckHelperImageJob.php
Normal file
40
app/Jobs/CheckHelperImageJob.php
Normal file
@@ -0,0 +1,40 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Jobs;
|
||||||
|
|
||||||
|
use Illuminate\Bus\Queueable;
|
||||||
|
use Illuminate\Contracts\Queue\ShouldBeEncrypted;
|
||||||
|
use Illuminate\Contracts\Queue\ShouldQueue;
|
||||||
|
use Illuminate\Foundation\Bus\Dispatchable;
|
||||||
|
use Illuminate\Queue\InteractsWithQueue;
|
||||||
|
use Illuminate\Queue\SerializesModels;
|
||||||
|
use Illuminate\Support\Facades\Http;
|
||||||
|
|
||||||
|
class CheckHelperImageJob implements ShouldBeEncrypted, ShouldQueue
|
||||||
|
{
|
||||||
|
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
|
||||||
|
|
||||||
|
public $timeout = 1000;
|
||||||
|
|
||||||
|
public function __construct() {}
|
||||||
|
|
||||||
|
public function handle(): void
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
$response = Http::retry(3, 1000)->get('https://cdn.coollabs.io/coolify/versions.json');
|
||||||
|
if ($response->successful()) {
|
||||||
|
$versions = $response->json();
|
||||||
|
$settings = instanceSettings();
|
||||||
|
$latest_version = data_get($versions, 'coolify.helper.version');
|
||||||
|
$current_version = $settings->helper_version;
|
||||||
|
if (version_compare($latest_version, $current_version, '>')) {
|
||||||
|
$settings->update(['helper_version' => $latest_version]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
} catch (\Throwable $e) {
|
||||||
|
send_internal_notification('CheckHelperImageJob failed with: '.$e->getMessage());
|
||||||
|
throw $e;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@@ -504,8 +504,6 @@ class DatabaseBackupJob implements ShouldBeEncrypted, ShouldQueue
|
|||||||
$network = $this->database->destination->network;
|
$network = $this->database->destination->network;
|
||||||
}
|
}
|
||||||
|
|
||||||
$this->ensureHelperImageAvailable();
|
|
||||||
|
|
||||||
$fullImageName = $this->getFullImageName();
|
$fullImageName = $this->getFullImageName();
|
||||||
|
|
||||||
if (isDev()) {
|
if (isDev()) {
|
||||||
@@ -538,35 +536,6 @@ class DatabaseBackupJob implements ShouldBeEncrypted, ShouldQueue
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private function ensureHelperImageAvailable(): void
|
|
||||||
{
|
|
||||||
$fullImageName = $this->getFullImageName();
|
|
||||||
|
|
||||||
$imageExists = $this->checkImageExists($fullImageName);
|
|
||||||
|
|
||||||
if (! $imageExists) {
|
|
||||||
$this->pullHelperImage($fullImageName);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private function checkImageExists(string $fullImageName): bool
|
|
||||||
{
|
|
||||||
$result = instant_remote_process(["docker image inspect {$fullImageName} >/dev/null 2>&1 && echo 'exists' || echo 'not exists'"], $this->server, false);
|
|
||||||
|
|
||||||
return trim($result) === 'exists';
|
|
||||||
}
|
|
||||||
|
|
||||||
private function pullHelperImage(string $fullImageName): void
|
|
||||||
{
|
|
||||||
try {
|
|
||||||
instant_remote_process(["docker pull {$fullImageName}"], $this->server);
|
|
||||||
} catch (\Exception $e) {
|
|
||||||
$errorMessage = 'Failed to pull helper image: '.$e->getMessage();
|
|
||||||
$this->add_to_backup_output($errorMessage);
|
|
||||||
throw new \RuntimeException($errorMessage);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private function getFullImageName(): string
|
private function getFullImageName(): string
|
||||||
{
|
{
|
||||||
$settings = instanceSettings();
|
$settings = instanceSettings();
|
||||||
|
@@ -9,7 +9,6 @@ use Illuminate\Contracts\Queue\ShouldQueue;
|
|||||||
use Illuminate\Foundation\Bus\Dispatchable;
|
use Illuminate\Foundation\Bus\Dispatchable;
|
||||||
use Illuminate\Queue\InteractsWithQueue;
|
use Illuminate\Queue\InteractsWithQueue;
|
||||||
use Illuminate\Queue\SerializesModels;
|
use Illuminate\Queue\SerializesModels;
|
||||||
use Illuminate\Support\Facades\Http;
|
|
||||||
|
|
||||||
class PullHelperImageJob implements ShouldBeEncrypted, ShouldQueue
|
class PullHelperImageJob implements ShouldBeEncrypted, ShouldQueue
|
||||||
{
|
{
|
||||||
@@ -17,28 +16,15 @@ class PullHelperImageJob implements ShouldBeEncrypted, ShouldQueue
|
|||||||
|
|
||||||
public $timeout = 1000;
|
public $timeout = 1000;
|
||||||
|
|
||||||
public function __construct() {}
|
public function __construct(public Server $server) {}
|
||||||
|
|
||||||
public function handle(): void
|
public function handle(): void
|
||||||
{
|
{
|
||||||
try {
|
try {
|
||||||
$response = Http::retry(3, 1000)->get('https://cdn.coollabs.io/coolify/versions.json');
|
$helperImage = config('coolify.helper_image');
|
||||||
if ($response->successful()) {
|
$latest_version = instanceSettings()->helper_version;
|
||||||
$versions = $response->json();
|
instant_remote_process(["docker pull -q {$helperImage}:{$latest_version}"], $this->server, false);
|
||||||
$settings = instanceSettings();
|
|
||||||
$latest_version = data_get($versions, 'coolify.helper.version');
|
|
||||||
$current_version = $settings->helper_version;
|
|
||||||
if (version_compare($latest_version, $current_version, '>')) {
|
|
||||||
// New version available
|
|
||||||
// $helperImage = config('coolify.helper_image');
|
|
||||||
// instant_remote_process(["docker pull -q {$helperImage}:{$latest_version}"], $this->server);
|
|
||||||
$settings->update(['helper_version' => $latest_version]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
} catch (\Throwable $e) {
|
} catch (\Throwable $e) {
|
||||||
send_internal_notification('PullHelperImageJob failed with: '.$e->getMessage());
|
|
||||||
ray($e->getMessage());
|
|
||||||
throw $e;
|
throw $e;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -2,17 +2,23 @@
|
|||||||
|
|
||||||
namespace App\Jobs;
|
namespace App\Jobs;
|
||||||
|
|
||||||
|
use App\Actions\Database\StartDatabaseProxy;
|
||||||
|
use App\Actions\Database\StopDatabaseProxy;
|
||||||
|
use App\Actions\Proxy\CheckProxy;
|
||||||
use App\Actions\Proxy\StartProxy;
|
use App\Actions\Proxy\StartProxy;
|
||||||
|
use App\Actions\Server\InstallLogDrain;
|
||||||
|
use App\Actions\Shared\ComplexStatusCheck;
|
||||||
use App\Models\Application;
|
use App\Models\Application;
|
||||||
use App\Models\ApplicationPreview;
|
use App\Models\ApplicationPreview;
|
||||||
use App\Models\Server;
|
use App\Models\Server;
|
||||||
|
use App\Models\ServiceApplication;
|
||||||
|
use App\Models\ServiceDatabase;
|
||||||
use Illuminate\Bus\Queueable;
|
use Illuminate\Bus\Queueable;
|
||||||
use Illuminate\Contracts\Queue\ShouldQueue;
|
use Illuminate\Contracts\Queue\ShouldQueue;
|
||||||
use Illuminate\Foundation\Bus\Dispatchable;
|
use Illuminate\Foundation\Bus\Dispatchable;
|
||||||
use Illuminate\Queue\InteractsWithQueue;
|
use Illuminate\Queue\InteractsWithQueue;
|
||||||
use Illuminate\Queue\SerializesModels;
|
use Illuminate\Queue\SerializesModels;
|
||||||
use Illuminate\Support\Collection;
|
use Illuminate\Support\Collection;
|
||||||
use Illuminate\Support\Facades\Log;
|
|
||||||
|
|
||||||
class PushServerUpdateJob implements ShouldQueue
|
class PushServerUpdateJob implements ShouldQueue
|
||||||
{
|
{
|
||||||
@@ -20,7 +26,45 @@ class PushServerUpdateJob implements ShouldQueue
|
|||||||
|
|
||||||
public $tries = 1;
|
public $tries = 1;
|
||||||
|
|
||||||
public $timeout = 60;
|
public $timeout = 30;
|
||||||
|
|
||||||
|
public Collection $containers;
|
||||||
|
|
||||||
|
public Collection $applications;
|
||||||
|
|
||||||
|
public Collection $previews;
|
||||||
|
|
||||||
|
public Collection $databases;
|
||||||
|
|
||||||
|
public Collection $services;
|
||||||
|
|
||||||
|
public Collection $allApplicationIds;
|
||||||
|
|
||||||
|
public Collection $allDatabaseUuids;
|
||||||
|
|
||||||
|
public Collection $allTcpProxyUuids;
|
||||||
|
|
||||||
|
public Collection $allServiceApplicationIds;
|
||||||
|
|
||||||
|
public Collection $allApplicationPreviewsIds;
|
||||||
|
|
||||||
|
public Collection $allServiceDatabaseIds;
|
||||||
|
|
||||||
|
public Collection $allApplicationsWithAdditionalServers;
|
||||||
|
|
||||||
|
public Collection $foundApplicationIds;
|
||||||
|
|
||||||
|
public Collection $foundDatabaseUuids;
|
||||||
|
|
||||||
|
public Collection $foundServiceApplicationIds;
|
||||||
|
|
||||||
|
public Collection $foundServiceDatabaseIds;
|
||||||
|
|
||||||
|
public Collection $foundApplicationPreviewsIds;
|
||||||
|
|
||||||
|
public bool $foundProxy = false;
|
||||||
|
|
||||||
|
public bool $foundLogDrainContainer = false;
|
||||||
|
|
||||||
public function backoff(): int
|
public function backoff(): int
|
||||||
{
|
{
|
||||||
@@ -29,108 +73,335 @@ class PushServerUpdateJob implements ShouldQueue
|
|||||||
|
|
||||||
public function __construct(public Server $server, public $data)
|
public function __construct(public Server $server, public $data)
|
||||||
{
|
{
|
||||||
// TODO: Handle multiple servers
|
$this->containers = collect();
|
||||||
// TODO: Handle Preview deployments
|
$this->foundApplicationIds = collect();
|
||||||
// TODO: Handle DB TCP proxies
|
$this->foundDatabaseUuids = collect();
|
||||||
// TODO: Handle DBs
|
$this->foundServiceApplicationIds = collect();
|
||||||
// TODO: Handle services
|
$this->foundApplicationPreviewsIds = collect();
|
||||||
// TODO: Handle proxies
|
$this->foundServiceDatabaseIds = collect();
|
||||||
|
$this->allApplicationIds = collect();
|
||||||
|
$this->allDatabaseUuids = collect();
|
||||||
|
$this->allTcpProxyUuids = collect();
|
||||||
|
$this->allServiceApplicationIds = collect();
|
||||||
|
$this->allServiceDatabaseIds = collect();
|
||||||
}
|
}
|
||||||
|
|
||||||
public function handle()
|
public function handle()
|
||||||
{
|
{
|
||||||
if (! $this->data) {
|
try {
|
||||||
throw new \Exception('No data provided');
|
if (! $this->data) {
|
||||||
}
|
throw new \Exception('No data provided');
|
||||||
$data = collect($this->data);
|
}
|
||||||
$containers = collect(data_get($data, 'containers'));
|
$data = collect($this->data);
|
||||||
if ($containers->isEmpty()) {
|
|
||||||
return;
|
$this->serverStatus();
|
||||||
}
|
|
||||||
$foundApplicationIds = collect();
|
$this->server->sentinelHeartbeat();
|
||||||
$foundServiceIds = collect();
|
|
||||||
$foundProxy = false;
|
$this->containers = collect(data_get($data, 'containers'));
|
||||||
foreach ($containers as $container) {
|
if ($this->containers->isEmpty()) {
|
||||||
$containerStatus = data_get($container, 'state', 'exited');
|
return;
|
||||||
$containerHealth = data_get($container, 'health_status', 'unhealthy');
|
}
|
||||||
$containerStatus = "$containerStatus ($containerHealth)";
|
$this->applications = $this->server->applications();
|
||||||
$labels = collect(data_get($container, 'labels'));
|
$this->databases = $this->server->databases();
|
||||||
$coolify_managed = $labels->has('coolify.managed');
|
$this->previews = $this->server->previews();
|
||||||
if ($coolify_managed) {
|
$this->services = $this->server->services()->get();
|
||||||
if ($labels->has('coolify.applicationId')) {
|
$this->allApplicationIds = $this->applications->filter(function ($application) {
|
||||||
$applicationId = $labels->get('coolify.applicationId');
|
return $application->additional_servers->count() === 0;
|
||||||
$pullRequestId = data_get($labels, 'coolify.pullRequestId', '0');
|
})->pluck('id');
|
||||||
$foundApplicationIds->push($applicationId);
|
$this->allApplicationsWithAdditionalServers = $this->applications->filter(function ($application) {
|
||||||
try {
|
return $application->additional_servers->count() > 0;
|
||||||
$this->updateApplicationStatus($applicationId, $pullRequestId, $containerStatus);
|
});
|
||||||
} catch (\Exception $e) {
|
$this->allApplicationPreviewsIds = $this->previews->pluck('id');
|
||||||
Log::error($e);
|
$this->allDatabaseUuids = $this->databases->pluck('uuid');
|
||||||
}
|
$this->allTcpProxyUuids = $this->databases->where('is_public', true)->pluck('uuid');
|
||||||
} elseif ($labels->has('coolify.serviceId')) {
|
$this->services->each(function ($service) {
|
||||||
$serviceId = $labels->get('coolify.serviceId');
|
$service->applications()->pluck('id')->each(function ($applicationId) {
|
||||||
$foundServiceIds->push($serviceId);
|
$this->allServiceApplicationIds->push($applicationId);
|
||||||
Log::info("Service: $serviceId, $containerStatus");
|
});
|
||||||
} else {
|
$service->databases()->pluck('id')->each(function ($databaseId) {
|
||||||
|
$this->allServiceDatabaseIds->push($databaseId);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
ray('allServiceApplicationIds', ['allServiceApplicationIds' => $this->allServiceApplicationIds]);
|
||||||
|
|
||||||
|
foreach ($this->containers as $container) {
|
||||||
|
$containerStatus = data_get($container, 'state', 'exited');
|
||||||
|
$containerHealth = data_get($container, 'health_status', 'unhealthy');
|
||||||
|
$containerStatus = "$containerStatus ($containerHealth)";
|
||||||
|
$labels = collect(data_get($container, 'labels'));
|
||||||
|
$coolify_managed = $labels->has('coolify.managed');
|
||||||
|
if ($coolify_managed) {
|
||||||
$name = data_get($container, 'name');
|
$name = data_get($container, 'name');
|
||||||
$uuid = $labels->get('com.docker.compose.service');
|
if ($name === 'coolify-log-drain' && $this->isRunning($containerStatus)) {
|
||||||
$type = $labels->get('coolify.type');
|
$this->foundLogDrainContainer = true;
|
||||||
if ($name === 'coolify-proxy') {
|
}
|
||||||
$foundProxy = true;
|
if ($labels->has('coolify.applicationId')) {
|
||||||
Log::info("Proxy: $uuid, $containerStatus");
|
$applicationId = $labels->get('coolify.applicationId');
|
||||||
} elseif ($type === 'service') {
|
$pullRequestId = data_get($labels, 'coolify.pullRequestId', '0');
|
||||||
Log::info("Service: $uuid, $containerStatus");
|
try {
|
||||||
|
if ($pullRequestId === '0') {
|
||||||
|
if ($this->allApplicationIds->contains($applicationId) && $this->isRunning($containerStatus)) {
|
||||||
|
$this->foundApplicationIds->push($applicationId);
|
||||||
|
}
|
||||||
|
$this->updateApplicationStatus($applicationId, $containerStatus);
|
||||||
|
} else {
|
||||||
|
if ($this->allApplicationPreviewsIds->contains($applicationId) && $this->isRunning($containerStatus)) {
|
||||||
|
$this->foundApplicationPreviewsIds->push($applicationId);
|
||||||
|
}
|
||||||
|
$this->updateApplicationPreviewStatus($applicationId, $containerStatus);
|
||||||
|
}
|
||||||
|
} catch (\Exception $e) {
|
||||||
|
ray()->error($e);
|
||||||
|
}
|
||||||
|
} elseif ($labels->has('coolify.serviceId')) {
|
||||||
|
$serviceId = $labels->get('coolify.serviceId');
|
||||||
|
$subType = $labels->get('coolify.service.subType');
|
||||||
|
$subId = $labels->get('coolify.service.subId');
|
||||||
|
if ($subType === 'application' && $this->isRunning($containerStatus)) {
|
||||||
|
$this->foundServiceApplicationIds->push($subId);
|
||||||
|
$this->updateServiceSubStatus($serviceId, $subType, $subId, $containerStatus);
|
||||||
|
} elseif ($subType === 'database' && $this->isRunning($containerStatus)) {
|
||||||
|
$this->foundServiceDatabaseIds->push($subId);
|
||||||
|
$this->updateServiceSubStatus($serviceId, $subType, $subId, $containerStatus);
|
||||||
|
}
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
Log::info("Database: $uuid, $containerStatus");
|
$uuid = $labels->get('com.docker.compose.service');
|
||||||
|
$type = $labels->get('coolify.type');
|
||||||
|
if ($name === 'coolify-proxy' && $this->isRunning($containerStatus)) {
|
||||||
|
$this->foundProxy = true;
|
||||||
|
} elseif ($type === 'service' && $this->isRunning($containerStatus)) {
|
||||||
|
ray("Service: $uuid, $containerStatus");
|
||||||
|
} else {
|
||||||
|
if ($this->allDatabaseUuids->contains($uuid) && $this->isRunning($containerStatus)) {
|
||||||
|
$this->foundDatabaseUuids->push($uuid);
|
||||||
|
if ($this->allTcpProxyUuids->contains($uuid) && $this->isRunning($containerStatus)) {
|
||||||
|
$this->updateDatabaseStatus($uuid, $containerStatus, tcpProxy: true);
|
||||||
|
} else {
|
||||||
|
$this->updateDatabaseStatus($uuid, $containerStatus, tcpProxy: false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$this->updateProxyStatus();
|
||||||
|
|
||||||
|
$this->updateNotFoundApplicationStatus();
|
||||||
|
$this->updateNotFoundApplicationPreviewStatus();
|
||||||
|
$this->updateNotFoundDatabaseStatus();
|
||||||
|
$this->updateNotFoundServiceStatus();
|
||||||
|
|
||||||
|
$this->updateAdditionalServersStatus();
|
||||||
|
|
||||||
|
$this->checkLogDrainContainer();
|
||||||
|
|
||||||
|
} catch (\Exception $e) {
|
||||||
|
throw $e;
|
||||||
}
|
}
|
||||||
|
|
||||||
// If proxy is not found, start it
|
}
|
||||||
if (! $foundProxy && $this->server->isProxyShouldRun()) {
|
|
||||||
Log::info('Proxy not found, starting it');
|
|
||||||
StartProxy::dispatch($this->server);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Update not found applications
|
private function serverStatus()
|
||||||
$allApplicationIds = $this->server->applications()->pluck('id');
|
{
|
||||||
$notFoundApplicationIds = $allApplicationIds->diff($foundApplicationIds);
|
if ($this->server->isFunctional() === false) {
|
||||||
|
throw new \Exception('Server is not ready.');
|
||||||
|
}
|
||||||
|
if ($this->server->status() === false) {
|
||||||
|
throw new \Exception('Server is not reachable.');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private function updateApplicationStatus(string $applicationId, string $containerStatus)
|
||||||
|
{
|
||||||
|
$application = $this->applications->where('id', $applicationId)->first();
|
||||||
|
if (! $application) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
$application->status = $containerStatus;
|
||||||
|
$application->save();
|
||||||
|
ray('Application updated', ['application_id' => $applicationId, 'status' => $containerStatus]);
|
||||||
|
}
|
||||||
|
|
||||||
|
private function updateApplicationPreviewStatus(string $applicationId, string $containerStatus)
|
||||||
|
{
|
||||||
|
$application = $this->previews->where('id', $applicationId)->first();
|
||||||
|
if (! $application) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
$application->status = $containerStatus;
|
||||||
|
$application->save();
|
||||||
|
ray('Application preview updated', ['application_id' => $applicationId, 'status' => $containerStatus]);
|
||||||
|
}
|
||||||
|
|
||||||
|
private function updateNotFoundApplicationStatus()
|
||||||
|
{
|
||||||
|
$notFoundApplicationIds = $this->allApplicationIds->diff($this->foundApplicationIds);
|
||||||
if ($notFoundApplicationIds->isNotEmpty()) {
|
if ($notFoundApplicationIds->isNotEmpty()) {
|
||||||
Log::info('Not found application ids', ['application_ids' => $notFoundApplicationIds]);
|
ray('Not found application ids', ['application_ids' => $notFoundApplicationIds]);
|
||||||
$this->updateNotFoundApplications($notFoundApplicationIds);
|
$notFoundApplicationIds->each(function ($applicationId) {
|
||||||
|
ray('Updating application status', ['application_id' => $applicationId, 'status' => 'exited']);
|
||||||
|
$application = Application::find($applicationId);
|
||||||
|
if ($application) {
|
||||||
|
$application->status = 'exited';
|
||||||
|
$application->save();
|
||||||
|
ray('Application status updated', ['application_id' => $applicationId, 'status' => 'exited']);
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private function updateApplicationStatus(string $applicationId, string $pullRequestId, string $containerStatus)
|
private function updateNotFoundApplicationPreviewStatus()
|
||||||
{
|
{
|
||||||
if ($pullRequestId === '0') {
|
$notFoundApplicationPreviewsIds = $this->allApplicationPreviewsIds->diff($this->foundApplicationPreviewsIds);
|
||||||
$application = Application::find($applicationId);
|
if ($notFoundApplicationPreviewsIds->isNotEmpty()) {
|
||||||
if (! $application) {
|
ray('Not found application previews ids', ['application_previews_ids' => $notFoundApplicationPreviewsIds]);
|
||||||
return;
|
$notFoundApplicationPreviewsIds->each(function ($applicationPreviewId) {
|
||||||
|
ray('Updating application preview status', ['application_preview_id' => $applicationPreviewId, 'status' => 'exited']);
|
||||||
|
$applicationPreview = ApplicationPreview::find($applicationPreviewId);
|
||||||
|
if ($applicationPreview) {
|
||||||
|
$applicationPreview->status = 'exited';
|
||||||
|
$applicationPreview->save();
|
||||||
|
ray('Application preview status updated', ['application_preview_id' => $applicationPreviewId, 'status' => 'exited']);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private function updateProxyStatus()
|
||||||
|
{
|
||||||
|
// If proxy is not found, start it
|
||||||
|
if ($this->server->isProxyShouldRun()) {
|
||||||
|
if ($this->foundProxy === false) {
|
||||||
|
try {
|
||||||
|
if (CheckProxy::run($this->server)) {
|
||||||
|
StartProxy::run($this->server, false);
|
||||||
|
}
|
||||||
|
} catch (\Throwable $e) {
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
$connectProxyToDockerNetworks = connectProxyToNetworks($this->server);
|
||||||
|
instant_remote_process($connectProxyToDockerNetworks, $this->server, false);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
private function updateDatabaseStatus(string $databaseUuid, string $containerStatus, bool $tcpProxy = false)
|
||||||
|
{
|
||||||
|
$database = $this->databases->where('uuid', $databaseUuid)->first();
|
||||||
|
if (! $database) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
$database->status = $containerStatus;
|
||||||
|
$database->save();
|
||||||
|
ray('Database status updated', ['database_uuid' => $databaseUuid, 'status' => $containerStatus]);
|
||||||
|
if ($this->isRunning($containerStatus) && $tcpProxy) {
|
||||||
|
$tcpProxyContainerFound = $this->containers->filter(function ($value, $key) use ($databaseUuid) {
|
||||||
|
return data_get($value, 'name') === "$databaseUuid-proxy" && data_get($value, 'state') === 'running';
|
||||||
|
})->first();
|
||||||
|
if (! $tcpProxyContainerFound) {
|
||||||
|
ray('Starting TCP proxy for database', ['database_uuid' => $databaseUuid]);
|
||||||
|
StartDatabaseProxy::dispatch($database);
|
||||||
|
} else {
|
||||||
|
ray('TCP proxy for database found in containers', ['database_uuid' => $databaseUuid]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private function updateNotFoundDatabaseStatus()
|
||||||
|
{
|
||||||
|
$notFoundDatabaseUuids = $this->allDatabaseUuids->diff($this->foundDatabaseUuids);
|
||||||
|
if ($notFoundDatabaseUuids->isNotEmpty()) {
|
||||||
|
ray('Not found database uuids', ['database_uuids' => $notFoundDatabaseUuids]);
|
||||||
|
$notFoundDatabaseUuids->each(function ($databaseUuid) {
|
||||||
|
ray('Updating database status', ['database_uuid' => $databaseUuid, 'status' => 'exited']);
|
||||||
|
$database = $this->databases->where('uuid', $databaseUuid)->first();
|
||||||
|
if ($database) {
|
||||||
|
$database->status = 'exited';
|
||||||
|
$database->save();
|
||||||
|
ray('Database status updated', ['database_uuid' => $databaseUuid, 'status' => 'exited']);
|
||||||
|
ray('Database is public', ['database_uuid' => $databaseUuid, 'is_public' => $database->is_public]);
|
||||||
|
if ($database->is_public) {
|
||||||
|
ray('Stopping TCP proxy for database', ['database_uuid' => $databaseUuid]);
|
||||||
|
StopDatabaseProxy::dispatch($database);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private function updateServiceSubStatus(string $serviceId, string $subType, string $subId, string $containerStatus)
|
||||||
|
{
|
||||||
|
$service = $this->services->where('id', $serviceId)->first();
|
||||||
|
if (! $service) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if ($subType === 'application') {
|
||||||
|
$application = $service->applications()->where('id', $subId)->first();
|
||||||
$application->status = $containerStatus;
|
$application->status = $containerStatus;
|
||||||
$application->save();
|
$application->save();
|
||||||
Log::info('Application updated', ['application_id' => $applicationId, 'status' => $containerStatus]);
|
ray('Service application updated', ['service_id' => $serviceId, 'sub_type' => $subType, 'sub_id' => $subId, 'status' => $containerStatus]);
|
||||||
|
} elseif ($subType === 'database') {
|
||||||
|
$database = $service->databases()->where('id', $subId)->first();
|
||||||
|
$database->status = $containerStatus;
|
||||||
|
$database->save();
|
||||||
|
ray('Service database updated', ['service_id' => $serviceId, 'sub_type' => $subType, 'sub_id' => $subId, 'status' => $containerStatus]);
|
||||||
} else {
|
} else {
|
||||||
$application = ApplicationPreview::where('application_id', $applicationId)->where('pull_request_id', $pullRequestId)->first();
|
ray()->warning('Unknown sub type', ['service_id' => $serviceId, 'sub_type' => $subType, 'sub_id' => $subId, 'status' => $containerStatus]);
|
||||||
if (! $application) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
$application->status = $containerStatus;
|
|
||||||
$application->save();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private function updateNotFoundApplications(Collection $applicationIds)
|
private function updateNotFoundServiceStatus()
|
||||||
{
|
{
|
||||||
$applicationIds->each(function ($applicationId) {
|
$notFoundServiceApplicationIds = $this->allServiceApplicationIds->diff($this->foundServiceApplicationIds);
|
||||||
Log::info('Updating application status', ['application_id' => $applicationId, 'status' => 'exited']);
|
$notFoundServiceDatabaseIds = $this->allServiceDatabaseIds->diff($this->foundServiceDatabaseIds);
|
||||||
$application = Application::find($applicationId);
|
if ($notFoundServiceApplicationIds->isNotEmpty()) {
|
||||||
if ($application) {
|
ray('Not found service application ids', ['service_application_ids' => $notFoundServiceApplicationIds]);
|
||||||
$application->status = 'exited';
|
$notFoundServiceApplicationIds->each(function ($serviceApplicationId) {
|
||||||
$application->save();
|
ray('Updating service application status', ['service_application_id' => $serviceApplicationId, 'status' => 'exited']);
|
||||||
Log::info('Application status updated', ['application_id' => $applicationId, 'status' => 'exited']);
|
$application = ServiceApplication::find($serviceApplicationId);
|
||||||
}
|
if ($application) {
|
||||||
|
$application->status = 'exited';
|
||||||
|
$application->save();
|
||||||
|
ray('Service application status updated', ['service_application_id' => $serviceApplicationId, 'status' => 'exited']);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
if ($notFoundServiceDatabaseIds->isNotEmpty()) {
|
||||||
|
ray('Not found service database ids', ['service_database_ids' => $notFoundServiceDatabaseIds]);
|
||||||
|
$notFoundServiceDatabaseIds->each(function ($serviceDatabaseId) {
|
||||||
|
ray('Updating service database status', ['service_database_id' => $serviceDatabaseId, 'status' => 'exited']);
|
||||||
|
$database = ServiceDatabase::find($serviceDatabaseId);
|
||||||
|
if ($database) {
|
||||||
|
$database->status = 'exited';
|
||||||
|
$database->save();
|
||||||
|
ray('Service database status updated', ['service_database_id' => $serviceDatabaseId, 'status' => 'exited']);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private function updateAdditionalServersStatus()
|
||||||
|
{
|
||||||
|
$this->allApplicationsWithAdditionalServers->each(function ($application) {
|
||||||
|
ray('Updating additional servers status for application', ['application_id' => $application->id]);
|
||||||
|
ComplexStatusCheck::run($application);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private function isRunning(string $containerStatus)
|
||||||
|
{
|
||||||
|
return str($containerStatus)->contains('running');
|
||||||
|
}
|
||||||
|
|
||||||
|
private function checkLogDrainContainer()
|
||||||
|
{
|
||||||
|
if ($this->server->isLogDrainEnabled() && $this->foundLogDrainContainer === false) {
|
||||||
|
InstallLogDrain::dispatch($this->server);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@@ -72,6 +72,32 @@ class ServerCheckJob implements ShouldBeEncrypted, ShouldQueue
|
|||||||
if ($this->server->isLogDrainEnabled()) {
|
if ($this->server->isLogDrainEnabled()) {
|
||||||
$this->checkLogDrainContainer();
|
$this->checkLogDrainContainer();
|
||||||
}
|
}
|
||||||
|
if ($this->server->proxySet() && ! $this->server->proxy->force_stop) {
|
||||||
|
$this->server->proxyType();
|
||||||
|
$foundProxyContainer = $this->containers->filter(function ($value, $key) {
|
||||||
|
if ($this->server->isSwarm()) {
|
||||||
|
return data_get($value, 'Spec.Name') === 'coolify-proxy_traefik';
|
||||||
|
} else {
|
||||||
|
return data_get($value, 'Name') === '/coolify-proxy';
|
||||||
|
}
|
||||||
|
})->first();
|
||||||
|
if (! $foundProxyContainer) {
|
||||||
|
try {
|
||||||
|
$shouldStart = CheckProxy::run($this->server);
|
||||||
|
if ($shouldStart) {
|
||||||
|
StartProxy::run($this->server, false);
|
||||||
|
$this->server->team?->notify(new ContainerRestarted('coolify-proxy', $this->server));
|
||||||
|
}
|
||||||
|
} catch (\Throwable $e) {
|
||||||
|
ray($e);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
$this->server->proxy->status = data_get($foundProxyContainer, 'State.Status');
|
||||||
|
$this->server->save();
|
||||||
|
$connectProxyToDockerNetworks = connectProxyToNetworks($this->server);
|
||||||
|
instant_remote_process($connectProxyToDockerNetworks, $this->server, false);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
} catch (\Throwable $e) {
|
} catch (\Throwable $e) {
|
||||||
@@ -387,31 +413,5 @@ class ServerCheckJob implements ShouldBeEncrypted, ShouldQueue
|
|||||||
}
|
}
|
||||||
// $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
|
|
||||||
$this->server->proxyType();
|
|
||||||
$foundProxyContainer = $this->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);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -66,7 +66,7 @@ class Show extends Component
|
|||||||
return ! $alreadyAddedNetworks->contains('network', $network['Name']);
|
return ! $alreadyAddedNetworks->contains('network', $network['Name']);
|
||||||
});
|
});
|
||||||
if ($this->networks->count() === 0) {
|
if ($this->networks->count() === 0) {
|
||||||
$this->dispatch('success', 'No new networks found.');
|
$this->dispatch('success', 'No new destinations found on this server.');
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@@ -241,7 +241,6 @@ class General extends Component
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public function updatedApplicationBuildPack()
|
public function updatedApplicationBuildPack()
|
||||||
{
|
{
|
||||||
if ($this->application->build_pack !== 'nixpacks') {
|
if ($this->application->build_pack !== 'nixpacks') {
|
||||||
@@ -314,7 +313,7 @@ class General extends Component
|
|||||||
public function set_redirect()
|
public function set_redirect()
|
||||||
{
|
{
|
||||||
try {
|
try {
|
||||||
$has_www = collect($this->application->fqdns)->filter(fn($fqdn) => str($fqdn)->contains('www.'))->count();
|
$has_www = collect($this->application->fqdns)->filter(fn ($fqdn) => str($fqdn)->contains('www.'))->count();
|
||||||
if ($has_www === 0 && $this->application->redirect === 'www') {
|
if ($has_www === 0 && $this->application->redirect === 'www') {
|
||||||
$this->dispatch('error', 'You want to redirect to www, but you do not have a www domain set.<br><br>Please add www to your domain list and as an A DNS record (if applicable).');
|
$this->dispatch('error', 'You want to redirect to www, but you do not have a www domain set.<br><br>Please add www to your domain list and as an A DNS record (if applicable).');
|
||||||
|
|
||||||
@@ -335,9 +334,15 @@ class General extends Component
|
|||||||
$this->application->fqdn = str($this->application->fqdn)->replaceStart(',', '')->trim();
|
$this->application->fqdn = str($this->application->fqdn)->replaceStart(',', '')->trim();
|
||||||
$this->application->fqdn = str($this->application->fqdn)->trim()->explode(',')->map(function ($domain) {
|
$this->application->fqdn = str($this->application->fqdn)->trim()->explode(',')->map(function ($domain) {
|
||||||
Url::fromString($domain, ['http', 'https']);
|
Url::fromString($domain, ['http', 'https']);
|
||||||
|
|
||||||
return str($domain)->trim()->lower();
|
return str($domain)->trim()->lower();
|
||||||
});
|
});
|
||||||
|
|
||||||
$this->application->fqdn = $this->application->fqdn->unique()->implode(',');
|
$this->application->fqdn = $this->application->fqdn->unique()->implode(',');
|
||||||
|
$warning = sslipDomainWarning($this->application->fqdn);
|
||||||
|
if ($warning) {
|
||||||
|
$this->dispatch('warning', __('warning.sslipdomain'));
|
||||||
|
}
|
||||||
$this->resetDefaultLabels();
|
$this->resetDefaultLabels();
|
||||||
|
|
||||||
if ($this->application->isDirty('redirect')) {
|
if ($this->application->isDirty('redirect')) {
|
||||||
@@ -403,17 +408,19 @@ class General extends Component
|
|||||||
}
|
}
|
||||||
$this->application->custom_labels = base64_encode($this->customLabels);
|
$this->application->custom_labels = base64_encode($this->customLabels);
|
||||||
$this->application->save();
|
$this->application->save();
|
||||||
$showToaster && $this->dispatch('success', 'Application settings updated!');
|
$showToaster && ! $warning && $this->dispatch('success', 'Application settings updated!');
|
||||||
} catch (\Throwable $e) {
|
} catch (\Throwable $e) {
|
||||||
$originalFqdn = $this->application->getOriginal('fqdn');
|
$originalFqdn = $this->application->getOriginal('fqdn');
|
||||||
if ($originalFqdn !== $this->application->fqdn) {
|
if ($originalFqdn !== $this->application->fqdn) {
|
||||||
$this->application->fqdn = $originalFqdn;
|
$this->application->fqdn = $originalFqdn;
|
||||||
}
|
}
|
||||||
|
|
||||||
return handleError($e, $this);
|
return handleError($e, $this);
|
||||||
} finally {
|
} finally {
|
||||||
$this->dispatch('configurationChanged');
|
$this->dispatch('configurationChanged');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public function downloadConfig()
|
public function downloadConfig()
|
||||||
{
|
{
|
||||||
$config = GenerateConfig::run($this->application, true);
|
$config = GenerateConfig::run($this->application, true);
|
||||||
@@ -423,7 +430,7 @@ class General extends Component
|
|||||||
echo $config;
|
echo $config;
|
||||||
}, $fileName, [
|
}, $fileName, [
|
||||||
'Content-Type' => 'application/json',
|
'Content-Type' => 'application/json',
|
||||||
'Content-Disposition' => 'attachment; filename=' . $fileName,
|
'Content-Disposition' => 'attachment; filename='.$fileName,
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -11,12 +11,21 @@ use Livewire\Component;
|
|||||||
|
|
||||||
class General extends Component
|
class General extends Component
|
||||||
{
|
{
|
||||||
protected $listeners = ['refresh'];
|
protected $listeners = [
|
||||||
|
'envsUpdated' => 'refresh',
|
||||||
|
'refresh',
|
||||||
|
];
|
||||||
|
|
||||||
public Server $server;
|
public Server $server;
|
||||||
|
|
||||||
public StandaloneRedis $database;
|
public StandaloneRedis $database;
|
||||||
|
|
||||||
|
public string $redis_username;
|
||||||
|
|
||||||
|
public string $redis_password;
|
||||||
|
|
||||||
|
public string $redis_version;
|
||||||
|
|
||||||
public ?string $db_url = null;
|
public ?string $db_url = null;
|
||||||
|
|
||||||
public ?string $db_url_public = null;
|
public ?string $db_url_public = null;
|
||||||
@@ -25,33 +34,33 @@ class General extends Component
|
|||||||
'database.name' => 'required',
|
'database.name' => 'required',
|
||||||
'database.description' => 'nullable',
|
'database.description' => 'nullable',
|
||||||
'database.redis_conf' => 'nullable',
|
'database.redis_conf' => 'nullable',
|
||||||
'database.redis_password' => 'required',
|
|
||||||
'database.image' => 'required',
|
'database.image' => 'required',
|
||||||
'database.ports_mappings' => 'nullable',
|
'database.ports_mappings' => 'nullable',
|
||||||
'database.is_public' => 'nullable|boolean',
|
'database.is_public' => 'nullable|boolean',
|
||||||
'database.public_port' => 'nullable|integer',
|
'database.public_port' => 'nullable|integer',
|
||||||
'database.is_log_drain_enabled' => 'nullable|boolean',
|
'database.is_log_drain_enabled' => 'nullable|boolean',
|
||||||
'database.custom_docker_run_options' => 'nullable',
|
'database.custom_docker_run_options' => 'nullable',
|
||||||
|
'redis_username' => 'required',
|
||||||
|
'redis_password' => 'required',
|
||||||
];
|
];
|
||||||
|
|
||||||
protected $validationAttributes = [
|
protected $validationAttributes = [
|
||||||
'database.name' => 'Name',
|
'database.name' => 'Name',
|
||||||
'database.description' => 'Description',
|
'database.description' => 'Description',
|
||||||
'database.redis_conf' => 'Redis Configuration',
|
'database.redis_conf' => 'Redis Configuration',
|
||||||
'database.redis_password' => 'Redis Password',
|
|
||||||
'database.image' => 'Image',
|
'database.image' => 'Image',
|
||||||
'database.ports_mappings' => 'Port Mapping',
|
'database.ports_mappings' => 'Port Mapping',
|
||||||
'database.is_public' => 'Is Public',
|
'database.is_public' => 'Is Public',
|
||||||
'database.public_port' => 'Public Port',
|
'database.public_port' => 'Public Port',
|
||||||
'database.custom_docker_run_options' => 'Custom Docker Options',
|
'database.custom_docker_run_options' => 'Custom Docker Options',
|
||||||
|
'redis_username' => 'Redis Username',
|
||||||
|
'redis_password' => 'Redis Password',
|
||||||
];
|
];
|
||||||
|
|
||||||
public function mount()
|
public function mount()
|
||||||
{
|
{
|
||||||
$this->db_url = $this->database->internal_db_url;
|
|
||||||
$this->db_url_public = $this->database->external_db_url;
|
|
||||||
$this->server = data_get($this->database, 'destination.server');
|
$this->server = data_get($this->database, 'destination.server');
|
||||||
|
$this->refreshView();
|
||||||
}
|
}
|
||||||
|
|
||||||
public function instantSaveAdvanced()
|
public function instantSaveAdvanced()
|
||||||
@@ -75,13 +84,24 @@ class General extends Component
|
|||||||
{
|
{
|
||||||
try {
|
try {
|
||||||
$this->validate();
|
$this->validate();
|
||||||
if ($this->database->redis_conf === '') {
|
|
||||||
$this->database->redis_conf = null;
|
if (version_compare($this->redis_version, '6.0', '>=')) {
|
||||||
|
$this->database->runtime_environment_variables()->updateOrCreate(
|
||||||
|
['key' => 'REDIS_USERNAME'],
|
||||||
|
['value' => $this->redis_username, 'standalone_redis_id' => $this->database->id]
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
$this->database->runtime_environment_variables()->updateOrCreate(
|
||||||
|
['key' => 'REDIS_PASSWORD'],
|
||||||
|
['value' => $this->redis_password, 'standalone_redis_id' => $this->database->id]
|
||||||
|
);
|
||||||
|
|
||||||
$this->database->save();
|
$this->database->save();
|
||||||
$this->dispatch('success', 'Database updated.');
|
$this->dispatch('success', 'Database updated.');
|
||||||
} catch (Exception $e) {
|
} catch (Exception $e) {
|
||||||
return handleError($e, $this);
|
return handleError($e, $this);
|
||||||
|
} finally {
|
||||||
|
$this->dispatch('refreshEnvs');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -119,10 +139,25 @@ class General extends Component
|
|||||||
public function refresh(): void
|
public function refresh(): void
|
||||||
{
|
{
|
||||||
$this->database->refresh();
|
$this->database->refresh();
|
||||||
|
$this->refreshView();
|
||||||
|
}
|
||||||
|
|
||||||
|
private function refreshView()
|
||||||
|
{
|
||||||
|
$this->db_url = $this->database->internal_db_url;
|
||||||
|
$this->db_url_public = $this->database->external_db_url;
|
||||||
|
$this->redis_version = $this->database->getRedisVersion();
|
||||||
|
$this->redis_username = $this->database->redis_username;
|
||||||
|
$this->redis_password = $this->database->redis_password;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function render()
|
public function render()
|
||||||
{
|
{
|
||||||
return view('livewire.project.database.redis.general');
|
return view('livewire.project.database.redis.general');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function isSharedVariable($name)
|
||||||
|
{
|
||||||
|
return $this->database->runtime_environment_variables()->where('key', $name)->where('is_shared', true)->exists();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@@ -7,18 +7,22 @@ use Livewire\Component;
|
|||||||
|
|
||||||
class DeleteEnvironment extends Component
|
class DeleteEnvironment extends Component
|
||||||
{
|
{
|
||||||
public array $parameters;
|
|
||||||
|
|
||||||
public int $environment_id;
|
public int $environment_id;
|
||||||
|
|
||||||
public bool $disabled = false;
|
public bool $disabled = false;
|
||||||
|
|
||||||
public string $environmentName = '';
|
public string $environmentName = '';
|
||||||
|
|
||||||
|
public array $parameters;
|
||||||
|
|
||||||
public function mount()
|
public function mount()
|
||||||
{
|
{
|
||||||
$this->parameters = get_route_parameters();
|
try {
|
||||||
$this->environmentName = Environment::findOrFail($this->environment_id)->name;
|
$this->environmentName = Environment::findOrFail($this->environment_id)->name;
|
||||||
|
$this->parameters = get_route_parameters();
|
||||||
|
} catch (\Exception $e) {
|
||||||
|
return handleError($e, $this);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public function delete()
|
public function delete()
|
||||||
@@ -30,7 +34,7 @@ class DeleteEnvironment extends Component
|
|||||||
if ($environment->isEmpty()) {
|
if ($environment->isEmpty()) {
|
||||||
$environment->delete();
|
$environment->delete();
|
||||||
|
|
||||||
return redirect()->route('project.show', ['project_uuid' => $this->parameters['project_uuid']]);
|
return redirect()->route('project.show', parameters: ['project_uuid' => $this->parameters['project_uuid']]);
|
||||||
}
|
}
|
||||||
|
|
||||||
return $this->dispatch('error', 'Environment has defined resources, please delete them first.');
|
return $this->dispatch('error', 'Environment has defined resources, please delete them first.');
|
||||||
|
@@ -18,7 +18,11 @@ class Index extends Component
|
|||||||
public function mount()
|
public function mount()
|
||||||
{
|
{
|
||||||
$this->private_keys = PrivateKey::ownedByCurrentTeam()->get();
|
$this->private_keys = PrivateKey::ownedByCurrentTeam()->get();
|
||||||
$this->projects = Project::ownedByCurrentTeam()->get();
|
$this->projects = Project::ownedByCurrentTeam()->get()->map(function ($project) {
|
||||||
|
$project->settingsRoute = route('project.edit', ['project_uuid' => $project->uuid]);
|
||||||
|
|
||||||
|
return $project;
|
||||||
|
});
|
||||||
$this->servers = Server::ownedByCurrentTeam()->count();
|
$this->servers = Server::ownedByCurrentTeam()->count();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -317,6 +317,7 @@ class PublicGitRepository extends Component
|
|||||||
// $application->setConfig($config);
|
// $application->setConfig($config);
|
||||||
// }
|
// }
|
||||||
}
|
}
|
||||||
|
|
||||||
return redirect()->route('project.application.configuration', [
|
return redirect()->route('project.application.configuration', [
|
||||||
'application_uuid' => $application->uuid,
|
'application_uuid' => $application->uuid,
|
||||||
'environment_name' => $environment->name,
|
'environment_name' => $environment->name,
|
||||||
|
@@ -32,8 +32,11 @@ class Index extends Component
|
|||||||
|
|
||||||
public $services = [];
|
public $services = [];
|
||||||
|
|
||||||
|
public array $parameters;
|
||||||
|
|
||||||
public function mount()
|
public function mount()
|
||||||
{
|
{
|
||||||
|
$this->parameters = get_route_parameters();
|
||||||
$project = currentTeam()->load(['projects'])->projects->where('uuid', request()->route('project_uuid'))->first();
|
$project = currentTeam()->load(['projects'])->projects->where('uuid', request()->route('project_uuid'))->first();
|
||||||
if (! $project) {
|
if (! $project) {
|
||||||
return redirect()->route('dashboard');
|
return redirect()->route('dashboard');
|
||||||
@@ -44,7 +47,6 @@ class Index extends Component
|
|||||||
}
|
}
|
||||||
$this->project = $project;
|
$this->project = $project;
|
||||||
$this->environment = $environment;
|
$this->environment = $environment;
|
||||||
|
|
||||||
$this->applications = $this->environment->applications->load(['tags']);
|
$this->applications = $this->environment->applications->load(['tags']);
|
||||||
$this->applications = $this->applications->map(function ($application) {
|
$this->applications = $this->applications->map(function ($application) {
|
||||||
if (data_get($application, 'environment.project.uuid')) {
|
if (data_get($application, 'environment.project.uuid')) {
|
||||||
|
@@ -21,6 +21,7 @@ class EditDomain extends Component
|
|||||||
{
|
{
|
||||||
$this->application = ServiceApplication::find($this->applicationId);
|
$this->application = ServiceApplication::find($this->applicationId);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function submit()
|
public function submit()
|
||||||
{
|
{
|
||||||
try {
|
try {
|
||||||
@@ -28,9 +29,14 @@ class EditDomain extends Component
|
|||||||
$this->application->fqdn = str($this->application->fqdn)->replaceStart(',', '')->trim();
|
$this->application->fqdn = str($this->application->fqdn)->replaceStart(',', '')->trim();
|
||||||
$this->application->fqdn = str($this->application->fqdn)->trim()->explode(',')->map(function ($domain) {
|
$this->application->fqdn = str($this->application->fqdn)->trim()->explode(',')->map(function ($domain) {
|
||||||
Url::fromString($domain, ['http', 'https']);
|
Url::fromString($domain, ['http', 'https']);
|
||||||
|
|
||||||
return str($domain)->trim()->lower();
|
return str($domain)->trim()->lower();
|
||||||
});
|
});
|
||||||
$this->application->fqdn = $this->application->fqdn->unique()->implode(',');
|
$this->application->fqdn = $this->application->fqdn->unique()->implode(',');
|
||||||
|
$warning = sslipDomainWarning($this->application->fqdn);
|
||||||
|
if ($warning) {
|
||||||
|
$this->dispatch('warning', __('warning.sslipdomain'));
|
||||||
|
}
|
||||||
check_domain_usage(resource: $this->application);
|
check_domain_usage(resource: $this->application);
|
||||||
$this->validate();
|
$this->validate();
|
||||||
$this->application->save();
|
$this->application->save();
|
||||||
@@ -38,7 +44,7 @@ class EditDomain extends Component
|
|||||||
if (str($this->application->fqdn)->contains(',')) {
|
if (str($this->application->fqdn)->contains(',')) {
|
||||||
$this->dispatch('warning', 'Some services do not support multiple domains, which can lead to problems and is NOT RECOMMENDED.<br><br>Only use multiple domains if you know what you are doing.');
|
$this->dispatch('warning', 'Some services do not support multiple domains, which can lead to problems and is NOT RECOMMENDED.<br><br>Only use multiple domains if you know what you are doing.');
|
||||||
} else {
|
} else {
|
||||||
$this->dispatch('success', 'Service saved.');
|
! $warning && $this->dispatch('success', 'Service saved.');
|
||||||
}
|
}
|
||||||
$this->application->service->parse();
|
$this->application->service->parse();
|
||||||
$this->dispatch('refresh');
|
$this->dispatch('refresh');
|
||||||
@@ -48,6 +54,7 @@ class EditDomain extends Component
|
|||||||
if ($originalFqdn !== $this->application->fqdn) {
|
if ($originalFqdn !== $this->application->fqdn) {
|
||||||
$this->application->fqdn = $originalFqdn;
|
$this->application->fqdn = $originalFqdn;
|
||||||
}
|
}
|
||||||
|
|
||||||
return handleError($e, $this);
|
return handleError($e, $this);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -39,7 +39,7 @@ class Navbar extends Component
|
|||||||
|
|
||||||
return [
|
return [
|
||||||
"echo-private:user.{$userId},ServiceStatusChanged" => 'serviceStarted',
|
"echo-private:user.{$userId},ServiceStatusChanged" => 'serviceStarted',
|
||||||
"envsUpdated" => '$refresh',
|
'envsUpdated' => '$refresh',
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -30,11 +30,6 @@ class ServiceApplicationView extends Component
|
|||||||
'application.is_stripprefix_enabled' => 'nullable|boolean',
|
'application.is_stripprefix_enabled' => 'nullable|boolean',
|
||||||
];
|
];
|
||||||
|
|
||||||
public function updatedApplicationFqdn()
|
|
||||||
{
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
public function instantSave()
|
public function instantSave()
|
||||||
{
|
{
|
||||||
$this->submit();
|
$this->submit();
|
||||||
@@ -82,10 +77,14 @@ class ServiceApplicationView extends Component
|
|||||||
$this->application->fqdn = str($this->application->fqdn)->replaceStart(',', '')->trim();
|
$this->application->fqdn = str($this->application->fqdn)->replaceStart(',', '')->trim();
|
||||||
$this->application->fqdn = str($this->application->fqdn)->trim()->explode(',')->map(function ($domain) {
|
$this->application->fqdn = str($this->application->fqdn)->trim()->explode(',')->map(function ($domain) {
|
||||||
Url::fromString($domain, ['http', 'https']);
|
Url::fromString($domain, ['http', 'https']);
|
||||||
|
|
||||||
return str($domain)->trim()->lower();
|
return str($domain)->trim()->lower();
|
||||||
});
|
});
|
||||||
$this->application->fqdn = $this->application->fqdn->unique()->implode(',');
|
$this->application->fqdn = $this->application->fqdn->unique()->implode(',');
|
||||||
|
$warning = sslipDomainWarning($this->application->fqdn);
|
||||||
|
if ($warning) {
|
||||||
|
$this->dispatch('warning', __('warning.sslipdomain'));
|
||||||
|
}
|
||||||
check_domain_usage(resource: $this->application);
|
check_domain_usage(resource: $this->application);
|
||||||
$this->validate();
|
$this->validate();
|
||||||
$this->application->save();
|
$this->application->save();
|
||||||
@@ -93,7 +92,7 @@ class ServiceApplicationView extends Component
|
|||||||
if (str($this->application->fqdn)->contains(',')) {
|
if (str($this->application->fqdn)->contains(',')) {
|
||||||
$this->dispatch('warning', 'Some services do not support multiple domains, which can lead to problems and is NOT RECOMMENDED.<br><br>Only use multiple domains if you know what you are doing.');
|
$this->dispatch('warning', 'Some services do not support multiple domains, which can lead to problems and is NOT RECOMMENDED.<br><br>Only use multiple domains if you know what you are doing.');
|
||||||
} else {
|
} else {
|
||||||
$this->dispatch('success', 'Service saved.');
|
! $warning && $this->dispatch('success', 'Service saved.');
|
||||||
}
|
}
|
||||||
$this->dispatch('generateDockerCompose');
|
$this->dispatch('generateDockerCompose');
|
||||||
} catch (\Throwable $e) {
|
} catch (\Throwable $e) {
|
||||||
@@ -101,6 +100,7 @@ class ServiceApplicationView extends Component
|
|||||||
if ($originalFqdn !== $this->application->fqdn) {
|
if ($originalFqdn !== $this->application->fqdn) {
|
||||||
$this->application->fqdn = $originalFqdn;
|
$this->application->fqdn = $originalFqdn;
|
||||||
}
|
}
|
||||||
|
|
||||||
return handleError($e, $this);
|
return handleError($e, $this);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -31,13 +31,8 @@ class Metrics extends Component
|
|||||||
public function loadData()
|
public function loadData()
|
||||||
{
|
{
|
||||||
try {
|
try {
|
||||||
$metrics = $this->resource->getMetrics($this->interval);
|
$cpuMetrics = $this->resource->getCpuMetrics($this->interval);
|
||||||
$cpuMetrics = collect($metrics)->map(function ($metric) {
|
$memoryMetrics = $this->resource->getMemoryMetrics($this->interval);
|
||||||
return [$metric[0], $metric[1]];
|
|
||||||
});
|
|
||||||
$memoryMetrics = collect($metrics)->map(function ($metric) {
|
|
||||||
return [$metric[0], $metric[2]];
|
|
||||||
});
|
|
||||||
$this->dispatch("refreshChartData-{$this->chartId}-cpu", [
|
$this->dispatch("refreshChartData-{$this->chartId}-cpu", [
|
||||||
'seriesData' => $cpuMetrics,
|
'seriesData' => $cpuMetrics,
|
||||||
]);
|
]);
|
||||||
|
@@ -8,8 +8,11 @@ use Livewire\Component;
|
|||||||
class UploadConfig extends Component
|
class UploadConfig extends Component
|
||||||
{
|
{
|
||||||
public $config;
|
public $config;
|
||||||
|
|
||||||
public $applicationId;
|
public $applicationId;
|
||||||
public function mount() {
|
|
||||||
|
public function mount()
|
||||||
|
{
|
||||||
if (isDev()) {
|
if (isDev()) {
|
||||||
$this->config = '{
|
$this->config = '{
|
||||||
"build_pack": "nixpacks",
|
"build_pack": "nixpacks",
|
||||||
@@ -22,6 +25,7 @@ class UploadConfig extends Component
|
|||||||
}';
|
}';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public function uploadConfig()
|
public function uploadConfig()
|
||||||
{
|
{
|
||||||
try {
|
try {
|
||||||
@@ -30,10 +34,12 @@ class UploadConfig extends Component
|
|||||||
$this->dispatch('success', 'Application settings updated');
|
$this->dispatch('success', 'Application settings updated');
|
||||||
} catch (\Exception $e) {
|
} catch (\Exception $e) {
|
||||||
$this->dispatch('error', $e->getMessage());
|
$this->dispatch('error', $e->getMessage());
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public function render()
|
public function render()
|
||||||
{
|
{
|
||||||
return view('livewire.project.shared.upload-config');
|
return view('livewire.project.shared.upload-config');
|
||||||
|
77
app/Livewire/Server/Advanced.php
Normal file
77
app/Livewire/Server/Advanced.php
Normal file
@@ -0,0 +1,77 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Livewire\Server;
|
||||||
|
|
||||||
|
use App\Jobs\DockerCleanupJob;
|
||||||
|
use App\Models\Server;
|
||||||
|
use Livewire\Component;
|
||||||
|
|
||||||
|
class Advanced extends Component
|
||||||
|
{
|
||||||
|
public Server $server;
|
||||||
|
|
||||||
|
protected $rules = [
|
||||||
|
'server.settings.concurrent_builds' => 'required|integer|min:1',
|
||||||
|
'server.settings.dynamic_timeout' => 'required|integer|min:1',
|
||||||
|
'server.settings.force_docker_cleanup' => 'required|boolean',
|
||||||
|
'server.settings.docker_cleanup_frequency' => 'required_if:server.settings.force_docker_cleanup,true|string',
|
||||||
|
'server.settings.docker_cleanup_threshold' => 'required_if:server.settings.force_docker_cleanup,false|integer|min:1|max:100',
|
||||||
|
'server.settings.delete_unused_volumes' => 'boolean',
|
||||||
|
'server.settings.delete_unused_networks' => 'boolean',
|
||||||
|
];
|
||||||
|
|
||||||
|
protected $validationAttributes = [
|
||||||
|
|
||||||
|
'server.settings.concurrent_builds' => 'Concurrent Builds',
|
||||||
|
'server.settings.dynamic_timeout' => 'Dynamic Timeout',
|
||||||
|
'server.settings.force_docker_cleanup' => 'Force Docker Cleanup',
|
||||||
|
'server.settings.docker_cleanup_frequency' => 'Docker Cleanup Frequency',
|
||||||
|
'server.settings.docker_cleanup_threshold' => 'Docker Cleanup Threshold',
|
||||||
|
'server.settings.delete_unused_volumes' => 'Delete Unused Volumes',
|
||||||
|
'server.settings.delete_unused_networks' => 'Delete Unused Networks',
|
||||||
|
];
|
||||||
|
|
||||||
|
public function instantSave()
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
$this->validate();
|
||||||
|
$this->server->settings->save();
|
||||||
|
$this->dispatch('success', 'Server updated.');
|
||||||
|
$this->dispatch('refreshServerShow');
|
||||||
|
} catch (\Throwable $e) {
|
||||||
|
$this->server->settings->refresh();
|
||||||
|
|
||||||
|
return handleError($e, $this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public function manualCleanup()
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
DockerCleanupJob::dispatch($this->server, true);
|
||||||
|
$this->dispatch('success', 'Manual cleanup job started. Depending on the amount of data, this might take a while.');
|
||||||
|
} catch (\Throwable $e) {
|
||||||
|
return handleError($e, $this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public function submit()
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
$frequency = $this->server->settings->docker_cleanup_frequency;
|
||||||
|
if (empty($frequency) || ! validate_cron_expression($frequency)) {
|
||||||
|
$this->server->settings->docker_cleanup_frequency = '*/10 * * * *';
|
||||||
|
throw new \Exception('Invalid Cron / Human expression for Docker Cleanup Frequency. Resetting to default 10 minutes.');
|
||||||
|
}
|
||||||
|
$this->server->settings->save();
|
||||||
|
$this->dispatch('success', 'Server updated.');
|
||||||
|
} catch (\Throwable $e) {
|
||||||
|
return handleError($e, $this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public function render()
|
||||||
|
{
|
||||||
|
return view('livewire.server.advanced');
|
||||||
|
}
|
||||||
|
}
|
@@ -34,12 +34,12 @@ class Charts extends Component
|
|||||||
try {
|
try {
|
||||||
$cpuMetrics = $this->server->getCpuMetrics($this->interval);
|
$cpuMetrics = $this->server->getCpuMetrics($this->interval);
|
||||||
$memoryMetrics = $this->server->getMemoryMetrics($this->interval);
|
$memoryMetrics = $this->server->getMemoryMetrics($this->interval);
|
||||||
$cpuMetrics = collect($cpuMetrics)->map(function ($metric) {
|
// $cpuMetrics = collect($cpuMetrics)->map(function ($metric) {
|
||||||
return [$metric[0], $metric[1]];
|
// return [$metric[0], $metric[1]];
|
||||||
});
|
// });
|
||||||
$memoryMetrics = collect($memoryMetrics)->map(function ($metric) {
|
// $memoryMetrics = collect($memoryMetrics)->map(function ($metric) {
|
||||||
return [$metric[0], $metric[1]];
|
// return [$metric[0], $metric[1]];
|
||||||
});
|
// });
|
||||||
$this->dispatch("refreshChartData-{$this->chartId}-cpu", [
|
$this->dispatch("refreshChartData-{$this->chartId}-cpu", [
|
||||||
'seriesData' => $cpuMetrics,
|
'seriesData' => $cpuMetrics,
|
||||||
]);
|
]);
|
||||||
|
44
app/Livewire/Server/CloudflareTunnels.php
Normal file
44
app/Livewire/Server/CloudflareTunnels.php
Normal file
@@ -0,0 +1,44 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Livewire\Server;
|
||||||
|
|
||||||
|
use App\Models\Server;
|
||||||
|
use Livewire\Component;
|
||||||
|
|
||||||
|
class CloudflareTunnels extends Component
|
||||||
|
{
|
||||||
|
public Server $server;
|
||||||
|
|
||||||
|
protected $rules = [
|
||||||
|
'server.settings.is_cloudflare_tunnel' => 'required|boolean',
|
||||||
|
];
|
||||||
|
|
||||||
|
protected $validationAttributes = [
|
||||||
|
'server.settings.is_cloudflare_tunnel' => 'Cloudflare Tunnel',
|
||||||
|
];
|
||||||
|
|
||||||
|
public function instantSave()
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
$this->validate();
|
||||||
|
$this->server->settings->save();
|
||||||
|
$this->dispatch('success', 'Server updated.');
|
||||||
|
$this->dispatch('refreshServerShow');
|
||||||
|
} catch (\Throwable $e) {
|
||||||
|
return handleError($e, $this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public function manualCloudflareConfig()
|
||||||
|
{
|
||||||
|
$this->server->settings->is_cloudflare_tunnel = true;
|
||||||
|
$this->server->settings->save();
|
||||||
|
$this->server->refresh();
|
||||||
|
$this->dispatch('success', 'Cloudflare Tunnels enabled.');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function render()
|
||||||
|
{
|
||||||
|
return view('livewire.server.cloudflare-tunnels');
|
||||||
|
}
|
||||||
|
}
|
@@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
namespace App\Livewire\Server;
|
namespace App\Livewire\Server;
|
||||||
|
|
||||||
|
use App\Actions\Server\DeleteServer;
|
||||||
use Illuminate\Foundation\Auth\Access\AuthorizesRequests;
|
use Illuminate\Foundation\Auth\Access\AuthorizesRequests;
|
||||||
use Illuminate\Support\Facades\Auth;
|
use Illuminate\Support\Facades\Auth;
|
||||||
use Illuminate\Support\Facades\Hash;
|
use Illuminate\Support\Facades\Hash;
|
||||||
@@ -28,6 +29,7 @@ class Delete extends Component
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
$this->server->delete();
|
$this->server->delete();
|
||||||
|
DeleteServer::dispatch($this->server);
|
||||||
|
|
||||||
return redirect()->route('server.index');
|
return redirect()->route('server.index');
|
||||||
} catch (\Throwable $e) {
|
} catch (\Throwable $e) {
|
||||||
|
@@ -4,10 +4,7 @@ namespace App\Livewire\Server;
|
|||||||
|
|
||||||
use App\Actions\Server\StartSentinel;
|
use App\Actions\Server\StartSentinel;
|
||||||
use App\Actions\Server\StopSentinel;
|
use App\Actions\Server\StopSentinel;
|
||||||
use App\Jobs\DockerCleanupJob;
|
|
||||||
use App\Jobs\PullSentinelImageJob;
|
|
||||||
use App\Models\Server;
|
use App\Models\Server;
|
||||||
use Illuminate\Support\Facades\Http;
|
|
||||||
use Livewire\Component;
|
use Livewire\Component;
|
||||||
|
|
||||||
class Form extends Component
|
class Form extends Component
|
||||||
@@ -47,25 +44,19 @@ class Form extends Component
|
|||||||
'server.ip' => 'required',
|
'server.ip' => 'required',
|
||||||
'server.user' => 'required',
|
'server.user' => 'required',
|
||||||
'server.port' => 'required',
|
'server.port' => 'required',
|
||||||
'server.settings.is_cloudflare_tunnel' => 'required|boolean',
|
'wildcard_domain' => 'nullable|url',
|
||||||
'server.settings.is_reachable' => 'required',
|
'server.settings.is_reachable' => 'required',
|
||||||
'server.settings.is_swarm_manager' => 'required|boolean',
|
'server.settings.is_swarm_manager' => 'required|boolean',
|
||||||
'server.settings.is_swarm_worker' => 'required|boolean',
|
'server.settings.is_swarm_worker' => 'required|boolean',
|
||||||
'server.settings.is_build_server' => 'required|boolean',
|
'server.settings.is_build_server' => 'required|boolean',
|
||||||
'server.settings.concurrent_builds' => 'required|integer|min:1',
|
|
||||||
'server.settings.dynamic_timeout' => 'required|integer|min:1',
|
|
||||||
'server.settings.is_metrics_enabled' => 'required|boolean',
|
'server.settings.is_metrics_enabled' => 'required|boolean',
|
||||||
'server.settings.sentinel_token' => 'required',
|
'server.settings.sentinel_token' => 'required',
|
||||||
'server.settings.sentinel_metrics_refresh_rate_seconds' => 'required|integer|min:1',
|
'server.settings.sentinel_metrics_refresh_rate_seconds' => 'required|integer|min:1',
|
||||||
'server.settings.sentinel_metrics_history_days' => 'required|integer|min:1',
|
'server.settings.sentinel_metrics_history_days' => 'required|integer|min:1',
|
||||||
'wildcard_domain' => 'nullable|url',
|
'server.settings.sentinel_push_interval_seconds' => 'required|integer|min:10',
|
||||||
'server.settings.is_server_api_enabled' => 'required|boolean',
|
'server.settings.sentinel_custom_url' => 'nullable|url',
|
||||||
|
'server.settings.is_sentinel_enabled' => 'required|boolean',
|
||||||
'server.settings.server_timezone' => 'required|string|timezone',
|
'server.settings.server_timezone' => 'required|string|timezone',
|
||||||
'server.settings.force_docker_cleanup' => 'required|boolean',
|
|
||||||
'server.settings.docker_cleanup_frequency' => 'required_if:server.settings.force_docker_cleanup,true|string',
|
|
||||||
'server.settings.docker_cleanup_threshold' => 'required_if:server.settings.force_docker_cleanup,false|integer|min:1|max:100',
|
|
||||||
'server.settings.delete_unused_volumes' => 'boolean',
|
|
||||||
'server.settings.delete_unused_networks' => 'boolean',
|
|
||||||
];
|
];
|
||||||
|
|
||||||
protected $validationAttributes = [
|
protected $validationAttributes = [
|
||||||
@@ -74,21 +65,18 @@ class Form extends Component
|
|||||||
'server.ip' => 'IP address/Domain',
|
'server.ip' => 'IP address/Domain',
|
||||||
'server.user' => 'User',
|
'server.user' => 'User',
|
||||||
'server.port' => 'Port',
|
'server.port' => 'Port',
|
||||||
'server.settings.is_cloudflare_tunnel' => 'Cloudflare Tunnel',
|
|
||||||
'server.settings.is_reachable' => 'Is reachable',
|
'server.settings.is_reachable' => 'Is reachable',
|
||||||
'server.settings.is_swarm_manager' => 'Swarm Manager',
|
'server.settings.is_swarm_manager' => 'Swarm Manager',
|
||||||
'server.settings.is_swarm_worker' => 'Swarm Worker',
|
'server.settings.is_swarm_worker' => 'Swarm Worker',
|
||||||
'server.settings.is_build_server' => 'Build Server',
|
'server.settings.is_build_server' => 'Build Server',
|
||||||
'server.settings.concurrent_builds' => 'Concurrent Builds',
|
|
||||||
'server.settings.dynamic_timeout' => 'Dynamic Timeout',
|
|
||||||
'server.settings.is_metrics_enabled' => 'Metrics',
|
'server.settings.is_metrics_enabled' => 'Metrics',
|
||||||
'server.settings.sentinel_token' => 'Metrics Token',
|
'server.settings.sentinel_token' => 'Metrics Token',
|
||||||
'server.settings.sentinel_metrics_refresh_rate_seconds' => 'Metrics Interval',
|
'server.settings.sentinel_metrics_refresh_rate_seconds' => 'Metrics Interval',
|
||||||
'server.settings.sentinel_metrics_history_days' => 'Metrics History',
|
'server.settings.sentinel_metrics_history_days' => 'Metrics History',
|
||||||
'server.settings.is_server_api_enabled' => 'Server API',
|
'server.settings.sentinel_push_interval_seconds' => 'Push Interval',
|
||||||
|
'server.settings.is_sentinel_enabled' => 'Server API',
|
||||||
|
'server.settings.sentinel_custom_url' => 'Coolify URL',
|
||||||
'server.settings.server_timezone' => 'Server Timezone',
|
'server.settings.server_timezone' => 'Server Timezone',
|
||||||
'server.settings.delete_unused_volumes' => 'Delete Unused Volumes',
|
|
||||||
'server.settings.delete_unused_networks' => 'Delete Unused Networks',
|
|
||||||
];
|
];
|
||||||
|
|
||||||
public function mount(Server $server)
|
public function mount(Server $server)
|
||||||
@@ -96,20 +84,26 @@ class Form extends Component
|
|||||||
$this->server = $server;
|
$this->server = $server;
|
||||||
$this->timezones = collect(timezone_identifiers_list())->sort()->values()->toArray();
|
$this->timezones = collect(timezone_identifiers_list())->sort()->values()->toArray();
|
||||||
$this->wildcard_domain = $this->server->settings->wildcard_domain;
|
$this->wildcard_domain = $this->server->settings->wildcard_domain;
|
||||||
$this->server->settings->docker_cleanup_threshold = $this->server->settings->docker_cleanup_threshold;
|
|
||||||
$this->server->settings->docker_cleanup_frequency = $this->server->settings->docker_cleanup_frequency;
|
|
||||||
$this->server->settings->delete_unused_volumes = $server->settings->delete_unused_volumes;
|
|
||||||
$this->server->settings->delete_unused_networks = $server->settings->delete_unused_networks;
|
|
||||||
}
|
}
|
||||||
public function regenerateSentinelToken() {
|
|
||||||
|
public function checkSyncStatus()
|
||||||
|
{
|
||||||
|
$this->server->refresh();
|
||||||
|
$this->server->settings->refresh();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function regenerateSentinelToken()
|
||||||
|
{
|
||||||
try {
|
try {
|
||||||
$this->server->generateSentinelToken();
|
$this->server->settings->generateSentinelToken();
|
||||||
$this->server->settings->refresh();
|
$this->server->settings->refresh();
|
||||||
$this->dispatch('success', 'Metrics token regenerated.');
|
$this->restartSentinel(notification: false);
|
||||||
|
$this->dispatch('success', 'Token regenerated & Sentinel restarted.');
|
||||||
} catch (\Throwable $e) {
|
} catch (\Throwable $e) {
|
||||||
return handleError($e, $this);
|
return handleError($e, $this);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public function updated($field)
|
public function updated($field)
|
||||||
{
|
{
|
||||||
if ($field === 'server.settings.docker_cleanup_frequency') {
|
if ($field === 'server.settings.docker_cleanup_frequency') {
|
||||||
@@ -140,21 +134,35 @@ class Form extends Component
|
|||||||
$this->dispatch('proxyStatusUpdated');
|
$this->dispatch('proxyStatusUpdated');
|
||||||
}
|
}
|
||||||
|
|
||||||
public function checkPortForServerApi()
|
public function updatedServerSettingsIsSentinelEnabled($value)
|
||||||
{
|
{
|
||||||
try {
|
$this->validate();
|
||||||
if ($this->server->settings->is_server_api_enabled === true) {
|
$this->validate([
|
||||||
$this->server->checkServerApi();
|
'server.settings.sentinel_custom_url' => 'required|url',
|
||||||
$this->dispatch('success', 'Server API is reachable.');
|
]);
|
||||||
|
if ($value === false) {
|
||||||
|
StopSentinel::dispatch($this->server);
|
||||||
|
$this->server->settings->is_metrics_enabled = false;
|
||||||
|
$this->server->settings->save();
|
||||||
|
$this->server->sentinelHeartbeat(isReset: true);
|
||||||
|
} else {
|
||||||
|
try {
|
||||||
|
StartSentinel::run($this->server);
|
||||||
|
} catch (\Throwable $e) {
|
||||||
|
return handleError($e, $this);
|
||||||
}
|
}
|
||||||
} catch (\Throwable $e) {
|
|
||||||
return handleError($e, $this);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function updatedServerSettingsIsMetricsEnabled()
|
||||||
|
{
|
||||||
|
$this->restartSentinel();
|
||||||
|
}
|
||||||
|
|
||||||
public function instantSave()
|
public function instantSave()
|
||||||
{
|
{
|
||||||
try {
|
try {
|
||||||
|
$this->validate();
|
||||||
refresh_server_connection($this->server->privateKey);
|
refresh_server_connection($this->server->privateKey);
|
||||||
$this->validateServer(false);
|
$this->validateServer(false);
|
||||||
|
|
||||||
@@ -162,55 +170,27 @@ class Form extends Component
|
|||||||
$this->server->save();
|
$this->server->save();
|
||||||
$this->dispatch('success', 'Server updated.');
|
$this->dispatch('success', 'Server updated.');
|
||||||
$this->dispatch('refreshServerShow');
|
$this->dispatch('refreshServerShow');
|
||||||
if ($this->server->isSentinelEnabled()) {
|
|
||||||
PullSentinelImageJob::dispatchSync($this->server);
|
|
||||||
ray('Sentinel is enabled');
|
|
||||||
if ($this->server->settings->isDirty('is_metrics_enabled')) {
|
|
||||||
$this->dispatch('reloadWindow');
|
|
||||||
}
|
|
||||||
if ($this->server->settings->isDirty('is_server_api_enabled') && $this->server->settings->is_server_api_enabled === true) {
|
|
||||||
ray('Starting sentinel');
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
ray('Sentinel is not enabled');
|
|
||||||
StopSentinel::dispatch($this->server);
|
|
||||||
}
|
|
||||||
$this->server->settings->save();
|
$this->server->settings->save();
|
||||||
// $this->checkPortForServerApi();
|
|
||||||
|
|
||||||
} catch (\Throwable $e) {
|
} catch (\Throwable $e) {
|
||||||
|
$this->server->settings->refresh();
|
||||||
|
|
||||||
return handleError($e, $this);
|
return handleError($e, $this);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getPushData()
|
public function restartSentinel($notification = true)
|
||||||
{
|
{
|
||||||
try {
|
try {
|
||||||
if (!isDev()) {
|
$this->validate();
|
||||||
throw new \Exception('This feature is only available in dev mode.');
|
$this->validate([
|
||||||
}
|
'server.settings.sentinel_custom_url' => 'required|url',
|
||||||
$response = Http::withHeaders([
|
|
||||||
'Authorization' => 'Bearer ' . $this->server->settings->sentinel_token,
|
|
||||||
])->post('http://host.docker.internal:8888/api/push', [
|
|
||||||
'data' => 'test',
|
|
||||||
]);
|
]);
|
||||||
if ($response->successful()) {
|
|
||||||
$this->dispatch('success', 'Push data sent.');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
$error = data_get($response->json(), 'error');
|
|
||||||
throw new \Exception($error);
|
|
||||||
|
|
||||||
} catch(\Throwable $e) {
|
|
||||||
return handleError($e, $this);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
public function restartSentinel()
|
|
||||||
{
|
|
||||||
try {
|
|
||||||
$version = get_latest_sentinel_version();
|
$version = get_latest_sentinel_version();
|
||||||
StartSentinel::run($this->server, $version, true);
|
StartSentinel::run($this->server, $version, true);
|
||||||
$this->dispatch('success', 'Sentinel restarted.');
|
if ($notification) {
|
||||||
|
$this->dispatch('success', 'Sentinel started.');
|
||||||
|
}
|
||||||
} catch (\Throwable $e) {
|
} catch (\Throwable $e) {
|
||||||
return handleError($e, $this);
|
return handleError($e, $this);
|
||||||
}
|
}
|
||||||
@@ -267,11 +247,11 @@ class Form extends Component
|
|||||||
}
|
}
|
||||||
refresh_server_connection($this->server->privateKey);
|
refresh_server_connection($this->server->privateKey);
|
||||||
$this->server->settings->wildcard_domain = $this->wildcard_domain;
|
$this->server->settings->wildcard_domain = $this->wildcard_domain;
|
||||||
if ($this->server->settings->force_docker_cleanup) {
|
// if ($this->server->settings->force_docker_cleanup) {
|
||||||
$this->server->settings->docker_cleanup_frequency = $this->server->settings->docker_cleanup_frequency;
|
// $this->server->settings->docker_cleanup_frequency = $this->server->settings->docker_cleanup_frequency;
|
||||||
} else {
|
// } else {
|
||||||
$this->server->settings->docker_cleanup_threshold = $this->server->settings->docker_cleanup_threshold;
|
// $this->server->settings->docker_cleanup_threshold = $this->server->settings->docker_cleanup_threshold;
|
||||||
}
|
// }
|
||||||
$currentTimezone = $this->server->settings->getOriginal('server_timezone');
|
$currentTimezone = $this->server->settings->getOriginal('server_timezone');
|
||||||
$newTimezone = $this->server->settings->server_timezone;
|
$newTimezone = $this->server->settings->server_timezone;
|
||||||
if ($currentTimezone !== $newTimezone || $currentTimezone === '') {
|
if ($currentTimezone !== $newTimezone || $currentTimezone === '') {
|
||||||
@@ -285,21 +265,4 @@ class Form extends Component
|
|||||||
return handleError($e, $this);
|
return handleError($e, $this);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
public function manualCleanup()
|
|
||||||
{
|
|
||||||
try {
|
|
||||||
DockerCleanupJob::dispatch($this->server, true);
|
|
||||||
$this->dispatch('success', 'Manual cleanup job started. Depending on the amount of data, this might take a while.');
|
|
||||||
} catch (\Throwable $e) {
|
|
||||||
return handleError($e, $this);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public function manualCloudflareConfig()
|
|
||||||
{
|
|
||||||
$this->server->settings->is_cloudflare_tunnel = true;
|
|
||||||
$this->server->settings->save();
|
|
||||||
$this->server->refresh();
|
|
||||||
$this->dispatch('success', 'Cloudflare Tunnels enabled.');
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@@ -1,16 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
namespace App\Livewire\Server\Proxy;
|
|
||||||
|
|
||||||
use App\Models\Server;
|
|
||||||
use Livewire\Component;
|
|
||||||
|
|
||||||
class Modal extends Component
|
|
||||||
{
|
|
||||||
public Server $server;
|
|
||||||
|
|
||||||
public function proxyStatusUpdated()
|
|
||||||
{
|
|
||||||
$this->dispatch('proxyStatusUpdated');
|
|
||||||
}
|
|
||||||
}
|
|
@@ -22,10 +22,7 @@ class Show extends Component
|
|||||||
{
|
{
|
||||||
$this->parameters = get_route_parameters();
|
$this->parameters = get_route_parameters();
|
||||||
try {
|
try {
|
||||||
$this->server = Server::ownedByCurrentTeam()->whereUuid(request()->server_uuid)->first();
|
$this->server = Server::ownedByCurrentTeam()->whereUuid(request()->server_uuid)->firstOrFail();
|
||||||
if (is_null($this->server)) {
|
|
||||||
return redirect()->route('server.index');
|
|
||||||
}
|
|
||||||
} catch (\Throwable $e) {
|
} catch (\Throwable $e) {
|
||||||
return handleError($e, $this);
|
return handleError($e, $this);
|
||||||
}
|
}
|
||||||
|
@@ -15,7 +15,9 @@ class Resources extends Component
|
|||||||
|
|
||||||
public $parameters = [];
|
public $parameters = [];
|
||||||
|
|
||||||
public Collection $unmanagedContainers;
|
public Collection $containers;
|
||||||
|
|
||||||
|
public $activeTab = 'managed';
|
||||||
|
|
||||||
public function getListeners()
|
public function getListeners()
|
||||||
{
|
{
|
||||||
@@ -50,14 +52,29 @@ class Resources extends Component
|
|||||||
public function refreshStatus()
|
public function refreshStatus()
|
||||||
{
|
{
|
||||||
$this->server->refresh();
|
$this->server->refresh();
|
||||||
$this->loadUnmanagedContainers();
|
if ($this->activeTab === 'managed') {
|
||||||
|
$this->loadManagedContainers();
|
||||||
|
} else {
|
||||||
|
$this->loadUnmanagedContainers();
|
||||||
|
}
|
||||||
$this->dispatch('success', 'Resource statuses refreshed.');
|
$this->dispatch('success', 'Resource statuses refreshed.');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function loadManagedContainers()
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
$this->activeTab = 'managed';
|
||||||
|
$this->containers = $this->server->refresh()->definedResources();
|
||||||
|
} catch (\Throwable $e) {
|
||||||
|
return handleError($e, $this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public function loadUnmanagedContainers()
|
public function loadUnmanagedContainers()
|
||||||
{
|
{
|
||||||
|
$this->activeTab = 'unmanaged';
|
||||||
try {
|
try {
|
||||||
$this->unmanagedContainers = $this->server->loadUnmanagedContainers();
|
$this->containers = $this->server->loadUnmanagedContainers();
|
||||||
} catch (\Throwable $e) {
|
} catch (\Throwable $e) {
|
||||||
return handleError($e, $this);
|
return handleError($e, $this);
|
||||||
}
|
}
|
||||||
@@ -65,13 +82,14 @@ class Resources extends Component
|
|||||||
|
|
||||||
public function mount()
|
public function mount()
|
||||||
{
|
{
|
||||||
$this->unmanagedContainers = collect();
|
$this->containers = collect();
|
||||||
$this->parameters = get_route_parameters();
|
$this->parameters = get_route_parameters();
|
||||||
try {
|
try {
|
||||||
$this->server = Server::ownedByCurrentTeam()->whereUuid(request()->server_uuid)->first();
|
$this->server = Server::ownedByCurrentTeam()->whereUuid(request()->server_uuid)->first();
|
||||||
if (is_null($this->server)) {
|
if (is_null($this->server)) {
|
||||||
return redirect()->route('server.index');
|
return redirect()->route('server.index');
|
||||||
}
|
}
|
||||||
|
$this->loadManagedContainers();
|
||||||
} catch (\Throwable $e) {
|
} catch (\Throwable $e) {
|
||||||
return handleError($e, $this);
|
return handleError($e, $this);
|
||||||
}
|
}
|
||||||
|
@@ -10,20 +10,17 @@ class Show extends Component
|
|||||||
{
|
{
|
||||||
use AuthorizesRequests;
|
use AuthorizesRequests;
|
||||||
|
|
||||||
public ?Server $server = null;
|
public Server $server;
|
||||||
|
|
||||||
public $parameters = [];
|
public array $parameters;
|
||||||
|
|
||||||
protected $listeners = ['refreshServerShow'];
|
protected $listeners = ['refreshServerShow'];
|
||||||
|
|
||||||
public function mount()
|
public function mount()
|
||||||
{
|
{
|
||||||
$this->parameters = get_route_parameters();
|
|
||||||
try {
|
try {
|
||||||
$this->server = Server::ownedByCurrentTeam()->whereUuid(request()->server_uuid)->first();
|
$this->server = Server::ownedByCurrentTeam()->whereUuid(request()->server_uuid)->firstOrFail();
|
||||||
if (is_null($this->server)) {
|
$this->parameters = get_route_parameters();
|
||||||
return redirect()->route('server.index');
|
|
||||||
}
|
|
||||||
} catch (\Throwable $e) {
|
} catch (\Throwable $e) {
|
||||||
return handleError($e, $this);
|
return handleError($e, $this);
|
||||||
}
|
}
|
||||||
|
@@ -2,7 +2,6 @@
|
|||||||
|
|
||||||
namespace App\Livewire\Server;
|
namespace App\Livewire\Server;
|
||||||
|
|
||||||
use App\Models\PrivateKey;
|
|
||||||
use App\Models\Server;
|
use App\Models\Server;
|
||||||
use Livewire\Component;
|
use Livewire\Component;
|
||||||
|
|
||||||
@@ -14,15 +13,29 @@ class ShowPrivateKey extends Component
|
|||||||
|
|
||||||
public $parameters;
|
public $parameters;
|
||||||
|
|
||||||
|
public function mount()
|
||||||
|
{
|
||||||
|
$this->parameters = get_route_parameters();
|
||||||
|
}
|
||||||
|
|
||||||
public function setPrivateKey($privateKeyId)
|
public function setPrivateKey($privateKeyId)
|
||||||
{
|
{
|
||||||
|
$originalPrivateKeyId = $this->server->getOriginal('private_key_id');
|
||||||
try {
|
try {
|
||||||
$privateKey = PrivateKey::findOrFail($privateKeyId);
|
$this->server->update(['private_key_id' => $privateKeyId]);
|
||||||
$this->server->update(['private_key_id' => $privateKey->id]);
|
['uptime' => $uptime, 'error' => $error] = $this->server->validateConnection();
|
||||||
$this->server->refresh();
|
if ($uptime) {
|
||||||
$this->dispatch('success', 'Private key updated successfully.');
|
$this->dispatch('success', 'Private key updated successfully.');
|
||||||
|
} else {
|
||||||
|
throw new \Exception('Server is not reachable.<br><br>Check this <a target="_blank" class="underline" href="https://coolify.io/docs/knowledge-base/server/openssh">documentation</a> for further help.<br><br>Error: '.$error);
|
||||||
|
}
|
||||||
} catch (\Exception $e) {
|
} catch (\Exception $e) {
|
||||||
|
$this->server->update(['private_key_id' => $originalPrivateKeyId]);
|
||||||
|
$this->server->validateConnection();
|
||||||
$this->dispatch('error', 'Failed to update private key: '.$e->getMessage());
|
$this->dispatch('error', 'Failed to update private key: '.$e->getMessage());
|
||||||
|
} finally {
|
||||||
|
$this->dispatch('refreshServerShow');
|
||||||
|
$this->server->refresh();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -33,18 +46,15 @@ class ShowPrivateKey extends Component
|
|||||||
if ($uptime) {
|
if ($uptime) {
|
||||||
$this->dispatch('success', 'Server is reachable.');
|
$this->dispatch('success', 'Server is reachable.');
|
||||||
} else {
|
} else {
|
||||||
ray($error);
|
|
||||||
$this->dispatch('error', 'Server is not reachable.<br><br>Check this <a target="_blank" class="underline" href="https://coolify.io/docs/knowledge-base/server/openssh">documentation</a> for further help.<br><br>Error: '.$error);
|
$this->dispatch('error', 'Server is not reachable.<br><br>Check this <a target="_blank" class="underline" href="https://coolify.io/docs/knowledge-base/server/openssh">documentation</a> for further help.<br><br>Error: '.$error);
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
} catch (\Throwable $e) {
|
} catch (\Throwable $e) {
|
||||||
return handleError($e, $this);
|
return handleError($e, $this);
|
||||||
|
} finally {
|
||||||
|
$this->dispatch('refreshServerShow');
|
||||||
|
$this->server->refresh();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public function mount()
|
|
||||||
{
|
|
||||||
$this->parameters = get_route_parameters();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@@ -25,10 +25,13 @@ class Index extends Component
|
|||||||
|
|
||||||
public string $update_check_frequency;
|
public string $update_check_frequency;
|
||||||
|
|
||||||
|
public $timezones;
|
||||||
|
|
||||||
|
public bool $disable_two_step_confirmation;
|
||||||
|
|
||||||
protected string $dynamic_config_path = '/data/coolify/proxy/dynamic';
|
protected string $dynamic_config_path = '/data/coolify/proxy/dynamic';
|
||||||
|
|
||||||
protected Server $server;
|
protected Server $server;
|
||||||
public $timezones;
|
|
||||||
|
|
||||||
protected $rules = [
|
protected $rules = [
|
||||||
'settings.fqdn' => 'nullable',
|
'settings.fqdn' => 'nullable',
|
||||||
@@ -39,6 +42,8 @@ class Index extends Component
|
|||||||
'settings.instance_name' => 'nullable',
|
'settings.instance_name' => 'nullable',
|
||||||
'settings.allowed_ips' => 'nullable',
|
'settings.allowed_ips' => 'nullable',
|
||||||
'settings.is_auto_update_enabled' => 'boolean',
|
'settings.is_auto_update_enabled' => 'boolean',
|
||||||
|
'settings.public_ipv4' => 'nullable',
|
||||||
|
'settings.public_ipv6' => 'nullable',
|
||||||
'auto_update_frequency' => 'string',
|
'auto_update_frequency' => 'string',
|
||||||
'update_check_frequency' => 'string',
|
'update_check_frequency' => 'string',
|
||||||
'settings.instance_timezone' => 'required|string|timezone',
|
'settings.instance_timezone' => 'required|string|timezone',
|
||||||
@@ -52,16 +57,18 @@ class Index extends Component
|
|||||||
'settings.custom_dns_servers' => 'Custom DNS servers',
|
'settings.custom_dns_servers' => 'Custom DNS servers',
|
||||||
'settings.allowed_ips' => 'Allowed IPs',
|
'settings.allowed_ips' => 'Allowed IPs',
|
||||||
'settings.is_auto_update_enabled' => 'Auto Update Enabled',
|
'settings.is_auto_update_enabled' => 'Auto Update Enabled',
|
||||||
|
'settings.public_ipv4' => 'IPv4',
|
||||||
|
'settings.public_ipv6' => 'IPv6',
|
||||||
'auto_update_frequency' => 'Auto Update Frequency',
|
'auto_update_frequency' => 'Auto Update Frequency',
|
||||||
'update_check_frequency' => 'Update Check Frequency',
|
'update_check_frequency' => 'Update Check Frequency',
|
||||||
'settings.instance_timezone' => 'Instance Timezone',
|
'settings.instance_timezone' => 'Instance Timezone',
|
||||||
];
|
];
|
||||||
|
|
||||||
|
|
||||||
public function mount()
|
public function mount()
|
||||||
{
|
{
|
||||||
if (isInstanceAdmin()) {
|
if (isInstanceAdmin()) {
|
||||||
$this->settings = instanceSettings();
|
$this->settings = instanceSettings();
|
||||||
|
loggy($this->settings);
|
||||||
$this->do_not_track = $this->settings->do_not_track;
|
$this->do_not_track = $this->settings->do_not_track;
|
||||||
$this->is_auto_update_enabled = $this->settings->is_auto_update_enabled;
|
$this->is_auto_update_enabled = $this->settings->is_auto_update_enabled;
|
||||||
$this->is_registration_enabled = $this->settings->is_registration_enabled;
|
$this->is_registration_enabled = $this->settings->is_registration_enabled;
|
||||||
@@ -70,6 +77,7 @@ class Index extends Component
|
|||||||
$this->auto_update_frequency = $this->settings->auto_update_frequency;
|
$this->auto_update_frequency = $this->settings->auto_update_frequency;
|
||||||
$this->update_check_frequency = $this->settings->update_check_frequency;
|
$this->update_check_frequency = $this->settings->update_check_frequency;
|
||||||
$this->timezones = collect(timezone_identifiers_list())->sort()->values()->toArray();
|
$this->timezones = collect(timezone_identifiers_list())->sort()->values()->toArray();
|
||||||
|
$this->disable_two_step_confirmation = $this->settings->disable_two_step_confirmation;
|
||||||
} else {
|
} else {
|
||||||
return redirect()->route('dashboard');
|
return redirect()->route('dashboard');
|
||||||
}
|
}
|
||||||
@@ -84,6 +92,7 @@ class Index extends Component
|
|||||||
$this->settings->is_api_enabled = $this->is_api_enabled;
|
$this->settings->is_api_enabled = $this->is_api_enabled;
|
||||||
$this->settings->auto_update_frequency = $this->auto_update_frequency;
|
$this->settings->auto_update_frequency = $this->auto_update_frequency;
|
||||||
$this->settings->update_check_frequency = $this->update_check_frequency;
|
$this->settings->update_check_frequency = $this->update_check_frequency;
|
||||||
|
$this->settings->disable_two_step_confirmation = $this->disable_two_step_confirmation;
|
||||||
$this->settings->save();
|
$this->settings->save();
|
||||||
$this->dispatch('success', 'Settings updated!');
|
$this->dispatch('success', 'Settings updated!');
|
||||||
}
|
}
|
||||||
@@ -171,9 +180,16 @@ class Index extends Component
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public function render()
|
public function render()
|
||||||
{
|
{
|
||||||
return view('livewire.settings.index');
|
return view('livewire.settings.index');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function toggleTwoStepConfirmation()
|
||||||
|
{
|
||||||
|
$this->settings->disable_two_step_confirmation = true;
|
||||||
|
$this->settings->save();
|
||||||
|
$this->disable_two_step_confirmation = true;
|
||||||
|
$this->dispatch('success', 'Two step confirmation has been disabled.');
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@@ -23,7 +23,7 @@ class Create extends Component
|
|||||||
|
|
||||||
public function mount()
|
public function mount()
|
||||||
{
|
{
|
||||||
$this->name = generate_random_name();
|
$this->name = substr(generate_random_name(), 0, 34); // GitHub Apps names can only be 34 characters long
|
||||||
}
|
}
|
||||||
|
|
||||||
public function createGitHubApp()
|
public function createGitHubApp()
|
||||||
|
@@ -1400,13 +1400,21 @@ class Application extends BaseModel
|
|||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getMetrics(int $mins = 5)
|
public function getCpuMetrics(int $mins = 5)
|
||||||
{
|
{
|
||||||
$server = $this->destination->server;
|
$server = $this->destination->server;
|
||||||
$container_name = $this->uuid;
|
$container_name = $this->uuid;
|
||||||
if ($server->isMetricsEnabled()) {
|
if ($server->isMetricsEnabled()) {
|
||||||
$from = now()->subMinutes($mins)->toIso8601ZuluString();
|
$from = now()->subMinutes($mins)->toIso8601ZuluString();
|
||||||
$metrics = instant_remote_process(["docker exec coolify-sentinel sh -c 'curl -H \"Authorization: Bearer {$server->settings->sentinel_token}\" http://localhost:8888/api/container/{$container_name}/metrics/history?from=$from'"], $server, false);
|
if (isDev() && $server->id === 0) {
|
||||||
|
$process = Process::run("curl -H \"Authorization: Bearer {$this->settings->sentinel_token}\" http://host.docker.internal:8888/api/container/{$container_name}/cpu/history?from=$from");
|
||||||
|
if ($process->failed()) {
|
||||||
|
throw new \Exception($process->errorOutput());
|
||||||
|
}
|
||||||
|
$metrics = $process->output();
|
||||||
|
} else {
|
||||||
|
$metrics = instant_remote_process(["docker exec coolify-sentinel sh -c 'curl -H \"Authorization: Bearer {$server->settings->sentinel_token}\" http://localhost:8888/api/container/{$container_name}/cpu/history?from=$from'"], $server, false);
|
||||||
|
}
|
||||||
if (str($metrics)->contains('error')) {
|
if (str($metrics)->contains('error')) {
|
||||||
$error = json_decode($metrics, true);
|
$error = json_decode($metrics, true);
|
||||||
$error = data_get($error, 'error', 'Something is not okay, are you okay?');
|
$error = data_get($error, 'error', 'Something is not okay, are you okay?');
|
||||||
@@ -1415,14 +1423,41 @@ class Application extends BaseModel
|
|||||||
}
|
}
|
||||||
throw new \Exception($error);
|
throw new \Exception($error);
|
||||||
}
|
}
|
||||||
$metrics = str($metrics)->explode("\n")->skip(1)->all();
|
$metrics = json_decode($metrics, true);
|
||||||
$parsedCollection = collect($metrics)->flatMap(function ($item) {
|
$parsedCollection = collect($metrics)->map(function ($metric) {
|
||||||
return collect(explode("\n", trim($item)))->map(function ($line) {
|
return [(int) $metric['time'], (float) $metric['percent']];
|
||||||
[$time, $cpu_usage_percent, $memory_usage, $memory_usage_percent] = explode(',', trim($line));
|
});
|
||||||
$cpu_usage_percent = number_format($cpu_usage_percent, 2);
|
|
||||||
|
|
||||||
return [(int) $time, (float) $cpu_usage_percent, (int) $memory_usage];
|
return $parsedCollection->toArray();
|
||||||
});
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getMemoryMetrics(int $mins = 5)
|
||||||
|
{
|
||||||
|
$server = $this->destination->server;
|
||||||
|
$container_name = $this->uuid;
|
||||||
|
if ($server->isMetricsEnabled()) {
|
||||||
|
$from = now()->subMinutes($mins)->toIso8601ZuluString();
|
||||||
|
if (isDev() && $server->id === 0) {
|
||||||
|
$process = Process::run("curl -H \"Authorization: Bearer {$this->settings->sentinel_token}\" http://host.docker.internal:8888/api/container/{$container_name}/memory/history?from=$from");
|
||||||
|
if ($process->failed()) {
|
||||||
|
throw new \Exception($process->errorOutput());
|
||||||
|
}
|
||||||
|
$metrics = $process->output();
|
||||||
|
} else {
|
||||||
|
$metrics = instant_remote_process(["docker exec coolify-sentinel sh -c 'curl -H \"Authorization: Bearer {$server->settings->sentinel_token}\" http://localhost:8888/api/container/{$container_name}/memory/history?from=$from'"], $server, false);
|
||||||
|
}
|
||||||
|
if (str($metrics)->contains('error')) {
|
||||||
|
$error = json_decode($metrics, true);
|
||||||
|
$error = data_get($error, 'error', 'Something is not okay, are you okay?');
|
||||||
|
if ($error == 'Unauthorized') {
|
||||||
|
$error = 'Unauthorized, please check your metrics token or restart Sentinel to set a new token.';
|
||||||
|
}
|
||||||
|
throw new \Exception($error);
|
||||||
|
}
|
||||||
|
$metrics = json_decode($metrics, true);
|
||||||
|
$parsedCollection = collect($metrics)->map(function ($metric) {
|
||||||
|
return [(int) $metric['time'], (float) $metric['used']];
|
||||||
});
|
});
|
||||||
|
|
||||||
return $parsedCollection->toArray();
|
return $parsedCollection->toArray();
|
||||||
@@ -1459,7 +1494,9 @@ class Application extends BaseModel
|
|||||||
|
|
||||||
return $config;
|
return $config;
|
||||||
}
|
}
|
||||||
public function setConfig($config) {
|
|
||||||
|
public function setConfig($config)
|
||||||
|
{
|
||||||
|
|
||||||
$config = $config;
|
$config = $config;
|
||||||
$validator = Validator::make(['config' => $config], [
|
$validator = Validator::make(['config' => $config], [
|
||||||
|
@@ -74,6 +74,9 @@ class EnvironmentVariable extends Model
|
|||||||
'version' => config('version'),
|
'version' => config('version'),
|
||||||
]);
|
]);
|
||||||
});
|
});
|
||||||
|
static::saving(function (EnvironmentVariable $environmentVariable) {
|
||||||
|
$environmentVariable->updateIsShared();
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public function service()
|
public function service()
|
||||||
@@ -217,4 +220,11 @@ class EnvironmentVariable extends Model
|
|||||||
set: fn (string $value) => str($value)->trim()->replace(' ', '_')->value,
|
set: fn (string $value) => str($value)->trim()->replace(' ', '_')->value,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected function updateIsShared(): void
|
||||||
|
{
|
||||||
|
$type = str($this->value)->after('{{')->before('.')->value;
|
||||||
|
$isShared = str($this->value)->startsWith('{{'.$type) && str($this->value)->endsWith('}}');
|
||||||
|
$this->is_shared = $isShared;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
namespace App\Models;
|
namespace App\Models;
|
||||||
|
|
||||||
|
use App\Jobs\PullHelperImageJob;
|
||||||
use App\Notifications\Channels\SendsEmail;
|
use App\Notifications\Channels\SendsEmail;
|
||||||
use Illuminate\Database\Eloquent\Casts\Attribute;
|
use Illuminate\Database\Eloquent\Casts\Attribute;
|
||||||
use Illuminate\Database\Eloquent\Model;
|
use Illuminate\Database\Eloquent\Model;
|
||||||
@@ -24,6 +25,20 @@ class InstanceSettings extends Model implements SendsEmail
|
|||||||
'sentinel_token' => 'encrypted',
|
'sentinel_token' => 'encrypted',
|
||||||
];
|
];
|
||||||
|
|
||||||
|
protected static function booted(): void
|
||||||
|
{
|
||||||
|
static::updated(function ($settings) {
|
||||||
|
if ($settings->isDirty('helper_version')) {
|
||||||
|
Server::chunkById(100, function ($servers) {
|
||||||
|
foreach ($servers as $server) {
|
||||||
|
PullHelperImageJob::dispatch($server);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
public function fqdn(): Attribute
|
public function fqdn(): Attribute
|
||||||
{
|
{
|
||||||
return Attribute::make(
|
return Attribute::make(
|
||||||
@@ -86,17 +101,4 @@ class InstanceSettings extends Model implements SendsEmail
|
|||||||
|
|
||||||
return "[{$instanceName}]";
|
return "[{$instanceName}]";
|
||||||
}
|
}
|
||||||
|
|
||||||
public function helperVersion(): Attribute
|
|
||||||
{
|
|
||||||
return Attribute::make(
|
|
||||||
get: function ($value) {
|
|
||||||
if (isDev()) {
|
|
||||||
return 'latest';
|
|
||||||
}
|
|
||||||
|
|
||||||
return $value;
|
|
||||||
}
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@@ -51,7 +51,6 @@ class ScheduledDatabaseBackup extends BaseModel
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -7,6 +7,8 @@ use App\Enums\ProxyTypes;
|
|||||||
use App\Jobs\PullSentinelImageJob;
|
use App\Jobs\PullSentinelImageJob;
|
||||||
use Illuminate\Database\Eloquent\Builder;
|
use Illuminate\Database\Eloquent\Builder;
|
||||||
use Illuminate\Database\Eloquent\Casts\Attribute;
|
use Illuminate\Database\Eloquent\Casts\Attribute;
|
||||||
|
use Illuminate\Database\Eloquent\SoftDeletes;
|
||||||
|
use Illuminate\Support\Carbon;
|
||||||
use Illuminate\Support\Collection;
|
use Illuminate\Support\Collection;
|
||||||
use Illuminate\Support\Facades\DB;
|
use Illuminate\Support\Facades\DB;
|
||||||
use Illuminate\Support\Facades\Process;
|
use Illuminate\Support\Facades\Process;
|
||||||
@@ -43,7 +45,7 @@ use Symfony\Component\Yaml\Yaml;
|
|||||||
|
|
||||||
class Server extends BaseModel
|
class Server extends BaseModel
|
||||||
{
|
{
|
||||||
use SchemalessAttributesTrait;
|
use SchemalessAttributesTrait,SoftDeletes;
|
||||||
|
|
||||||
public static $batch_counter = 0;
|
public static $batch_counter = 0;
|
||||||
|
|
||||||
@@ -103,7 +105,8 @@ class Server extends BaseModel
|
|||||||
$server->proxy->redirect_enabled = true;
|
$server->proxy->redirect_enabled = true;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
static::deleting(function ($server) {
|
|
||||||
|
static::forceDeleting(function ($server) {
|
||||||
$server->destinations()->each(function ($destination) {
|
$server->destinations()->each(function ($destination) {
|
||||||
$destination->delete();
|
$destination->delete();
|
||||||
});
|
});
|
||||||
@@ -520,22 +523,20 @@ $schema://$host {
|
|||||||
Storage::disk('ssh-mux')->delete($this->muxFilename());
|
Storage::disk('ssh-mux')->delete($this->muxFilename());
|
||||||
}
|
}
|
||||||
|
|
||||||
public function generateSentinelToken()
|
public function sentinelHeartbeat(bool $isReset = false)
|
||||||
{
|
{
|
||||||
$data = [
|
$this->sentinel_updated_at = $isReset ? now()->subMinutes(6000) : now();
|
||||||
'server_uuid' => $this->uuid,
|
$this->save();
|
||||||
];
|
}
|
||||||
$token = json_encode($data);
|
|
||||||
$encrypted = encrypt($token);
|
|
||||||
$this->settings->sentinel_token = $encrypted;
|
|
||||||
$this->settings->save();
|
|
||||||
|
|
||||||
return $encrypted;
|
public function isSentinelLive()
|
||||||
|
{
|
||||||
|
return Carbon::parse($this->sentinel_updated_at)->isAfter(now()->subMinutes(4));
|
||||||
}
|
}
|
||||||
|
|
||||||
public function isSentinelEnabled()
|
public function isSentinelEnabled()
|
||||||
{
|
{
|
||||||
return $this->isMetricsEnabled() || $this->isServerApiEnabled();
|
return ($this->isMetricsEnabled() || $this->isServerApiEnabled()) && ! $this->isBuildServer();
|
||||||
}
|
}
|
||||||
|
|
||||||
public function isMetricsEnabled()
|
public function isMetricsEnabled()
|
||||||
@@ -545,7 +546,7 @@ $schema://$host {
|
|||||||
|
|
||||||
public function isServerApiEnabled()
|
public function isServerApiEnabled()
|
||||||
{
|
{
|
||||||
return $this->settings->is_server_api_enabled;
|
return $this->settings->is_sentinel_enabled;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function checkServerApi()
|
public function checkServerApi()
|
||||||
@@ -586,7 +587,15 @@ $schema://$host {
|
|||||||
{
|
{
|
||||||
if ($this->isMetricsEnabled()) {
|
if ($this->isMetricsEnabled()) {
|
||||||
$from = now()->subMinutes($mins)->toIso8601ZuluString();
|
$from = now()->subMinutes($mins)->toIso8601ZuluString();
|
||||||
$cpu = instant_remote_process(["docker exec coolify-sentinel sh -c 'curl -H \"Authorization: Bearer {$this->settings->sentinel_token}\" http://localhost:8888/api/cpu/history?from=$from'"], $this, false);
|
if (isDev() && $this->id === 0) {
|
||||||
|
$process = Process::run("curl -H \"Authorization: Bearer {$this->settings->sentinel_token}\" http://host.docker.internal:8888/api/cpu/history?from=$from");
|
||||||
|
if ($process->failed()) {
|
||||||
|
throw new \Exception($process->errorOutput());
|
||||||
|
}
|
||||||
|
$cpu = $process->output();
|
||||||
|
} else {
|
||||||
|
$cpu = instant_remote_process(["docker exec coolify-sentinel sh -c 'curl -H \"Authorization: Bearer {$this->settings->sentinel_token}\" http://localhost:8888/api/cpu/history?from=$from'"], $this, false);
|
||||||
|
}
|
||||||
if (str($cpu)->contains('error')) {
|
if (str($cpu)->contains('error')) {
|
||||||
$error = json_decode($cpu, true);
|
$error = json_decode($cpu, true);
|
||||||
$error = data_get($error, 'error', 'Something is not okay, are you okay?');
|
$error = data_get($error, 'error', 'Something is not okay, are you okay?');
|
||||||
@@ -595,17 +604,13 @@ $schema://$host {
|
|||||||
}
|
}
|
||||||
throw new \Exception($error);
|
throw new \Exception($error);
|
||||||
}
|
}
|
||||||
$cpu = str($cpu)->explode("\n")->skip(1)->all();
|
$cpu = json_decode($cpu, true);
|
||||||
$parsedCollection = collect($cpu)->flatMap(function ($item) {
|
$parsedCollection = collect($cpu)->map(function ($metric) {
|
||||||
return collect(explode("\n", trim($item)))->map(function ($line) {
|
return [(int) $metric['time'], (float) $metric['percent']];
|
||||||
[$time, $cpu_usage_percent] = explode(',', trim($line));
|
|
||||||
$cpu_usage_percent = number_format($cpu_usage_percent, 0);
|
|
||||||
|
|
||||||
return [(int) $time, (float) $cpu_usage_percent];
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
|
||||||
return $parsedCollection->toArray();
|
return $parsedCollection;
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -613,7 +618,15 @@ $schema://$host {
|
|||||||
{
|
{
|
||||||
if ($this->isMetricsEnabled()) {
|
if ($this->isMetricsEnabled()) {
|
||||||
$from = now()->subMinutes($mins)->toIso8601ZuluString();
|
$from = now()->subMinutes($mins)->toIso8601ZuluString();
|
||||||
$memory = instant_remote_process(["docker exec coolify-sentinel sh -c 'curl -H \"Authorization: Bearer {$this->settings->sentinel_token}\" http://localhost:8888/api/memory/history?from=$from'"], $this, false);
|
if (isDev() && $this->id === 0) {
|
||||||
|
$process = Process::run("curl -H \"Authorization: Bearer {$this->settings->sentinel_token}\" http://host.docker.internal:8888/api/memory/history?from=$from");
|
||||||
|
if ($process->failed()) {
|
||||||
|
throw new \Exception($process->errorOutput());
|
||||||
|
}
|
||||||
|
$memory = $process->output();
|
||||||
|
} else {
|
||||||
|
$memory = instant_remote_process(["docker exec coolify-sentinel sh -c 'curl -H \"Authorization: Bearer {$this->settings->sentinel_token}\" http://localhost:8888/api/memory/history?from=$from'"], $this, false);
|
||||||
|
}
|
||||||
if (str($memory)->contains('error')) {
|
if (str($memory)->contains('error')) {
|
||||||
$error = json_decode($memory, true);
|
$error = json_decode($memory, true);
|
||||||
$error = data_get($error, 'error', 'Something is not okay, are you okay?');
|
$error = data_get($error, 'error', 'Something is not okay, are you okay?');
|
||||||
@@ -622,14 +635,9 @@ $schema://$host {
|
|||||||
}
|
}
|
||||||
throw new \Exception($error);
|
throw new \Exception($error);
|
||||||
}
|
}
|
||||||
$memory = str($memory)->explode("\n")->skip(1)->all();
|
$memory = json_decode($memory, true);
|
||||||
$parsedCollection = collect($memory)->flatMap(function ($item) {
|
$parsedCollection = collect($memory)->map(function ($metric) {
|
||||||
return collect(explode("\n", trim($item)))->map(function ($line) {
|
return [(int) $metric['time'], (float) $metric['usedPercent']];
|
||||||
[$time, $used, $free, $usedPercent] = explode(',', trim($line));
|
|
||||||
$usedPercent = number_format($usedPercent, 0);
|
|
||||||
|
|
||||||
return [(int) $time, (float) $usedPercent];
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
|
||||||
return $parsedCollection->toArray();
|
return $parsedCollection->toArray();
|
||||||
@@ -1049,6 +1057,38 @@ $schema://$host {
|
|||||||
return data_get($this, 'settings.is_swarm_worker');
|
return data_get($this, 'settings.is_swarm_worker');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function status(): bool
|
||||||
|
{
|
||||||
|
['uptime' => $uptime] = $this->validateConnection(false);
|
||||||
|
if ($uptime) {
|
||||||
|
if ($this->unreachable_notification_sent === true) {
|
||||||
|
$this->update(['unreachable_notification_sent' => false]);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// $this->server->team?->notify(new Unreachable($this->server));
|
||||||
|
foreach ($this->applications as $application) {
|
||||||
|
$application->update(['status' => 'exited']);
|
||||||
|
}
|
||||||
|
foreach ($this->databases as $database) {
|
||||||
|
$database->update(['status' => 'exited']);
|
||||||
|
}
|
||||||
|
foreach ($this->services 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;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
public function validateConnection($isManualCheck = true)
|
public function validateConnection($isManualCheck = true)
|
||||||
{
|
{
|
||||||
config()->set('constants.ssh.mux_enabled', ! $isManualCheck);
|
config()->set('constants.ssh.mux_enabled', ! $isManualCheck);
|
||||||
|
@@ -24,7 +24,7 @@ use OpenApi\Attributes as OA;
|
|||||||
'is_logdrain_newrelic_enabled' => ['type' => 'boolean'],
|
'is_logdrain_newrelic_enabled' => ['type' => 'boolean'],
|
||||||
'is_metrics_enabled' => ['type' => 'boolean'],
|
'is_metrics_enabled' => ['type' => 'boolean'],
|
||||||
'is_reachable' => ['type' => 'boolean'],
|
'is_reachable' => ['type' => 'boolean'],
|
||||||
'is_server_api_enabled' => ['type' => 'boolean'],
|
'is_sentinel_enabled' => ['type' => 'boolean'],
|
||||||
'is_swarm_manager' => ['type' => 'boolean'],
|
'is_swarm_manager' => ['type' => 'boolean'],
|
||||||
'is_swarm_worker' => ['type' => 'boolean'],
|
'is_swarm_worker' => ['type' => 'boolean'],
|
||||||
'is_usable' => ['type' => 'boolean'],
|
'is_usable' => ['type' => 'boolean'],
|
||||||
@@ -56,6 +56,63 @@ class ServerSetting extends Model
|
|||||||
'sentinel_token' => 'encrypted',
|
'sentinel_token' => 'encrypted',
|
||||||
];
|
];
|
||||||
|
|
||||||
|
protected static function booted()
|
||||||
|
{
|
||||||
|
static::creating(function ($setting) {
|
||||||
|
try {
|
||||||
|
if (str($setting->sentinel_token)->isEmpty()) {
|
||||||
|
$setting->generateSentinelToken(save: false);
|
||||||
|
}
|
||||||
|
if (str($setting->sentinel_custom_url)->isEmpty()) {
|
||||||
|
$url = $setting->generateSentinelUrl(save: false);
|
||||||
|
if (str($url)->isEmpty()) {
|
||||||
|
$setting->is_sentinel_enabled = false;
|
||||||
|
} else {
|
||||||
|
$setting->is_sentinel_enabled = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (\Throwable $e) {
|
||||||
|
loggy('Error creating server setting: '.$e->getMessage());
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public function generateSentinelToken(bool $save = true)
|
||||||
|
{
|
||||||
|
$data = [
|
||||||
|
'server_uuid' => $this->server->uuid,
|
||||||
|
];
|
||||||
|
$token = json_encode($data);
|
||||||
|
$encrypted = encrypt($token);
|
||||||
|
$this->sentinel_token = $encrypted;
|
||||||
|
if ($save) {
|
||||||
|
$this->save();
|
||||||
|
}
|
||||||
|
|
||||||
|
return $encrypted;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function generateSentinelUrl(bool $save = true)
|
||||||
|
{
|
||||||
|
$domain = null;
|
||||||
|
$settings = InstanceSettings::get();
|
||||||
|
if ($this->server->isLocalhost()) {
|
||||||
|
$domain = 'http://host.docker.internal:8000';
|
||||||
|
} elseif ($settings->fqdn) {
|
||||||
|
$domain = $settings->fqdn;
|
||||||
|
} elseif ($settings->ipv4) {
|
||||||
|
$domain = $settings->ipv4.':8000';
|
||||||
|
} elseif ($settings->ipv6) {
|
||||||
|
$domain = $settings->ipv6.':8000';
|
||||||
|
}
|
||||||
|
$this->sentinel_custom_url = $domain;
|
||||||
|
if ($save) {
|
||||||
|
$this->save();
|
||||||
|
}
|
||||||
|
|
||||||
|
return $domain;
|
||||||
|
}
|
||||||
|
|
||||||
public function server()
|
public function server()
|
||||||
{
|
{
|
||||||
return $this->belongsTo(Server::class);
|
return $this->belongsTo(Server::class);
|
||||||
|
@@ -297,7 +297,7 @@ class Service extends BaseModel
|
|||||||
'key' => 'CP_DISABLE_HTTPS',
|
'key' => 'CP_DISABLE_HTTPS',
|
||||||
'value' => data_get($disable_https, 'value'),
|
'value' => data_get($disable_https, 'value'),
|
||||||
'rules' => 'required',
|
'rules' => 'required',
|
||||||
'customHelper' => "If you want to use https, set this to 0. Variable name: CP_DISABLE_HTTPS",
|
'customHelper' => 'If you want to use https, set this to 0. Variable name: CP_DISABLE_HTTPS',
|
||||||
],
|
],
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
@@ -997,8 +997,8 @@ class Service extends BaseModel
|
|||||||
break;
|
break;
|
||||||
case $image->contains('mysql'):
|
case $image->contains('mysql'):
|
||||||
$userVariables = ['SERVICE_USER_MYSQL', 'SERVICE_USER_WORDPRESS', 'MYSQL_USER'];
|
$userVariables = ['SERVICE_USER_MYSQL', 'SERVICE_USER_WORDPRESS', 'MYSQL_USER'];
|
||||||
$passwordVariables = ['SERVICE_PASSWORD_MYSQL', 'SERVICE_PASSWORD_WORDPRESS', 'MYSQL_PASSWORD','SERVICE_PASSWORD_64_MYSQL'];
|
$passwordVariables = ['SERVICE_PASSWORD_MYSQL', 'SERVICE_PASSWORD_WORDPRESS', 'MYSQL_PASSWORD', 'SERVICE_PASSWORD_64_MYSQL'];
|
||||||
$rootPasswordVariables = ['SERVICE_PASSWORD_MYSQLROOT', 'SERVICE_PASSWORD_ROOT','SERVICE_PASSWORD_64_MYSQLROOT'];
|
$rootPasswordVariables = ['SERVICE_PASSWORD_MYSQLROOT', 'SERVICE_PASSWORD_ROOT', 'SERVICE_PASSWORD_64_MYSQLROOT'];
|
||||||
$dbNameVariables = ['MYSQL_DATABASE'];
|
$dbNameVariables = ['MYSQL_DATABASE'];
|
||||||
$mysql_user = $this->environment_variables()->whereIn('key', $userVariables)->first();
|
$mysql_user = $this->environment_variables()->whereIn('key', $userVariables)->first();
|
||||||
$mysql_password = $this->environment_variables()->whereIn('key', $passwordVariables)->first();
|
$mysql_password = $this->environment_variables()->whereIn('key', $passwordVariables)->first();
|
||||||
@@ -1326,9 +1326,9 @@ class Service extends BaseModel
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@@ -210,7 +210,12 @@ class StandaloneRedis extends BaseModel
|
|||||||
protected function internalDbUrl(): Attribute
|
protected function internalDbUrl(): Attribute
|
||||||
{
|
{
|
||||||
return new Attribute(
|
return new Attribute(
|
||||||
get: fn () => "redis://:{$this->redis_password}@{$this->uuid}:6379/0",
|
get: function () {
|
||||||
|
$redis_version = $this->getRedisVersion();
|
||||||
|
$username_part = version_compare($redis_version, '6.0', '>=') ? "{$this->redis_username}:" : '';
|
||||||
|
|
||||||
|
return "redis://{$username_part}{$this->redis_password}@{$this->uuid}:6379/0";
|
||||||
|
}
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -219,7 +224,10 @@ class StandaloneRedis extends BaseModel
|
|||||||
return new Attribute(
|
return new Attribute(
|
||||||
get: function () {
|
get: function () {
|
||||||
if ($this->is_public && $this->public_port) {
|
if ($this->is_public && $this->public_port) {
|
||||||
return "redis://:{$this->redis_password}@{$this->destination->server->getIp}:{$this->public_port}/0";
|
$redis_version = $this->getRedisVersion();
|
||||||
|
$username_part = version_compare($redis_version, '6.0', '>=') ? "{$this->redis_username}:" : '';
|
||||||
|
|
||||||
|
return "redis://{$username_part}{$this->redis_password}@{$this->destination->server->getIp}:{$this->public_port}/0";
|
||||||
}
|
}
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
@@ -227,6 +235,13 @@ class StandaloneRedis extends BaseModel
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function getRedisVersion()
|
||||||
|
{
|
||||||
|
$image_parts = explode(':', $this->image);
|
||||||
|
|
||||||
|
return $image_parts[1] ?? '0.0';
|
||||||
|
}
|
||||||
|
|
||||||
public function environment()
|
public function environment()
|
||||||
{
|
{
|
||||||
return $this->belongsTo(Environment::class);
|
return $this->belongsTo(Environment::class);
|
||||||
@@ -295,4 +310,33 @@ class StandaloneRedis extends BaseModel
|
|||||||
{
|
{
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function redisPassword(): Attribute
|
||||||
|
{
|
||||||
|
return new Attribute(
|
||||||
|
get: function () {
|
||||||
|
$password = $this->runtime_environment_variables()->where('key', 'REDIS_PASSWORD')->first();
|
||||||
|
if (! $password) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $password->value;
|
||||||
|
},
|
||||||
|
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function redisUsername(): Attribute
|
||||||
|
{
|
||||||
|
return new Attribute(
|
||||||
|
get: function () {
|
||||||
|
$username = $this->runtime_environment_variables()->where('key', 'REDIS_USERNAME')->first();
|
||||||
|
if (! $username) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $username->value;
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@@ -1,5 +1,6 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
|
use App\Models\EnvironmentVariable;
|
||||||
use App\Models\Server;
|
use App\Models\Server;
|
||||||
use App\Models\StandaloneClickhouse;
|
use App\Models\StandaloneClickhouse;
|
||||||
use App\Models\StandaloneDocker;
|
use App\Models\StandaloneDocker;
|
||||||
@@ -48,7 +49,7 @@ function create_standalone_redis($environment_id, $destination_uuid, ?array $oth
|
|||||||
}
|
}
|
||||||
$database = new StandaloneRedis;
|
$database = new StandaloneRedis;
|
||||||
$database->name = generate_database_name('redis');
|
$database->name = generate_database_name('redis');
|
||||||
$database->redis_password = \Illuminate\Support\Str::password(length: 64, symbols: false);
|
$redis_password = \Illuminate\Support\Str::password(length: 64, symbols: false);
|
||||||
$database->environment_id = $environment_id;
|
$database->environment_id = $environment_id;
|
||||||
$database->destination_id = $destination->id;
|
$database->destination_id = $destination->id;
|
||||||
$database->destination_type = $destination->getMorphClass();
|
$database->destination_type = $destination->getMorphClass();
|
||||||
@@ -57,6 +58,20 @@ function create_standalone_redis($environment_id, $destination_uuid, ?array $oth
|
|||||||
}
|
}
|
||||||
$database->save();
|
$database->save();
|
||||||
|
|
||||||
|
EnvironmentVariable::create([
|
||||||
|
'key' => 'REDIS_PASSWORD',
|
||||||
|
'value' => $redis_password,
|
||||||
|
'standalone_redis_id' => $database->id,
|
||||||
|
'is_shared' => false,
|
||||||
|
]);
|
||||||
|
|
||||||
|
EnvironmentVariable::create([
|
||||||
|
'key' => 'REDIS_USERNAME',
|
||||||
|
'value' => 'default',
|
||||||
|
'standalone_redis_id' => $database->id,
|
||||||
|
'is_shared' => false,
|
||||||
|
]);
|
||||||
|
|
||||||
return $database;
|
return $database;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -335,10 +335,11 @@ function fqdnLabelsForTraefik(string $uuid, Collection $domains, bool $is_force_
|
|||||||
if (preg_match('/coolify\.traefik\.middlewares=(.*)/', $item, $matches)) {
|
if (preg_match('/coolify\.traefik\.middlewares=(.*)/', $item, $matches)) {
|
||||||
return explode(',', $matches[1]);
|
return explode(',', $matches[1]);
|
||||||
}
|
}
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
})->flatten()
|
})->flatten()
|
||||||
->filter()
|
->filter()
|
||||||
->unique();
|
->unique();
|
||||||
}
|
}
|
||||||
foreach ($domains as $loop => $domain) {
|
foreach ($domains as $loop => $domain) {
|
||||||
try {
|
try {
|
||||||
@@ -388,7 +389,7 @@ function fqdnLabelsForTraefik(string $uuid, Collection $domains, bool $is_force_
|
|||||||
if ($path !== '/') {
|
if ($path !== '/') {
|
||||||
// Middleware handling
|
// Middleware handling
|
||||||
$middlewares = collect([]);
|
$middlewares = collect([]);
|
||||||
if ($is_stripprefix_enabled && !str($image)->contains('ghost')) {
|
if ($is_stripprefix_enabled && ! str($image)->contains('ghost')) {
|
||||||
$labels->push("traefik.http.middlewares.{$https_label}-stripprefix.stripprefix.prefixes={$path}");
|
$labels->push("traefik.http.middlewares.{$https_label}-stripprefix.stripprefix.prefixes={$path}");
|
||||||
$middlewares->push("{$https_label}-stripprefix");
|
$middlewares->push("{$https_label}-stripprefix");
|
||||||
}
|
}
|
||||||
@@ -402,7 +403,7 @@ function fqdnLabelsForTraefik(string $uuid, Collection $domains, bool $is_force_
|
|||||||
$labels = $labels->merge($redirect_to_non_www);
|
$labels = $labels->merge($redirect_to_non_www);
|
||||||
$middlewares->push($to_non_www_name);
|
$middlewares->push($to_non_www_name);
|
||||||
}
|
}
|
||||||
if ($redirect_direction === 'www' && !str($host)->startsWith('www.')) {
|
if ($redirect_direction === 'www' && ! str($host)->startsWith('www.')) {
|
||||||
$labels = $labels->merge($redirect_to_www);
|
$labels = $labels->merge($redirect_to_www);
|
||||||
$middlewares->push($to_www_name);
|
$middlewares->push($to_www_name);
|
||||||
}
|
}
|
||||||
@@ -417,7 +418,7 @@ function fqdnLabelsForTraefik(string $uuid, Collection $domains, bool $is_force_
|
|||||||
$middlewares = collect([]);
|
$middlewares = collect([]);
|
||||||
if ($is_gzip_enabled) {
|
if ($is_gzip_enabled) {
|
||||||
$middlewares->push('gzip');
|
$middlewares->push('gzip');
|
||||||
}
|
}
|
||||||
if (str($image)->contains('ghost')) {
|
if (str($image)->contains('ghost')) {
|
||||||
$middlewares->push('redir-ghost');
|
$middlewares->push('redir-ghost');
|
||||||
}
|
}
|
||||||
|
@@ -241,9 +241,11 @@ function generate_default_proxy_configuration(Server $server)
|
|||||||
'ports' => [
|
'ports' => [
|
||||||
'80:80',
|
'80:80',
|
||||||
'443:443',
|
'443:443',
|
||||||
|
'443:443/udp',
|
||||||
],
|
],
|
||||||
'labels' => [
|
'labels' => [
|
||||||
'coolify.managed=true',
|
'coolify.managed=true',
|
||||||
|
'coolify.proxy=true',
|
||||||
],
|
],
|
||||||
'volumes' => [
|
'volumes' => [
|
||||||
'/var/run/docker.sock:/var/run/docker.sock:ro',
|
'/var/run/docker.sock:/var/run/docker.sock:ro',
|
||||||
|
@@ -126,7 +126,7 @@ function refreshSession(?Team $team = null): void
|
|||||||
}
|
}
|
||||||
function handleError(?Throwable $error = null, ?Livewire\Component $livewire = null, ?string $customErrorMessage = null)
|
function handleError(?Throwable $error = null, ?Livewire\Component $livewire = null, ?string $customErrorMessage = null)
|
||||||
{
|
{
|
||||||
ray($error);
|
loggy($error);
|
||||||
if ($error instanceof TooManyRequestsException) {
|
if ($error instanceof TooManyRequestsException) {
|
||||||
if (isset($livewire)) {
|
if (isset($livewire)) {
|
||||||
return $livewire->dispatch('error', "Too many requests. Please try again in {$error->secondsUntilAvailable} seconds.");
|
return $livewire->dispatch('error', "Too many requests. Please try again in {$error->secondsUntilAvailable} seconds.");
|
||||||
@@ -142,6 +142,10 @@ function handleError(?Throwable $error = null, ?Livewire\Component $livewire = n
|
|||||||
return 'Duplicate entry found. Please use a different name.';
|
return 'Duplicate entry found. Please use a different name.';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if ($error instanceof \Illuminate\Database\Eloquent\ModelNotFoundException) {
|
||||||
|
abort(404);
|
||||||
|
}
|
||||||
|
|
||||||
if ($error instanceof Throwable) {
|
if ($error instanceof Throwable) {
|
||||||
$message = $error->getMessage();
|
$message = $error->getMessage();
|
||||||
} else {
|
} else {
|
||||||
@@ -164,10 +168,10 @@ function get_route_parameters(): array
|
|||||||
function get_latest_sentinel_version(): string
|
function get_latest_sentinel_version(): string
|
||||||
{
|
{
|
||||||
try {
|
try {
|
||||||
$response = Http::get('https://cdn.coollabs.io/sentinel/versions.json');
|
$response = Http::get('https://cdn.coollabs.io/coolify/versions.json');
|
||||||
$versions = $response->json();
|
$versions = $response->json();
|
||||||
|
|
||||||
return data_get($versions, 'sentinel.version');
|
return data_get($versions, 'coolify.sentinel.version');
|
||||||
} catch (\Throwable $e) {
|
} catch (\Throwable $e) {
|
||||||
//throw $e;
|
//throw $e;
|
||||||
ray($e->getMessage());
|
ray($e->getMessage());
|
||||||
@@ -3785,7 +3789,6 @@ function newParser(Application|Service $resource, int $pull_request_id = 0, ?int
|
|||||||
service_name: $serviceName,
|
service_name: $serviceName,
|
||||||
image: $image,
|
image: $image,
|
||||||
predefinedPort: $predefinedPort
|
predefinedPort: $predefinedPort
|
||||||
|
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -3983,13 +3986,14 @@ function instanceSettings()
|
|||||||
return InstanceSettings::get();
|
return InstanceSettings::get();
|
||||||
}
|
}
|
||||||
|
|
||||||
function loadConfigFromGit(string $repository, string $branch, string $base_directory, int $server_id, int $team_id) {
|
function loadConfigFromGit(string $repository, string $branch, string $base_directory, int $server_id, int $team_id)
|
||||||
|
{
|
||||||
|
|
||||||
$server = Server::find($server_id)->where('team_id', $team_id)->first();
|
$server = Server::find($server_id)->where('team_id', $team_id)->first();
|
||||||
if (!$server) {
|
if (! $server) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
$uuid = new Cuid2();
|
$uuid = new Cuid2;
|
||||||
$cloneCommand = "git clone --no-checkout -b $branch $repository .";
|
$cloneCommand = "git clone --no-checkout -b $branch $repository .";
|
||||||
$workdir = rtrim($base_directory, '/');
|
$workdir = rtrim($base_directory, '/');
|
||||||
$fileList = collect([".$workdir/coolify.json"]);
|
$fileList = collect([".$workdir/coolify.json"]);
|
||||||
@@ -4007,6 +4011,33 @@ function loadConfigFromGit(string $repository, string $branch, string $base_dire
|
|||||||
try {
|
try {
|
||||||
return instant_remote_process($commands, $server);
|
return instant_remote_process($commands, $server);
|
||||||
} catch (\Exception $e) {
|
} catch (\Exception $e) {
|
||||||
// continue
|
// continue
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function loggy($message = null, array $context = [])
|
||||||
|
{
|
||||||
|
if (! isDev()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (function_exists('ray') && config('app.debug')) {
|
||||||
|
ray($message, $context);
|
||||||
|
}
|
||||||
|
if (is_null($message)) {
|
||||||
|
return app('log');
|
||||||
|
}
|
||||||
|
|
||||||
|
return app('log')->debug($message, $context);
|
||||||
|
}
|
||||||
|
function sslipDomainWarning(string $domains)
|
||||||
|
{
|
||||||
|
$domains = str($domains)->trim()->explode(',');
|
||||||
|
$showSslipHttpsWarning = false;
|
||||||
|
$domains->each(function ($domain) use (&$showSslipHttpsWarning) {
|
||||||
|
if (str($domain)->contains('https') && str($domain)->contains('sslip')) {
|
||||||
|
$showSslipHttpsWarning = true;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return $showSslipHttpsWarning;
|
||||||
|
}
|
||||||
|
@@ -15,6 +15,7 @@
|
|||||||
"laravel/fortify": "^v1.16.0",
|
"laravel/fortify": "^v1.16.0",
|
||||||
"laravel/framework": "^v11",
|
"laravel/framework": "^v11",
|
||||||
"laravel/horizon": "^5.29.1",
|
"laravel/horizon": "^5.29.1",
|
||||||
|
"laravel/pail": "^1.1",
|
||||||
"laravel/prompts": "^0.1.6",
|
"laravel/prompts": "^0.1.6",
|
||||||
"laravel/sanctum": "^v4.0",
|
"laravel/sanctum": "^v4.0",
|
||||||
"laravel/socialite": "^v5.14.0",
|
"laravel/socialite": "^v5.14.0",
|
||||||
|
79
composer.lock
generated
79
composer.lock
generated
@@ -4,7 +4,7 @@
|
|||||||
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
|
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
|
||||||
"This file is @generated automatically"
|
"This file is @generated automatically"
|
||||||
],
|
],
|
||||||
"content-hash": "c47adf3684eb727e22503937435c0914",
|
"content-hash": "943975ec232403b96a40d215253492d8",
|
||||||
"packages": [
|
"packages": [
|
||||||
{
|
{
|
||||||
"name": "amphp/amp",
|
"name": "amphp/amp",
|
||||||
@@ -3144,6 +3144,83 @@
|
|||||||
},
|
},
|
||||||
"time": "2024-10-08T18:23:02+00:00"
|
"time": "2024-10-08T18:23:02+00:00"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"name": "laravel/pail",
|
||||||
|
"version": "v1.1.5",
|
||||||
|
"source": {
|
||||||
|
"type": "git",
|
||||||
|
"url": "https://github.com/laravel/pail.git",
|
||||||
|
"reference": "b33ad8321416fe86efed7bf398f3306c47b4871b"
|
||||||
|
},
|
||||||
|
"dist": {
|
||||||
|
"type": "zip",
|
||||||
|
"url": "https://api.github.com/repos/laravel/pail/zipball/b33ad8321416fe86efed7bf398f3306c47b4871b",
|
||||||
|
"reference": "b33ad8321416fe86efed7bf398f3306c47b4871b",
|
||||||
|
"shasum": ""
|
||||||
|
},
|
||||||
|
"require": {
|
||||||
|
"ext-mbstring": "*",
|
||||||
|
"illuminate/console": "^10.24|^11.0",
|
||||||
|
"illuminate/contracts": "^10.24|^11.0",
|
||||||
|
"illuminate/log": "^10.24|^11.0",
|
||||||
|
"illuminate/process": "^10.24|^11.0",
|
||||||
|
"illuminate/support": "^10.24|^11.0",
|
||||||
|
"nunomaduro/termwind": "^1.15|^2.0",
|
||||||
|
"php": "^8.2",
|
||||||
|
"symfony/console": "^6.0|^7.0"
|
||||||
|
},
|
||||||
|
"require-dev": {
|
||||||
|
"laravel/pint": "^1.13",
|
||||||
|
"orchestra/testbench": "^8.12|^9.0",
|
||||||
|
"pestphp/pest": "^2.20",
|
||||||
|
"pestphp/pest-plugin-type-coverage": "^2.3",
|
||||||
|
"phpstan/phpstan": "^1.10",
|
||||||
|
"symfony/var-dumper": "^6.3|^7.0"
|
||||||
|
},
|
||||||
|
"type": "library",
|
||||||
|
"extra": {
|
||||||
|
"branch-alias": {
|
||||||
|
"dev-main": "1.x-dev"
|
||||||
|
},
|
||||||
|
"laravel": {
|
||||||
|
"providers": [
|
||||||
|
"Laravel\\Pail\\PailServiceProvider"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"autoload": {
|
||||||
|
"psr-4": {
|
||||||
|
"Laravel\\Pail\\": "src/"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"notification-url": "https://packagist.org/downloads/",
|
||||||
|
"license": [
|
||||||
|
"MIT"
|
||||||
|
],
|
||||||
|
"authors": [
|
||||||
|
{
|
||||||
|
"name": "Taylor Otwell",
|
||||||
|
"email": "taylor@laravel.com"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Nuno Maduro",
|
||||||
|
"email": "enunomaduro@gmail.com"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"description": "Easily delve into your Laravel application's log files directly from the command line.",
|
||||||
|
"homepage": "https://github.com/laravel/pail",
|
||||||
|
"keywords": [
|
||||||
|
"laravel",
|
||||||
|
"logs",
|
||||||
|
"php",
|
||||||
|
"tail"
|
||||||
|
],
|
||||||
|
"support": {
|
||||||
|
"issues": "https://github.com/laravel/pail/issues",
|
||||||
|
"source": "https://github.com/laravel/pail"
|
||||||
|
},
|
||||||
|
"time": "2024-10-15T20:06:24+00:00"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"name": "laravel/prompts",
|
"name": "laravel/prompts",
|
||||||
"version": "v0.1.25",
|
"version": "v0.1.25",
|
||||||
|
6
config/testing.php
Normal file
6
config/testing.php
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
return [
|
||||||
|
'dusk_test_email' => env('DUSK_TEST_EMAIL', 'test@example.com'),
|
||||||
|
'dusk_test_password' => env('DUSK_TEST_PASSWORD', 'password'),
|
||||||
|
];
|
@@ -4,6 +4,7 @@ use App\Models\EnvironmentVariable;
|
|||||||
use App\Models\Server;
|
use App\Models\Server;
|
||||||
use Illuminate\Database\Migrations\Migration;
|
use Illuminate\Database\Migrations\Migration;
|
||||||
use Illuminate\Database\Schema\Blueprint;
|
use Illuminate\Database\Schema\Blueprint;
|
||||||
|
use Illuminate\Support\Facades\DB;
|
||||||
use Illuminate\Support\Facades\Schema;
|
use Illuminate\Support\Facades\Schema;
|
||||||
use Visus\Cuid2\Cuid2;
|
use Visus\Cuid2\Cuid2;
|
||||||
|
|
||||||
@@ -14,44 +15,45 @@ return new class extends Migration
|
|||||||
*/
|
*/
|
||||||
public function up(): void
|
public function up(): void
|
||||||
{
|
{
|
||||||
Schema::table('applications', function (Blueprint $table) {
|
try {
|
||||||
$table->dropColumn('docker_compose_pr_location');
|
Schema::table('applications', function (Blueprint $table) {
|
||||||
$table->dropColumn('docker_compose_pr');
|
$table->dropColumn('docker_compose_pr_location');
|
||||||
$table->dropColumn('docker_compose_pr_raw');
|
$table->dropColumn('docker_compose_pr');
|
||||||
});
|
$table->dropColumn('docker_compose_pr_raw');
|
||||||
Schema::table('subscriptions', function (Blueprint $table) {
|
});
|
||||||
$table->dropColumn('lemon_subscription_id');
|
Schema::table('subscriptions', function (Blueprint $table) {
|
||||||
$table->dropColumn('lemon_order_id');
|
$table->dropColumn('lemon_subscription_id');
|
||||||
$table->dropColumn('lemon_product_id');
|
$table->dropColumn('lemon_order_id');
|
||||||
$table->dropColumn('lemon_variant_id');
|
$table->dropColumn('lemon_product_id');
|
||||||
$table->dropColumn('lemon_variant_name');
|
$table->dropColumn('lemon_variant_id');
|
||||||
$table->dropColumn('lemon_customer_id');
|
$table->dropColumn('lemon_variant_name');
|
||||||
$table->dropColumn('lemon_status');
|
$table->dropColumn('lemon_customer_id');
|
||||||
$table->dropColumn('lemon_renews_at');
|
$table->dropColumn('lemon_status');
|
||||||
$table->dropColumn('lemon_update_payment_menthod_url');
|
$table->dropColumn('lemon_renews_at');
|
||||||
$table->dropColumn('lemon_trial_ends_at');
|
$table->dropColumn('lemon_update_payment_menthod_url');
|
||||||
$table->dropColumn('lemon_ends_at');
|
$table->dropColumn('lemon_trial_ends_at');
|
||||||
});
|
$table->dropColumn('lemon_ends_at');
|
||||||
Schema::table('environment_variables', function (Blueprint $table) {
|
});
|
||||||
$table->string('uuid')->nullable()->after('id');
|
Schema::table('environment_variables', function (Blueprint $table) {
|
||||||
});
|
$table->string('uuid')->nullable()->after('id');
|
||||||
|
});
|
||||||
|
|
||||||
EnvironmentVariable::all()->each(function (EnvironmentVariable $environmentVariable) {
|
EnvironmentVariable::all()->each(function (EnvironmentVariable $environmentVariable) {
|
||||||
$environmentVariable->update([
|
$environmentVariable->update([
|
||||||
'uuid' => (string) new Cuid2,
|
'uuid' => (string) new Cuid2,
|
||||||
]);
|
]);
|
||||||
});
|
});
|
||||||
Schema::table('environment_variables', function (Blueprint $table) {
|
Schema::table('environment_variables', function (Blueprint $table) {
|
||||||
$table->string('uuid')->nullable(false)->change();
|
$table->string('uuid')->nullable(false)->change();
|
||||||
});
|
});
|
||||||
Schema::table('server_settings', function (Blueprint $table) {
|
Schema::table('server_settings', function (Blueprint $table) {
|
||||||
$table->integer('metrics_history_days')->default(7)->change();
|
$table->integer('metrics_history_days')->default(7)->change();
|
||||||
});
|
});
|
||||||
Server::all()->each(function (Server $server) {
|
|
||||||
$server->settings->update([
|
DB::table('server_settings')->update(['metrics_history_days' => 7]);
|
||||||
'metrics_history_days' => 7,
|
} catch (\Exception $e) {
|
||||||
]);
|
loggy($e);
|
||||||
});
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@@ -12,7 +12,7 @@ return new class extends Migration
|
|||||||
public function up(): void
|
public function up(): void
|
||||||
{
|
{
|
||||||
Schema::table('server_settings', function (Blueprint $table) {
|
Schema::table('server_settings', function (Blueprint $table) {
|
||||||
$table->boolean('is_force_cleanup_enabled')->default(false)->after('is_sentinel_enabled');
|
$table->boolean('is_force_cleanup_enabled')->default(false);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -15,12 +15,17 @@ return new class extends Migration
|
|||||||
$table->dropColumn('metrics_token');
|
$table->dropColumn('metrics_token');
|
||||||
$table->dropColumn('metrics_refresh_rate_seconds');
|
$table->dropColumn('metrics_refresh_rate_seconds');
|
||||||
$table->dropColumn('metrics_history_days');
|
$table->dropColumn('metrics_history_days');
|
||||||
|
$table->dropColumn('is_server_api_enabled');
|
||||||
|
|
||||||
|
$table->boolean('is_sentinel_enabled')->default(false);
|
||||||
$table->text('sentinel_token')->nullable();
|
$table->text('sentinel_token')->nullable();
|
||||||
$table->integer('sentinel_metrics_refresh_rate_seconds')->default(5);
|
$table->integer('sentinel_metrics_refresh_rate_seconds')->default(10);
|
||||||
$table->integer('sentinel_metrics_history_days')->default(30);
|
$table->integer('sentinel_metrics_history_days')->default(7);
|
||||||
|
$table->integer('sentinel_push_interval_seconds')->default(60);
|
||||||
|
$table->string('sentinel_custom_url')->nullable();
|
||||||
});
|
});
|
||||||
Schema::table('servers', function (Blueprint $table) {
|
Schema::table('servers', function (Blueprint $table) {
|
||||||
$table->dateTime('sentinel_update_at')->default(now());
|
$table->dateTime('sentinel_updated_at')->default(now());
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -33,12 +38,17 @@ return new class extends Migration
|
|||||||
$table->string('metrics_token')->nullable();
|
$table->string('metrics_token')->nullable();
|
||||||
$table->integer('metrics_refresh_rate_seconds')->default(5);
|
$table->integer('metrics_refresh_rate_seconds')->default(5);
|
||||||
$table->integer('metrics_history_days')->default(30);
|
$table->integer('metrics_history_days')->default(30);
|
||||||
|
$table->boolean('is_server_api_enabled')->default(false);
|
||||||
|
|
||||||
|
$table->dropColumn('is_sentinel_enabled');
|
||||||
$table->dropColumn('sentinel_token');
|
$table->dropColumn('sentinel_token');
|
||||||
$table->dropColumn('sentinel_metrics_refresh_rate_seconds');
|
$table->dropColumn('sentinel_metrics_refresh_rate_seconds');
|
||||||
$table->dropColumn('sentinel_metrics_history_days');
|
$table->dropColumn('sentinel_metrics_history_days');
|
||||||
|
$table->dropColumn('sentinel_push_interval_seconds');
|
||||||
|
$table->dropColumn('sentinel_custom_url');
|
||||||
});
|
});
|
||||||
Schema::table('servers', function (Blueprint $table) {
|
Schema::table('servers', function (Blueprint $table) {
|
||||||
$table->dropColumn('sentinel_update_at');
|
$table->dropColumn('sentinel_updated_at');
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
@@ -0,0 +1,22 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
use Illuminate\Database\Migrations\Migration;
|
||||||
|
use Illuminate\Database\Schema\Blueprint;
|
||||||
|
use Illuminate\Support\Facades\Schema;
|
||||||
|
|
||||||
|
class AddIsSharedToEnvironmentVariables extends Migration
|
||||||
|
{
|
||||||
|
public function up()
|
||||||
|
{
|
||||||
|
Schema::table('environment_variables', function (Blueprint $table) {
|
||||||
|
$table->boolean('is_shared')->default(false);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public function down()
|
||||||
|
{
|
||||||
|
Schema::table('environment_variables', function (Blueprint $table) {
|
||||||
|
$table->dropColumn('is_shared');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
@@ -0,0 +1,41 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
use App\Models\EnvironmentVariable;
|
||||||
|
use App\Models\StandaloneRedis;
|
||||||
|
use Illuminate\Database\Migrations\Migration;
|
||||||
|
use Illuminate\Database\Schema\Blueprint;
|
||||||
|
use Illuminate\Support\Facades\DB;
|
||||||
|
use Illuminate\Support\Facades\Schema;
|
||||||
|
|
||||||
|
class MoveRedisPasswordToEnvs extends Migration
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Run the migrations.
|
||||||
|
*/
|
||||||
|
public function up(): void
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
StandaloneRedis::chunkById(100, function ($redisInstances) {
|
||||||
|
foreach ($redisInstances as $redis) {
|
||||||
|
$redis_password = DB::table('standalone_redis')->where('id', $redis->id)->value('redis_password');
|
||||||
|
EnvironmentVariable::create([
|
||||||
|
'standalone_redis_id' => $redis->id,
|
||||||
|
'key' => 'REDIS_PASSWORD',
|
||||||
|
'value' => $redis_password,
|
||||||
|
]);
|
||||||
|
EnvironmentVariable::create([
|
||||||
|
'standalone_redis_id' => $redis->id,
|
||||||
|
'key' => 'REDIS_USERNAME',
|
||||||
|
'value' => 'default',
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
Schema::table('standalone_redis', function (Blueprint $table) {
|
||||||
|
$table->dropColumn('redis_password');
|
||||||
|
});
|
||||||
|
} catch (\Exception $e) {
|
||||||
|
echo 'Moving Redis passwords to envs failed.';
|
||||||
|
echo $e->getMessage();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@@ -0,0 +1,22 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
use Illuminate\Database\Migrations\Migration;
|
||||||
|
use Illuminate\Database\Schema\Blueprint;
|
||||||
|
use Illuminate\Support\Facades\Schema;
|
||||||
|
|
||||||
|
return new class extends Migration
|
||||||
|
{
|
||||||
|
public function up()
|
||||||
|
{
|
||||||
|
Schema::table('instance_settings', function (Blueprint $table) {
|
||||||
|
$table->boolean('disable_two_step_confirmation')->default(false);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public function down()
|
||||||
|
{
|
||||||
|
Schema::table('instance_settings', function (Blueprint $table) {
|
||||||
|
$table->dropColumn('disable_two_step_confirmation');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
@@ -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->softDeletes();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reverse the migrations.
|
||||||
|
*/
|
||||||
|
public function down(): void
|
||||||
|
{
|
||||||
|
Schema::table('servers', function (Blueprint $table) {
|
||||||
|
$table->dropSoftDeletes();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
@@ -26,6 +26,8 @@ class DatabaseSeeder extends Seeder
|
|||||||
S3StorageSeeder::class,
|
S3StorageSeeder::class,
|
||||||
StandalonePostgresqlSeeder::class,
|
StandalonePostgresqlSeeder::class,
|
||||||
OauthSettingSeeder::class,
|
OauthSettingSeeder::class,
|
||||||
|
DisableTwoStepConfirmationSeeder::class,
|
||||||
|
SentinelSeeder::class,
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
20
database/seeders/DisableTwoStepConfirmationSeeder.php
Normal file
20
database/seeders/DisableTwoStepConfirmationSeeder.php
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Database\Seeders;
|
||||||
|
|
||||||
|
use Illuminate\Database\Seeder;
|
||||||
|
use Illuminate\Support\Facades\DB;
|
||||||
|
|
||||||
|
class DisableTwoStepConfirmationSeeder extends Seeder
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Run the database seeds.
|
||||||
|
*/
|
||||||
|
public function run(): void
|
||||||
|
{
|
||||||
|
DB::table('instance_settings')->updateOrInsert(
|
||||||
|
[],
|
||||||
|
['disable_two_step_confirmation' => true]
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
@@ -186,6 +186,7 @@ uZx9iFkCELtxrh31QJ68AAAAEXNhaWxANzZmZjY2ZDJlMmRkAQIDBA==
|
|||||||
|
|
||||||
$this->call(OauthSettingSeeder::class);
|
$this->call(OauthSettingSeeder::class);
|
||||||
$this->call(PopulateSshKeysDirectorySeeder::class);
|
$this->call(PopulateSshKeysDirectorySeeder::class);
|
||||||
|
$this->call(SentinelSeeder::class);
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
31
database/seeders/SentinelSeeder.php
Normal file
31
database/seeders/SentinelSeeder.php
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Database\Seeders;
|
||||||
|
|
||||||
|
use App\Models\Server;
|
||||||
|
use Illuminate\Database\Seeder;
|
||||||
|
|
||||||
|
class SentinelSeeder extends Seeder
|
||||||
|
{
|
||||||
|
public function run()
|
||||||
|
{
|
||||||
|
Server::chunk(100, function ($servers) {
|
||||||
|
foreach ($servers as $server) {
|
||||||
|
try {
|
||||||
|
if (str($server->settings->sentinel_token)->isEmpty()) {
|
||||||
|
$server->settings->generateSentinelToken();
|
||||||
|
}
|
||||||
|
if (str($server->settings->sentinel_custom_url)->isEmpty()) {
|
||||||
|
$url = $server->settings->generateSentinelUrl();
|
||||||
|
if (str($url)->isEmpty()) {
|
||||||
|
$server->settings->is_sentinel_enabled = false;
|
||||||
|
$server->settings->save();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (\Throwable $e) {
|
||||||
|
loggy("Error: {$e->getMessage()}\n");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
@@ -5,34 +5,38 @@ ARG TARGETPLATFORM
|
|||||||
ARG CLOUDFLARED_VERSION=2024.4.1
|
ARG CLOUDFLARED_VERSION=2024.4.1
|
||||||
|
|
||||||
ARG POSTGRES_VERSION=15
|
ARG POSTGRES_VERSION=15
|
||||||
RUN apt-get update
|
|
||||||
# Postgres version requirements
|
|
||||||
RUN apt install dirmngr ca-certificates software-properties-common gnupg gnupg2 apt-transport-https curl -y
|
|
||||||
RUN curl -fSsL https://www.postgresql.org/media/keys/ACCC4CF8.asc | gpg --dearmor | tee /usr/share/keyrings/postgresql.gpg > /dev/null
|
|
||||||
|
|
||||||
RUN echo deb [arch=amd64,arm64,ppc64el signed-by=/usr/share/keyrings/postgresql.gpg] http://apt.postgresql.org/pub/repos/apt/ jammy-pgdg main | tee -a /etc/apt/sources.list.d/postgresql.list
|
# Use build arguments for caching
|
||||||
|
ARG BUILDTIME_DEPS="dirmngr ca-certificates software-properties-common gnupg gnupg2 apt-transport-https curl"
|
||||||
|
ARG RUNTIME_DEPS="postgresql-client-$POSTGRES_VERSION php8.2-pgsql openssh-client git git-lfs jq lsof"
|
||||||
|
|
||||||
RUN apt-get update
|
# Install dependencies
|
||||||
RUN apt-get install postgresql-client-$POSTGRES_VERSION -y
|
RUN --mount=type=cache,target=/var/cache/apt \
|
||||||
|
apt-get update && \
|
||||||
|
apt-get install -y $BUILDTIME_DEPS && \
|
||||||
|
curl -fSsL https://www.postgresql.org/media/keys/ACCC4CF8.asc | gpg --dearmor | tee /usr/share/keyrings/postgresql.gpg > /dev/null && \
|
||||||
|
echo deb [arch=amd64,arm64,ppc64el signed-by=/usr/share/keyrings/postgresql.gpg] http://apt.postgresql.org/pub/repos/apt/ jammy-pgdg main | tee -a /etc/apt/sources.list.d/postgresql.list && \
|
||||||
|
apt-get update && \
|
||||||
|
apt-get install -y $RUNTIME_DEPS && \
|
||||||
|
apt-get -y autoremove && apt-get clean && rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/* /usr/share/doc/*
|
||||||
|
|
||||||
# Coolify requirements
|
|
||||||
RUN apt-get install -y php8.2-pgsql openssh-client git git-lfs jq lsof
|
|
||||||
RUN apt-get -y autoremove && apt-get clean && rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/* /usr/share/doc/*
|
|
||||||
COPY --chmod=755 docker/dev/etc/s6-overlay/ /etc/s6-overlay/
|
COPY --chmod=755 docker/dev/etc/s6-overlay/ /etc/s6-overlay/
|
||||||
|
|
||||||
COPY docker/dev/nginx.conf /etc/nginx/conf.d/custom.conf
|
COPY docker/dev/nginx.conf /etc/nginx/conf.d/custom.conf
|
||||||
|
|
||||||
RUN echo "alias ll='ls -al'" >>/etc/bash.bashrc
|
RUN echo "alias ll='ls -al'" >>/etc/bash.bashrc && \
|
||||||
RUN echo "alias a='php artisan'" >>/etc/bash.bashrc
|
echo "alias a='php artisan'" >>/etc/bash.bashrc
|
||||||
|
|
||||||
RUN mkdir -p /usr/local/bin
|
RUN mkdir -p /usr/local/bin
|
||||||
|
|
||||||
RUN /bin/bash -c "if [[ ${TARGETPLATFORM} == 'linux/amd64' ]]; then \
|
RUN --mount=type=cache,target=/root/.cache \
|
||||||
|
/bin/bash -c "if [[ ${TARGETPLATFORM} == 'linux/amd64' ]]; then \
|
||||||
echo 'amd64' && \
|
echo 'amd64' && \
|
||||||
curl -sSL https://github.com/cloudflare/cloudflared/releases/download/${CLOUDFLARED_VERSION}/cloudflared-linux-amd64 -o /usr/local/bin/cloudflared && chmod +x /usr/local/bin/cloudflared \
|
curl -sSL https://github.com/cloudflare/cloudflared/releases/download/${CLOUDFLARED_VERSION}/cloudflared-linux-amd64 -o /usr/local/bin/cloudflared && chmod +x /usr/local/bin/cloudflared \
|
||||||
;fi"
|
;fi"
|
||||||
|
|
||||||
RUN /bin/bash -c "if [[ ${TARGETPLATFORM} == 'linux/arm64' ]]; then \
|
RUN --mount=type=cache,target=/root/.cache \
|
||||||
|
/bin/bash -c "if [[ ${TARGETPLATFORM} == 'linux/arm64' ]]; then \
|
||||||
echo 'arm64' && \
|
echo 'arm64' && \
|
||||||
curl -L https://github.com/cloudflare/cloudflared/releases/download/${CLOUDFLARED_VERSION}/cloudflared-linux-arm64 -o /usr/local/bin/cloudflared && chmod +x /usr/local/bin/cloudflared \
|
curl -L https://github.com/cloudflare/cloudflared/releases/download/${CLOUDFLARED_VERSION}/cloudflared-linux-arm64 -o /usr/local/bin/cloudflared && chmod +x /usr/local/bin/cloudflared \
|
||||||
;fi"
|
;fi"
|
||||||
|
@@ -33,5 +33,6 @@
|
|||||||
"resource.delete_volumes": "Permanently delete all volumes associated with this resource.",
|
"resource.delete_volumes": "Permanently delete all volumes associated with this resource.",
|
||||||
"resource.delete_connected_networks": "Permanently delete all non-predefined networks associated with this resource.",
|
"resource.delete_connected_networks": "Permanently delete all non-predefined networks associated with this resource.",
|
||||||
"resource.delete_configurations": "Permanently delete all configuration files from the server.",
|
"resource.delete_configurations": "Permanently delete all configuration files from the server.",
|
||||||
"database.delete_backups_locally": "All backups will be permanently deleted from local storage."
|
"database.delete_backups_locally": "All backups will be permanently deleted from local storage.",
|
||||||
|
"warning.sslipdomain": "Your configuration is saved, but sslip domain with https is <span class='dark:text-red-500 text-red-500 font-bold'>NOT</span> recommended, because Let's Encrypt servers with this public domain are rate limited (SSL certificate validation will fail). <br><br>Use your own domain instead."
|
||||||
}
|
}
|
||||||
|
20
openapi.yaml
20
openapi.yaml
@@ -98,6 +98,10 @@ paths:
|
|||||||
is_static:
|
is_static:
|
||||||
type: boolean
|
type: boolean
|
||||||
description: 'The flag to indicate if the application is static.'
|
description: 'The flag to indicate if the application is static.'
|
||||||
|
static_image:
|
||||||
|
type: string
|
||||||
|
enum: ['nginx:alpine']
|
||||||
|
description: 'The static image.'
|
||||||
install_command:
|
install_command:
|
||||||
type: string
|
type: string
|
||||||
description: 'The install command.'
|
description: 'The install command.'
|
||||||
@@ -323,6 +327,10 @@ paths:
|
|||||||
is_static:
|
is_static:
|
||||||
type: boolean
|
type: boolean
|
||||||
description: 'The flag to indicate if the application is static.'
|
description: 'The flag to indicate if the application is static.'
|
||||||
|
static_image:
|
||||||
|
type: string
|
||||||
|
enum: ['nginx:alpine']
|
||||||
|
description: 'The static image.'
|
||||||
install_command:
|
install_command:
|
||||||
type: string
|
type: string
|
||||||
description: 'The install command.'
|
description: 'The install command.'
|
||||||
@@ -548,6 +556,10 @@ paths:
|
|||||||
is_static:
|
is_static:
|
||||||
type: boolean
|
type: boolean
|
||||||
description: 'The flag to indicate if the application is static.'
|
description: 'The flag to indicate if the application is static.'
|
||||||
|
static_image:
|
||||||
|
type: string
|
||||||
|
enum: ['nginx:alpine']
|
||||||
|
description: 'The static image.'
|
||||||
install_command:
|
install_command:
|
||||||
type: string
|
type: string
|
||||||
description: 'The install command.'
|
description: 'The install command.'
|
||||||
@@ -3093,7 +3105,7 @@ paths:
|
|||||||
security:
|
security:
|
||||||
-
|
-
|
||||||
bearerAuth: []
|
bearerAuth: []
|
||||||
/healthcheck:
|
/health:
|
||||||
get:
|
get:
|
||||||
summary: Healthcheck
|
summary: Healthcheck
|
||||||
description: 'Healthcheck endpoint.'
|
description: 'Healthcheck endpoint.'
|
||||||
@@ -4959,7 +4971,7 @@ components:
|
|||||||
type: boolean
|
type: boolean
|
||||||
is_reachable:
|
is_reachable:
|
||||||
type: boolean
|
type: boolean
|
||||||
is_server_api_enabled:
|
is_sentinel_enabled:
|
||||||
type: boolean
|
type: boolean
|
||||||
is_swarm_manager:
|
is_swarm_manager:
|
||||||
type: boolean
|
type: boolean
|
||||||
@@ -4981,10 +4993,10 @@ components:
|
|||||||
type: string
|
type: string
|
||||||
logdrain_newrelic_license_key:
|
logdrain_newrelic_license_key:
|
||||||
type: string
|
type: string
|
||||||
sentinel_metrics_refresh_rate_seconds:
|
|
||||||
type: integer
|
|
||||||
sentinel_metrics_history_days:
|
sentinel_metrics_history_days:
|
||||||
type: integer
|
type: integer
|
||||||
|
sentinel_metrics_refresh_rate_seconds:
|
||||||
|
type: integer
|
||||||
sentinel_token:
|
sentinel_token:
|
||||||
type: string
|
type: string
|
||||||
docker_cleanup_frequency:
|
docker_cleanup_frequency:
|
||||||
|
3
public/svgs/edgedb.svg
Normal file
3
public/svgs/edgedb.svg
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
<svg width="176" height="80" viewBox="0 0 176 80" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path fill-rule="evenodd" clip-rule="evenodd" d="M149.073 39.613C149.073 51.7589 144.203 53.9359 138.76 53.9359H127.187V25.2902H138.76C144.203 25.2902 149.073 27.4672 149.073 39.613V39.613ZM143.172 39.6143C143.172 31.1352 140.594 30.7342 136.87 30.7342H133.261V48.4945H136.87C140.594 48.4945 143.172 48.0935 143.172 39.6143V39.6143ZM81.8689 53.9359V25.2902H100.088V30.7329H87.9418V36.5766H97.1084V41.962H87.9418V48.4932H100.088V53.9359H81.8689ZM110 79.9998H116V0H110V79.9998ZM161.217 41.2193V48.4953H166.259C169.41 48.4953 170.212 46.4328 170.212 44.8859C170.212 43.6828 169.639 41.2193 165.342 41.2193H161.217ZM161.217 30.7342V36.1768H165.342C167.691 36.1768 169.066 35.1456 169.066 33.4269C169.066 31.7081 167.691 30.7342 165.342 30.7342H161.217ZM155.146 25.2902H166.833C172.964 25.2902 174.797 29.587 174.797 32.6808C174.797 35.5454 172.964 37.6078 171.703 38.1808C175.37 39.9568 176 43.5662 176 45.3995C176 47.8057 174.797 53.9359 166.833 53.9359H155.146V25.2902ZM46.9799 39.613C46.9799 51.7589 42.1101 53.9359 36.6674 53.9359H25.0945V25.2902H36.6674C42.1101 25.2902 46.9799 27.4672 46.9799 39.613V39.613ZM64.8538 48.725C67.9475 48.725 69.5517 47.6937 70.1246 47.0062V43.8552H65.1975V38.9281H74.9944V50.3291C74.135 51.6468 69.4371 54.2249 65.1402 54.2249C58.0934 54.2249 52.1351 51.4749 52.1351 39.3291C52.1351 27.1833 58.1507 25.0063 63.5934 25.0063C72.1298 25.0063 74.2496 29.475 74.9371 33.4281L69.8954 34.5739C69.609 32.7406 68.0048 30.449 64.3382 30.449C60.6142 30.449 58.0361 30.85 58.0361 39.3291C58.0361 47.8083 60.7288 48.725 64.8538 48.725V48.725ZM41.0781 39.6143C41.0781 31.1352 38.5 30.7342 34.7761 30.7342H31.1667V48.4945H34.7761C38.5 48.4945 41.0781 48.0935 41.0781 39.6143V39.6143ZM0 53.9359V25.2902H18.2187V30.7329H6.0729V36.5766H15.2395V41.962H6.0729V48.4932H18.2187V53.9359H0Z" fill="#0CCB93"/>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 1.9 KiB |
12
public/svgs/mindsdb.svg
Normal file
12
public/svgs/mindsdb.svg
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
<svg width="264" height="151" viewBox="0 0 264 151" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<g clip-path="url(#clip0_13_35)">
|
||||||
|
<path d="M186.86 75.8418C189.798 98.8976 192.525 120.544 195.356 142.168C195.691 144.759 195.155 145.673 192.254 145.818C174.783 146.686 174.793 146.755 168.478 130.847C165.848 124.227 163.286 117.581 160.486 111.028C159.869 109.951 159.642 108.7 159.84 107.479C160.039 106.259 160.652 105.14 161.579 104.306C169.843 95.2778 177.892 86.0052 186.86 75.8418Z" fill="#00A587"/>
|
||||||
|
<path d="M84.1747 95.1655C86.6767 107.857 89.145 119.785 91.3184 131.767C91.9891 135.476 95.0077 140.188 92.4252 142.696C90.1982 144.851 85.2546 141.802 81.4547 141.94C71.3595 142.317 66.6607 137.651 65.0207 127.854C64.0581 122.102 63.6321 118.119 68.4516 113.664C74.0774 107.825 79.3286 101.647 84.1747 95.1655V95.1655Z" fill="#00A587"/>
|
||||||
|
<path d="M112.68 118.314C112.68 111.186 112.569 104.059 112.734 96.9342C112.797 94.136 112.006 93.1922 108.984 93.0932C88.369 92.4223 67.7583 91.6083 47.1521 90.6514C43.9156 90.5029 42.6377 91.407 41.9502 94.555C38.7707 109.134 35.1619 123.626 32.0562 138.222C31.1842 142.32 29.5945 143.831 25.2378 143.366C20.4038 142.873 15.5373 142.763 10.6853 143.036C7.02954 143.218 6.32523 141.716 6.3789 138.585C6.69416 116.694 7.16037 94.8026 6.94572 72.9181C6.90613 66.1838 9.19198 59.6359 13.4288 54.3467C24.832 38.7551 35.8159 22.8829 46.8636 7.0504C47.4595 5.96628 48.3973 5.10199 49.5357 4.58777C50.6742 4.07355 51.9516 3.93721 53.1756 4.19931C94.3679 8.53528 135.576 12.736 176.8 16.8013C179.378 17.1054 181.84 18.0315 183.967 19.4973C208.497 35.1813 232.984 50.9215 257.638 66.4142C261.327 68.724 261.428 70.044 257.883 72.1328C257.12 72.5383 256.438 73.0773 255.871 73.7233C248.77 82.7483 240.443 82.3424 230.381 78.343C218.22 73.522 205.395 70.3245 192.925 66.2261C191.738 65.7528 190.451 65.5735 189.178 65.7041C187.904 65.8347 186.682 66.2712 185.62 66.9751C174.15 73.9839 162.627 80.907 151.052 87.7442C150.009 88.2936 149.119 89.0852 148.458 90.0502C147.797 91.0151 147.385 92.1243 147.259 93.2813C144.72 108.328 141.815 123.31 139.481 138.396C138.81 142.719 137.244 144.089 132.877 143.788C127.332 143.466 121.772 143.457 116.225 143.762C112.935 143.907 112.093 142.772 112.2 139.687C112.415 132.562 112.268 125.428 112.268 118.301L112.68 118.314Z" fill="#00A587"/>
|
||||||
|
</g>
|
||||||
|
<defs>
|
||||||
|
<clipPath id="clip0_13_35">
|
||||||
|
<rect width="264" height="151" fill="white"/>
|
||||||
|
</clipPath>
|
||||||
|
</defs>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 2.4 KiB |
BIN
public/svgs/mosquitto.png
Normal file
BIN
public/svgs/mosquitto.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 16 KiB |
@@ -14,7 +14,10 @@
|
|||||||
'w-full' => $fullWidth,
|
'w-full' => $fullWidth,
|
||||||
])>
|
])>
|
||||||
@if (!$hideLabel)
|
@if (!$hideLabel)
|
||||||
<label class="flex gap-4 px-0 min-w-fit label">
|
<label @class([
|
||||||
|
"flex gap-4 px-0 min-w-fit label",
|
||||||
|
'opacity-40' => $disabled,
|
||||||
|
])>
|
||||||
<span class="flex gap-2">
|
<span class="flex gap-2">
|
||||||
@if ($label)
|
@if ($label)
|
||||||
{!! $label !!}
|
{!! $label !!}
|
||||||
|
@@ -22,18 +22,23 @@
|
|||||||
'dispatchEventMessage' => '',
|
'dispatchEventMessage' => '',
|
||||||
])
|
])
|
||||||
|
|
||||||
|
@php
|
||||||
|
$settings = instanceSettings();
|
||||||
|
$disableTwoStepConfirmation = $settings->disable_two_step_confirmation ?? false;
|
||||||
|
@endphp
|
||||||
|
|
||||||
<div x-data="{
|
<div x-data="{
|
||||||
modalOpen: false,
|
modalOpen: false,
|
||||||
step: {{ empty($checkboxes) ? 2 : 1 }},
|
step: {{ empty($checkboxes) ? 2 : 1 }},
|
||||||
initialStep: {{ empty($checkboxes) ? 2 : 1 }},
|
initialStep: {{ empty($checkboxes) ? 2 : 1 }},
|
||||||
finalStep: {{ $confirmWithPassword ? 3 : 2 }},
|
finalStep: {{ $confirmWithPassword && !$disableTwoStepConfirmation ? 3 : 2 }},
|
||||||
deleteText: '',
|
deleteText: '',
|
||||||
password: '',
|
password: '',
|
||||||
actions: @js($actions),
|
actions: @js($actions),
|
||||||
confirmationText: @js($confirmationText),
|
confirmationText: @js($confirmationText),
|
||||||
userConfirmationText: '',
|
userConfirmationText: '',
|
||||||
confirmWithText: @js($confirmWithText),
|
confirmWithText: @js($confirmWithText && !$disableTwoStepConfirmation),
|
||||||
confirmWithPassword: @js($confirmWithPassword),
|
confirmWithPassword: @js($confirmWithPassword && !$disableTwoStepConfirmation),
|
||||||
copied: false,
|
copied: false,
|
||||||
submitAction: @js($submitAction),
|
submitAction: @js($submitAction),
|
||||||
passwordError: '',
|
passwordError: '',
|
||||||
@@ -41,6 +46,7 @@
|
|||||||
dispatchEvent: @js($dispatchEvent),
|
dispatchEvent: @js($dispatchEvent),
|
||||||
dispatchEventType: @js($dispatchEventType),
|
dispatchEventType: @js($dispatchEventType),
|
||||||
dispatchEventMessage: @js($dispatchEventMessage),
|
dispatchEventMessage: @js($dispatchEventMessage),
|
||||||
|
disableTwoStepConfirmation: @js($disableTwoStepConfirmation),
|
||||||
resetModal() {
|
resetModal() {
|
||||||
this.step = this.initialStep;
|
this.step = this.initialStep;
|
||||||
this.deleteText = '';
|
this.deleteText = '';
|
||||||
@@ -153,8 +159,8 @@
|
|||||||
<template x-teleport="body">
|
<template x-teleport="body">
|
||||||
<div x-show="modalOpen"
|
<div x-show="modalOpen"
|
||||||
class="fixed top-0 lg:pt-10 left-0 z-[99] flex items-start justify-center w-screen h-screen" x-cloak>
|
class="fixed top-0 lg:pt-10 left-0 z-[99] flex items-start justify-center w-screen h-screen" x-cloak>
|
||||||
<div x-show="modalOpen"
|
<div x-show="modalOpen" class="absolute inset-0 w-full h-full bg-black bg-opacity-20 backdrop-blur-sm">
|
||||||
class="absolute inset-0 w-full h-full bg-black bg-opacity-20 backdrop-blur-sm"></div>
|
</div>
|
||||||
<div x-show="modalOpen" x-trap.inert.noscroll="modalOpen" x-transition:enter="ease-out duration-100"
|
<div x-show="modalOpen" x-trap.inert.noscroll="modalOpen" x-transition:enter="ease-out duration-100"
|
||||||
x-transition:enter-start="opacity-0 -translate-y-2 sm:scale-95"
|
x-transition:enter-start="opacity-0 -translate-y-2 sm:scale-95"
|
||||||
x-transition:enter-end="opacity-100 translate-y-0 sm:scale-100"
|
x-transition:enter-end="opacity-100 translate-y-0 sm:scale-100"
|
||||||
@@ -222,66 +228,71 @@
|
|||||||
</template>
|
</template>
|
||||||
@endforeach
|
@endforeach
|
||||||
</ul>
|
</ul>
|
||||||
@if ($confirmWithText)
|
@if (!$disableTwoStepConfirmation)
|
||||||
<div class="mb-4">
|
@if ($confirmWithText)
|
||||||
<h4 class="mb-2 text-lg font-semibold">Confirm Actions</h4>
|
<div class="mb-4">
|
||||||
<p class="mb-2 text-sm">{{ $confirmationLabel }}</p>
|
<h4 class="mb-2 text-lg font-semibold">Confirm Actions</h4>
|
||||||
<div class="relative mb-2">
|
<p class="mb-2 text-sm">{{ $confirmationLabel }}</p>
|
||||||
<input type="text" x-model="confirmationText"
|
<div class="relative mb-2">
|
||||||
class="p-2 pr-10 w-full text-black rounded cursor-text input" readonly>
|
<input type="text" x-model="confirmationText"
|
||||||
<button @click="copyConfirmationText()"
|
class="p-2 pr-10 w-full text-black rounded cursor-text input" readonly>
|
||||||
class="absolute right-2 top-1/2 text-gray-500 transform -translate-y-1/2 hover:text-gray-700"
|
<button @click="copyConfirmationText()"
|
||||||
title="Copy confirmation text" x-ref="copyButton">
|
class="absolute right-2 top-1/2 text-gray-500 transform -translate-y-1/2 hover:text-gray-700"
|
||||||
<template x-if="!copied">
|
title="Copy confirmation text" x-ref="copyButton">
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" class="w-5 h-5" viewBox="0 0 20 20"
|
<template x-if="!copied">
|
||||||
fill="currentColor">
|
<svg xmlns="http://www.w3.org/2000/svg" class="w-5 h-5"
|
||||||
<path d="M8 3a1 1 0 011-1h2a1 1 0 110 2H9a1 1 0 01-1-1z" />
|
viewBox="0 0 20 20" fill="currentColor">
|
||||||
<path
|
<path d="M8 3a1 1 0 011-1h2a1 1 0 110 2H9a1 1 0 01-1-1z" />
|
||||||
d="M6 3a2 2 0 00-2 2v11a2 2 0 002 2h8a2 2 0 002-2V5a2 2 0 00-2-2 3 3 0 01-3 3H9a3 3 0 01-3-3z" />
|
<path
|
||||||
</svg>
|
d="M6 3a2 2 0 00-2 2v11a2 2 0 002 2h8a2 2 0 002-2V5a2 2 0 00-2-2 3 3 0 01-3 3H9a3 3 0 01-3-3z" />
|
||||||
</template>
|
</svg>
|
||||||
<template x-if="copied">
|
</template>
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" class="w-5 h-5 text-green-500"
|
<template x-if="copied">
|
||||||
viewBox="0 0 20 20" fill="currentColor">
|
<svg xmlns="http://www.w3.org/2000/svg" class="w-5 h-5 text-green-500"
|
||||||
<path fill-rule="evenodd"
|
viewBox="0 0 20 20" fill="currentColor">
|
||||||
d="M10 18a8 8 0 100-16 8 8 0 000 16zm3.707-9.293a1 1 0 00-1.414-1.414L9 10.586 7.707 9.293a1 1 0 00-1.414 1.414l2 2a1 1 0 001.414 0l4-4z"
|
<path fill-rule="evenodd"
|
||||||
clip-rule="evenodd" />
|
d="M10 18a8 8 0 100-16 8 8 0 000 16zm3.707-9.293a1 1 0 00-1.414-1.414L9 10.586 7.707 9.293a1 1 0 00-1.414 1.414l2 2a1 1 0 001.414 0l4-4z"
|
||||||
</svg>
|
clip-rule="evenodd" />
|
||||||
</template>
|
</svg>
|
||||||
</button>
|
</template>
|
||||||
</div>
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
<label for="userConfirmationText"
|
<label for="userConfirmationText"
|
||||||
class="block mt-4 text-sm font-medium text-gray-700 dark:text-gray-300">
|
class="block mt-4 text-sm font-medium text-gray-700 dark:text-gray-300">
|
||||||
{{ $shortConfirmationLabel }}
|
{{ $shortConfirmationLabel }}
|
||||||
</label>
|
</label>
|
||||||
<input type="text" x-model="userConfirmationText"
|
<input type="text" x-model="userConfirmationText"
|
||||||
class="p-2 mt-1 w-full text-black rounded input">
|
class="p-2 mt-1 w-full text-black rounded input">
|
||||||
</div>
|
</div>
|
||||||
|
@endif
|
||||||
@endif
|
@endif
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Step 3: Password confirmation -->
|
<!-- Step 3: Password confirmation -->
|
||||||
<div x-show="step === 3 && confirmWithPassword">
|
@if (!$disableTwoStepConfirmation)
|
||||||
<div class="p-4 mb-4 text-white border-l-4 border-red-500 bg-error" role="alert">
|
<div x-show="step === 3 && confirmWithPassword">
|
||||||
<p class="font-bold">Final Confirmation</p>
|
<div class="p-4 mb-4 text-white border-l-4 border-red-500 bg-error" role="alert">
|
||||||
<p>Please enter your password to confirm this destructive action.</p>
|
<p class="font-bold">Final Confirmation</p>
|
||||||
|
<p>Please enter your password to confirm this destructive action.</p>
|
||||||
|
</div>
|
||||||
|
<div class="flex flex-col gap-2 mb-4">
|
||||||
|
<label for="password-confirm"
|
||||||
|
class="block text-sm font-medium text-gray-700 dark:text-gray-300">
|
||||||
|
Your Password
|
||||||
|
</label>
|
||||||
|
<form @submit.prevent @keydown.enter.prevent>
|
||||||
|
<input type="password" id="password-confirm" x-model="password"
|
||||||
|
class="w-full input" placeholder="Enter your password">
|
||||||
|
</form>
|
||||||
|
<p x-show="passwordError" x-text="passwordError" class="mt-1 text-sm text-red-500">
|
||||||
|
</p>
|
||||||
|
@error('password')
|
||||||
|
<p class="mt-1 text-sm text-red-500">{{ $message }}</p>
|
||||||
|
@enderror
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="flex flex-col gap-2 mb-4">
|
@endif
|
||||||
<label for="password-confirm"
|
|
||||||
class="block text-sm font-medium text-gray-700 dark:text-gray-300">
|
|
||||||
Your Password
|
|
||||||
</label>
|
|
||||||
<form @submit.prevent @keydown.enter.prevent>
|
|
||||||
<input type="password" id="password-confirm" x-model="password" class="w-full input"
|
|
||||||
placeholder="Enter your password">
|
|
||||||
</form>
|
|
||||||
<p x-show="passwordError" x-text="passwordError" class="mt-1 text-sm text-red-500"></p>
|
|
||||||
@error('password')
|
|
||||||
<p class="mt-1 text-sm text-red-500">{{ $message }}</p>
|
|
||||||
@enderror
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
<!-- Navigation buttons -->
|
<!-- Navigation buttons -->
|
||||||
<div class="flex flex-wrap gap-2 justify-between mt-4">
|
<div class="flex flex-wrap gap-2 justify-between mt-4">
|
||||||
@@ -304,41 +315,45 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<template x-if="step === 2">
|
<template x-if="step === 2">
|
||||||
<x-forms.button x-bind:disabled="confirmWithText && userConfirmationText !== confirmationText"
|
<x-forms.button
|
||||||
|
x-bind:disabled="!disableTwoStepConfirmation && confirmWithText && userConfirmationText !== confirmationText"
|
||||||
class="w-auto" isError
|
class="w-auto" isError
|
||||||
@click="
|
@click="
|
||||||
if (dispatchEvent) {
|
if (dispatchEvent) {
|
||||||
$wire.dispatch(dispatchEventType, dispatchEventMessage);
|
$wire.dispatch(dispatchEventType, dispatchEventMessage);
|
||||||
}
|
}
|
||||||
if (confirmWithPassword) {
|
if (confirmWithPassword) {
|
||||||
step++;
|
step++;
|
||||||
} else {
|
} else {
|
||||||
modalOpen = false;
|
modalOpen = false;
|
||||||
resetModal();
|
resetModal();
|
||||||
submitForm();
|
submitForm();
|
||||||
}">
|
}
|
||||||
|
">
|
||||||
<span x-text="step2ButtonText"></span>
|
<span x-text="step2ButtonText"></span>
|
||||||
</x-forms.button>
|
</x-forms.button>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<template x-if="step === 3 && confirmWithPassword">
|
@if (!$disableTwoStepConfirmation)
|
||||||
<x-forms.button x-bind:disabled="!password" class="w-auto" isError
|
<template x-if="step === 3 && confirmWithPassword">
|
||||||
@click="
|
<x-forms.button x-bind:disabled="!password" class="w-auto" isError
|
||||||
if (dispatchEvent) {
|
@click="
|
||||||
$wire.dispatch(dispatchEventType, dispatchEventMessage);
|
if (dispatchEvent) {
|
||||||
}
|
$wire.dispatch(dispatchEventType, dispatchEventMessage);
|
||||||
submitForm().then((result) => {
|
|
||||||
if (result === true) {
|
|
||||||
modalOpen = false;
|
|
||||||
resetModal();
|
|
||||||
} else {
|
|
||||||
passwordError = result;
|
|
||||||
}
|
}
|
||||||
});
|
submitForm().then((result) => {
|
||||||
">
|
if (result === true) {
|
||||||
<span x-text="step3ButtonText"></span>
|
modalOpen = false;
|
||||||
</x-forms.button>
|
resetModal();
|
||||||
</template>
|
} else {
|
||||||
|
passwordError = result;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
">
|
||||||
|
<span x-text="step3ButtonText"></span>
|
||||||
|
</x-forms.button>
|
||||||
|
</template>
|
||||||
|
@endif
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@@ -1,5 +1,14 @@
|
|||||||
<div class="pb-6">
|
<div class="pb-6">
|
||||||
<livewire:server.proxy.modal :server="$server" />
|
<x-modal modalId="startProxy">
|
||||||
|
<x-slot:modalBody>
|
||||||
|
<livewire:activity-monitor header="Proxy Startup Logs" />
|
||||||
|
</x-slot:modalBody>
|
||||||
|
<x-slot:modalSubmit>
|
||||||
|
<x-forms.button onclick="startProxy.close()" type="submit">
|
||||||
|
Close
|
||||||
|
</x-forms.button>
|
||||||
|
</x-slot:modalSubmit>
|
||||||
|
</x-modal>
|
||||||
<div class="flex items-center gap-2">
|
<div class="flex items-center gap-2">
|
||||||
<h1>Server</h1>
|
<h1>Server</h1>
|
||||||
@if ($server->proxySet())
|
@if ($server->proxySet())
|
||||||
@@ -13,20 +22,9 @@
|
|||||||
href="{{ route('server.show', [
|
href="{{ route('server.show', [
|
||||||
'server_uuid' => data_get($parameters, 'server_uuid'),
|
'server_uuid' => data_get($parameters, 'server_uuid'),
|
||||||
]) }}">
|
]) }}">
|
||||||
<button>General</button>
|
<button>Configuration</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>
|
</a>
|
||||||
|
|
||||||
@if (!$server->isSwarmWorker() && !$server->settings->is_build_server)
|
@if (!$server->isSwarmWorker() && !$server->settings->is_build_server)
|
||||||
<a class="{{ request()->routeIs('server.proxy') ? 'dark:text-white' : '' }}"
|
<a class="{{ request()->routeIs('server.proxy') ? 'dark:text-white' : '' }}"
|
||||||
href="{{ route('server.proxy', [
|
href="{{ route('server.proxy', [
|
||||||
@@ -34,18 +32,6 @@
|
|||||||
]) }}">
|
]) }}">
|
||||||
<button>Proxy</button>
|
<button>Proxy</button>
|
||||||
</a>
|
</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
|
@endif
|
||||||
</nav>
|
</nav>
|
||||||
<div class="order-first sm:order-last">
|
<div class="order-first sm:order-last">
|
||||||
|
@@ -1,18 +1,16 @@
|
|||||||
@if ($server->proxySet())
|
@if ($server->proxySet())
|
||||||
<div class="flex h-full pr-4">
|
<div class="flex flex-col items-start gap-2 min-w-fit">
|
||||||
<div class="flex flex-col w-48 gap-4 min-w-fit">
|
<a class="{{ request()->routeIs('server.proxy') ? 'menu-item menu-item-active' : 'menu-item' }}"
|
||||||
<a class="{{ request()->routeIs('server.proxy') ? 'dark:text-white' : '' }}"
|
href="{{ route('server.proxy', $parameters) }}">
|
||||||
href="{{ route('server.proxy', $parameters) }}">
|
<button>Configuration</button>
|
||||||
<button>Configuration</button>
|
</a>
|
||||||
</a>
|
<a class="{{ request()->routeIs('server.proxy.dynamic-confs') ? 'menu-item menu-item-active' : 'menu-item' }}"
|
||||||
<a class="{{ request()->routeIs('server.proxy.dynamic-confs') ? 'dark:text-white' : '' }}"
|
href="{{ route('server.proxy.dynamic-confs', $parameters) }}">
|
||||||
href="{{ route('server.proxy.dynamic-confs', $parameters) }}">
|
<button>Dynamic Configurations</button>
|
||||||
<button>Dynamic Configurations</button>
|
</a>
|
||||||
</a>
|
<a class="{{ request()->routeIs('server.proxy.logs') ? 'menu-item menu-item-active' : 'menu-item' }}"
|
||||||
<a class="{{ request()->routeIs('server.proxy.logs') ? 'dark:text-white' : '' }}"
|
href="{{ route('server.proxy.logs', $parameters) }}">
|
||||||
href="{{ route('server.proxy.logs', $parameters) }}">
|
<button>Logs</button>
|
||||||
<button>Logs</button>
|
</a>
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
@endif
|
@endif
|
||||||
|
@@ -5,10 +5,10 @@
|
|||||||
<x-modal-input buttonTitle="+ Add" title="New Destination">
|
<x-modal-input buttonTitle="+ Add" title="New Destination">
|
||||||
<livewire:destination.new.docker :server_id="$server->id" />
|
<livewire:destination.new.docker :server_id="$server->id" />
|
||||||
</x-modal-input>
|
</x-modal-input>
|
||||||
<x-forms.button wire:click='scan'>Scan Destinations</x-forms.button>
|
<x-forms.button wire:click='scan'>Scan for Destinations</x-forms.button>
|
||||||
</div>
|
</div>
|
||||||
<div class="pt-2 pb-6 ">Destinations are used to segregate resources by network.</div>
|
<div>Destinations are used to segregate resources by network.</div>
|
||||||
<div class="flex gap-2 ">
|
<div class="flex gap-2 pt-6">
|
||||||
Available for using:
|
Available for using:
|
||||||
@forelse ($server->standaloneDockers as $docker)
|
@forelse ($server->standaloneDockers as $docker)
|
||||||
<a href="{{ route('destination.show', ['destination_uuid' => data_get($docker, 'uuid')]) }}">
|
<a href="{{ route('destination.show', ['destination_uuid' => data_get($docker, 'uuid')]) }}">
|
||||||
|
@@ -9,8 +9,25 @@
|
|||||||
<div class="flex gap-2">
|
<div class="flex gap-2">
|
||||||
<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 helper="For all available images, check here:<br><br><a target='_blank' href='https://hub.docker.com/_/redis'>https://hub.docker.com/_/redis</a>" />
|
||||||
helper="For all available images, check here:<br><br><a target='_blank' href='https://hub.docker.com/_/redis'>https://hub.docker.com/_/redis</a>" />
|
</div>
|
||||||
|
<div class="flex flex-col gap-2">
|
||||||
|
@if (version_compare($redis_version, '6.0', '>='))
|
||||||
|
<x-forms.input label="Username" id="redis_username" required
|
||||||
|
helper="You can change the Redis Username in the input field below or by editing the value of the REDIS_USERNAME environment variable.
|
||||||
|
<br><br>
|
||||||
|
If you change the Redis Username in the database, please sync it here, otherwise automations (like backups) won't work.
|
||||||
|
<br><br>
|
||||||
|
Note: If the environment variable REDIS_USERNAME is set as a shared variable (environment, project, or team-based), this input field will become read-only."
|
||||||
|
:disabled="$this->isSharedVariable('REDIS_USERNAME')" />
|
||||||
|
@endif
|
||||||
|
<x-forms.input label="Password" id="redis_password" type="password" required
|
||||||
|
helper="You can change the Redis Password in the input field below or by editing the value of the REDIS_PASSWORD environment variable.
|
||||||
|
<br><br>
|
||||||
|
If you change the Redis Password in the database, please sync it here, otherwise automations (like backups) won't work.
|
||||||
|
<br><br>
|
||||||
|
Note: If the environment variable REDIS_PASSWORD is set as a shared variable (environment, project, or team-based), this input field will become read-only."
|
||||||
|
:disabled="$this->isSharedVariable('REDIS_PASSWORD')" />
|
||||||
</div>
|
</div>
|
||||||
<x-forms.input
|
<x-forms.input
|
||||||
helper="You can add custom docker run options that will be used when your container is started.<br>Note: Not all options are supported, as they could mess up Coolify's automation and could cause bad experience for users.<br><br>Check the <a class='underline dark:text-white' href='https://coolify.io/docs/knowledge-base/docker/custom-commands'>docs.</a>"
|
helper="You can add custom docker run options that will be used when your container is started.<br>Note: Not all options are supported, as they could mess up Coolify's automation and could cause bad experience for users.<br><br>Check the <a class='underline dark:text-white' href='https://coolify.io/docs/knowledge-base/docker/custom-commands'>docs.</a>"
|
||||||
@@ -19,42 +36,32 @@
|
|||||||
<div class="flex flex-col gap-2">
|
<div class="flex flex-col gap-2">
|
||||||
<h3 class="py-2">Network</h3>
|
<h3 class="py-2">Network</h3>
|
||||||
<div class="flex items-end gap-2">
|
<div class="flex items-end gap-2">
|
||||||
<x-forms.input placeholder="3000:5432" id="database.ports_mappings" label="Ports Mappings"
|
<x-forms.input placeholder="3000:5432" id="database.ports_mappings" label="Ports Mappings" helper="A comma separated list of ports you would like to map to the host system.<br><span class='inline-block font-bold dark:text-warning'>Example</span>3000:5432,3002:5433" />
|
||||||
helper="A comma separated list of ports you would like to map to the host system.<br><span class='inline-block font-bold dark:text-warning'>Example</span>3000:5432,3002:5433" />
|
|
||||||
</div>
|
</div>
|
||||||
<x-forms.input label="Redis URL (internal)"
|
<x-forms.input label="Redis URL (internal)" helper="If you change the user/password/port, this could be different. This is with the default values." type="password" readonly wire:model="db_url" />
|
||||||
helper="If you change the user/password/port, this could be different. This is with the default values."
|
|
||||||
type="password" readonly wire:model="db_url" />
|
|
||||||
@if ($db_url_public)
|
@if ($db_url_public)
|
||||||
<x-forms.input label="Redis URL (public)"
|
<x-forms.input label="Redis URL (public)" helper="If you change the user/password/port, this could be different. This is with the default values." type="password" readonly wire:model="db_url_public" />
|
||||||
helper="If you change the user/password/port, this could be different. This is with the default values."
|
|
||||||
type="password" readonly wire:model="db_url_public" />
|
|
||||||
@endif
|
@endif
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<h3 class="py-2">Proxy</h3>
|
<h3 class="py-2">Proxy</h3>
|
||||||
<div class="flex items-end gap-2">
|
<div class="flex items-end gap-2">
|
||||||
<x-forms.input placeholder="5432" disabled="{{ data_get($database, 'is_public') }}"
|
<x-forms.input placeholder="5432" disabled="{{ data_get($database, 'is_public') }}" id="database.public_port" label="Public Port" />
|
||||||
id="database.public_port" label="Public Port" />
|
|
||||||
<x-slide-over fullScreen>
|
<x-slide-over fullScreen>
|
||||||
<x-slot:title>Proxy Logs</x-slot:title>
|
<x-slot:title>Proxy Logs</x-slot:title>
|
||||||
<x-slot:content>
|
<x-slot:content>
|
||||||
<livewire:project.shared.get-logs :server="$server" :resource="$database"
|
<livewire:project.shared.get-logs :server="$server" :resource="$database" container="{{ data_get($database, 'uuid') }}-proxy" lazy />
|
||||||
container="{{ data_get($database, 'uuid') }}-proxy" lazy />
|
|
||||||
</x-slot:content>
|
</x-slot:content>
|
||||||
<x-forms.button disabled="{{ !data_get($database, 'is_public') }}" @click="slideOverOpen=true"
|
<x-forms.button disabled="{{ !data_get($database, 'is_public') }}" @click="slideOverOpen=true" class="w-28">Proxy Logs</x-forms.button>
|
||||||
class="w-28">Proxy Logs</x-forms.button>
|
|
||||||
</x-slide-over>
|
</x-slide-over>
|
||||||
<x-forms.checkbox instantSave id="database.is_public" label="Make it publicly available" />
|
<x-forms.checkbox instantSave id="database.is_public" label="Make it publicly available" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<x-forms.textarea
|
<x-forms.textarea helper="<a target='_blank' class='underline dark:text-white' href='https://raw.githubusercontent.com/redis/redis/7.2/redis.conf'>Redis Default Configuration</a>" label="Custom Redis Configuration" rows="10" id="database.redis_conf" />
|
||||||
helper="<a target='_blank' class='underline dark:text-white' href='https://raw.githubusercontent.com/redis/redis/7.2/redis.conf'>Redis Default Configuration</a>"
|
|
||||||
label="Custom Redis Configuration" rows="10" id="database.redis_conf" />
|
|
||||||
<h3 class="pt-4">Advanced</h3>
|
<h3 class="pt-4">Advanced</h3>
|
||||||
<div class="flex flex-col">
|
<div class="flex flex-col">
|
||||||
<x-forms.checkbox helper="Drain logs to your configured log drain endpoint in your Server settings."
|
<x-forms.checkbox helper="Drain logs to your configured log drain endpoint in your Server settings." instantSave="instantSaveAdvanced" id="database.is_log_drain_enabled" label="Drain Logs" />
|
||||||
instantSave="instantSaveAdvanced" id="database.is_log_drain_enabled" label="Drain Logs" />
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
|
@@ -24,6 +24,12 @@
|
|||||||
<div x-text="item.description"></div>
|
<div x-text="item.description"></div>
|
||||||
</div>
|
</div>
|
||||||
</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"
|
||||||
|
:href="item.settingsRoute">
|
||||||
|
Settings
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
</div>
|
</div>
|
||||||
|
@@ -7,15 +7,15 @@
|
|||||||
<h1>Resources</h1>
|
<h1>Resources</h1>
|
||||||
@if ($environment->isEmpty())
|
@if ($environment->isEmpty())
|
||||||
<a class="button"
|
<a class="button"
|
||||||
href="{{ route('project.clone-me', ['project_uuid' => data_get($project, 'uuid'), 'environment_name' => request()->route('environment_name')]) }}">
|
href="{{ route('project.clone-me', ['project_uuid' => data_get($project, 'uuid'), 'environment_name' => data_get($parameters, 'environment_name')]) }}">
|
||||||
Clone
|
Clone
|
||||||
</a>
|
</a>
|
||||||
@else
|
@else
|
||||||
<a href="{{ route('project.resource.create', ['project_uuid' => request()->route('project_uuid'), 'environment_name' => request()->route('environment_name')]) }} "
|
<a href="{{ route('project.resource.create', ['project_uuid' => data_get($parameters, 'project_uuid'), 'environment_name' => data_get($parameters, 'environment_name')]) }} "
|
||||||
class="button">+
|
class="button">+
|
||||||
New</a>
|
New</a>
|
||||||
<a class="button"
|
<a class="button"
|
||||||
href="{{ route('project.clone-me', ['project_uuid' => data_get($project, 'uuid'), 'environment_name' => request()->route('environment_name')]) }}">
|
href="{{ route('project.clone-me', ['project_uuid' => data_get($project, 'uuid'), 'environment_name' => data_get($parameters, 'environment_name')]) }}">
|
||||||
Clone
|
Clone
|
||||||
</a>
|
</a>
|
||||||
@endif
|
@endif
|
||||||
@@ -25,7 +25,7 @@
|
|||||||
<ol class="flex items-center">
|
<ol class="flex items-center">
|
||||||
<li class="inline-flex items-center">
|
<li class="inline-flex items-center">
|
||||||
<a class="text-xs truncate lg:text-sm"
|
<a class="text-xs truncate lg:text-sm"
|
||||||
href="{{ route('project.show', ['project_uuid' => request()->route('project_uuid')]) }}">
|
href="{{ route('project.show', ['project_uuid' => data_get($parameters, 'project_uuid')]) }}">
|
||||||
{{ $project->name }}</a>
|
{{ $project->name }}</a>
|
||||||
</li>
|
</li>
|
||||||
<li>
|
<li>
|
||||||
@@ -44,7 +44,7 @@
|
|||||||
</nav>
|
</nav>
|
||||||
</div>
|
</div>
|
||||||
@if ($environment->isEmpty())
|
@if ($environment->isEmpty())
|
||||||
<a href="{{ route('project.resource.create', ['project_uuid' => request()->route('project_uuid'), 'environment_name' => request()->route('environment_name')]) }} "
|
<a href="{{ route('project.resource.create', ['project_uuid' => data_get($parameters, 'project_uuid'), 'environment_name' => data_get($parameters, 'environment_name')]) }} "
|
||||||
class="items-center justify-center box">+ Add New Resource</a>
|
class="items-center justify-center box">+ Add New Resource</a>
|
||||||
@else
|
@else
|
||||||
<div x-data="searchComponent()">
|
<div x-data="searchComponent()">
|
||||||
|
@@ -1,4 +1,4 @@
|
|||||||
<div x-data="{ activeTab: window.location.hash ? window.location.hash.substring(1) : 'service-stack' }" x-init="$wire.check_status">
|
<div x-data="{ activeTab: window.location.hash ? window.location.hash.substring(1) : 'service-stack' }">
|
||||||
<x-slot:title>
|
<x-slot:title>
|
||||||
{{ data_get_str($service, 'name')->limit(10) }} > Configuration | Coolify
|
{{ data_get_str($service, 'name')->limit(10) }} > Configuration | Coolify
|
||||||
</x-slot>
|
</x-slot>
|
||||||
|
@@ -17,7 +17,6 @@
|
|||||||
label="Image Tag" id="database.image"></x-forms.input>
|
label="Image Tag" id="database.image"></x-forms.input>
|
||||||
</div>
|
</div>
|
||||||
<div class="flex items-end gap-2">
|
<div class="flex items-end gap-2">
|
||||||
|
|
||||||
<x-forms.input placeholder="5432" disabled="{{ $database->is_public }}" id="database.public_port"
|
<x-forms.input placeholder="5432" disabled="{{ $database->is_public }}" id="database.public_port"
|
||||||
label="Public Port" />
|
label="Public Port" />
|
||||||
<x-forms.checkbox instantSave id="database.is_public" label="Make it publicly available" />
|
<x-forms.checkbox instantSave id="database.is_public" label="Make it publicly available" />
|
||||||
|
@@ -1,10 +1,11 @@
|
|||||||
<div>
|
<div>
|
||||||
<div class="pb-0 subtitle">
|
<div class="pb-0 subtitle">
|
||||||
<div >Private Keys are used to connect to your servers without passwords.</div>
|
<div>Private Keys are used to connect to your servers without passwords.</div>
|
||||||
<div class="font-bold">You should not use passphrase protected keys.</div>
|
<div class="font-bold">You should not use passphrase protected keys.</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="flex gap-2 mb-4">
|
<div class="flex gap-2 mb-4 w-full">
|
||||||
<x-forms.button wire:click="generateNewEDKey">Generate new ED25519 SSH Key (Recommended, fastest and most secure)</x-forms.button>
|
<x-forms.button wire:click="generateNewEDKey" isHighlighted class="w-full">Generate new ED25519 SSH
|
||||||
|
Key</x-forms.button>
|
||||||
<x-forms.button wire:click="generateNewRSAKey">Generate new RSA SSH Key</x-forms.button>
|
<x-forms.button wire:click="generateNewRSAKey">Generate new RSA SSH Key</x-forms.button>
|
||||||
</div>
|
</div>
|
||||||
<form class="flex flex-col gap-2" wire:submit='createPrivateKey'>
|
<form class="flex flex-col gap-2" wire:submit='createPrivateKey'>
|
||||||
|
84
resources/views/livewire/server/advanced.blade.php
Normal file
84
resources/views/livewire/server/advanced.blade.php
Normal file
@@ -0,0 +1,84 @@
|
|||||||
|
<form wire:submit='submit'>
|
||||||
|
<div>
|
||||||
|
<div class="flex items-center gap-2">
|
||||||
|
<h2>Advanced</h2>
|
||||||
|
<x-forms.button type="submit">Save</x-forms.button>
|
||||||
|
<x-modal-confirmation title="Confirm Docker Cleanup?" buttonTitle="Trigger Manual Cleanup"
|
||||||
|
submitAction="manualCleanup" :actions="[
|
||||||
|
'Permanently deletes all stopped containers managed by Coolify (as containers are non-persistent, no data will be lost)',
|
||||||
|
'Permanently deletes all unused images',
|
||||||
|
'Clears build cache',
|
||||||
|
'Removes old versions of the Coolify helper image',
|
||||||
|
'Optionally permanently deletes all unused volumes (if enabled in advanced options).',
|
||||||
|
'Optionally permanently deletes all unused networks (if enabled in advanced options).',
|
||||||
|
]" :confirmWithText="false" :confirmWithPassword="false"
|
||||||
|
step2ButtonText="Trigger Docker Cleanup" />
|
||||||
|
</div>
|
||||||
|
<div>Advanced configuration for your server.</div>
|
||||||
|
</div>
|
||||||
|
<div class="flex flex-col gap-4 pt-4">
|
||||||
|
<div class="flex flex-col gap-2">
|
||||||
|
<div class="flex items-center gap-2">
|
||||||
|
<h3>Docker Cleanup</h3>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
<div class="flex flex-wrap items-center gap-4">
|
||||||
|
@if ($server->settings->force_docker_cleanup)
|
||||||
|
<x-forms.input placeholder="*/10 * * * *" id="server.settings.docker_cleanup_frequency"
|
||||||
|
label="Docker cleanup frequency" required
|
||||||
|
helper="Cron expression for Docker Cleanup.<br>You can use every_minute, hourly, daily, weekly, monthly, yearly.<br><br>Default is every night at midnight." />
|
||||||
|
@else
|
||||||
|
<x-forms.input id="server.settings.docker_cleanup_threshold" label="Docker cleanup threshold (%)"
|
||||||
|
required
|
||||||
|
helper="The Docker cleanup tasks will run when the disk usage exceeds this threshold." />
|
||||||
|
@endif
|
||||||
|
<div class="w-96">
|
||||||
|
<x-forms.checkbox
|
||||||
|
helper="Enabling Force Docker Cleanup or manually triggering a cleanup will perform the following actions:
|
||||||
|
<ul class='list-disc pl-4 mt-2'>
|
||||||
|
<li>Removes stopped containers manged by Coolify (as containers are none persistent, no data will be lost).</li>
|
||||||
|
<li>Deletes unused images.</li>
|
||||||
|
<li>Clears build cache.</li>
|
||||||
|
<li>Removes old versions of the Coolify helper image.</li>
|
||||||
|
<li>Optionally delete unused volumes (if enabled in advanced options).</li>
|
||||||
|
<li>Optionally remove unused networks (if enabled in advanced options).</li>
|
||||||
|
</ul>"
|
||||||
|
instantSave id="server.settings.force_docker_cleanup" label="Force Docker Cleanup" />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
<p class="text-sm text-gray-600 dark:text-gray-400 mb-2">
|
||||||
|
<span class="dark:text-warning font-bold">Warning: Enable these
|
||||||
|
options only if you fully understand their implications and
|
||||||
|
consequences!</span><br>Improper use will result in data loss and could cause
|
||||||
|
functional issues.
|
||||||
|
</p>
|
||||||
|
<div class="w-96">
|
||||||
|
<x-forms.checkbox instantSave id="server.settings.delete_unused_volumes" label="Delete Unused Volumes"
|
||||||
|
helper="This option will remove all unused Docker volumes during cleanup.<br><br><strong>Warning: Data form stopped containers will be lost!</strong><br><br>Consequences include:<br>
|
||||||
|
<ul class='list-disc pl-4 mt-2'>
|
||||||
|
<li>Volumes not attached to running containers will be deleted and data will be permanently lost (stopped containers are affected).</li>
|
||||||
|
<li>Data from stopped containers volumes will be permanently lost.</li>
|
||||||
|
<li>No way to recover deleted volume data.</li>
|
||||||
|
</ul>" />
|
||||||
|
<x-forms.checkbox instantSave id="server.settings.delete_unused_networks" label="Delete Unused Networks"
|
||||||
|
helper="This option will remove all unused Docker networks during cleanup.<br><br><strong>Warning: Functionality may be lost and containers may not be able to communicate with each other!</strong><br><br>Consequences include:<br>
|
||||||
|
<ul class='list-disc pl-4 mt-2'>
|
||||||
|
<li>Networks not attached to running containers will be permanently deleted (stopped containers are affected).</li>
|
||||||
|
<li>Custom networks for stopped containers will be permanently deleted.</li>
|
||||||
|
<li>Functionality may be lost and containers may not be able to communicate with each other.</li>
|
||||||
|
</ul>" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="flex flex-col">
|
||||||
|
<h3>Builds</h3>
|
||||||
|
<div>Customize the build process.</div>
|
||||||
|
<div class="flex flex-wrap gap-2 sm:flex-nowrap pt-4">
|
||||||
|
<x-forms.input id="server.settings.concurrent_builds" label="Number of concurrent builds" required
|
||||||
|
helper="You can specify the number of simultaneous build processes/deployments that should run concurrently." />
|
||||||
|
<x-forms.input id="server.settings.dynamic_timeout" label="Deployment timeout (seconds)" required
|
||||||
|
helper="You can define the maximum duration for a deployment to run before timing it out." />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</form>
|
42
resources/views/livewire/server/cloudflare-tunnels.blade.php
Normal file
42
resources/views/livewire/server/cloudflare-tunnels.blade.php
Normal file
@@ -0,0 +1,42 @@
|
|||||||
|
<div>
|
||||||
|
<div class="flex gap-1 items-center">
|
||||||
|
<h2>Cloudflare Tunnels</h2>
|
||||||
|
<x-helper class="inline-flex"
|
||||||
|
helper="If you are using Cloudflare Tunnels, enable this. It will proxy all SSH requests to your server through Cloudflare.<br> You then can close your server's SSH port in the firewall of your hosting provider.<br><span class='dark:text-warning'>If you choose manual configuration, Coolify does not install or set up Cloudflare (cloudflared) on your server.</span>" />
|
||||||
|
</div>
|
||||||
|
<div class="flex flex-col gap-2 pt-6">
|
||||||
|
@if ($server->settings->is_cloudflare_tunnel)
|
||||||
|
<div class="w-64">
|
||||||
|
<x-forms.checkbox instantSave id="server.settings.is_cloudflare_tunnel"
|
||||||
|
label="Enabled" />
|
||||||
|
</div>
|
||||||
|
@elseif (!$server->isFunctional())
|
||||||
|
<div
|
||||||
|
class="p-4 mb-4 w-full text-sm text-yellow-800 bg-yellow-100 rounded dark:bg-yellow-900 dark:text-yellow-300">
|
||||||
|
To <span class="font-semibold">automatically</span> configure Cloudflare Tunnels, please
|
||||||
|
validate your server first.</span> Then you will need a Cloudflare token and an SSH
|
||||||
|
domain configured.
|
||||||
|
<br />
|
||||||
|
To <span class="font-semibold">manually</span> configure Cloudflare Tunnels, please
|
||||||
|
click <span wire:click="manualCloudflareConfig"
|
||||||
|
class="underline cursor-pointer">here</span>, then you should validate the server.
|
||||||
|
<br /><br />
|
||||||
|
For more information, please read our <a
|
||||||
|
href="https://coolify.io/docs/knowledge-base/cloudflare/tunnels/" target="_blank"
|
||||||
|
class="font-medium underline hover:text-yellow-600 dark:hover:text-yellow-200">documentation</a>.
|
||||||
|
</div>
|
||||||
|
@endif
|
||||||
|
@if (!$server->settings->is_cloudflare_tunnel && $server->isFunctional())
|
||||||
|
<x-modal-input buttonTitle="Automated Configuration" title="Cloudflare Tunnels"
|
||||||
|
class="w-full" :closeOutside="false">
|
||||||
|
<livewire:server.configure-cloudflare-tunnels :server_id="$server->id" />
|
||||||
|
</x-modal-input>
|
||||||
|
@endif
|
||||||
|
@if ($server->isFunctional() && !$server->settings->is_cloudflare_tunnel)
|
||||||
|
<div wire:click="manualCloudflareConfig" class="w-full underline cursor-pointer">
|
||||||
|
I have configured Cloudflare Tunnels manually
|
||||||
|
</div>
|
||||||
|
@endif
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</div>
|
@@ -1,6 +1,6 @@
|
|||||||
<div>
|
<div>
|
||||||
@if ($server->id !== 0)
|
@if ($server->id !== 0)
|
||||||
<h2 class="pt-4">Danger Zone</h2>
|
<h2>Danger Zone</h2>
|
||||||
<div class="">Woah. I hope you know what are you doing.</div>
|
<div class="">Woah. I hope you know what are you doing.</div>
|
||||||
<h4 class="pt-4">Delete Server</h4>
|
<h4 class="pt-4">Delete Server</h4>
|
||||||
<div class="pb-4">This will remove this server from Coolify. Beware! There is no coming
|
<div class="pb-4">This will remove this server from Coolify. Beware! There is no coming
|
||||||
@@ -16,7 +16,7 @@
|
|||||||
<x-modal-confirmation title="Confirm Server Deletion?" isErrorButton buttonTitle="Delete"
|
<x-modal-confirmation title="Confirm Server Deletion?" isErrorButton buttonTitle="Delete"
|
||||||
submitAction="delete" :actions="['This server will be permanently deleted.']" confirmationText="{{ $server->name }}"
|
submitAction="delete" :actions="['This server will be permanently deleted.']" confirmationText="{{ $server->name }}"
|
||||||
confirmationLabel="Please confirm the execution of the actions by entering the Server Name below"
|
confirmationLabel="Please confirm the execution of the actions by entering the Server Name below"
|
||||||
shortConfirmationLabel="Server Name" step2ButtonText="Continue" step3ButtonText="Permanently Delete" />
|
shortConfirmationLabel="Server Name" step3ButtonText="Permanently Delete" />
|
||||||
@endif
|
@endif
|
||||||
@endif
|
@endif
|
||||||
</div>
|
</div>
|
||||||
|
@@ -2,6 +2,6 @@
|
|||||||
<x-slot:title>
|
<x-slot:title>
|
||||||
{{ data_get_str($server, 'name')->limit(10) }} > Server Destinations | Coolify
|
{{ data_get_str($server, 'name')->limit(10) }} > Server Destinations | Coolify
|
||||||
</x-slot>
|
</x-slot>
|
||||||
<x-server.navbar :server="$server" :parameters="$parameters" />
|
{{-- <x-server.navbar :server="$server" :parameters="$parameters" /> --}}
|
||||||
<livewire:destination.show :server="$server" />
|
<livewire:destination.show :server="$server" />
|
||||||
</div>
|
</div>
|
||||||
|
@@ -119,192 +119,98 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="{{ $server->isFunctional() ? 'w-96' : 'w-full' }}">
|
<div class="w-full">
|
||||||
@if (!$server->isLocalhost())
|
@if (!$server->isLocalhost())
|
||||||
<x-forms.checkbox instantSave id="server.settings.is_build_server"
|
<div class="w-96">
|
||||||
label="Use it as a build server?" />
|
<x-forms.checkbox instantSave id="server.settings.is_build_server"
|
||||||
<div class="flex flex-col gap-2 pt-6">
|
label="Use it as a build server?" />
|
||||||
<div class="flex gap-1 items-center">
|
|
||||||
<h3 class="text-lg font-semibold">Cloudflare Tunnels</h3>
|
|
||||||
<x-helper class="inline-flex"
|
|
||||||
helper="If you are using Cloudflare Tunnels, enable this. It will proxy all SSH requests to your server through Cloudflare.<br> You then can close your server's SSH port in the firewall of your hosting provider.<br><span class='dark:text-warning'>If you choose manual configuration, Coolify does not install or set up Cloudflare (cloudflared) on your server.</span>" />
|
|
||||||
</div>
|
|
||||||
@if ($server->settings->is_cloudflare_tunnel)
|
|
||||||
<div class="w-64">
|
|
||||||
<x-forms.checkbox instantSave id="server.settings.is_cloudflare_tunnel"
|
|
||||||
label="Enabled" />
|
|
||||||
</div>
|
|
||||||
@elseif (!$server->isFunctional())
|
|
||||||
<div
|
|
||||||
class="p-4 mb-4 w-full text-sm text-yellow-800 bg-yellow-100 rounded dark:bg-yellow-900 dark:text-yellow-300">
|
|
||||||
To <span class="font-semibold">automatically</span> configure Cloudflare Tunnels, please
|
|
||||||
validate your server first.</span> Then you will need a Cloudflare token and an SSH
|
|
||||||
domain configured.
|
|
||||||
<br />
|
|
||||||
To <span class="font-semibold">manually</span> configure Cloudflare Tunnels, please
|
|
||||||
click <span wire:click="manualCloudflareConfig"
|
|
||||||
class="underline cursor-pointer">here</span>, then you should validate the server.
|
|
||||||
<br /><br />
|
|
||||||
For more information, please read our <a
|
|
||||||
href="https://coolify.io/docs/knowledge-base/cloudflare/tunnels/" target="_blank"
|
|
||||||
class="font-medium underline hover:text-yellow-600 dark:hover:text-yellow-200">documentation</a>.
|
|
||||||
</div>
|
|
||||||
@endif
|
|
||||||
@if (!$server->settings->is_cloudflare_tunnel && $server->isFunctional())
|
|
||||||
<x-modal-input buttonTitle="Automated Configuration" title="Cloudflare Tunnels"
|
|
||||||
class="w-full" :closeOutside="false">
|
|
||||||
<livewire:server.configure-cloudflare-tunnels :server_id="$server->id" />
|
|
||||||
</x-modal-input>
|
|
||||||
@endif
|
|
||||||
@if ($server->isFunctional() && !$server->settings->is_cloudflare_tunnel)
|
|
||||||
<div wire:click="manualCloudflareConfig" class="w-full underline cursor-pointer">
|
|
||||||
I have configured Cloudflare Tunnels manually
|
|
||||||
</div>
|
|
||||||
@endif
|
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@if (!$server->isBuildServer() && !$server->settings->is_cloudflare_tunnel)
|
@if (!$server->isBuildServer() && !$server->settings->is_cloudflare_tunnel)
|
||||||
<h3 class="pt-6">Swarm <span class="text-xs text-neutral-500">(experimental)</span></h3>
|
<h3 class="pt-6">Swarm <span class="text-xs text-neutral-500">(experimental)</span></h3>
|
||||||
<div class="pb-4">Read the docs <a class='underline dark:text-white'
|
<div class="pb-4">Read the docs <a class='underline dark:text-white'
|
||||||
href='https://coolify.io/docs/knowledge-base/docker/swarm' target='_blank'>here</a>.
|
href='https://coolify.io/docs/knowledge-base/docker/swarm' target='_blank'>here</a>.
|
||||||
</div>
|
</div>
|
||||||
@if ($server->settings->is_swarm_worker)
|
<div class="w-96">
|
||||||
<x-forms.checkbox disabled instantSave type="checkbox"
|
@if ($server->settings->is_swarm_worker)
|
||||||
id="server.settings.is_swarm_manager"
|
<x-forms.checkbox disabled instantSave type="checkbox"
|
||||||
helper="For more information, please read the documentation <a class='dark:text-white' href='https://coolify.io/docs/knowledge-base/docker/swarm' target='_blank'>here</a>."
|
id="server.settings.is_swarm_manager"
|
||||||
label="Is it a Swarm Manager?" />
|
helper="For more information, please read the documentation <a class='dark:text-white' href='https://coolify.io/docs/knowledge-base/docker/swarm' target='_blank'>here</a>."
|
||||||
@else
|
label="Is it a Swarm Manager?" />
|
||||||
<x-forms.checkbox instantSave type="checkbox" id="server.settings.is_swarm_manager"
|
@else
|
||||||
helper="For more information, please read the documentation <a class='dark:text-white' href='https://coolify.io/docs/knowledge-base/docker/swarm' target='_blank'>here</a>."
|
<x-forms.checkbox instantSave type="checkbox" id="server.settings.is_swarm_manager"
|
||||||
label="Is it a Swarm Manager?" />
|
helper="For more information, please read the documentation <a class='dark:text-white' href='https://coolify.io/docs/knowledge-base/docker/swarm' target='_blank'>here</a>."
|
||||||
@endif
|
label="Is it a Swarm Manager?" />
|
||||||
|
@endif
|
||||||
|
|
||||||
@if ($server->settings->is_swarm_manager)
|
@if ($server->settings->is_swarm_manager)
|
||||||
<x-forms.checkbox disabled instantSave type="checkbox"
|
<x-forms.checkbox disabled instantSave type="checkbox"
|
||||||
id="server.settings.is_swarm_worker"
|
id="server.settings.is_swarm_worker"
|
||||||
helper="For more information, please read the documentation <a class='dark:text-white' href='https://coolify.io/docs/knowledge-base/docker/swarm' target='_blank'>here</a>."
|
helper="For more information, please read the documentation <a class='dark:text-white' href='https://coolify.io/docs/knowledge-base/docker/swarm' target='_blank'>here</a>."
|
||||||
label="Is it a Swarm Worker?" />
|
label="Is it a Swarm Worker?" />
|
||||||
@else
|
@else
|
||||||
<x-forms.checkbox instantSave type="checkbox" id="server.settings.is_swarm_worker"
|
<x-forms.checkbox instantSave type="checkbox" id="server.settings.is_swarm_worker"
|
||||||
helper="For more information, please read the documentation <a class='dark:text-white' href='https://coolify.io/docs/knowledge-base/docker/swarm' target='_blank'>here</a>."
|
helper="For more information, please read the documentation <a class='dark:text-white' href='https://coolify.io/docs/knowledge-base/docker/swarm' target='_blank'>here</a>."
|
||||||
label="Is it a Swarm Worker?" />
|
label="Is it a Swarm Worker?" />
|
||||||
@endif
|
@endif
|
||||||
|
</div>
|
||||||
@endif
|
@endif
|
||||||
@endif
|
@endif
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@if (isDev())
|
||||||
@if ($server->isFunctional())
|
|
||||||
<h3 class="pt-4">Settings</h3>
|
|
||||||
<div class="flex flex-col gap-4">
|
|
||||||
<div class="flex flex-col gap-2">
|
|
||||||
<div class="flex flex-wrap items-center gap-4">
|
|
||||||
<div class="w-64">
|
|
||||||
<x-forms.checkbox
|
|
||||||
helper="Enabling Force Docker Cleanup or manually triggering a cleanup will perform the following actions:
|
|
||||||
<ul class='list-disc pl-4 mt-2'>
|
|
||||||
<li>Removes stopped containers manged by Coolify (as containers are none persistent, no data will be lost).</li>
|
|
||||||
<li>Deletes unused images.</li>
|
|
||||||
<li>Clears build cache.</li>
|
|
||||||
<li>Removes old versions of the Coolify helper image.</li>
|
|
||||||
<li>Optionally delete unused volumes (if enabled in advanced options).</li>
|
|
||||||
<li>Optionally remove unused networks (if enabled in advanced options).</li>
|
|
||||||
</ul>"
|
|
||||||
instantSave id="server.settings.force_docker_cleanup" label="Force Docker Cleanup" />
|
|
||||||
</div>
|
|
||||||
<x-modal-confirmation title="Confirm Docker Cleanup?" buttonTitle="Trigger Docker Cleanup"
|
|
||||||
submitAction="manualCleanup" :actions="[
|
|
||||||
'Permanently deletes all stopped containers managed by Coolify (as containers are non-persistent, no data will be lost)',
|
|
||||||
'Permanently deletes all unused images',
|
|
||||||
'Clears build cache',
|
|
||||||
'Removes old versions of the Coolify helper image',
|
|
||||||
'Optionally permanently deletes all unused volumes (if enabled in advanced options).',
|
|
||||||
'Optionally permanently deletes all unused networks (if enabled in advanced options).',
|
|
||||||
]" :confirmWithText="false" :confirmWithPassword="false"
|
|
||||||
step2ButtonText="Trigger Docker Cleanup" />
|
|
||||||
</div>
|
|
||||||
@if ($server->settings->force_docker_cleanup)
|
|
||||||
<x-forms.input placeholder="*/10 * * * *" id="server.settings.docker_cleanup_frequency"
|
|
||||||
label="Docker cleanup frequency" required
|
|
||||||
helper="Cron expression for Docker Cleanup.<br>You can use every_minute, hourly, daily, weekly, monthly, yearly.<br><br>Default is every night at midnight." />
|
|
||||||
@else
|
|
||||||
<x-forms.input id="server.settings.docker_cleanup_threshold"
|
|
||||||
label="Docker cleanup threshold (%)" required
|
|
||||||
helper="The Docker cleanup tasks will run when the disk usage exceeds this threshold." />
|
|
||||||
@endif
|
|
||||||
<div x-data="{ open: false }" class="mt-4 max-w-md">
|
|
||||||
<button @click="open = !open" type="button"
|
|
||||||
class="flex items-center justify-between w-full text-left text-sm font-medium text-gray-700 dark:text-gray-300 hover:text-gray-900 dark:hover:text-gray-100">
|
|
||||||
<span>Advanced Options</span>
|
|
||||||
<svg :class="{ 'rotate-180': open }" class="w-5 h-5 transition-transform duration-200"
|
|
||||||
xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor">
|
|
||||||
<path fill-rule="evenodd"
|
|
||||||
d="M5.293 7.293a1 1 0 011.414 0L10 10.586l3.293-3.293a1 1 0 111.414 1.414l-4 4a1 1 0 01-1.414 0l-4-4a1 1 0 010-1.414z"
|
|
||||||
clip-rule="evenodd" />
|
|
||||||
</svg>
|
|
||||||
</button>
|
|
||||||
<div x-show="open" class="mt-2 space-y-2">
|
|
||||||
<p class="text-sm text-gray-600 dark:text-gray-400 mb-2"><strong>Warning: Enable these
|
|
||||||
options only if you fully understand their implications and
|
|
||||||
consequences!</strong><br>Improper use will result in data loss and could cause
|
|
||||||
functional issues.</p>
|
|
||||||
<x-forms.checkbox instantSave id="server.settings.delete_unused_volumes"
|
|
||||||
label="Delete Unused Volumes"
|
|
||||||
helper="This option will remove all unused Docker volumes during cleanup.<br><br><strong>Warning: Data form stopped containers will be lost!</strong><br><br>Consequences include:<br>
|
|
||||||
<ul class='list-disc pl-4 mt-2'>
|
|
||||||
<li>Volumes not attached to running containers will be deleted and data will be permanently lost (stopped containers are affected).</li>
|
|
||||||
<li>Data from stopped containers volumes will be permanently lost.</li>
|
|
||||||
<li>No way to recover deleted volume data.</li>
|
|
||||||
</ul>" />
|
|
||||||
<x-forms.checkbox instantSave id="server.settings.delete_unused_networks"
|
|
||||||
label="Delete Unused Networks"
|
|
||||||
helper="This option will remove all unused Docker networks during cleanup.<br><br><strong>Warning: Functionality may be lost and containers may not be able to communicate with each other!</strong><br><br>Consequences include:<br>
|
|
||||||
<ul class='list-disc pl-4 mt-2'>
|
|
||||||
<li>Networks not attached to running containers will be permanently deleted (stopped containers are affected).</li>
|
|
||||||
<li>Custom networks for stopped containers will be permanently deleted.</li>
|
|
||||||
<li>Functionality may be lost and containers may not be able to communicate with each other.</li>
|
|
||||||
</ul>" />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="flex flex-wrap gap-4 sm:flex-nowrap">
|
|
||||||
<x-forms.input id="server.settings.concurrent_builds" label="Number of concurrent builds" required
|
|
||||||
helper="You can specify the number of simultaneous build processes/deployments that should run concurrently." />
|
|
||||||
<x-forms.input id="server.settings.dynamic_timeout" label="Deployment timeout (seconds)" required
|
|
||||||
helper="You can define the maximum duration for a deployment to run before timing it out." />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="flex gap-2 items-center pt-4 pb-2">
|
<div class="flex gap-2 items-center pt-4 pb-2">
|
||||||
<h3>Sentinel</h3>
|
<h3>Sentinel</h3>
|
||||||
{{-- @if ($server->isSentinelEnabled()) --}}
|
@if ($server->isSentinelEnabled())
|
||||||
{{-- <x-forms.button wire:click='restartSentinel'>Restart</x-forms.button> --}}
|
<div class="flex gap-2 items-center"
|
||||||
{{-- @endif --}}
|
wire:poll.{{ $server->settings->sentinel_push_interval_seconds }}s="checkSyncStatus">
|
||||||
|
@if ($server->isSentinelLive())
|
||||||
|
<x-status.running status="In-sync" noLoading />
|
||||||
|
<x-forms.button wire:click='restartSentinel'>Restart</x-forms.button>
|
||||||
|
@else
|
||||||
|
<x-status.stopped status="Out-of-sync" noLoading />
|
||||||
|
<x-forms.button wire:click='restartSentinel'>Sync</x-forms.button>
|
||||||
|
@endif
|
||||||
|
</div>
|
||||||
|
@endif
|
||||||
</div>
|
</div>
|
||||||
@if (isDev())
|
<div class="flex flex-col gap-2">
|
||||||
<x-forms.button wire:click="getPushData"> Push Test </x-forms.button>
|
<div class="w-64">
|
||||||
{{-- <div class="w-64">
|
<x-forms.checkbox instantSave id="server.settings.is_sentinel_enabled" label="Enable Sentinel" />
|
||||||
<x-forms.checkbox instantSave id="server.settings.is_metrics_enabled" label="Enable Metrics" />
|
@if ($server->isSentinelEnabled())
|
||||||
<x-forms.button>Start Sentinel</x-forms.button>
|
<x-forms.checkbox instantSave id="server.settings.is_metrics_enabled"
|
||||||
</div> --}}
|
label="Enable Metrics" />
|
||||||
<div class="flex flex-col gap-2">
|
@else
|
||||||
|
<x-forms.checkbox instantSave disabled id="server.settings.is_metrics_enabled"
|
||||||
|
label="Enable Metrics" />
|
||||||
|
@endif
|
||||||
|
</div>
|
||||||
|
@if ($server->isSentinelEnabled())
|
||||||
<div class="flex flex-wrap gap-2 sm:flex-nowrap items-end">
|
<div class="flex flex-wrap gap-2 sm:flex-nowrap items-end">
|
||||||
<x-forms.input type="password" id="server.settings.sentinel_token" label="Metrics token"
|
<x-forms.input type="password" id="server.settings.sentinel_token" label="Sentinel token"
|
||||||
required helper="Token for collector (Sentinel)." />
|
required helper="Token for Sentinel." />
|
||||||
<x-forms.button wire:click="regenerateSentinelToken">Regenerate</x-forms.button>
|
<x-forms.button wire:click="regenerateSentinelToken">Regenerate</x-forms.button>
|
||||||
</div>
|
</div>
|
||||||
<div class="flex flex-wrap gap-2 sm:flex-nowrap">
|
|
||||||
<x-forms.input id="server.settings.sentinel_metrics_refresh_rate_seconds"
|
<x-forms.input id="server.settings.sentinel_custom_url" required label="Coolify URL"
|
||||||
label="Metrics rate (seconds)" required
|
helper="URL to your Coolify instance. If it is empty that means you do not have a FQDN set for your Coolify instance." />
|
||||||
helper="The interval for gathering metrics. Lower means more disk space will be used." />
|
|
||||||
<x-forms.input id="server.settings.sentinel_metrics_history_days"
|
<div class="flex flex-col gap-2">
|
||||||
label="Metrics history (days)" required
|
<div class="flex flex-wrap gap-2 sm:flex-nowrap">
|
||||||
helper="How many days should the metrics data should be reserved." />
|
<x-forms.input id="server.settings.sentinel_metrics_refresh_rate_seconds"
|
||||||
|
label="Metrics rate (seconds)" required
|
||||||
|
helper="The interval for gathering metrics. Lower means more disk space will be used." />
|
||||||
|
<x-forms.input id="server.settings.sentinel_metrics_history_days"
|
||||||
|
label="Metrics history (days)" required
|
||||||
|
helper="How many days should the metrics data should be reserved." />
|
||||||
|
<x-forms.input id="server.settings.sentinel_push_interval_seconds"
|
||||||
|
label="Push interval (seconds)" required
|
||||||
|
helper="How many seconds should the metrics data should be pushed to the collector." />
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
@endif
|
||||||
@else
|
</div>
|
||||||
<div>Metrics are disabled until a few bugs are fixed.</div>
|
|
||||||
@endif
|
|
||||||
@endif
|
@endif
|
||||||
|
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
|
@@ -2,7 +2,7 @@
|
|||||||
<x-slot:title>
|
<x-slot:title>
|
||||||
{{ data_get_str($server, 'name')->limit(10) }} > Server LogDrains | Coolify
|
{{ data_get_str($server, 'name')->limit(10) }} > Server LogDrains | Coolify
|
||||||
</x-slot>
|
</x-slot>
|
||||||
<x-server.navbar :server="$server" :parameters="$parameters" />
|
{{-- <x-server.navbar :server="$server" :parameters="$parameters" /> --}}
|
||||||
@if ($server->isFunctional())
|
@if ($server->isFunctional())
|
||||||
<h2>Log Drains</h2>
|
<h2>Log Drains</h2>
|
||||||
<div class="pb-4">Sends service logs to 3rd party tools.</div>
|
<div class="pb-4">Sends service logs to 3rd party tools.</div>
|
||||||
|
@@ -2,6 +2,5 @@
|
|||||||
<x-slot:title>
|
<x-slot:title>
|
||||||
Server Connection | Coolify
|
Server Connection | Coolify
|
||||||
</x-slot>
|
</x-slot>
|
||||||
<x-server.navbar :server="$server" :parameters="$parameters" />
|
|
||||||
<livewire:server.show-private-key :server="$server" :privateKeys="$privateKeys" />
|
<livewire:server.show-private-key :server="$server" :privateKeys="$privateKeys" />
|
||||||
</div>
|
</div>
|
||||||
|
@@ -3,7 +3,7 @@
|
|||||||
Proxy Dynamic Configuration | Coolify
|
Proxy Dynamic Configuration | Coolify
|
||||||
</x-slot>
|
</x-slot>
|
||||||
<x-server.navbar :server="$server" :parameters="$parameters" />
|
<x-server.navbar :server="$server" :parameters="$parameters" />
|
||||||
<div class="flex gap-2">
|
<div class="flex flex-col h-full gap-8 sm:flex-row">
|
||||||
<x-server.sidebar :server="$server" :parameters="$parameters" />
|
<x-server.sidebar :server="$server" :parameters="$parameters" />
|
||||||
<div class="w-full">
|
<div class="w-full">
|
||||||
@if ($server->isFunctional())
|
@if ($server->isFunctional())
|
||||||
|
@@ -3,7 +3,7 @@
|
|||||||
Proxy Logs | Coolify
|
Proxy Logs | Coolify
|
||||||
</x-slot>
|
</x-slot>
|
||||||
<x-server.navbar :server="$server" :parameters="$parameters" />
|
<x-server.navbar :server="$server" :parameters="$parameters" />
|
||||||
<div class="flex gap-2">
|
<div class="flex flex-col h-full gap-8 sm:flex-row">
|
||||||
<x-server.sidebar :server="$server" :parameters="$parameters" />
|
<x-server.sidebar :server="$server" :parameters="$parameters" />
|
||||||
<div class="w-full">
|
<div class="w-full">
|
||||||
<h2 class="pb-4">Logs</h2>
|
<h2 class="pb-4">Logs</h2>
|
||||||
|
@@ -1,12 +0,0 @@
|
|||||||
<div>
|
|
||||||
<x-modal submitWireAction="proxyStatusUpdated" modalId="startProxy">
|
|
||||||
<x-slot:modalBody>
|
|
||||||
<livewire:activity-monitor header="Proxy Startup Logs" />
|
|
||||||
</x-slot:modalBody>
|
|
||||||
<x-slot:modalSubmit>
|
|
||||||
<x-forms.button onclick="startProxy.close()" type="submit">
|
|
||||||
Close
|
|
||||||
</x-forms.button>
|
|
||||||
</x-slot:modalSubmit>
|
|
||||||
</x-modal>
|
|
||||||
</div>
|
|
@@ -4,13 +4,13 @@
|
|||||||
</x-slot>
|
</x-slot>
|
||||||
<x-server.navbar :server="$server" :parameters="$parameters" />
|
<x-server.navbar :server="$server" :parameters="$parameters" />
|
||||||
@if ($server->isFunctional())
|
@if ($server->isFunctional())
|
||||||
<div class="flex gap-2">
|
<div class="flex flex-col h-full gap-8 sm:flex-row">
|
||||||
<x-server.sidebar :server="$server" :parameters="$parameters" />
|
<x-server.sidebar :server="$server" :parameters="$parameters" />
|
||||||
<div class="w-full">
|
<div class="w-full">
|
||||||
<livewire:server.proxy :server="$server" />
|
<livewire:server.proxy :server="$server" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@else
|
@else
|
||||||
<div>Server is not validated. Validate first.</div>
|
<div>Server is not validated. Validate first.</div>
|
||||||
@endif
|
@endif
|
||||||
</div>
|
</div>
|
||||||
|
@@ -2,24 +2,38 @@
|
|||||||
<x-slot:title>
|
<x-slot:title>
|
||||||
{{ data_get_str($server, 'name')->limit(10) }} > Server Resources | Coolify
|
{{ data_get_str($server, 'name')->limit(10) }} > Server Resources | Coolify
|
||||||
</x-slot>
|
</x-slot>
|
||||||
<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 flex-col h-full gap-8 md:flex-row">
|
<div x-data="{ activeTab: 'managed' }" class="flex flex-col h-full gap-8 md:flex-row">
|
||||||
<div class="flex flex-row gap-4 md:flex-col">
|
|
||||||
<a :class="activeTab === 'managed' && 'dark:text-white'"
|
|
||||||
@click.prevent="activeTab = 'managed'; window.location.hash = 'managed'" href="#">Managed</a>
|
|
||||||
<a :class="activeTab === 'unmanaged' && 'dark:text-white'"
|
|
||||||
@click.prevent="activeTab = 'unmanaged'; window.location.hash = 'unmanaged'" href="#">Unmanaged</a>
|
|
||||||
</div>
|
|
||||||
<div class="w-full">
|
<div class="w-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">
|
<h2>Resources</h2>
|
||||||
<h2>Resources</h2>
|
<x-forms.button wire:click="refreshStatus">Refresh</x-forms.button>
|
||||||
<x-forms.button wire:click="refreshStatus">Refresh</x-forms.button>
|
|
||||||
</div>
|
|
||||||
<div class="subtitle">Here you can find all resources that are managed by Coolify.</div>
|
|
||||||
</div>
|
</div>
|
||||||
@if ($server->definedResources()->count() > 0)
|
<div>Here you can find all resources that are managed by Coolify.</div>
|
||||||
|
<div class="flex flex-row gap-4 py-10">
|
||||||
|
<div @class([
|
||||||
|
'box-without-bg cursor-pointer bg-coolgray-100 text-white w-full text-center items-center justify-center',
|
||||||
|
'bg-coollabs' => $activeTab === 'managed',
|
||||||
|
]) wire:click="loadManagedContainers">
|
||||||
|
Managed
|
||||||
|
<div class="flex flex-col items-center justify-center">
|
||||||
|
<x-loading wire:loading wire:target="loadManagedContainers" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div @class([
|
||||||
|
'box-without-bg cursor-pointer bg-coolgray-100 text-white w-full text-center items-center justify-center',
|
||||||
|
'bg-coollabs' => $activeTab === 'unmanaged',
|
||||||
|
]) wire:click="loadUnmanagedContainers">
|
||||||
|
Unmanaged
|
||||||
|
<div class="flex flex-col items-center justify-center">
|
||||||
|
<x-loading wire:loading wire:target="loadUnmanagedContainers" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
@if ($containers->count() > 0)
|
||||||
|
@if ($activeTab === 'managed')
|
||||||
<div class="flex flex-col">
|
<div class="flex flex-col">
|
||||||
<div class="flex flex-col">
|
<div class="flex flex-col">
|
||||||
<div class="overflow-x-auto">
|
<div class="overflow-x-auto">
|
||||||
@@ -78,19 +92,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@else
|
@elseif ($activeTab === 'unmanaged')
|
||||||
<div>No resources found.</div>
|
|
||||||
@endif
|
|
||||||
</div>
|
|
||||||
<div x-cloak x-show="activeTab === 'unmanaged'" class="h-full">
|
|
||||||
<div class="flex flex-col" x-init="$wire.loadUnmanagedContainers()">
|
|
||||||
<div class="flex gap-2">
|
|
||||||
<h2>Resources</h2>
|
|
||||||
<x-forms.button wire:click="refreshStatus">Refresh</x-forms.button>
|
|
||||||
</div>
|
|
||||||
<div class="subtitle">Here you can find all other containers running on the server.</div>
|
|
||||||
</div>
|
|
||||||
@if ($unmanagedContainers->count() > 0)
|
|
||||||
<div class="flex flex-col">
|
<div class="flex flex-col">
|
||||||
<div class="flex flex-col">
|
<div class="flex flex-col">
|
||||||
<div class="overflow-x-auto">
|
<div class="overflow-x-auto">
|
||||||
@@ -114,7 +116,7 @@
|
|||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
@forelse ($unmanagedContainers->sortBy('name',SORT_NATURAL) as $resource)
|
@forelse ($containers->sortBy('name',SORT_NATURAL) as $resource)
|
||||||
<tr>
|
<tr>
|
||||||
<td class="px-5 py-4 text-sm whitespace-nowrap">
|
<td class="px-5 py-4 text-sm whitespace-nowrap">
|
||||||
{{ data_get($resource, 'Names') }}
|
{{ data_get($resource, 'Names') }}
|
||||||
@@ -152,11 +154,14 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
@else
|
|
||||||
<div>No resources found.</div>
|
|
||||||
@endif
|
@endif
|
||||||
</div>
|
@else
|
||||||
|
@if ($activeTab === 'managed')
|
||||||
|
<div>No managed resources found.</div>
|
||||||
|
@elseif ($activeTab === 'unmanaged')
|
||||||
|
<div>No unmanaged resources found.</div>
|
||||||
|
@endif
|
||||||
|
@endif
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@@ -1,5 +1,5 @@
|
|||||||
<div>
|
<div>
|
||||||
<div class="flex items-end gap-2 pb-6 ">
|
<div class="flex items-end gap-2">
|
||||||
<h2>Private Key</h2>
|
<h2>Private Key</h2>
|
||||||
<x-modal-input buttonTitle="+ Add" title="New Private Key">
|
<x-modal-input buttonTitle="+ Add" title="New Private Key">
|
||||||
<livewire:security.private-key.create />
|
<livewire:security.private-key.create />
|
||||||
@@ -9,29 +9,25 @@
|
|||||||
</x-forms.button>
|
</x-forms.button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="flex flex-col gap-2 pb-6">
|
<div class="flex flex-col gap-2">
|
||||||
@if (data_get($server, 'privateKey.uuid'))
|
<div class="pb-4">Change your server's private key.</div>
|
||||||
<div>
|
|
||||||
Currently attached Private Key:
|
|
||||||
<a
|
|
||||||
href="{{ route('security.private-key.show', ['private_key_uuid' => data_get($server, 'privateKey.uuid')]) }}">
|
|
||||||
<button class="dark:text-white btn-link">{{ data_get($server, 'privateKey.name') }}</button>
|
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
@else
|
|
||||||
<div class="">No private key attached.</div>
|
|
||||||
@endif
|
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
<h3 class="pb-4">Choose another Key</h3>
|
<div class="grid xl:grid-cols-2 grid-cols-1 gap-2">
|
||||||
<div class="grid grid-cols-3 gap-2">
|
|
||||||
@forelse ($privateKeys as $private_key)
|
@forelse ($privateKeys as $private_key)
|
||||||
<div class="box group cursor-pointer"
|
<div class="box-without-bg justify-between dark:bg-coolgray-100 bg-white items-center">
|
||||||
wire:click='setPrivateKey({{ $private_key->id }})'>
|
|
||||||
<div class="flex flex-col ">
|
<div class="flex flex-col ">
|
||||||
<div class="box-title">{{ $private_key->name }}</div>
|
<div class="box-title">{{ $private_key->name }}</div>
|
||||||
<div class="box-description">{{ $private_key->description }}</div>
|
<div class="box-description">{{ $private_key->description }}</div>
|
||||||
</div>
|
</div>
|
||||||
|
@if (data_get($server, 'privateKey.uuid') !== $private_key->uuid)
|
||||||
|
<x-forms.button wire:click='setPrivateKey({{ $private_key->id }})'>
|
||||||
|
Use this key
|
||||||
|
</x-forms.button>
|
||||||
|
@else
|
||||||
|
<x-forms.button disabled>
|
||||||
|
Currently used
|
||||||
|
</x-forms.button>
|
||||||
|
@endif
|
||||||
</div>
|
</div>
|
||||||
@empty
|
@empty
|
||||||
<div>No private keys found. </div>
|
<div>No private keys found. </div>
|
||||||
|
@@ -3,11 +3,75 @@
|
|||||||
{{ data_get_str($server, 'name')->limit(10) }} > Server Configurations | Coolify
|
{{ data_get_str($server, 'name')->limit(10) }} > Server Configurations | Coolify
|
||||||
</x-slot>
|
</x-slot>
|
||||||
<x-server.navbar :server="$server" :parameters="$parameters" />
|
<x-server.navbar :server="$server" :parameters="$parameters" />
|
||||||
<livewire:server.form :server="$server" />
|
<div x-data="{ activeTab: window.location.hash ? window.location.hash.substring(1) : 'general' }" class="flex flex-col h-full gap-8 sm:flex-row">
|
||||||
@if ($server->isFunctional() && $server->isMetricsEnabled())
|
<div class="flex flex-col items-start gap-2 min-w-fit">
|
||||||
<div class="pt-10">
|
<a class="menu-item" :class="activeTab === 'general' && 'menu-item-active'"
|
||||||
<livewire:server.charts :server="$server" />
|
@click.prevent="activeTab = 'general'; window.location.hash = 'general'" href="#">General</a>
|
||||||
|
@if ($server->isFunctional())
|
||||||
|
<a class="menu-item" :class="activeTab === 'advanced' && 'menu-item-active'"
|
||||||
|
@click.prevent="activeTab = 'advanced'; window.location.hash = 'advanced'" href="#">Advanced
|
||||||
|
</a>
|
||||||
|
@endif
|
||||||
|
<a class="menu-item" :class="activeTab === 'private-key' && 'menu-item-active'"
|
||||||
|
@click.prevent="activeTab = 'private-key'; window.location.hash = 'private-key'" href="#">Private
|
||||||
|
Key</a>
|
||||||
|
@if ($server->isFunctional())
|
||||||
|
<a class="menu-item" :class="activeTab === 'cloudflare-tunnels' && 'menu-item-active'"
|
||||||
|
@click.prevent="activeTab = 'cloudflare-tunnels'; window.location.hash = 'cloudflare-tunnels'"
|
||||||
|
href="#">Cloudflare Tunnels</a>
|
||||||
|
<a class="menu-item" :class="activeTab === 'resources' && 'menu-item-active'"
|
||||||
|
@click.prevent="activeTab = 'resources'; window.location.hash = 'resources'"
|
||||||
|
href="#">Resources</a>
|
||||||
|
<a class="menu-item" :class="activeTab === 'destinations' && 'menu-item-active'"
|
||||||
|
@click.prevent="activeTab = 'destinations'; window.location.hash = 'destinations'"
|
||||||
|
href="#">Destinations</a>
|
||||||
|
<a class="menu-item" :class="activeTab === 'log-drains' && 'menu-item-active'"
|
||||||
|
@click.prevent="activeTab = 'log-drains'; window.location.hash = 'log-drains'" href="#">Log
|
||||||
|
Drains</a>
|
||||||
|
<a class="menu-item" :class="activeTab === 'metrics' && 'menu-item-active'"
|
||||||
|
@click.prevent="activeTab = 'metrics'; window.location.hash = 'metrics'" href="#">Metrics</a>
|
||||||
|
@endif
|
||||||
|
@if (!$server->isLocalhost())
|
||||||
|
<a class="menu-item" :class="activeTab === 'danger' && 'menu-item-active'"
|
||||||
|
@click.prevent="activeTab = 'danger'; window.location.hash = 'danger'" href="#">Danger</a>
|
||||||
|
@endif
|
||||||
</div>
|
</div>
|
||||||
@endif
|
<div class="w-full">
|
||||||
<livewire:server.delete :server="$server" />
|
<div x-cloak x-show="activeTab === 'general'" class="h-full">
|
||||||
|
<livewire:server.form :server="$server" />
|
||||||
|
</div>
|
||||||
|
<div x-cloak x-show="activeTab === 'advanced'" class="h-full">
|
||||||
|
<livewire:server.advanced :server="$server" />
|
||||||
|
</div>
|
||||||
|
<div x-cloak x-show="activeTab === 'private-key'" class="h-full">
|
||||||
|
<livewire:server.private-key.show :server="$server" />
|
||||||
|
</div>
|
||||||
|
<div x-cloak x-show="activeTab === 'cloudflare-tunnels'" class="h-full">
|
||||||
|
<livewire:server.cloudflare-tunnels :server="$server" />
|
||||||
|
</div>
|
||||||
|
<div x-cloak x-show="activeTab === 'resources'" class="h-full">
|
||||||
|
<livewire:server.resources :server="$server" />
|
||||||
|
</div>
|
||||||
|
<div x-cloak x-show="activeTab === 'destinations'" class="h-full">
|
||||||
|
<livewire:server.destination.show :server="$server" />
|
||||||
|
</div>
|
||||||
|
<div x-cloak x-show="activeTab === 'log-drains'" class="h-full">
|
||||||
|
<livewire:server.log-drains :server="$server" />
|
||||||
|
</div>
|
||||||
|
<div x-cloak x-show="activeTab === 'metrics'" class="h-full">
|
||||||
|
@if ($server->isFunctional() && $server->isMetricsEnabled())
|
||||||
|
<div class="pt-10">
|
||||||
|
<livewire:server.charts :server="$server" />
|
||||||
|
</div>
|
||||||
|
@else
|
||||||
|
No metrics available.
|
||||||
|
@endif
|
||||||
|
</div>
|
||||||
|
@if (!$server->isLocalhost())
|
||||||
|
<div x-cloak x-show="activeTab === 'danger'" class="h-full">
|
||||||
|
<livewire:server.delete :server="$server" />
|
||||||
|
</div>
|
||||||
|
@endif
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@@ -41,7 +41,8 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="relative">
|
<div class="relative">
|
||||||
<div class="inline-flex items-center relative w-full">
|
<div class="inline-flex items-center relative w-full">
|
||||||
<input autocomplete="off" wire:dirty.class.remove='dark:focus:ring-coolgray-300 dark:ring-coolgray-300'
|
<input autocomplete="off"
|
||||||
|
wire:dirty.class.remove='dark:focus:ring-coolgray-300 dark:ring-coolgray-300'
|
||||||
wire:dirty.class="dark:focus:ring-warning dark:ring-warning" x-model="search"
|
wire:dirty.class="dark:focus:ring-warning dark:ring-warning" x-model="search"
|
||||||
@focus="open = true" @click.away="open = false" @input="open = true"
|
@focus="open = true" @click.away="open = false" @input="open = true"
|
||||||
class="w-full input " :placeholder="placeholder"
|
class="w-full input " :placeholder="placeholder"
|
||||||
@@ -65,8 +66,16 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
|
|
||||||
|
</div>
|
||||||
|
<div class="flex gap-2">
|
||||||
|
<x-forms.input id="settings.public_ipv4" type="password" label="Instance's IPv4"
|
||||||
|
helper="Enter the IPv4 address of the instance.<br><br>It is useful if you have several IPv4 addresses and Coolify could not detect the correct one."
|
||||||
|
placeholder="1.2.3.4" />
|
||||||
|
<x-forms.input id="settings.public_ipv6" type="password" label="Instance's IPv6"
|
||||||
|
helper="Enter the IPv6 address of the instance.<br><br>It is useful if you have several IPv6 addresses and Coolify could not detect the correct one."
|
||||||
|
placeholder="2001:db8::1" />
|
||||||
|
</div>
|
||||||
<h4 class="w-full pt-6">DNS Validation</h4>
|
<h4 class="w-full pt-6">DNS Validation</h4>
|
||||||
<div class="md:w-96">
|
<div class="md:w-96">
|
||||||
<x-forms.checkbox instantSave id="is_dns_validation_enabled" label="Enabled" />
|
<x-forms.checkbox instantSave id="is_dns_validation_enabled" label="Enabled" />
|
||||||
@@ -83,7 +92,7 @@
|
|||||||
|
|
||||||
</div>
|
</div>
|
||||||
<h4 class="pt-6">API</h4>
|
<h4 class="pt-6">API</h4>
|
||||||
<div class="md:w-96">
|
<div class="md:w-96 pb-2">
|
||||||
<x-forms.checkbox instantSave id="is_api_enabled" label="Enabled" />
|
<x-forms.checkbox instantSave id="is_api_enabled" label="Enabled" />
|
||||||
</div>
|
</div>
|
||||||
<x-forms.input id="settings.allowed_ips" label="Allowed IPs"
|
<x-forms.input id="settings.allowed_ips" label="Allowed IPs"
|
||||||
@@ -95,7 +104,7 @@
|
|||||||
<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" />
|
||||||
</div>
|
</div>
|
||||||
<h5 class="pt-4 font-bold text-white">Update</h5>
|
<h4 class="pt-6">Update</h4>
|
||||||
<div class="text-right md:w-96">
|
<div class="text-right md:w-96">
|
||||||
@if (!is_null(env('AUTOUPDATE', null)))
|
@if (!is_null(env('AUTOUPDATE', null)))
|
||||||
<div class="text-right md:w-96">
|
<div class="text-right md:w-96">
|
||||||
@@ -119,6 +128,34 @@
|
|||||||
helper="Cron expression for auto update frequency (automatically update coolify).<br>You can use every_minute, hourly, daily, weekly, monthly, yearly.<br><br>Default is every day at 00:00" />
|
helper="Cron expression for auto update frequency (automatically update coolify).<br>You can use every_minute, hourly, daily, weekly, monthly, yearly.<br><br>Default is every day at 00:00" />
|
||||||
@endif
|
@endif
|
||||||
</div>
|
</div>
|
||||||
</form>
|
|
||||||
|
|
||||||
|
<h4 class="pt-6">Advanced</h4>
|
||||||
|
<div class="text-right md:w-96">
|
||||||
|
<x-forms.checkbox instantSave id="is_registration_enabled" label="Registration Allowed" />
|
||||||
|
<x-forms.checkbox instantSave id="do_not_track" label="Do Not Track" />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<h5 class="py-4 font-bold text-white">Confirmation Settings</h5>
|
||||||
|
@if ($disable_two_step_confirmation)
|
||||||
|
<div class="md:w-96 pb-4">
|
||||||
|
<x-forms.checkbox instantSave id="disable_two_step_confirmation" label="Disable Two Step Confirmation"
|
||||||
|
helper="When disabled, you will not need to confirm actions with a text and user password. This significantly reduces security and may lead to accidental deletions or unwanted changes. Use with extreme caution, especially on production servers." />
|
||||||
|
</div>
|
||||||
|
@else
|
||||||
|
<x-modal-confirmation title="Disable Two Step Confirmation?" buttonTitle="Disable Two Step Confirmation"
|
||||||
|
isErrorButton submitAction="toggleTwoStepConfirmation" :actions="[
|
||||||
|
'Tow Step confimation will be disabled globally.',
|
||||||
|
'Disabling two step confirmation reduces security (as anyone can easily delete anything).',
|
||||||
|
'The risk of accidental actions will increase.',
|
||||||
|
]"
|
||||||
|
confirmationText="DISABLE TWO STEP CONFIRMATION"
|
||||||
|
confirmationLabel="Please type the confirmation text to disable two step confirmation."
|
||||||
|
shortConfirmationLabel="Confirmation text" step3ButtonText="Disable Two Step Confirmation" />
|
||||||
|
@endif
|
||||||
|
<div class="p-4 mb-4 text-white border-l-4 border-red-500 bg-error md:w-[40rem] w-full mb-32">
|
||||||
|
<p class="font-bold">Warning!</p>
|
||||||
|
<p>Disabling two step confirmation reduces security (as anyone can easily delete anything) and increases the
|
||||||
|
risk of accidental actions. This is not recommended for production servers.</p>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
</div>
|
</div>
|
||||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user