refactor: Remove unused server timezone seeder and related code

This commit is contained in:
Andras Bacsai 2024-08-26 15:26:08 +02:00
parent b8ff0540e2
commit 68169f75d1
12 changed files with 337 additions and 409 deletions

View File

@ -35,6 +35,7 @@ protected function schedule(Schedule $schedule): void
// Instance Jobs // Instance Jobs
$schedule->command('horizon:snapshot')->everyMinute(); $schedule->command('horizon:snapshot')->everyMinute();
$schedule->job(new CleanupInstanceStuffsJob)->everyMinute()->onOneServer(); $schedule->job(new CleanupInstanceStuffsJob)->everyMinute()->onOneServer();
$schedule->job(new PullCoolifyImageJob)->cron($settings->update_check_frequency)->timezone($settings->instance_timezone)->onOneServer();
// Server Jobs // Server Jobs
$this->check_scheduled_backups($schedule); $this->check_scheduled_backups($schedule);
$this->check_resources($schedule); $this->check_resources($schedule);
@ -134,6 +135,7 @@ private function check_scheduled_backups($schedule)
if (is_null(data_get($scheduled_backup, 'database'))) { if (is_null(data_get($scheduled_backup, 'database'))) {
ray('database not found'); ray('database not found');
$scheduled_backup->delete(); $scheduled_backup->delete();
continue; continue;
} }
@ -165,6 +167,7 @@ private function check_scheduled_tasks($schedule)
if (! $application && ! $service) { if (! $application && ! $service) {
ray('application/service attached to scheduled task does not exist'); ray('application/service attached to scheduled task does not exist');
$scheduled_task->delete(); $scheduled_task->delete();
continue; continue;
} }
if ($application) { if ($application) {
@ -192,8 +195,8 @@ private function check_scheduled_tasks($schedule)
protected function commands(): void protected function commands(): void
{ {
$this->load(__DIR__ . '/Commands'); $this->load(__DIR__.'/Commands');
require base_path('routes/console.php'); require base_path('routes/console.php');
} }
} }

View File

