@@ -5,13 +5,13 @@ namespace App\Actions\Server;
|
|||||||
use App\Models\Server;
|
use App\Models\Server;
|
||||||
use Lorisleiva\Actions\Concerns\AsAction;
|
use Lorisleiva\Actions\Concerns\AsAction;
|
||||||
|
|
||||||
class RemoveServer
|
class DeleteServer
|
||||||
{
|
{
|
||||||
use AsAction;
|
use AsAction;
|
||||||
|
|
||||||
public function handle(Server $server)
|
public function handle(Server $server)
|
||||||
{
|
{
|
||||||
StopSentinel::run($server);
|
StopSentinel::run($server);
|
||||||
$server->delete();
|
$server->forceDelete();
|
||||||
}
|
}
|
||||||
}
|
}
|
@@ -34,9 +34,9 @@ class StartSentinel
|
|||||||
'COLLECTOR_RETENTION_PERIOD_DAYS' => $metrics_history,
|
'COLLECTOR_RETENTION_PERIOD_DAYS' => $metrics_history,
|
||||||
];
|
];
|
||||||
if (isDev()) {
|
if (isDev()) {
|
||||||
data_set($environments, 'DEBUG', 'true');
|
// data_set($environments, 'DEBUG', 'true');
|
||||||
$mount_dir = '/var/lib/docker/volumes/coolify_dev_coolify_data/_data/sentinel';
|
$mount_dir = '/var/lib/docker/volumes/coolify_dev_coolify_data/_data/sentinel';
|
||||||
$image = 'sentinel';
|
// $image = 'sentinel';
|
||||||
}
|
}
|
||||||
$docker_environments = '-e "' . implode('" -e "', array_map(fn($key, $value) => "$key=$value", array_keys($environments), $environments)) . '"';
|
$docker_environments = '-e "' . implode('" -e "', array_map(fn($key, $value) => "$key=$value", array_keys($environments), $environments)) . '"';
|
||||||
|
|
||||||
|
@@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
namespace App\Http\Controllers\Api;
|
namespace App\Http\Controllers\Api;
|
||||||
|
|
||||||
use App\Actions\Server\RemoveServer;
|
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,7 +726,8 @@ class ServersController extends Controller
|
|||||||
if ($server->definedResources()->count() > 0) {
|
if ($server->definedResources()->count() > 0) {
|
||||||
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);
|
||||||
}
|
}
|
||||||
RemoveServer::dispatch($server);
|
$server->delete();
|
||||||
|
DeleteServer::dispatch($server);
|
||||||
|
|
||||||
return response()->json(['message' => 'Server deleted.']);
|
return response()->json(['message' => 'Server deleted.']);
|
||||||
}
|
}
|
||||||
|
@@ -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;
|
||||||
}
|
}
|
||||||
|
61
app/Livewire/Server/Advanced.php
Normal file
61
app/Livewire/Server/Advanced.php
Normal file
@@ -0,0 +1,61 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Livewire\Server;
|
||||||
|
|
||||||
|
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 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');
|
||||||
|
}
|
||||||
|
}
|
45
app/Livewire/Server/CloudflareTunnels.php
Normal file
45
app/Livewire/Server/CloudflareTunnels.php
Normal file
@@ -0,0 +1,45 @@
|
|||||||
|
<?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,7 +2,7 @@
|
|||||||
|
|
||||||
namespace App\Livewire\Server;
|
namespace App\Livewire\Server;
|
||||||
|
|
||||||
use App\Actions\Server\RemoveServer;
|
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,8 +28,8 @@ class Delete extends Component
|
|||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
RemoveServer::run($this->server);
|
$this->server->delete();
|
||||||
|
DeleteServer::dispatch($this->server);
|
||||||
return redirect()->route('server.index');
|
return redirect()->route('server.index');
|
||||||
} catch (\Throwable $e) {
|
} catch (\Throwable $e) {
|
||||||
return handleError($e, $this);
|
return handleError($e, $this);
|
||||||
|
@@ -7,7 +7,6 @@ use App\Actions\Server\StopSentinel;
|
|||||||
use App\Jobs\DockerCleanupJob;
|
use App\Jobs\DockerCleanupJob;
|
||||||
use App\Jobs\PullSentinelImageJob;
|
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,27 +46,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',
|
||||||
'server.settings.sentinel_push_interval_seconds' => 'required|integer|min:10',
|
'server.settings.sentinel_push_interval_seconds' => 'required|integer|min:10',
|
||||||
'wildcard_domain' => 'nullable|url',
|
|
||||||
'server.settings.sentinel_custom_url' => 'nullable|url',
|
'server.settings.sentinel_custom_url' => 'nullable|url',
|
||||||
'server.settings.is_sentinel_enabled' => 'required|boolean',
|
'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 = [
|
||||||
@@ -76,23 +67,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.sentinel_push_interval_seconds' => 'Push Interval',
|
'server.settings.sentinel_push_interval_seconds' => 'Push Interval',
|
||||||
'server.settings.is_sentinel_enabled' => 'Server API',
|
'server.settings.is_sentinel_enabled' => 'Server API',
|
||||||
'server.settings.sentinel_custom_url' => 'Sentinel URL',
|
'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)
|
||||||
@@ -100,13 +86,10 @@ 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 checkSyncStatus(){
|
public function checkSyncStatus()
|
||||||
|
{
|
||||||
$this->server->refresh();
|
$this->server->refresh();
|
||||||
$this->server->settings->refresh();
|
$this->server->settings->refresh();
|
||||||
}
|
}
|
||||||
@@ -114,9 +97,10 @@ class Form extends Component
|
|||||||
public function regenerateSentinelToken()
|
public function regenerateSentinelToken()
|
||||||
{
|
{
|
||||||
try {
|
try {
|
||||||
$this->server->generateSentinelToken();
|
$this->server->settings->generateSentinelToken();
|
||||||
$this->server->settings->refresh();
|
$this->server->settings->refresh();
|
||||||
$this->dispatch('success', 'Sentinel token regenerated. Please restart your Sentinel.');
|
$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);
|
||||||
}
|
}
|
||||||
@@ -152,25 +136,35 @@ class Form extends Component
|
|||||||
$this->dispatch('proxyStatusUpdated');
|
$this->dispatch('proxyStatusUpdated');
|
||||||
}
|
}
|
||||||
|
|
||||||
public function updatedServerSettingsIsSentinelEnabled($value){
|
public function updatedServerSettingsIsSentinelEnabled($value)
|
||||||
if($value === false){
|
{
|
||||||
|
$this->validate();
|
||||||
|
$this->validate([
|
||||||
|
'server.settings.sentinel_custom_url' => 'required|url',
|
||||||
|
]);
|
||||||
|
if ($value === false) {
|
||||||
StopSentinel::dispatch($this->server);
|
StopSentinel::dispatch($this->server);
|
||||||
$this->server->settings->is_metrics_enabled = false;
|
$this->server->settings->is_metrics_enabled = false;
|
||||||
$this->server->settings->save();
|
$this->server->settings->save();
|
||||||
$this->server->sentinelHeartbeat(isReset: true);
|
$this->server->sentinelHeartbeat(isReset: true);
|
||||||
} else {
|
} else {
|
||||||
StartSentinel::run($this->server);
|
try {
|
||||||
|
StartSentinel::run($this->server);
|
||||||
|
} catch (\Throwable $e) {
|
||||||
|
return handleError($e, $this);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public function updatedServerSettingsIsMetricsEnabled(){
|
public function updatedServerSettingsIsMetricsEnabled()
|
||||||
|
{
|
||||||
$this->restartSentinel();
|
$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);
|
||||||
|
|
||||||
@@ -179,6 +173,14 @@ class Form extends Component
|
|||||||
$this->dispatch('success', 'Server updated.');
|
$this->dispatch('success', 'Server updated.');
|
||||||
$this->dispatch('refreshServerShow');
|
$this->dispatch('refreshServerShow');
|
||||||
|
|
||||||
|
// if ($this->server->isSentinelEnabled()) {
|
||||||
|
// StartSentinel::run($this->server);
|
||||||
|
// } else {
|
||||||
|
// StopSentinel::run($this->server);
|
||||||
|
// $this->server->settings->is_metrics_enabled = false;
|
||||||
|
// $this->server->settings->save();
|
||||||
|
// $this->server->sentinelHeartbeat(isReset: true);
|
||||||
|
// }
|
||||||
// if ($this->server->isSentinelEnabled()) {
|
// if ($this->server->isSentinelEnabled()) {
|
||||||
// PullSentinelImageJob::dispatchSync($this->server);
|
// PullSentinelImageJob::dispatchSync($this->server);
|
||||||
// ray('Sentinel is enabled');
|
// ray('Sentinel is enabled');
|
||||||
@@ -196,16 +198,24 @@ class Form extends Component
|
|||||||
// $this->checkPortForServerApi();
|
// $this->checkPortForServerApi();
|
||||||
|
|
||||||
} catch (\Throwable $e) {
|
} catch (\Throwable $e) {
|
||||||
|
$this->server->settings->refresh();
|
||||||
|
|
||||||
return handleError($e, $this);
|
return handleError($e, $this);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public function restartSentinel()
|
public function restartSentinel($notification = true)
|
||||||
{
|
{
|
||||||
try {
|
try {
|
||||||
|
$this->validate();
|
||||||
|
$this->validate([
|
||||||
|
'server.settings.sentinel_custom_url' => 'required|url',
|
||||||
|
]);
|
||||||
$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 started.');
|
if ($notification) {
|
||||||
|
$this->dispatch('success', 'Sentinel started.');
|
||||||
|
}
|
||||||
} catch (\Throwable $e) {
|
} catch (\Throwable $e) {
|
||||||
return handleError($e, $this);
|
return handleError($e, $this);
|
||||||
}
|
}
|
||||||
|
@@ -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');
|
|
||||||
}
|
|
||||||
}
|
|
@@ -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);
|
||||||
}
|
}
|
||||||
|
@@ -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,16 @@ 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();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@@ -7,6 +7,7 @@ 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\Carbon;
|
||||||
use Illuminate\Support\Collection;
|
use Illuminate\Support\Collection;
|
||||||
use Illuminate\Support\Facades\DB;
|
use Illuminate\Support\Facades\DB;
|
||||||
@@ -17,7 +18,6 @@ use OpenApi\Attributes as OA;
|
|||||||
use Spatie\SchemalessAttributes\Casts\SchemalessAttributes;
|
use Spatie\SchemalessAttributes\Casts\SchemalessAttributes;
|
||||||
use Spatie\SchemalessAttributes\SchemalessAttributesTrait;
|
use Spatie\SchemalessAttributes\SchemalessAttributesTrait;
|
||||||
use Spatie\Url\Url;
|
use Spatie\Url\Url;
|
||||||
use Illuminate\Support\Str;
|
|
||||||
use Symfony\Component\Yaml\Yaml;
|
use Symfony\Component\Yaml\Yaml;
|
||||||
|
|
||||||
#[OA\Schema(
|
#[OA\Schema(
|
||||||
@@ -45,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;
|
||||||
|
|
||||||
@@ -97,7 +97,8 @@ class Server extends BaseModel
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
static::deleting(function ($server) {
|
|
||||||
|
static::forceDeleting(function ($server) {
|
||||||
$server->destinations()->each(function ($destination) {
|
$server->destinations()->each(function ($destination) {
|
||||||
$destination->delete();
|
$destination->delete();
|
||||||
});
|
});
|
||||||
@@ -527,34 +528,6 @@ $schema://$host {
|
|||||||
Storage::disk('ssh-mux')->delete($this->muxFilename());
|
Storage::disk('ssh-mux')->delete($this->muxFilename());
|
||||||
}
|
}
|
||||||
|
|
||||||
public function generateSentinelUrl() {
|
|
||||||
if ($this->isLocalhost()) {
|
|
||||||
return 'http://host.docker.internal:8000';
|
|
||||||
}
|
|
||||||
$settings = InstanceSettings::get();
|
|
||||||
if ($settings->fqdn) {
|
|
||||||
return $settings->fqdn;
|
|
||||||
}
|
|
||||||
if ($settings->ipv4) {
|
|
||||||
return $settings->ipv4 . ':8000';
|
|
||||||
}
|
|
||||||
if ($settings->ipv6) {
|
|
||||||
return $settings->ipv6 . ':8000';
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
public function generateSentinelToken()
|
|
||||||
{
|
|
||||||
$data = [
|
|
||||||
'server_uuid' => $this->uuid,
|
|
||||||
];
|
|
||||||
$token = json_encode($data);
|
|
||||||
$encrypted = encrypt($token);
|
|
||||||
$this->settings->sentinel_token = $encrypted;
|
|
||||||
$this->settings->save();
|
|
||||||
|
|
||||||
return $encrypted;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function sentinelHeartbeat(bool $isReset = false)
|
public function sentinelHeartbeat(bool $isReset = false)
|
||||||
{
|
{
|
||||||
@@ -568,7 +541,7 @@ $schema://$host {
|
|||||||
|
|
||||||
public function isSentinelEnabled()
|
public function isSentinelEnabled()
|
||||||
{
|
{
|
||||||
return $this->isMetricsEnabled() || $this->isServerApiEnabled() || !$this->isBuildServer();
|
return ($this->isMetricsEnabled() || $this->isServerApiEnabled()) && !$this->isBuildServer();
|
||||||
}
|
}
|
||||||
|
|
||||||
public function isMetricsEnabled()
|
public function isMetricsEnabled()
|
||||||
|
@@ -59,10 +59,59 @@ class ServerSetting extends Model
|
|||||||
protected static function booted()
|
protected static function booted()
|
||||||
{
|
{
|
||||||
static::creating(function ($setting) {
|
static::creating(function ($setting) {
|
||||||
$setting->is_sentinel_enabled = true;
|
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';
|
||||||
|
} else if ($settings->fqdn) {
|
||||||
|
$domain = $settings->fqdn;
|
||||||
|
} else if ($settings->ipv4) {
|
||||||
|
$domain = $settings->ipv4 . ':8000';
|
||||||
|
} else if ($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);
|
||||||
|
@@ -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.");
|
||||||
|
@@ -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();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
@@ -9,22 +9,23 @@ class SentinelSeeder extends Seeder
|
|||||||
{
|
{
|
||||||
public function run()
|
public function run()
|
||||||
{
|
{
|
||||||
try {
|
Server::chunk(100, function ($servers) {
|
||||||
Server::chunk(100, function ($servers) {
|
foreach ($servers as $server) {
|
||||||
foreach ($servers as $server) {
|
try {
|
||||||
if (str($server->settings->sentinel_token)->isEmpty()) {
|
if (str($server->settings->sentinel_token)->isEmpty()) {
|
||||||
$server->generateSentinelToken();
|
$server->settings->generateSentinelToken();
|
||||||
}
|
}
|
||||||
if (str($server->settings->sentinel_custom_url)->isEmpty()) {
|
if (str($server->settings->sentinel_custom_url)->isEmpty()) {
|
||||||
$url = $server->generateSentinelUrl();
|
$url = $server->settings->generateSentinelUrl();
|
||||||
$server->settings->sentinel_custom_url = $url;
|
if (str($url)->isEmpty()) {
|
||||||
$server->settings->save();
|
$server->settings->is_sentinel_enabled = false;
|
||||||
|
$server->settings->save();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
} catch (\Throwable $e) {
|
||||||
|
loggy("Error: {$e->getMessage()}\n");
|
||||||
}
|
}
|
||||||
});
|
}
|
||||||
} catch (\Throwable $e) {
|
});
|
||||||
echo "Error: {$e->getMessage()}\n";
|
|
||||||
ray($e->getMessage());
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -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">
|
||||||
|
@@ -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')]) }}">
|
||||||
|
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
|
||||||
|
@@ -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,197 +119,82 @@
|
|||||||
</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())
|
<div class="flex gap-2 items-center pt-4 pb-2">
|
||||||
<h3 class="pt-4">Settings</h3>
|
<h3>Sentinel</h3>
|
||||||
<div class="flex flex-col gap-4">
|
@if ($server->isSentinelEnabled())
|
||||||
<div class="flex flex-col gap-2">
|
<div class="flex gap-2 items-center"
|
||||||
<div class="flex flex-wrap items-center gap-4">
|
wire:poll.{{ $server->settings->sentinel_push_interval_seconds }}s="checkSyncStatus">
|
||||||
<div class="w-64">
|
@if ($server->isSentinelLive())
|
||||||
<x-forms.checkbox
|
<x-status.running status="In-sync" noLoading />
|
||||||
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>
|
|
||||||
@if (isDev())
|
|
||||||
<div class="flex gap-2 items-center pt-4 pb-2">
|
|
||||||
<h3>Sentinel</h3>
|
|
||||||
@if ($server->isSentinelEnabled())
|
|
||||||
<div class="flex gap-2 items-center"
|
|
||||||
wire:poll.{{ $server->settings->sentinel_push_interval_seconds }}s="checkSyncStatus">
|
|
||||||
@if ($server->isSentinelLive())
|
|
||||||
<x-status.running status="In-sync" noLoading />
|
|
||||||
@else
|
|
||||||
<x-status.stopped status="Out-of-sync" noLoading />
|
|
||||||
@endif
|
|
||||||
<x-forms.button wire:click='restartSentinel'>Restart</x-forms.button>
|
<x-forms.button wire:click='restartSentinel'>Restart</x-forms.button>
|
||||||
</div>
|
|
||||||
@endif
|
|
||||||
</div>
|
|
||||||
<div class="flex flex-col gap-2">
|
|
||||||
<div class="w-64">
|
|
||||||
<x-forms.checkbox instantSave id="server.settings.is_sentinel_enabled"
|
|
||||||
label="Enable Sentinel" />
|
|
||||||
@if ($server->isSentinelEnabled())
|
|
||||||
<x-forms.checkbox instantSave id="server.settings.is_metrics_enabled"
|
|
||||||
label="Enable Metrics" />
|
|
||||||
@else
|
@else
|
||||||
<x-forms.checkbox instantSave disabled id="server.settings.is_metrics_enabled"
|
<x-status.stopped status="Out-of-sync" noLoading />
|
||||||
label="Enable Metrics" />
|
<x-forms.button wire:click='restartSentinel'>Sync</x-forms.button>
|
||||||
@endif
|
@endif
|
||||||
</div>
|
</div>
|
||||||
|
@endif
|
||||||
|
</div>
|
||||||
|
<div class="flex flex-col gap-2">
|
||||||
|
<div class="w-64">
|
||||||
|
<x-forms.checkbox instantSave id="server.settings.is_sentinel_enabled" label="Enable Sentinel" />
|
||||||
|
@if ($server->isSentinelEnabled())
|
||||||
|
<x-forms.checkbox instantSave id="server.settings.is_metrics_enabled"
|
||||||
|
label="Enable Metrics" />
|
||||||
|
@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="Sentinel token"
|
<x-forms.input type="password" id="server.settings.sentinel_token" label="Sentinel token"
|
||||||
required helper="Token for Sentinel." />
|
required helper="Token for Sentinel." />
|
||||||
<x-forms.input id="server.settings.sentinel_custom_url" label="Sentinel custom URL"
|
|
||||||
helper="Custom URL for Sentinel." />
|
|
||||||
|
|
||||||
<x-forms.button wire:click="regenerateSentinelToken">Regenerate</x-forms.button>
|
<x-forms.button wire:click="regenerateSentinelToken">Regenerate</x-forms.button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<x-forms.input id="server.settings.sentinel_custom_url" required label="Coolify URL"
|
||||||
|
helper="URL to your Coolify instance. If it is empty that means you do not have a FQDN set for your Coolify instance." />
|
||||||
|
|
||||||
<div class="flex flex-col gap-2">
|
<div class="flex flex-col gap-2">
|
||||||
<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.sentinel_metrics_refresh_rate_seconds"
|
<x-forms.input id="server.settings.sentinel_metrics_refresh_rate_seconds"
|
||||||
@@ -323,8 +208,9 @@
|
|||||||
helper="How many seconds should the metrics data should be pushed to the collector." />
|
helper="How many seconds should the metrics data should be pushed to the collector." />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
@endif
|
||||||
@endif
|
</div>
|
||||||
@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>
|
||||||
|
@@ -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>
|
|
@@ -2,24 +2,30 @@
|
|||||||
<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>
|
||||||
|
<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>
|
||||||
|
</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 +84,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 +108,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 +146,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>
|
||||||
|
Reference in New Issue
Block a user