mirror of
https://github.com/coollabsio/coolify.git
synced 2026-06-14 03:19:51 +00:00
feat(ui): move sentinel to new tab (#9544)
This commit is contained in:
@@ -2,11 +2,15 @@
|
|||||||
|
|
||||||
namespace App\Livewire\Server;
|
namespace App\Livewire\Server;
|
||||||
|
|
||||||
|
use App\Actions\Server\StartSentinel;
|
||||||
use App\Models\Server;
|
use App\Models\Server;
|
||||||
|
use Illuminate\Foundation\Auth\Access\AuthorizesRequests;
|
||||||
use Livewire\Component;
|
use Livewire\Component;
|
||||||
|
|
||||||
class Charts extends Component
|
class Charts extends Component
|
||||||
{
|
{
|
||||||
|
use AuthorizesRequests;
|
||||||
|
|
||||||
public Server $server;
|
public Server $server;
|
||||||
|
|
||||||
public $chartId = 'server';
|
public $chartId = 'server';
|
||||||
@@ -28,6 +32,29 @@ class Charts extends Component
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function toggleMetrics(): void
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
$this->authorize('update', $this->server);
|
||||||
|
$this->server->settings->is_metrics_enabled = ! $this->server->settings->is_metrics_enabled;
|
||||||
|
$this->server->settings->save();
|
||||||
|
$this->server->refresh();
|
||||||
|
|
||||||
|
if ($this->server->isMetricsEnabled()) {
|
||||||
|
StartSentinel::run($this->server, true);
|
||||||
|
$this->dispatch('success', 'Metrics enabled. Starting Sentinel.');
|
||||||
|
$this->dispatch('refreshServerShow');
|
||||||
|
$this->redirect(route('server.metrics', ['server_uuid' => $this->server->uuid]), navigate: true);
|
||||||
|
} else {
|
||||||
|
$this->server->restartSentinel();
|
||||||
|
$this->dispatch('success', 'Metrics disabled. Restarting Sentinel.');
|
||||||
|
$this->dispatch('refreshServerShow');
|
||||||
|
}
|
||||||
|
} catch (\Throwable $e) {
|
||||||
|
handleError($e, $this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public function pollData()
|
public function pollData()
|
||||||
{
|
{
|
||||||
if ($this->poll || $this->interval <= 10) {
|
if ($this->poll || $this->interval <= 10) {
|
||||||
|
|||||||
@@ -15,8 +15,6 @@ class Sentinel extends Component
|
|||||||
|
|
||||||
public Server $server;
|
public Server $server;
|
||||||
|
|
||||||
public array $parameters = [];
|
|
||||||
|
|
||||||
public bool $isMetricsEnabled;
|
public bool $isMetricsEnabled;
|
||||||
|
|
||||||
#[Validate(['required', 'string', 'max:500', 'regex:/\A[a-zA-Z0-9._\-+=\/]+\z/'])]
|
#[Validate(['required', 'string', 'max:500', 'regex:/\A[a-zA-Z0-9._\-+=\/]+\z/'])]
|
||||||
@@ -51,15 +49,9 @@ class Sentinel extends Component
|
|||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
public function mount(string $server_uuid)
|
public function mount()
|
||||||
{
|
{
|
||||||
try {
|
$this->syncData();
|
||||||
$this->server = Server::ownedByCurrentTeam()->whereUuid($server_uuid)->firstOrFail();
|
|
||||||
$this->parameters = get_route_parameters();
|
|
||||||
$this->syncData();
|
|
||||||
} catch (\Throwable) {
|
|
||||||
return redirect()->route('server.index');
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public function syncData(bool $toModel = false)
|
public function syncData(bool $toModel = false)
|
||||||
@@ -112,27 +104,29 @@ class Sentinel extends Component
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public function updatedIsSentinelEnabled($value)
|
public function toggleSentinel(): void
|
||||||
{
|
{
|
||||||
try {
|
try {
|
||||||
$this->authorize('manageSentinel', $this->server);
|
$this->authorize('manageSentinel', $this->server);
|
||||||
if ($value === true) {
|
if (! $this->isSentinelEnabled) {
|
||||||
if ($this->server->isBuildServer()) {
|
if ($this->server->isBuildServer()) {
|
||||||
$this->isSentinelEnabled = false;
|
|
||||||
$this->dispatch('error', 'Sentinel cannot be enabled on build servers.');
|
$this->dispatch('error', 'Sentinel cannot be enabled on build servers.');
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
$this->isSentinelEnabled = true;
|
||||||
$customImage = isDev() ? $this->sentinelCustomDockerImage : null;
|
$customImage = isDev() ? $this->sentinelCustomDockerImage : null;
|
||||||
StartSentinel::run($this->server, true, null, $customImage);
|
StartSentinel::run($this->server, true, null, $customImage);
|
||||||
} else {
|
} else {
|
||||||
|
$this->isSentinelEnabled = false;
|
||||||
$this->isMetricsEnabled = false;
|
$this->isMetricsEnabled = false;
|
||||||
$this->isSentinelDebugEnabled = false;
|
$this->isSentinelDebugEnabled = false;
|
||||||
StopSentinel::dispatch($this->server);
|
StopSentinel::dispatch($this->server);
|
||||||
}
|
}
|
||||||
$this->submit();
|
$this->submit();
|
||||||
|
$this->dispatch('refreshServerShow');
|
||||||
} catch (\Throwable $e) {
|
} catch (\Throwable $e) {
|
||||||
return handleError($e, $this);
|
handleError($e, $this);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,29 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Livewire\Server\Sentinel;
|
||||||
|
|
||||||
|
use App\Models\Server;
|
||||||
|
use Illuminate\View\View;
|
||||||
|
use Livewire\Component;
|
||||||
|
|
||||||
|
class Logs extends Component
|
||||||
|
{
|
||||||
|
public ?Server $server = null;
|
||||||
|
|
||||||
|
public array $parameters = [];
|
||||||
|
|
||||||
|
public function mount(): void
|
||||||
|
{
|
||||||
|
$this->parameters = get_route_parameters();
|
||||||
|
try {
|
||||||
|
$this->server = Server::ownedByCurrentTeam()->whereUuid(request()->server_uuid)->firstOrFail();
|
||||||
|
} catch (\Throwable $e) {
|
||||||
|
handleError($e, $this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public function render(): View
|
||||||
|
{
|
||||||
|
return view('livewire.server.sentinel.logs');
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,29 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Livewire\Server\Sentinel;
|
||||||
|
|
||||||
|
use App\Models\Server;
|
||||||
|
use Illuminate\View\View;
|
||||||
|
use Livewire\Component;
|
||||||
|
|
||||||
|
class Show extends Component
|
||||||
|
{
|
||||||
|
public ?Server $server = null;
|
||||||
|
|
||||||
|
public array $parameters = [];
|
||||||
|
|
||||||
|
public function mount(): void
|
||||||
|
{
|
||||||
|
$this->parameters = get_route_parameters();
|
||||||
|
try {
|
||||||
|
$this->server = Server::ownedByCurrentTeam()->whereUuid(request()->server_uuid)->firstOrFail();
|
||||||
|
} catch (\Throwable $e) {
|
||||||
|
handleError($e, $this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public function render(): View
|
||||||
|
{
|
||||||
|
return view('livewire.server.sentinel.show');
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
<div class="sub-menu-wrapper">
|
||||||
|
<a class="{{ request()->routeIs('server.sentinel') ? 'sub-menu-item menu-item-active' : 'sub-menu-item' }}" {{ wireNavigate() }}
|
||||||
|
href="{{ route('server.sentinel', $parameters) }}">
|
||||||
|
<span class="menu-item-label">Configuration</span>
|
||||||
|
</a>
|
||||||
|
<a class="{{ request()->routeIs('server.sentinel.logs') ? 'sub-menu-item menu-item-active' : 'sub-menu-item' }}" {{ wireNavigate() }}
|
||||||
|
href="{{ route('server.sentinel.logs', $parameters) }}">
|
||||||
|
<span class="menu-item-label">Logs</span>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
@@ -6,11 +6,6 @@
|
|||||||
href="{{ route('server.advanced', ['server_uuid' => $server->uuid]) }}"><span class="menu-item-label">Advanced</span>
|
href="{{ route('server.advanced', ['server_uuid' => $server->uuid]) }}"><span class="menu-item-label">Advanced</span>
|
||||||
</a>
|
</a>
|
||||||
@endif
|
@endif
|
||||||
@if ($server->isFunctional() && !$server->isSwarm() && !$server->isBuildServer())
|
|
||||||
<a class="sub-menu-item {{ $activeMenu === 'sentinel' ? 'menu-item-active' : '' }}" {{ wireNavigate() }}
|
|
||||||
href="{{ route('server.sentinel', ['server_uuid' => $server->uuid]) }}"><span class="menu-item-label">Sentinel</span>
|
|
||||||
</a>
|
|
||||||
@endif
|
|
||||||
<a class="sub-menu-item {{ $activeMenu === 'private-key' ? 'menu-item-active' : '' }}" {{ wireNavigate() }}
|
<a class="sub-menu-item {{ $activeMenu === 'private-key' ? 'menu-item-active' : '' }}" {{ wireNavigate() }}
|
||||||
href="{{ route('server.private-key', ['server_uuid' => $server->uuid]) }}"><span class="menu-item-label">Private Key</span>
|
href="{{ route('server.private-key', ['server_uuid' => $server->uuid]) }}"><span class="menu-item-label">Private Key</span>
|
||||||
</a>
|
</a>
|
||||||
@@ -37,7 +32,7 @@
|
|||||||
<a class="sub-menu-item {{ $activeMenu === 'log-drains' ? 'menu-item-active' : '' }}" {{ wireNavigate() }}
|
<a class="sub-menu-item {{ $activeMenu === 'log-drains' ? 'menu-item-active' : '' }}" {{ wireNavigate() }}
|
||||||
href="{{ route('server.log-drains', ['server_uuid' => $server->uuid]) }}"><span class="menu-item-label">Log Drains</span></a>
|
href="{{ route('server.log-drains', ['server_uuid' => $server->uuid]) }}"><span class="menu-item-label">Log Drains</span></a>
|
||||||
<a class="sub-menu-item {{ $activeMenu === 'metrics' ? 'menu-item-active' : '' }}" {{ wireNavigate() }}
|
<a class="sub-menu-item {{ $activeMenu === 'metrics' ? 'menu-item-active' : '' }}" {{ wireNavigate() }}
|
||||||
href="{{ route('server.charts', ['server_uuid' => $server->uuid]) }}"><span class="menu-item-label">Metrics</span></a>
|
href="{{ route('server.metrics', ['server_uuid' => $server->uuid]) }}"><span class="menu-item-label">Metrics</span></a>
|
||||||
@endif
|
@endif
|
||||||
@if (!$server->isBuildServer() && !$server->settings->is_cloudflare_tunnel)
|
@if (!$server->isBuildServer() && !$server->settings->is_cloudflare_tunnel)
|
||||||
<a class="sub-menu-item {{ $activeMenu === 'swarm' ? 'menu-item-active' : '' }}" {{ wireNavigate() }}
|
<a class="sub-menu-item {{ $activeMenu === 'swarm' ? 'menu-item-active' : '' }}" {{ wireNavigate() }}
|
||||||
|
|||||||
@@ -5,13 +5,19 @@
|
|||||||
<div class="pb-4">Basic metrics for your application container.</div>
|
<div class="pb-4">Basic metrics for your application container.</div>
|
||||||
<div>
|
<div>
|
||||||
@if ($resource->getMorphClass() === 'App\Models\Application' && $resource->build_pack === 'dockercompose')
|
@if ($resource->getMorphClass() === 'App\Models\Application' && $resource->build_pack === 'dockercompose')
|
||||||
<div class="alert alert-warning">Metrics are not available for Docker Compose applications yet!</div>
|
<x-callout type="warning" title="Not Available">
|
||||||
|
Metrics are not available for Docker Compose applications yet!
|
||||||
|
</x-callout>
|
||||||
@elseif(!$resource->destination->server->isMetricsEnabled())
|
@elseif(!$resource->destination->server->isMetricsEnabled())
|
||||||
<div class="alert alert-warning pb-1">Metrics are only available for servers with Sentinel & Metrics enabled!</div>
|
<x-callout type="info" title="Metrics Not Enabled">
|
||||||
<div>Go to <a class="underline dark:text-white" href="{{ route('server.show', $resource->destination->server->uuid) }}/sentinel" {{ wireNavigate() }}>Server settings</a> to enable it.</div>
|
Metrics are only available for servers with Sentinel & Metrics enabled.
|
||||||
|
Go to <a class="underline font-semibold" href="{{ route('server.metrics', ['server_uuid' => $resource->destination->server->uuid]) }}" {{ wireNavigate() }}>Server Metrics</a> to enable it.
|
||||||
|
</x-callout>
|
||||||
@else
|
@else
|
||||||
@if (!str($resource->status)->contains('running'))
|
@if (!str($resource->status)->contains('running'))
|
||||||
<div class="alert alert-warning">Metrics are only available when the application container is running!</div>
|
<x-callout type="warning" title="Container Not Running">
|
||||||
|
Metrics are only available when the application container is running!
|
||||||
|
</x-callout>
|
||||||
@else
|
@else
|
||||||
<div>
|
<div>
|
||||||
<x-forms.select label="Interval" wire:change="setInterval" id="interval">
|
<x-forms.select label="Interval" wire:change="setInterval" id="interval">
|
||||||
|
|||||||
@@ -6,7 +6,18 @@
|
|||||||
<div class="flex flex-col h-full gap-8 sm:flex-row">
|
<div class="flex flex-col h-full gap-8 sm:flex-row">
|
||||||
<x-server.sidebar :server="$server" activeMenu="metrics" />
|
<x-server.sidebar :server="$server" activeMenu="metrics" />
|
||||||
<div class="w-full">
|
<div class="w-full">
|
||||||
<h2>Metrics</h2>
|
<div class="flex items-center gap-2">
|
||||||
|
<h2>Metrics</h2>
|
||||||
|
@if ($server->isMetricsEnabled())
|
||||||
|
<x-forms.button canGate="update" :canResource="$server" wire:click='toggleMetrics'>
|
||||||
|
Disable Metrics
|
||||||
|
</x-forms.button>
|
||||||
|
@elseif ($server->isSentinelEnabled())
|
||||||
|
<x-forms.button canGate="update" :canResource="$server" isHighlighted wire:click='toggleMetrics'>
|
||||||
|
Enable Metrics
|
||||||
|
</x-forms.button>
|
||||||
|
@endif
|
||||||
|
</div>
|
||||||
<div class="pb-4">Basic metrics for your server.</div>
|
<div class="pb-4">Basic metrics for your server.</div>
|
||||||
@if ($server->isMetricsEnabled())
|
@if ($server->isMetricsEnabled())
|
||||||
<div @if ($poll) wire:poll.5000ms='pollData' @endif x-init="$wire.loadData()">
|
<div @if ($poll) wire:poll.5000ms='pollData' @endif x-init="$wire.loadData()">
|
||||||
@@ -288,8 +299,16 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@else
|
@else
|
||||||
<div>Metrics are disabled for this server. Enable them in <a class="underline dark:text-white"
|
@if ($server->isSentinelEnabled())
|
||||||
href="{{ route('server.show', ['server_uuid' => $server->uuid]) }}/sentinel" {{ wireNavigate() }}>Sentinel</a> settings.</div>
|
<x-callout type="info" title="Metrics Disabled">
|
||||||
|
Metrics are disabled for this server. Click "Enable Metrics" above to start collecting metrics.
|
||||||
|
</x-callout>
|
||||||
|
@else
|
||||||
|
<x-callout type="info" title="Sentinel Required">
|
||||||
|
Metrics require Sentinel to be enabled.
|
||||||
|
Please <a class="underline font-semibold" href="{{ route('server.sentinel', ['server_uuid' => $server->uuid]) }}" {{ wireNavigate() }}>enable Sentinel</a> first.
|
||||||
|
</x-callout>
|
||||||
|
@endif
|
||||||
@endif
|
@endif
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -58,6 +58,17 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@endif
|
@endif
|
||||||
|
@if ($server->isSentinelEnabled())
|
||||||
|
<div class="flex">
|
||||||
|
<div class="flex items-center">
|
||||||
|
@if ($server->isSentinelLive())
|
||||||
|
<x-status.running status="Sentinel In Sync" noLoading />
|
||||||
|
@else
|
||||||
|
<x-status.stopped status="Sentinel Out of Sync" noLoading />
|
||||||
|
@endif
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
@endif
|
||||||
</div>
|
</div>
|
||||||
<div class="subtitle">{{ data_get($server, 'name') }}</div>
|
<div class="subtitle">{{ data_get($server, 'name') }}</div>
|
||||||
<div class="navbar-main">
|
<div class="navbar-main">
|
||||||
@@ -70,7 +81,7 @@
|
|||||||
</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' : '' }} flex items-center gap-1" href="{{ route('server.proxy', [
|
<a class="{{ request()->routeIs('server.proxy') || request()->routeIs('server.proxy.*') ? 'dark:text-white' : '' }} flex items-center gap-1" href="{{ route('server.proxy', [
|
||||||
'server_uuid' => data_get($server, 'uuid'),
|
'server_uuid' => data_get($server, 'uuid'),
|
||||||
]) }}" {{ wireNavigate() }}>
|
]) }}" {{ wireNavigate() }}>
|
||||||
Proxy
|
Proxy
|
||||||
@@ -82,6 +93,19 @@
|
|||||||
@endif
|
@endif
|
||||||
</a>
|
</a>
|
||||||
@endif
|
@endif
|
||||||
|
@if ($server->isFunctional() && !$server->isSwarm() && !$server->settings->is_build_server)
|
||||||
|
<a class="{{ request()->routeIs('server.sentinel') || request()->routeIs('server.sentinel.*') ? 'dark:text-white' : '' }} flex items-center gap-1" href="{{ route('server.sentinel', [
|
||||||
|
'server_uuid' => data_get($server, 'uuid'),
|
||||||
|
]) }}" {{ wireNavigate() }}>
|
||||||
|
Sentinel
|
||||||
|
@if ($server->isSentinelEnabled() && !$server->isSentinelLive())
|
||||||
|
<svg class="w-4 h-4 text-warning" viewBox="0 0 256 256" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path fill="currentColor"
|
||||||
|
d="M236.8 188.09L149.35 36.22a24.76 24.76 0 0 0-42.7 0L19.2 188.09a23.51 23.51 0 0 0 0 23.72A24.35 24.35 0 0 0 40.55 224h174.9a24.35 24.35 0 0 0 21.33-12.19a23.51 23.51 0 0 0 .02-23.72m-13.87 15.71a8.5 8.5 0 0 1-7.48 4.2H40.55a8.5 8.5 0 0 1-7.48-4.2a7.59 7.59 0 0 1 0-7.72l87.45-151.87a8.75 8.75 0 0 1 15 0l87.45 151.87a7.59 7.59 0 0 1-.04 7.72M120 144v-40a8 8 0 0 1 16 0v40a8 8 0 0 1-16 0m20 36a12 12 0 1 1-12-12a12 12 0 0 1 12 12" />
|
||||||
|
</svg>
|
||||||
|
@endif
|
||||||
|
</a>
|
||||||
|
@endif
|
||||||
<a class="{{ request()->routeIs('server.resources') ? 'dark:text-white' : '' }}" href="{{ route('server.resources', [
|
<a class="{{ request()->routeIs('server.resources') ? 'dark:text-white' : '' }}" href="{{ route('server.resources', [
|
||||||
'server_uuid' => data_get($server, 'uuid'),
|
'server_uuid' => data_get($server, 'uuid'),
|
||||||
]) }}" {{ wireNavigate() }}>
|
]) }}" {{ wireNavigate() }}>
|
||||||
|
|||||||
@@ -1,111 +1,73 @@
|
|||||||
<div>
|
<div>
|
||||||
<x-slot:title>
|
<form wire:submit.prevent='submit'>
|
||||||
{{ data_get_str($server, 'name')->limit(10) }} > Sentinel | Coolify
|
<div class="flex gap-2 items-center pb-2">
|
||||||
</x-slot>
|
<h2>Sentinel</h2>
|
||||||
<livewire:server.navbar :server="$server" />
|
<x-helper helper="Sentinel reports your server's & container's health and collects metrics." />
|
||||||
<div class="flex flex-col h-full gap-8 sm:flex-row">
|
@if (!$isSentinelEnabled)
|
||||||
<x-server.sidebar :server="$server" activeMenu="sentinel" />
|
<x-forms.button canGate="update" :canResource="$server" isHighlighted wire:click="toggleSentinel">Enable Sentinel</x-forms.button>
|
||||||
<div class="w-full">
|
@else
|
||||||
<form wire:submit.prevent='submit'>
|
<div class="flex gap-2 items-center">
|
||||||
<div class="flex gap-2 items-center pb-2">
|
<x-forms.button type="submit" canGate="update" :canResource="$server">Save</x-forms.button>
|
||||||
<h2>Sentinel</h2>
|
<x-forms.button wire:click='restartSentinel' canGate="update" :canResource="$server">
|
||||||
<x-helper helper="Sentinel reports your server's & container's health and collects metrics." />
|
{{ $server->isSentinelLive() ? 'Restart' : 'Sync' }}
|
||||||
@if ($server->isSentinelEnabled())
|
</x-forms.button>
|
||||||
<div class="flex gap-2 items-center">
|
<x-forms.button canGate="update" :canResource="$server" wire:click="toggleSentinel">Disable Sentinel</x-forms.button>
|
||||||
@if ($server->isSentinelLive())
|
|
||||||
<x-status.running status="In sync" noLoading title="{{ $sentinelUpdatedAt }}" />
|
|
||||||
<x-forms.button type="submit" canGate="update" :canResource="$server">Save</x-forms.button>
|
|
||||||
<x-forms.button wire:click='restartSentinel' canGate="update" :canResource="$server">Restart</x-forms.button>
|
|
||||||
<x-slide-over fullScreen>
|
|
||||||
<x-slot:title>Sentinel Logs</x-slot:title>
|
|
||||||
<x-slot:content>
|
|
||||||
<livewire:project.shared.get-logs :server="$server"
|
|
||||||
container="coolify-sentinel" displayName="Sentinel" :collapsible="false"
|
|
||||||
lazy />
|
|
||||||
</x-slot:content>
|
|
||||||
<x-forms.button @click="slideOverOpen=true">Logs</x-forms.button>
|
|
||||||
</x-slide-over>
|
|
||||||
@else
|
|
||||||
<x-status.stopped status="Out of sync" noLoading
|
|
||||||
title="{{ $sentinelUpdatedAt }}" />
|
|
||||||
<x-forms.button type="submit" canGate="update" :canResource="$server">Save</x-forms.button>
|
|
||||||
<x-forms.button wire:click='restartSentinel' canGate="update" :canResource="$server">Sync</x-forms.button>
|
|
||||||
<x-slide-over fullScreen>
|
|
||||||
<x-slot:title>Sentinel Logs</x-slot:title>
|
|
||||||
<x-slot:content>
|
|
||||||
<livewire:project.shared.get-logs :server="$server"
|
|
||||||
container="coolify-sentinel" displayName="Sentinel" :collapsible="false"
|
|
||||||
lazy />
|
|
||||||
</x-slot:content>
|
|
||||||
<x-forms.button @click="slideOverOpen=true">Logs</x-forms.button>
|
|
||||||
</x-slide-over>
|
|
||||||
@endif
|
|
||||||
</div>
|
|
||||||
@endif
|
|
||||||
</div>
|
</div>
|
||||||
<div class="flex flex-col gap-2">
|
@endif
|
||||||
<div class="w-full sm:w-96">
|
|
||||||
<x-forms.checkbox canGate="update" :canResource="$server" wire:model.live="isSentinelEnabled"
|
|
||||||
label="Enable Sentinel" />
|
|
||||||
@if ($server->isSentinelEnabled())
|
|
||||||
@if (isDev())
|
|
||||||
<x-forms.checkbox canGate="update" :canResource="$server" id="isSentinelDebugEnabled"
|
|
||||||
label="Enable Sentinel (with debug)" instantSave />
|
|
||||||
@endif
|
|
||||||
<x-forms.checkbox canGate="update" :canResource="$server" instantSave
|
|
||||||
id="isMetricsEnabled" label="Enable Metrics" />
|
|
||||||
@else
|
|
||||||
@if (isDev())
|
|
||||||
<x-forms.checkbox id="isSentinelDebugEnabled" label="Enable Sentinel (with debug)"
|
|
||||||
disabled instantSave />
|
|
||||||
@endif
|
|
||||||
<x-forms.checkbox instantSave disabled id="isMetricsEnabled"
|
|
||||||
label="Enable Metrics (enable Sentinel first)" />
|
|
||||||
@endif
|
|
||||||
</div>
|
|
||||||
@if (isDev() && $server->isSentinelEnabled())
|
|
||||||
<div class="pt-4" x-data="{
|
|
||||||
customImage: localStorage.getItem('sentinel_custom_docker_image_{{ $server->uuid }}') || '',
|
|
||||||
saveCustomImage() {
|
|
||||||
localStorage.setItem('sentinel_custom_docker_image_{{ $server->uuid }}', this.customImage);
|
|
||||||
$wire.set('sentinelCustomDockerImage', this.customImage);
|
|
||||||
}
|
|
||||||
}" x-init="$wire.set('sentinelCustomDockerImage', customImage)">
|
|
||||||
<x-forms.input x-model="customImage" @input.debounce.500ms="saveCustomImage()"
|
|
||||||
placeholder="e.g., sentinel:latest or myregistry/sentinel:dev"
|
|
||||||
label="Custom Sentinel Docker Image (Dev Only)"
|
|
||||||
helper="Override the default Sentinel Docker image for testing. Leave empty to use the default." />
|
|
||||||
</div>
|
|
||||||
@endif
|
|
||||||
@if ($server->isSentinelEnabled())
|
|
||||||
<div class="flex flex-wrap gap-2 sm:flex-nowrap items-end">
|
|
||||||
<x-forms.input canGate="update" :canResource="$server" type="password" id="sentinelToken"
|
|
||||||
label="Sentinel token" required helper="Token for Sentinel." />
|
|
||||||
<x-forms.button canGate="update" :canResource="$server"
|
|
||||||
wire:click="regenerateSentinelToken">Regenerate</x-forms.button>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<x-forms.input canGate="update" :canResource="$server" id="sentinelCustomUrl" 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-wrap gap-2 sm:flex-nowrap">
|
|
||||||
<x-forms.input canGate="update" :canResource="$server" type="number" min="1"
|
|
||||||
id="sentinelMetricsRefreshRateSeconds" label="Metrics rate (seconds)" required
|
|
||||||
helper="Interval used for gathering metrics. Lower values result in more disk space usage." />
|
|
||||||
<x-forms.input canGate="update" :canResource="$server" type="number" min="1"
|
|
||||||
id="sentinelMetricsHistoryDays"
|
|
||||||
label="Metrics history (days)" required
|
|
||||||
helper="Number of days to retain metrics data for." />
|
|
||||||
<x-forms.input canGate="update" :canResource="$server" type="number" min="10"
|
|
||||||
id="sentinelPushIntervalSeconds" label="Push interval (seconds)" required
|
|
||||||
helper="Interval at which metrics data is sent to the collector." />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
@endif
|
|
||||||
</div>
|
|
||||||
</form>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
@if ($isSentinelEnabled && !$server->isSentinelLive())
|
||||||
|
<x-callout type="warning" title="Out of Sync" class="mt-2">
|
||||||
|
Sentinel is not in sync with your server. Click "Sync" to re-sync.
|
||||||
|
</x-callout>
|
||||||
|
@endif
|
||||||
|
<div class="flex flex-col gap-2 pt-2">
|
||||||
|
@if ($isSentinelEnabled && isDev())
|
||||||
|
<div class="w-full sm:w-96">
|
||||||
|
<x-forms.checkbox canGate="update" :canResource="$server" id="isSentinelDebugEnabled"
|
||||||
|
label="Enable Sentinel (with debug)" instantSave />
|
||||||
|
</div>
|
||||||
|
@endif
|
||||||
|
@if (isDev() && $server->isSentinelEnabled())
|
||||||
|
<div class="pt-4" x-data="{
|
||||||
|
customImage: localStorage.getItem('sentinel_custom_docker_image_{{ $server->uuid }}') || '',
|
||||||
|
saveCustomImage() {
|
||||||
|
localStorage.setItem('sentinel_custom_docker_image_{{ $server->uuid }}', this.customImage);
|
||||||
|
$wire.set('sentinelCustomDockerImage', this.customImage);
|
||||||
|
}
|
||||||
|
}" x-init="$wire.set('sentinelCustomDockerImage', customImage)">
|
||||||
|
<x-forms.input canGate="update" :canResource="$server" x-model="customImage"
|
||||||
|
@input.debounce.500ms="saveCustomImage()"
|
||||||
|
placeholder="e.g., sentinel:latest or myregistry/sentinel:dev"
|
||||||
|
label="Custom Sentinel Docker Image (Dev Only)"
|
||||||
|
helper="Override the default Sentinel Docker image for testing. Leave empty to use the default." />
|
||||||
|
</div>
|
||||||
|
@endif
|
||||||
|
@if ($server->isSentinelEnabled())
|
||||||
|
<div class="flex flex-wrap gap-2 sm:flex-nowrap items-end">
|
||||||
|
<x-forms.input canGate="update" :canResource="$server" id="sentinelCustomUrl" 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." />
|
||||||
|
<x-forms.input canGate="update" :canResource="$server" type="password" id="sentinelToken"
|
||||||
|
label="Sentinel token" required helper="Token for Sentinel." />
|
||||||
|
<x-forms.button canGate="update" :canResource="$server"
|
||||||
|
wire:click="regenerateSentinelToken">Regenerate</x-forms.button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="flex flex-col gap-2">
|
||||||
|
<div class="flex flex-wrap gap-2 sm:flex-nowrap">
|
||||||
|
<x-forms.input canGate="update" :canResource="$server" type="number" min="1"
|
||||||
|
id="sentinelMetricsRefreshRateSeconds" label="Metrics rate (seconds)" required
|
||||||
|
helper="Interval used for gathering metrics. Lower values result in more disk space usage." />
|
||||||
|
<x-forms.input canGate="update" :canResource="$server" type="number" min="1"
|
||||||
|
id="sentinelMetricsHistoryDays"
|
||||||
|
label="Metrics history (days)" required
|
||||||
|
helper="Number of days to retain metrics data for." />
|
||||||
|
<x-forms.input canGate="update" :canResource="$server" type="number" min="10"
|
||||||
|
id="sentinelPushIntervalSeconds" label="Push interval (seconds)" required
|
||||||
|
helper="Interval at which metrics data is sent to the collector." />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
@endif
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -0,0 +1,13 @@
|
|||||||
|
<div>
|
||||||
|
<x-slot:title>
|
||||||
|
Sentinel Logs | Coolify
|
||||||
|
</x-slot>
|
||||||
|
<livewire:server.navbar :server="$server" />
|
||||||
|
<div class="flex flex-col h-full gap-8 sm:flex-row">
|
||||||
|
<x-server.sidebar-sentinel :server="$server" :parameters="$parameters" />
|
||||||
|
<div class="w-full">
|
||||||
|
<h2 class="pb-4">Logs</h2>
|
||||||
|
<livewire:project.shared.get-logs :server="$server" container="coolify-sentinel" displayName="Sentinel" :collapsible="false" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
<div>
|
||||||
|
<x-slot:title>
|
||||||
|
Sentinel Configuration | Coolify
|
||||||
|
</x-slot>
|
||||||
|
<livewire:server.navbar :server="$server" />
|
||||||
|
@if ($server->isFunctional())
|
||||||
|
<div class="flex flex-col h-full gap-8 sm:flex-row">
|
||||||
|
<x-server.sidebar-sentinel :server="$server" :parameters="$parameters" />
|
||||||
|
<div class="w-full">
|
||||||
|
<livewire:server.sentinel :server="$server" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
@else
|
||||||
|
<div>Server is not validated. Validate first.</div>
|
||||||
|
@endif
|
||||||
|
</div>
|
||||||
+5
-3
@@ -57,7 +57,8 @@ use App\Livewire\Server\Proxy\Show as ProxyShow;
|
|||||||
use App\Livewire\Server\Resources as ResourcesShow;
|
use App\Livewire\Server\Resources as ResourcesShow;
|
||||||
use App\Livewire\Server\Security\Patches;
|
use App\Livewire\Server\Security\Patches;
|
||||||
use App\Livewire\Server\Security\TerminalAccess;
|
use App\Livewire\Server\Security\TerminalAccess;
|
||||||
use App\Livewire\Server\Sentinel as ServerSentinel;
|
use App\Livewire\Server\Sentinel\Logs as SentinelLogs;
|
||||||
|
use App\Livewire\Server\Sentinel\Show as SentinelShow;
|
||||||
use App\Livewire\Server\Show as ServerShow;
|
use App\Livewire\Server\Show as ServerShow;
|
||||||
use App\Livewire\Server\Swarm as ServerSwarm;
|
use App\Livewire\Server\Swarm as ServerSwarm;
|
||||||
use App\Livewire\Settings\Advanced as SettingsAdvanced;
|
use App\Livewire\Settings\Advanced as SettingsAdvanced;
|
||||||
@@ -281,7 +282,8 @@ Route::middleware(['auth', 'verified'])->group(function () {
|
|||||||
Route::get('/', ServerShow::class)->name('server.show');
|
Route::get('/', ServerShow::class)->name('server.show');
|
||||||
Route::get('/advanced', ServerAdvanced::class)->name('server.advanced');
|
Route::get('/advanced', ServerAdvanced::class)->name('server.advanced');
|
||||||
Route::get('/swarm', ServerSwarm::class)->name('server.swarm');
|
Route::get('/swarm', ServerSwarm::class)->name('server.swarm');
|
||||||
Route::get('/sentinel', ServerSentinel::class)->name('server.sentinel');
|
Route::get('/sentinel', SentinelShow::class)->name('server.sentinel');
|
||||||
|
Route::get('/sentinel/logs', SentinelLogs::class)->name('server.sentinel.logs');
|
||||||
Route::get('/private-key', PrivateKeyShow::class)->name('server.private-key');
|
Route::get('/private-key', PrivateKeyShow::class)->name('server.private-key');
|
||||||
Route::get('/cloud-provider-token', CloudProviderTokenShow::class)->name('server.cloud-provider-token');
|
Route::get('/cloud-provider-token', CloudProviderTokenShow::class)->name('server.cloud-provider-token');
|
||||||
Route::get('/ca-certificate', CaCertificateShow::class)->name('server.ca-certificate');
|
Route::get('/ca-certificate', CaCertificateShow::class)->name('server.ca-certificate');
|
||||||
@@ -289,7 +291,7 @@ Route::middleware(['auth', 'verified'])->group(function () {
|
|||||||
Route::get('/cloudflare-tunnel', CloudflareTunnel::class)->name('server.cloudflare-tunnel');
|
Route::get('/cloudflare-tunnel', CloudflareTunnel::class)->name('server.cloudflare-tunnel');
|
||||||
Route::get('/destinations', ServerDestinations::class)->name('server.destinations');
|
Route::get('/destinations', ServerDestinations::class)->name('server.destinations');
|
||||||
Route::get('/log-drains', LogDrains::class)->name('server.log-drains');
|
Route::get('/log-drains', LogDrains::class)->name('server.log-drains');
|
||||||
Route::get('/metrics', ServerCharts::class)->name('server.charts');
|
Route::get('/metrics', ServerCharts::class)->name('server.metrics');
|
||||||
Route::get('/danger', DeleteServer::class)->name('server.delete');
|
Route::get('/danger', DeleteServer::class)->name('server.delete');
|
||||||
Route::get('/proxy', ProxyShow::class)->name('server.proxy');
|
Route::get('/proxy', ProxyShow::class)->name('server.proxy');
|
||||||
Route::get('/proxy/dynamic', ProxyDynamicConfigurations::class)->name('server.proxy.dynamic-confs');
|
Route::get('/proxy/dynamic', ProxyDynamicConfigurations::class)->name('server.proxy.dynamic-confs');
|
||||||
|
|||||||
@@ -0,0 +1,21 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
it('keeps sentinel restarted events from re-syncing editable form fields', function () {
|
||||||
|
$componentSource = file_get_contents(app_path('Livewire/Server/Sentinel.php'));
|
||||||
|
|
||||||
|
preg_match('/public function handleSentinelRestarted\([^)]*\)\s*\{(?<body>.*?)\n \}/s', $componentSource, $matches);
|
||||||
|
|
||||||
|
expect($matches['body'] ?? '')
|
||||||
|
->toContain('$this->sentinelUpdatedAt = $this->server->sentinel_updated_at;')
|
||||||
|
->not->toContain('$this->syncData();');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('dispatches a server navbar refresh after toggling sentinel', function () {
|
||||||
|
$componentSource = file_get_contents(app_path('Livewire/Server/Sentinel.php'));
|
||||||
|
|
||||||
|
preg_match('/public function toggleSentinel\([^)]*\).*?\{(?<body>.*?)
|
||||||
|
\}/s', $componentSource, $matches);
|
||||||
|
|
||||||
|
expect($matches['body'] ?? '')
|
||||||
|
->toContain("\$this->dispatch('refreshServerShow');");
|
||||||
|
});
|
||||||
Reference in New Issue
Block a user