@ -5,8 +5,7 @@
use App\Actions\Server\StartSentinel; use App\Actions\Server\StartSentinel;
use App\Actions\Server\StopSentinel; use App\Actions\Server\StopSentinel;
use App\Jobs\PullSentinelImageJob; use App\Jobs\PullSentinelImageJob;
use App\Models\Server;; use App\Models\Server;
use Livewire\Component; use Livewire\Component;
class Form extends Component class Form extends Component
@ -23,6 +22,8 @@ class Form extends Component
public bool $revalidate = false; public bool $revalidate = false;
public $timezones;
protected $listeners = ['serverInstalled', 'revalidate' => '$refresh']; protected $listeners = ['serverInstalled', 'revalidate' => '$refresh'];
protected $rules = [ protected $rules = [
@ -71,8 +72,6 @@ class Form extends Component
'server.settings.server_timezone' => 'Server Timezone', 'server.settings.server_timezone' => 'Server Timezone',
]; ];
public $timezones;
public function mount(Server $server) public function mount(Server $server)
{ {
$this->server = $server; $this->server = $server;
@ -174,7 +173,7 @@ public function checkLocalhostConnection()
$this->server->settings->save(); $this->server->settings->save();
$this->dispatch('proxyStatusUpdated'); $this->dispatch('proxyStatusUpdated');
} else { } else {
$this->dispatch('error', 'Server is not reachable.', 'Please validate your configuration and connection.<br><br>Check this <a target="_blank" class="underline" href="https://coolify.io/docs/knowledge-base/server/openssh">documentation</a> for further help. <br><br>Error: ' . $error); $this->dispatch('error', 'Server is not reachable.', 'Please validate your configuration and connection.<br><br>Check this <a target="_blank" class="underline" href="https://coolify.io/docs/knowledge-base/server/openssh">documentation</a> for further help. <br><br>Error: '.$error);
return; return;
} }
@ -214,22 +213,12 @@ public function submit()
} 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 === '') {
try { $this->server->settings->server_timezone = $newTimezone;
$timezoneUpdated = $this->updateServerTimezone($newTimezone); $this->server->settings->save();
if ($timezoneUpdated) {
$this->server->settings->server_timezone = $newTimezone;
$this->server->settings->save();
} else {
return;
}
} catch (\Exception $e) {
$this->dispatch('error', 'Failed to update server timezone: ' . $e->getMessage());
return;
} }
}
$this->server->settings->save(); $this->server->settings->save();
$this->server->save(); $this->server->save();
@ -239,89 +228,10 @@ public function submit()
} }
} }
public function updatedServerTimezone($value) public function updatedServerSettingsServerTimezone($value)
{ {
if (!is_string($value) || !in_array($value, timezone_identifiers_list())) {
$this->addError('server.settings.server_timezone', 'Invalid timezone.');
return;
}
$this->server->settings->server_timezone = $value; $this->server->settings->server_timezone = $value;
$this->updateServerTimezone($value); $this->server->settings->save();
} $this->dispatch('success', 'Server timezone updated.');
private function updateServerTimezone($desired_timezone)
{
try {
$commands = [
"if command -v timedatectl > /dev/null 2>&1 && pidof systemd > /dev/null; then",
" timedatectl set-timezone " . escapeshellarg($desired_timezone),
"elif [ -f /etc/timezone ]; then",
" echo " . escapeshellarg($desired_timezone) . " > /etc/timezone",
" rm -f /etc/localtime",
" ln -sf /usr/share/zoneinfo/" . escapeshellarg($desired_timezone) . " /etc/localtime",
"elif [ -f /etc/localtime ]; then",
" rm -f /etc/localtime",
" ln -sf /usr/share/zoneinfo/" . escapeshellarg($desired_timezone) . " /etc/localtime",
"else",
" echo 'Unable to set timezone'",
" exit 1",
"fi",
"if command -v dpkg-reconfigure > /dev/null 2>&1; then",
" dpkg-reconfigure -f noninteractive tzdata",
"elif command -v tzdata-update > /dev/null 2>&1; then",
" tzdata-update",
"elif [ -f /etc/sysconfig/clock ]; then",
" sed -i 's/^ZONE=.*/ZONE=\"" . $desired_timezone . "\"/' /etc/sysconfig/clock",
" source /etc/sysconfig/clock",
"fi",
"if command -v systemctl > /dev/null 2>&1 && pidof systemd > /dev/null; then",
" systemctl try-restart systemd-timesyncd.service || true",
"elif command -v service > /dev/null 2>&1; then",
" service ntpd restart || service ntp restart || true",
"fi",
"echo \"Timezone updated to: $desired_timezone\"",
"date"
];
instant_remote_process($commands, $this->server);
$verificationCommands = [
"readlink /etc/localtime | sed 's#/usr/share/zoneinfo/##'",
"date +'%Z %:z'"
];
$verificationResult = instant_remote_process($verificationCommands, $this->server, false);
$verificationLines = explode("\n", trim($verificationResult));
if (count($verificationLines) !== 2) {
$this->dispatch('error', 'Failed to verify timezone update. Unexpected server response.');
return false;
}
$actualTimezone = trim($verificationLines[0]);
[$abbreviation, $offset] = explode(' ', trim($verificationLines[1]));
$desiredTz = new \DateTimeZone($desired_timezone);
$desiredAbbr = (new \DateTime('now', $desiredTz))->format('T');
$desiredOffset = $this->formatOffset($desiredTz->getOffset(new \DateTime('now', $desiredTz)));
if ($actualTimezone === $desired_timezone && $abbreviation === $desiredAbbr && $offset === $desiredOffset) {
$this->server->settings->server_timezone = $desired_timezone;
$this->server->settings->save();
return true;
} else {
$this->dispatch('error', 'Failed to update server timezone. The server reported a different timezone than requested.');
return false;
}
} catch (\Exception $e) {
$this->dispatch('error', 'Failed to update server timezone: ' . $e->getMessage());
return false;
}
}
private function formatOffset($offsetSeconds)
{
$hours = abs($offsetSeconds) / 3600;
$minutes = (abs($offsetSeconds) % 3600) / 60;
return sprintf('%s%02d:%02d', $offsetSeconds >= 0 ? '+' : '-', $hours, $minutes);
} }
} }

View File

@ -170,8 +170,15 @@ public function checkManually()
} }
} }
public function updatedSettingsInstanceTimezone($value)
{
$this->settings->instance_timezone = $value;
$this->settings->save();
$this->dispatch('success', 'Instance timezone updated.');
}
public function render() public function render()
{ {
return view('livewire.settings.index'); return view('livewire.settings.index');
} }
} }

View File

@ -37,6 +37,30 @@ public function fqdn(): Attribute
); );
} }
public function updateCheckFrequency(): Attribute
{
return Attribute::make(
set: function ($value) {
return translate_cron_expression($value);
},
get: function ($value) {
return translate_cron_expression($value);
}
);
}
public function autoUpdateFrequency(): Attribute
{
return Attribute::make(
set: function ($value) {
return translate_cron_expression($value);
},
get: function ($value) {
return translate_cron_expression($value);
}
);
}
public static function get() public static function get()
{ {
return InstanceSettings::findOrFail(0); return InstanceSettings::findOrFail(0);

View File

@ -2,6 +2,7 @@
namespace App\Models; namespace App\Models;
use Illuminate\Database\Eloquent\Casts\Attribute;
use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\Model;
use OpenApi\Attributes as OA; use OpenApi\Attributes as OA;
@ -58,4 +59,18 @@ public function server()
{ {
return $this->belongsTo(Server::class); return $this->belongsTo(Server::class);
} }
}
public function dockerCleanupFrequency(): Attribute
{
return Attribute::make(
set: function ($value) {
ray($value);
return translate_cron_expression($value);
},
get: function ($value) {
return translate_cron_expression($value);
}
);
}
}

View File

@ -353,6 +353,14 @@ function isCloud(): bool
return ! config('coolify.self_hosted'); return ! config('coolify.self_hosted');
} }
function translate_cron_expression($expression_to_validate): string
{
if (isset(VALID_CRON_STRINGS[$expression_to_validate])) {
return VALID_CRON_STRINGS[$expression_to_validate];
}
return $expression_to_validate;
}
function validate_cron_expression($expression_to_validate): bool function validate_cron_expression($expression_to_validate): bool
{ {
$isValid = false; $isValid = false;

View File

@ -33,7 +33,6 @@ public function run(): void
ScheduledDatabaseBackupSeeder::class, ScheduledDatabaseBackupSeeder::class,
ScheduledDatabaseBackupExecutionSeeder::class, ScheduledDatabaseBackupExecutionSeeder::class,
OauthSettingSeeder::class, OauthSettingSeeder::class,
ServerTimezoneSeeder::class,
]); ]);
} }
} }

View File

@ -182,7 +182,5 @@ public function run(): void
$oauth_settings_seeder = new OauthSettingSeeder; $oauth_settings_seeder = new OauthSettingSeeder;
$oauth_settings_seeder->run(); $oauth_settings_seeder->run();
$server_timezone_seeder = new ServerTimezoneSeeder;
$server_timezone_seeder->run();
} }
} }

View File

@ -1,58 +0,0 @@
<?php
namespace Database\Seeders;
use App\Models\Server;
use Illuminate\Database\Seeder;
use Illuminate\Support\Facades\DB;
class ServerTimezoneSeeder extends Seeder
{
public function run(): void
{
$defaultTimezone = config('app.timezone');
Server::whereHas('settings', function ($query) {
$query->whereNull('server_timezone')->orWhere('server_timezone', '');
})->each(function ($server) use ($defaultTimezone) {
DB::transaction(function () use ($server, $defaultTimezone) {
$this->updateServerTimezone($server, $defaultTimezone);
});
});
}
private function updateServerTimezone($server, $desired_timezone)
{
$commands = [
"if command -v timedatectl > /dev/null 2>&1 && pidof systemd > /dev/null; then",
" timedatectl set-timezone " . escapeshellarg($desired_timezone),
"elif [ -f /etc/timezone ]; then",
" echo " . escapeshellarg($desired_timezone) . " > /etc/timezone",
" rm -f /etc/localtime",
" ln -sf /usr/share/zoneinfo/" . escapeshellarg($desired_timezone) . " /etc/localtime",
"elif [ -f /etc/localtime ]; then",
" rm -f /etc/localtime",
" ln -sf /usr/share/zoneinfo/" . escapeshellarg($desired_timezone) . " /etc/localtime",
"fi",
"if command -v dpkg-reconfigure > /dev/null 2>&1; then",
" dpkg-reconfigure -f noninteractive tzdata",
"elif command -v tzdata-update > /dev/null 2>&1; then",
" tzdata-update",
"elif [ -f /etc/sysconfig/clock ]; then",
" sed -i 's/^ZONE=.*/ZONE=\"" . $desired_timezone . "\"/' /etc/sysconfig/clock",
" source /etc/sysconfig/clock",
"fi",
"if command -v systemctl > /dev/null 2>&1 && pidof systemd > /dev/null; then",
" systemctl try-restart systemd-timesyncd.service || true",
"elif command -v service > /dev/null 2>&1; then",
" service ntpd restart || service ntp restart || true",
"fi"
];
instant_remote_process($commands, $server);
$server->settings->server_timezone = $desired_timezone;
$server->settings->save();
}
}

View File

@ -1,66 +1,66 @@
<div wire:init='refreshBackupExecutions'> <div wire:init='refreshBackupExecutions'>
@isset($backup) @isset($backup)
<div class="flex items-center gap-2"> <div class="flex items-center gap-2">
<h3 class="py-4">Executions</h3> <h3 class="py-4">Executions</h3>
<x-forms.button wire:click='cleanupFailed'>Cleanup Failed Backups</x-forms.button> <x-forms.button wire:click='cleanupFailed'>Cleanup Failed Backups</x-forms.button>
</div>
<div class="flex flex-col-reverse gap-4">
@forelse($executions as $execution)
<div wire:key="{{ data_get($execution, 'id') }}"
@class([
'flex flex-col border-l-4 transition-colors cursor-pointer p-4 rounded',
'bg-white hover:bg-gray-100 dark:bg-coolgray-100 dark:hover:bg-coolgray-200',
'text-black dark:text-white',
'border-green-500' => data_get($execution, 'status') === 'success',
'border-red-500' => data_get($execution, 'status') === 'failed',
'border-yellow-500' => data_get($execution, 'status') === 'running',
])>
@if (data_get($execution, 'status') === 'running')
<div class="absolute top-2 right-2">
<x-loading />
</div>
@endif
<div class="text-gray-700 dark:text-gray-300 font-semibold mb-1">Status: {{ data_get($execution, 'status') }}</div>
<div class="text-gray-600 dark:text-gray-400 text-sm">
Started At: {{ $this->formatDateInServerTimezone(data_get($execution, 'created_at')) }}
</div>
<div class="text-gray-600 dark:text-gray-400 text-sm">
Database: {{ data_get($execution, 'database_name', 'N/A') }}
</div>
<div class="text-gray-600 dark:text-gray-400 text-sm">
Size: {{ data_get($execution, 'size') }} B /
{{ round((int) data_get($execution, 'size') / 1024, 2) }} kB /
{{ round((int) data_get($execution, 'size') / 1024 / 1024, 3) }} MB
</div>
<div class="text-gray-600 dark:text-gray-400 text-sm">
Location: {{ data_get($execution, 'filename', 'N/A') }}
</div>
@if (data_get($execution, 'message'))
<div class="mt-2 p-2 bg-gray-100 dark:bg-coolgray-200 rounded">
<pre class="whitespace-pre-wrap text-sm">{{ data_get($execution, 'message') }}</pre>
</div>
@endif
<div class="flex gap-2 mt-4">
@if (data_get($execution, 'status') === 'success')
<x-forms.button class="dark:hover:bg-coolgray-400"
x-on:click="download_file('{{ data_get($execution, 'id') }}')">Download</x-forms.button>
@endif
<x-modal-confirmation isErrorButton action="deleteBackup({{ data_get($execution, 'id') }})">
<x-slot:button-title>
Delete
</x-slot:button-title>
This will delete this backup. It is not reversible.<br>Please think again.
</x-modal-confirmation>
</div>
</div> </div>
@empty <div class="flex flex-col-reverse gap-4">
<div class="p-4 bg-gray-100 dark:bg-coolgray-200 rounded">No executions found.</div> @forelse($executions as $execution)
@endforelse <div wire:key="{{ data_get($execution, 'id') }}" @class([
</div> 'flex flex-col border-l-2 transition-colors p-4 ',
<script> 'bg-white dark:bg-coolgray-100 ',
function download_file(executionId) { 'text-black dark:text-white',
window.open('/download/backup/' + executionId, '_blank'); 'border-green-500' => data_get($execution, 'status') === 'success',
} 'border-red-500' => data_get($execution, 'status') === 'failed',
</script> 'border-yellow-500' => data_get($execution, 'status') === 'running',
])>
@if (data_get($execution, 'status') === 'running')
<div class="absolute top-2 right-2">
<x-loading />
</div>
@endif
<div class="text-gray-700 dark:text-gray-300 font-semibold mb-1">Status:
{{ data_get($execution, 'status') }}</div>
<div class="text-gray-600 dark:text-gray-400 text-sm">
Started At: {{ $this->formatDateInServerTimezone(data_get($execution, 'created_at')) }}
</div>
<div class="text-gray-600 dark:text-gray-400 text-sm">
Database: {{ data_get($execution, 'database_name', 'N/A') }}
</div>
<div class="text-gray-600 dark:text-gray-400 text-sm">
Size: {{ data_get($execution, 'size') }} B /
{{ round((int) data_get($execution, 'size') / 1024, 2) }} kB /
{{ round((int) data_get($execution, 'size') / 1024 / 1024, 3) }} MB
</div>
<div class="text-gray-600 dark:text-gray-400 text-sm">
Location: {{ data_get($execution, 'filename', 'N/A') }}
</div>
@if (data_get($execution, 'message'))
<div class="mt-2 p-2 bg-gray-100 dark:bg-coolgray-200 rounded">
<pre class="whitespace-pre-wrap text-sm">{{ data_get($execution, 'message') }}</pre>
</div>
@endif
<div class="flex gap-2 mt-4">
@if (data_get($execution, 'status') === 'success')
<x-forms.button class="dark:hover:bg-coolgray-400"
x-on:click="download_file('{{ data_get($execution, 'id') }}')">Download</x-forms.button>
@endif
<x-modal-confirmation isErrorButton action="deleteBackup({{ data_get($execution, 'id') }})">
<x-slot:button-title>
Delete
</x-slot:button-title>
This will delete this backup. It is not reversible.<br>Please think again.
</x-modal-confirmation>
</div>
</div>
@empty
<div class="p-4 bg-gray-100 dark:bg-coolgray-200 rounded">No executions found.</div>
@endforelse
</div>
<script>
function download_file(executionId) {
window.open('/download/backup/' + executionId, '_blank');
}
</script>
@endisset @endisset
</div> </div>

View File

@ -3,104 +3,69 @@
<div class="flex gap-2"> <div class="flex gap-2">
<h2>General</h2> <h2>General</h2>
@if ($server->id === 0) @if ($server->id === 0)
<x-modal-confirmation buttonTitle="Save" title="Change Localhost" action="submit"> <x-modal-confirmation buttonTitle="Save" title="Change Localhost" action="submit">
You could lose a lot of functionalities if you change the server details of the server where Coolify You could lose a lot of functionalities if you change the server details of the server where Coolify
is is
running on.<br>Please think again. running on.<br>Please think again.
</x-modal-confirmation> </x-modal-confirmation>
@else @else
<x-forms.button type="submit">Save</x-forms.button> <x-forms.button type="submit">Save</x-forms.button>
@if ($server->isFunctional()) @if ($server->isFunctional())
<x-slide-over closeWithX fullScreen> <x-slide-over closeWithX fullScreen>
<x-slot:title>Validate & configure</x-slot:title> <x-slot:title>Validate & configure</x-slot:title>
<x-slot:content> <x-slot:content>
<livewire:server.validate-and-install :server="$server" ask /> <livewire:server.validate-and-install :server="$server" ask />
</x-slot:content> </x-slot:content>
<x-forms.button @click="slideOverOpen=true" wire:click.prevent='validateServer' isHighlighted> <x-forms.button @click="slideOverOpen=true" wire:click.prevent='validateServer' isHighlighted>
Revalidate server Revalidate server
</x-forms.button> </x-forms.button>
</x-slide-over> </x-slide-over>
@endif @endif
@endif @endif
</div> </div>
@if ($server->isFunctional()) @if ($server->isFunctional())
Server is reachable and validated. Server is reachable and validated.
@else @else
You can't use this server until it is validated. You can't use this server until it is validated.
@endif @endif
@if ((!$server->settings->is_reachable || !$server->settings->is_usable) && $server->id !== 0) @if ((!$server->settings->is_reachable || !$server->settings->is_usable) && $server->id !== 0)
<x-slide-over closeWithX fullScreen> <x-slide-over closeWithX fullScreen>
<x-slot:title>Validate & configure</x-slot:title> <x-slot:title>Validate & configure</x-slot:title>
<x-slot:content> <x-slot:content>
<livewire:server.validate-and-install :server="$server" /> <livewire:server.validate-and-install :server="$server" />
</x-slot:content> </x-slot:content>
<x-forms.button @click="slideOverOpen=true" <x-forms.button @click="slideOverOpen=true"
class="w-full mt-8 mb-4 font-bold box-without-bg bg-coollabs hover:bg-coollabs-100" class="w-full mt-8 mb-4 font-bold box-without-bg bg-coollabs hover:bg-coollabs-100"
wire:click.prevent='validateServer' isHighlighted> wire:click.prevent='validateServer' isHighlighted>
Validate Server & Install Docker Engine Validate Server & Install Docker Engine
</x-forms.button> </x-forms.button>
</x-slide-over> </x-slide-over>
@if ($server->validation_logs) @if ($server->validation_logs)
<h4>Previous Validation Logs</h4> <h4>Previous Validation Logs</h4>
<div class="pb-8"> <div class="pb-8">
{!! $server->validation_logs !!} {!! $server->validation_logs !!}
</div> </div>
@endif @endif
@endif @endif
@if ((!$server->settings->is_reachable || !$server->settings->is_usable) && $server->id === 0) @if ((!$server->settings->is_reachable || !$server->settings->is_usable) && $server->id === 0)
<x-forms.button class="mt-8 mb-4 font-bold box-without-bg bg-coollabs hover:bg-coollabs-100" <x-forms.button class="mt-8 mb-4 font-bold box-without-bg bg-coollabs hover:bg-coollabs-100"
wire:click.prevent='checkLocalhostConnection' isHighlighted> wire:click.prevent='checkLocalhostConnection' isHighlighted>
Validate Server Validate Server
</x-forms.button> </x-forms.button>
@endif @endif
@if ($server->isForceDisabled() && isCloud()) @if ($server->isForceDisabled() && isCloud())
<div class="pt-4 font-bold text-red-500">The system has disabled the server because you have exceeded the <div class="pt-4 font-bold text-red-500">The system has disabled the server because you have exceeded the
number of servers for which you have paid.</div> number of servers for which you have paid.</div>
@endif @endif
<div class="flex flex-col gap-2 pt-4"> <div class="flex flex-col gap-2 pt-4">
<div class="flex flex-col w-full gap-2 lg:flex-row"> <div class="flex flex-col w-full gap-2 lg:flex-row">
<x-forms.input id="server.name" label="Name" required /> <x-forms.input id="server.name" label="Name" required />
<x-forms.input id="server.description" label="Description" /> <x-forms.input id="server.description" label="Description" />
@if (!$server->settings->is_swarm_worker && !$server->settings->is_build_server) @if (!$server->settings->is_swarm_worker && !$server->settings->is_build_server)
<x-forms.input placeholder="https://example.com" id="wildcard_domain" label="Wildcard Domain" <x-forms.input placeholder="https://example.com" id="wildcard_domain" label="Wildcard Domain"
helper='A wildcard domain allows you to receive a randomly generated domain for your new applications. <br><br>For instance, if you set "https://example.com" as your wildcard domain, your applications will receive domains like "https://randomId.example.com".' /> helper='A wildcard domain allows you to receive a randomly generated domain for your new applications. <br><br>For instance, if you set "https://example.com" as your wildcard domain, your applications will receive domains like "https://randomId.example.com".' />
@endif @endif
<div class="w-full" x-data="{
open: false,
search: '{{ $server->settings->server_timezone ?: '' }}',
timezones: @js($timezones),
placeholder: '{{ $server->settings->server_timezone ? 'Search timezone...' : 'Select Server Timezone' }}',
init() {
this.$watch('search', value => {
if (value === '') {
this.open = true;
}
})
}
}">
<div class="flex items-center">
<label for="server.settings.server_timezone" class="dark:text-white">Server Timezone</label>
<x-helper class="ml-2" helper="Current server's timezone (This setting changes your server's timezone in /etc/timezone, /etc/localtime, etc.). This is used for backups, cron jobs, etc." />
</div>
<div class="relative">
<input
x-model="search"
@focus="open = true"
@click.away="open = false"
@input="open = true"
class="w-full input"
:placeholder="placeholder"
wire:model.debounce.300ms="server.settings.server_timezone">
<div x-show="open" class="absolute z-50 w-full mt-1 bg-white dark:bg-coolgray-100 border border-gray-300 dark:border-white rounded-md shadow-lg max-h-60 overflow-auto">
<template x-for="timezone in timezones.filter(tz => tz.toLowerCase().includes(search.toLowerCase()))" :key="timezone">
<div
@click="search = timezone; open = false; $wire.set('server.settings.server_timezone', timezone)"
class="px-4 py-2 cursor-pointer hover:bg-gray-100 dark:hover:bg-gray-800 text-gray-800 dark:text-gray-200"
x-text="timezone"></div>
</template>
</div>
</div>
</div>
</div> </div>
<div class="flex flex-col w-full gap-2 lg:flex-row"> <div class="flex flex-col w-full gap-2 lg:flex-row">
<x-forms.input type="password" id="server.ip" label="IP Address/Domain" <x-forms.input type="password" id="server.ip" label="IP Address/Domain"
@ -110,66 +75,108 @@ class="px-4 py-2 cursor-pointer hover:bg-gray-100 dark:hover:bg-gray-800 text-gr
<x-forms.input type="number" id="server.port" label="Port" required /> <x-forms.input type="number" id="server.port" label="Port" required />
</div> </div>
</div> </div>
<div class="w-full" x-data="{
open: false,
search: '{{ $server->settings->server_timezone ?: '' }}',
timezones: @js($timezones),
placeholder: '{{ $server->settings->server_timezone ? 'Search timezone...' : 'Select Server Timezone' }}',
init() {
this.$watch('search', value => {
if (value === '') {
this.open = true;
}
})
}
}">
<div class="flex items-center mb-1">
<label for="server.settings.server_timezone">Server
Timezone</label>
<x-helper class="ml-2" helper="Server's timezone. This is used for backups, cron jobs, etc." />
</div>
<div class="relative">
<div class="inline-flex items-center relative w-64">
<input 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"
@focus="open = true" @click.away="open = false" @input="open = true" class="w-full input "
:placeholder="placeholder" wire:model.debounce.300ms="server.settings.server_timezone">
<svg class="absolute right-0 w-4 h-4 mr-2" xmlns="http://www.w3.org/2000/svg" fill="none"
viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" @click="open = true">
<path stroke-linecap="round" stroke-linejoin="round"
d="M8.25 15L12 18.75 15.75 15m-7.5-6L12 5.25 15.75 9" />
</svg>
</div>
<div x-show="open"
class="absolute z-50 w-64 mt-1 bg-white dark:bg-coolgray-100 border dark:border-coolgray-200 rounded-md shadow-lg max-h-60 overflow-auto scrollbar overflow-x-hidden">
<template
x-for="timezone in timezones.filter(tz => tz.toLowerCase().includes(search.toLowerCase()))"
:key="timezone">
<div @click="search = timezone; open = false; $wire.set('server.settings.server_timezone', timezone)"
class="px-4 py-2 cursor-pointer hover:bg-gray-100 dark:hover:bg-coolgray-300 text-gray-800 dark:text-gray-200"
x-text="timezone"></div>
</template>
</div>
</div>
</div>
<div class="w-64"> <div class="w-64">
@if ($server->isFunctional()) @if ($server->isFunctional())
@if (!$server->isLocalhost()) @if (!$server->isLocalhost())
<x-forms.checkbox instantSave id="server.settings.is_build_server" <x-forms.checkbox instantSave id="server.settings.is_build_server"
label="Use it as a build server?" /> label="Use it as a build server?" />
<div class="flex items-center gap-1 pt-6"> <div class="flex items-center gap-1 pt-6">
<h3 class="">Cloudflare Tunnels <h3 class="">Cloudflare Tunnels
</h3> </h3>
<x-helper class="inline-flex" <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><span class='dark:text-warning'>Coolify does not install or set up Cloudflare (cloudflared) on your server.</span>" /> helper="If you are using Cloudflare Tunnels, enable this. It will proxy all SSH requests to your server through Cloudflare.<br><span class='dark:text-warning'>Coolify does not install or set up Cloudflare (cloudflared) on your server.</span>" />
</div> </div>
@if ($server->settings->is_cloudflare_tunnel) @if ($server->settings->is_cloudflare_tunnel)
<x-forms.checkbox instantSave id="server.settings.is_cloudflare_tunnel" label="Enabled" /> <x-forms.checkbox instantSave id="server.settings.is_cloudflare_tunnel" label="Enabled" />
@else @else
<x-modal-input buttonTitle="Configure" title="Cloudflare Tunnels"> <x-modal-input buttonTitle="Configure" title="Cloudflare Tunnels">
<livewire:server.configure-cloudflare-tunnels :server_id="$server->id" /> <livewire:server.configure-cloudflare-tunnels :server_id="$server->id" />
</x-modal-input> </x-modal-input>
@endif @endif
@if (!$server->isBuildServer()) @if (!$server->isBuildServer())
<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) @if ($server->settings->is_swarm_worker)
<x-forms.checkbox disabled instantSave type="checkbox" <x-forms.checkbox disabled instantSave type="checkbox"
id="server.settings.is_swarm_manager" id="server.settings.is_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>." 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 Manager?" /> label="Is it a Swarm Manager?" />
@else @else
<x-forms.checkbox instantSave type="checkbox" id="server.settings.is_swarm_manager" <x-forms.checkbox instantSave type="checkbox" id="server.settings.is_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>." 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 Manager?" /> label="Is it a Swarm Manager?" />
@endif @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
<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>."
label="Is it a Swarm Worker?" />
@endif
@endif
@endif
@else @else
<x-forms.checkbox instantSave type="checkbox" id="server.settings.is_swarm_worker" <div class="flex items-center gap-1 pt-6">
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>." <h3 class="">Cloudflare Tunnels
label="Is it a Swarm Worker?" /> </h3>
@endif <x-helper class="inline-flex"
@endif helper="If you are using Cloudflare Tunnels, enable this. It will proxy all SSH requests to your server through Cloudflare.<br><span class='dark:text-warning'>Coolify does not install or set up Cloudflare (cloudflared) on your server.</span>" />
@endif </div>
@else @if ($server->settings->is_cloudflare_tunnel)
<div class="flex items-center gap-1 pt-6"> <x-forms.checkbox instantSave id="server.settings.is_cloudflare_tunnel" label="Enabled" />
<h3 class="">Cloudflare Tunnels @else
</h3> <x-modal-input buttonTitle="Configure" title="Cloudflare Tunnels">
<x-helper class="inline-flex" <livewire:server.configure-cloudflare-tunnels :server_id="$server->id" />
helper="If you are using Cloudflare Tunnels, enable this. It will proxy all SSH requests to your server through Cloudflare.<br><span class='dark:text-warning'>Coolify does not install or set up Cloudflare (cloudflared) on your server.</span>" /> </x-modal-input>
</div> @endif
@if ($server->settings->is_cloudflare_tunnel)
<x-forms.checkbox instantSave id="server.settings.is_cloudflare_tunnel" label="Enabled" />
@else
<x-modal-input buttonTitle="Configure" title="Cloudflare Tunnels">
<livewire:server.configure-cloudflare-tunnels :server_id="$server->id" />
</x-modal-input>
@endif
@endif @endif
</div> </div>
@ -194,23 +201,20 @@ class="px-4 py-2 cursor-pointer hover:bg-gray-100 dark:hover:bg-gray-800 text-gr
helper="The Docker cleanup tasks will run when the disk usage exceeds this threshold." /> helper="The Docker cleanup tasks will run when the disk usage exceeds this threshold." />
@endif @endif
</div> </div>
@else @else
<x-forms.input id="cleanup_after_percentage" label="Disk cleanup threshold (%)" required <x-forms.input id="cleanup_after_percentage" label="Disk cleanup threshold (%)" required
helper="The disk cleanup task will run when the disk usage exceeds this threshold." /> helper="The disk cleanup task will run when the disk usage exceeds this threshold." />
<div class="w-64"> <div class="w-64">
<x-forms.checkbox <x-forms.checkbox helper="This will cleanup build caches / unused images / etc every 10 minutes."
helper="This will cleanup build caches / unused images / etc every 10 minutes."
instantSave id="server.settings.is_force_cleanup_enabled" instantSave id="server.settings.is_force_cleanup_enabled"
label="Force Cleanup Docker Engine" /> label="Force Cleanup Docker Engine" />
</div> </div>
@endif @endif
</div> <div class="flex flex-wrap gap-2 sm:flex-nowrap">
<div class="flex flex-wrap gap-2 sm:flex-nowrap"> <x-forms.input id="server.settings.concurrent_builds" label="Number of concurrent builds" required
<x-forms.input id="server.settings.concurrent_builds" label="Number of concurrent builds" required helper="You can specify the number of simultaneous build processes/deployments that should run concurrently." />
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
<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." />
helper="You can define the maximum duration for a deployment to run before timing it out." />
</div>
</div> </div>
<div class="flex items-center gap-2 pt-4 pb-2"> <div class="flex items-center gap-2 pt-4 pb-2">
<h3>Sentinel</h3> <h3>Sentinel</h3>
@ -233,6 +237,5 @@ class="px-4 py-2 cursor-pointer hover:bg-gray-100 dark:hover:bg-gray-800 text-gr
helper="How many days should the metrics data should be reserved." /> helper="How many days should the metrics data should be reserved." />
</div> </div>
</div> --}} </div> --}}
@endif
</form> </form>
</div> </div>

View File

@ -13,41 +13,60 @@
<div>General configuration for your Coolify instance.</div> <div>General configuration for your Coolify instance.</div>
<div class="flex flex-col gap-2"> <div class="flex flex-col gap-2">
<h4 class="pt-6">Instance Settings</h4>
<div class="flex flex-wrap items-end gap-2"> <div class="flex flex-wrap items-end gap-2">
<h4 class="pt-6">Instance Settings</h4> <div class="flex gap-2 md:flex-row flex-col w-full">
<x-forms.input id="settings.fqdn" label="Instance's Domain" helper="Enter the full domain name (FQDN) of the instance, including 'https://' if you want to secure the dashboard with HTTPS. Setting this will make the dashboard accessible via this domain, secured by HTTPS, instead of just the IP address." placeholder="https://coolify.yourdomain.com" /> <x-forms.input id="settings.fqdn" label="Instance's Domain"
<x-forms.input id="settings.instance_name" label="Instance's Name" placeholder="Coolify" /> helper="Enter the full domain name (FQDN) of the instance, including 'https://' if you want to secure the dashboard with HTTPS. Setting this will make the dashboard accessible via this domain, secured by HTTPS, instead of just the IP address."
<div class="w-full" x-data="{ placeholder="https://coolify.yourdomain.com" />
open: false, <x-forms.input id="settings.instance_name" label="Instance's Name" placeholder="Coolify" />
search: '{{ $settings->instance_timezone }}', <div class="w-full" x-data="{
timezones: @js($timezones), open: false,
placeholder: 'Select Instance Timezone' search: '{{ $settings->instance_timezone ?: '' }}',
}"> timezones: @js($timezones),
<label for="settings.instance_timezone" class="dark:text-white flex items-center"> placeholder: '{{ $settings->instance_timezone ? 'Search timezone...' : 'Select Server Timezone' }}',
Instance Timezone init() {
<x-helper class="ml-2" helper="Timezone for the Coolify instance (this does NOT change your server's timezone in /etc/timezone, /etc/localtime, etc.). This is used for the update check and automatic update frequency." /> this.$watch('search', value => {
</label> if (value === '') {
<div class="relative"> this.open = true;
<input }
x-model="search" })
@focus="open = true" }
@click.away="open = false" }">
@input="open = true" <div class="flex items-center mb-1">
class="w-full input" <label for="settings.instance_timezone">Instance
:placeholder="placeholder" Timezone</label>
wire:model.debounce.300ms="settings.instance_timezone" <x-helper class="ml-2"
> helper="Timezone for the Coolify instance. This is used for the update check and automatic update frequency." />
<div x-show="open" class="absolute z-50 w-full mt-1 bg-white dark:bg-coolgray-100 border border-gray-300 dark:border-white rounded-md shadow-lg max-h-60 overflow-auto"> </div>
<template x-for="timezone in timezones.filter(tz => tz.toLowerCase().includes(search.toLowerCase()))" :key="timezone"> <div class="relative">
<div <div class="inline-flex items-center relative w-full">
@click="search = timezone; open = false; $wire.set('settings.instance_timezone', timezone)" <input wire:dirty.class.remove='dark:focus:ring-coolgray-300 dark:ring-coolgray-300'
class="px-4 py-2 cursor-pointer hover:bg-gray-100 dark:hover:bg-gray-800 text-gray-800 dark:text-gray-200" wire:dirty.class="dark:focus:ring-warning dark:ring-warning" x-model="search"
x-text="timezone" @focus="open = true" @click.away="open = false" @input="open = true"
></div> class="w-full input " :placeholder="placeholder"
</template> wire:model.debounce.300ms="settings.instance_timezone">
<svg class="absolute right-0 w-4 h-4 mr-2" xmlns="http://www.w3.org/2000/svg"
fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor"
@click="open = true">
<path stroke-linecap="round" stroke-linejoin="round"
d="M8.25 15L12 18.75 15.75 15m-7.5-6L12 5.25 15.75 9" />
</svg>
</div>
<div x-show="open"
class="absolute z-50 w-full mt-1 bg-white dark:bg-coolgray-100 border dark:border-coolgray-200 rounded-md shadow-lg max-h-60 overflow-auto scrollbar overflow-x-hidden">
<template
x-for="timezone in timezones.filter(tz => tz.toLowerCase().includes(search.toLowerCase()))"
:key="timezone">
<div @click="search = timezone; open = false; $wire.set('settings.instance_timezone', timezone)"
class="px-4 py-2 cursor-pointer hover:bg-gray-100 dark:hover:bg-coolgray-300 text-gray-800 dark:text-gray-200"
x-text="timezone"></div>
</template>
</div>
</div> </div>
</div> </div>
</div> </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" />
@ -102,4 +121,4 @@ class="px-4 py-2 cursor-pointer hover:bg-gray-100 dark:hover:bg-gray-800 text-gr
</div> </div>
</form> </form>
</div> </div>