refactor: move Swarm and Sentinel to dedicated sidebar menu items (#7687)

This commit is contained in:
Andras Bacsai 2025-12-18 12:20:47 +01:00 committed by GitHub
commit 9a2522ed84
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
8 changed files with 476 additions and 143 deletions

View file

@ -0,0 +1,182 @@
<?php
namespace App\Livewire\Server;
use App\Actions\Server\StartSentinel;
use App\Actions\Server\StopSentinel;
use App\Models\Server;
use Illuminate\Foundation\Auth\Access\AuthorizesRequests;
use Livewire\Attributes\Validate;
use Livewire\Component;
class Sentinel extends Component
{
use AuthorizesRequests;
public Server $server;
public array $parameters = [];
public bool $isMetricsEnabled;
#[Validate(['required'])]
public string $sentinelToken;
public ?string $sentinelUpdatedAt = null;
#[Validate(['required', 'integer', 'min:1'])]
public int $sentinelMetricsRefreshRateSeconds;
#[Validate(['required', 'integer', 'min:1'])]
public int $sentinelMetricsHistoryDays;
#[Validate(['required', 'integer', 'min:10'])]
public int $sentinelPushIntervalSeconds;
#[Validate(['nullable', 'url'])]
public ?string $sentinelCustomUrl = null;
public bool $isSentinelEnabled;
public bool $isSentinelDebugEnabled;
public ?string $sentinelCustomDockerImage = null;
public function getListeners()
{
$teamId = $this->server->team_id ?? auth()->user()->currentTeam()->id;
return [
"echo-private:team.{$teamId},SentinelRestarted" => 'handleSentinelRestarted',
];
}
public function mount(string $server_uuid)
{
try {
$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)
{
if ($toModel) {
$this->authorize('update', $this->server);
$this->validate();
$this->server->settings->is_metrics_enabled = $this->isMetricsEnabled;
$this->server->settings->sentinel_token = $this->sentinelToken;
$this->server->settings->sentinel_metrics_refresh_rate_seconds = $this->sentinelMetricsRefreshRateSeconds;
$this->server->settings->sentinel_metrics_history_days = $this->sentinelMetricsHistoryDays;
$this->server->settings->sentinel_push_interval_seconds = $this->sentinelPushIntervalSeconds;
$this->server->settings->sentinel_custom_url = $this->sentinelCustomUrl;
$this->server->settings->is_sentinel_enabled = $this->isSentinelEnabled;
$this->server->settings->is_sentinel_debug_enabled = $this->isSentinelDebugEnabled;
$this->server->settings->save();
} else {
$this->isMetricsEnabled = $this->server->settings->is_metrics_enabled;
$this->sentinelToken = $this->server->settings->sentinel_token;
$this->sentinelMetricsRefreshRateSeconds = $this->server->settings->sentinel_metrics_refresh_rate_seconds;
$this->sentinelMetricsHistoryDays = $this->server->settings->sentinel_metrics_history_days;
$this->sentinelPushIntervalSeconds = $this->server->settings->sentinel_push_interval_seconds;
$this->sentinelCustomUrl = $this->server->settings->sentinel_custom_url;
$this->isSentinelEnabled = $this->server->settings->is_sentinel_enabled;
$this->isSentinelDebugEnabled = $this->server->settings->is_sentinel_debug_enabled;
$this->sentinelUpdatedAt = $this->server->sentinel_updated_at;
}
}
public function handleSentinelRestarted($event)
{
if ($event['serverUuid'] === $this->server->uuid) {
$this->server->refresh();
$this->syncData();
$this->dispatch('success', 'Sentinel has been restarted successfully.');
}
}
public function restartSentinel()
{
try {
$this->authorize('manageSentinel', $this->server);
$customImage = isDev() ? $this->sentinelCustomDockerImage : null;
$this->server->restartSentinel($customImage);
$this->dispatch('info', 'Restarting Sentinel.');
} catch (\Throwable $e) {
return handleError($e, $this);
}
}
public function updatedIsSentinelDebugEnabled($value)
{
try {
$this->submit();
$this->restartSentinel();
} catch (\Throwable $e) {
return handleError($e, $this);
}
}
public function updatedIsMetricsEnabled($value)
{
try {
$this->submit();
$this->restartSentinel();
} catch (\Throwable $e) {
return handleError($e, $this);
}
}
public function updatedIsSentinelEnabled($value)
{
try {
$this->authorize('manageSentinel', $this->server);
if ($value === true) {
if ($this->server->isBuildServer()) {
$this->isSentinelEnabled = false;
$this->dispatch('error', 'Sentinel cannot be enabled on build servers.');
return;
}
$customImage = isDev() ? $this->sentinelCustomDockerImage : null;
StartSentinel::run($this->server, true, null, $customImage);
} else {
$this->isMetricsEnabled = false;
$this->isSentinelDebugEnabled = false;
StopSentinel::dispatch($this->server);
}
$this->submit();
} catch (\Throwable $e) {
return handleError($e, $this);
}
}
public function regenerateSentinelToken()
{
try {
$this->authorize('manageSentinel', $this->server);
$this->server->settings->generateSentinelToken();
$this->dispatch('success', 'Token regenerated. Restarting Sentinel.');
} catch (\Throwable $e) {
return handleError($e, $this);
}
}
public function submit()
{
try {
$this->syncData(true);
$this->dispatch('success', 'Sentinel settings updated.');
} catch (\Throwable $e) {
return handleError($e, $this);
}
}
public function render()
{
return view('livewire.server.sentinel');
}
}

View file

@ -81,6 +81,8 @@ class Show extends Component
public ?int $selectedHetznerTokenId = null;
public ?string $manualHetznerServerId = null;
public ?array $matchedHetznerServer = null;
public ?string $hetznerSearchError = null;
@ -447,6 +449,10 @@ public function handleServerValidated($event = null)
// Update validation state
$this->isValidating = $this->server->is_validating ?? false;
// Reload Hetzner tokens in case the linking section should now be shown
$this->loadHetznerTokens();
$this->dispatch('refreshServerShow');
$this->dispatch('refreshServer');
}
@ -524,6 +530,47 @@ public function searchHetznerServer(): void
}
}
public function searchHetznerServerById(): void
{
$this->hetznerSearchError = null;
$this->hetznerNoMatchFound = false;
$this->matchedHetznerServer = null;
if (! $this->selectedHetznerTokenId) {
$this->hetznerSearchError = 'Please select a Hetzner token first.';
return;
}
if (! $this->manualHetznerServerId) {
$this->hetznerSearchError = 'Please enter a Hetzner Server ID.';
return;
}
try {
$this->authorize('update', $this->server);
$token = $this->availableHetznerTokens->firstWhere('id', $this->selectedHetznerTokenId);
if (! $token) {
$this->hetznerSearchError = 'Invalid token selected.';
return;
}
$hetznerService = new HetznerService($token->token);
$serverData = $hetznerService->getServer((int) $this->manualHetznerServerId);
if (! empty($serverData)) {
$this->matchedHetznerServer = $serverData;
} else {
$this->hetznerNoMatchFound = true;
}
} catch (\Throwable $e) {
$this->hetznerSearchError = 'Failed to fetch Hetzner server: '.$e->getMessage();
}
}
public function linkToHetzner()
{
if (! $this->matchedHetznerServer) {
@ -564,6 +611,7 @@ public function linkToHetzner()
// Clear the linking state
$this->matchedHetznerServer = null;
$this->selectedHetznerTokenId = null;
$this->manualHetznerServerId = null;
$this->hetznerNoMatchFound = false;
$this->hetznerSearchError = null;

View file

@ -0,0 +1,59 @@
<?php
namespace App\Livewire\Server;
use App\Models\Server;
use Illuminate\Foundation\Auth\Access\AuthorizesRequests;
use Livewire\Component;
class Swarm extends Component
{
use AuthorizesRequests;
public Server $server;
public array $parameters = [];
public bool $isSwarmManager;
public bool $isSwarmWorker;
public function mount(string $server_uuid)
{
try {
$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)
{
if ($toModel) {
$this->authorize('update', $this->server);
$this->server->settings->is_swarm_manager = $this->isSwarmManager;
$this->server->settings->is_swarm_worker = $this->isSwarmWorker;
$this->server->settings->save();
} else {
$this->isSwarmManager = $this->server->settings->is_swarm_manager;
$this->isSwarmWorker = $this->server->settings->is_swarm_worker;
}
}
public function instantSave()
{
try {
$this->syncData(true);
$this->dispatch('success', 'Swarm settings updated.');
} catch (\Throwable $e) {
return handleError($e, $this);
}
}
public function render()
{
return view('livewire.server.swarm');
}
}

View file

@ -6,6 +6,16 @@
href="{{ route('server.advanced', ['server_uuid' => $server->uuid]) }}">Advanced
</a>
@endif
@if (!$server->isBuildServer() && !$server->settings->is_cloudflare_tunnel)
<a class="menu-item {{ $activeMenu === 'swarm' ? 'menu-item-active' : '' }}" {{ wireNavigate() }}
href="{{ route('server.swarm', ['server_uuid' => $server->uuid]) }}">Swarm
</a>
@endif
@if ($server->isFunctional() && !$server->isSwarm() && !$server->isBuildServer())
<a class="menu-item {{ $activeMenu === 'sentinel' ? 'menu-item-active' : '' }}" {{ wireNavigate() }}
href="{{ route('server.sentinel', ['server_uuid' => $server->uuid]) }}">Sentinel
</a>
@endif
<a class="menu-item {{ $activeMenu === 'private-key' ? 'menu-item-active' : '' }}" {{ wireNavigate() }}
href="{{ route('server.private-key', ['server_uuid' => $server->uuid]) }}">Private Key
</a>

View file

@ -0,0 +1,110 @@
<div>
<x-slot:title>
{{ data_get_str($server, 'name')->limit(10) }} > Sentinel | Coolify
</x-slot>
<livewire:server.navbar :server="$server" />
<div class="flex flex-col h-full gap-8 sm:flex-row">
<x-server.sidebar :server="$server" activeMenu="sentinel" />
<div class="w-full">
<form wire:submit.prevent='submit'>
<div class="flex gap-2 items-center pb-2">
<h2>Sentinel</h2>
<x-helper helper="Sentinel reports your server's & container's health and collects metrics." />
@if ($server->isSentinelEnabled())
<div class="flex gap-2 items-center">
@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 class="flex flex-col gap-2">
<div class="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"
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" id="sentinelMetricsHistoryDays"
label="Metrics history (days)" required
helper="Number of days to retain metrics data for." />
<x-forms.input canGate="update" :canResource="$server"
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>

View file

@ -285,37 +285,6 @@ class="w-full input opacity-50 cursor-not-allowed"
@endif
</div>
@if (!$server->isBuildServer() && !$server->settings->is_cloudflare_tunnel)
<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'
href='https://coolify.io/docs/knowledge-base/docker/swarm'
target='_blank'>here</a>.
</div>
<div class="w-96">
@if ($server->settings->is_swarm_worker)
<x-forms.checkbox disabled instantSave type="checkbox" id="isSwarmManager"
helper="For more information, please read the documentation <a class='dark:text-white' href='https://coolify.io/docs/knowledge-base/docker/swarm' target='_blank'>here</a>."
label="Is it a Swarm Manager?" />
@else
<x-forms.checkbox canGate="update" :canResource="$server" instantSave
type="checkbox" id="isSwarmManager"
helper="For more information, please read the documentation <a class='dark:text-white' href='https://coolify.io/docs/knowledge-base/docker/swarm' target='_blank'>here</a>."
label="Is it a Swarm Manager?" :disabled="$isValidating" />
@endif
@if ($server->settings->is_swarm_manager)
<x-forms.checkbox disabled instantSave type="checkbox" id="isSwarmWorker"
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?" />
@else
<x-forms.checkbox canGate="update" :canResource="$server" instantSave
type="checkbox" id="isSwarmWorker"
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?" :disabled="$isValidating" />
@endif
</div>
@endif
@endif
</div>
</div>
@ -337,6 +306,20 @@ class="w-full input opacity-50 cursor-not-allowed"
@endforeach
</x-forms.select>
</div>
<div class="w-48">
<x-forms.input wire:model="manualHetznerServerId"
label="Server ID"
placeholder="e.g., 12345678"
helper="Enter the Hetzner Server ID from your Hetzner Cloud console"
canGate="update" :canResource="$server" />
</div>
<x-forms.button wire:click="searchHetznerServerById"
wire:loading.attr="disabled"
canGate="update" :canResource="$server">
<span wire:loading.remove wire:target="searchHetznerServerById">Search by ID</span>
<span wire:loading wire:target="searchHetznerServerById">Searching...</span>
</x-forms.button>
<div class="self-end pb-2 text-sm dark:text-neutral-500">OR</div>
<x-forms.button wire:click="searchHetznerServer"
wire:loading.attr="disabled"
canGate="update" :canResource="$server">
@ -354,10 +337,14 @@ class="w-full input opacity-50 cursor-not-allowed"
@if ($hetznerNoMatchFound)
<div class="mt-4 p-4 border border-yellow-500 rounded-md bg-yellow-50 dark:bg-yellow-900/20">
<p class="text-yellow-600 dark:text-yellow-400">
No Hetzner server found matching IP: {{ $server->ip }}
@if ($manualHetznerServerId)
No Hetzner server found with ID: {{ $manualHetznerServerId }}
@else
No Hetzner server found matching IP: {{ $server->ip }}
@endif
</p>
<p class="text-sm dark:text-neutral-400 mt-1">
Try a different token or verify the server IP is correct.
Try a different token, enter the Server ID manually, or verify the details are correct.
</p>
</div>
@endif
@ -378,116 +365,6 @@ class="w-full input opacity-50 cursor-not-allowed"
@endif
</div>
@endif
@if ($server->isFunctional() && !$server->isSwarm() && !$server->isBuildServer())
<form wire:submit.prevent='submit'>
<div class="flex gap-2 items-center pt-4 pb-2">
<h3>Sentinel</h3>
<x-helper helper="Sentinel reports your server's & container's health and collects metrics." />
@if ($server->isSentinelEnabled())
<div class="flex gap-2 items-center">
@if ($server->isSentinelLive())
<x-status.running status="In sync" noLoading title="{{ $sentinelUpdatedAt }}" />
<x-forms.button type="submit" canGate="update" :canResource="$server"
:disabled="$isValidating">Save</x-forms.button>
<x-forms.button wire:click='restartSentinel' canGate="update" :canResource="$server"
:disabled="$isValidating">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"
:disabled="$isValidating">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"
:disabled="$isValidating">Save</x-forms.button>
<x-forms.button wire:click='restartSentinel' canGate="update" :canResource="$server"
:disabled="$isValidating">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"
:disabled="$isValidating">Logs</x-forms.button>
</x-slide-over>
@endif
</div>
@endif
</div>
<div class="flex flex-col gap-2">
<div class="w-96">
<x-forms.checkbox canGate="update" :canResource="$server" wire:model.live="isSentinelEnabled"
label="Enable Sentinel" :disabled="$isValidating" />
@if ($server->isSentinelEnabled())
@if (isDev())
<x-forms.checkbox canGate="update" :canResource="$server" id="isSentinelDebugEnabled"
label="Enable Sentinel (with debug)" instantSave :disabled="$isValidating" />
@endif
<x-forms.checkbox canGate="update" :canResource="$server" instantSave
id="isMetricsEnabled" label="Enable Metrics" :disabled="$isValidating" />
@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." :disabled="$isValidating" />
<x-forms.button canGate="update" :canResource="$server"
wire:click="regenerateSentinelToken" :disabled="$isValidating">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."
:disabled="$isValidating" />
<div class="flex flex-col gap-2">
<div class="flex flex-wrap gap-2 sm:flex-nowrap">
<x-forms.input canGate="update" :canResource="$server"
id="sentinelMetricsRefreshRateSeconds" label="Metrics rate (seconds)" required
helper="Interval used for gathering metrics. Lower values result in more disk space usage."
:disabled="$isValidating" />
<x-forms.input canGate="update" :canResource="$server" id="sentinelMetricsHistoryDays"
label="Metrics history (days)" required
helper="Number of days to retain metrics data for." :disabled="$isValidating" />
<x-forms.input canGate="update" :canResource="$server"
id="sentinelPushIntervalSeconds" label="Push interval (seconds)" required
helper="Interval at which metrics data is sent to the collector."
:disabled="$isValidating" />
</div>
</div>
@endif
</div>
</form>
@endif
</div>
</div>
</div>

View file

@ -0,0 +1,43 @@
<div>
<x-slot:title>
{{ data_get_str($server, 'name')->limit(10) }} > Swarm | Coolify
</x-slot>
<livewire:server.navbar :server="$server" />
<div class="flex flex-col h-full gap-8 sm:flex-row">
<x-server.sidebar :server="$server" activeMenu="swarm" />
<div class="w-full">
<div>
<div class="flex items-center gap-2">
<h2>Swarm <span class="text-xs text-neutral-500">(experimental)</span></h2>
</div>
<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>.
</div>
</div>
<div class="w-96">
@if ($server->settings->is_swarm_worker)
<x-forms.checkbox disabled instantSave type="checkbox" id="isSwarmManager"
helper="For more information, please read the documentation <a class='dark:text-white' href='https://coolify.io/docs/knowledge-base/docker/swarm' target='_blank'>here</a>."
label="Is it a Swarm Manager?" />
@else
<x-forms.checkbox canGate="update" :canResource="$server" instantSave
type="checkbox" id="isSwarmManager"
helper="For more information, please read the documentation <a class='dark:text-white' href='https://coolify.io/docs/knowledge-base/docker/swarm' target='_blank'>here</a>."
label="Is it a Swarm Manager?" />
@endif
@if ($server->settings->is_swarm_manager)
<x-forms.checkbox disabled instantSave type="checkbox" id="isSwarmWorker"
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?" />
@else
<x-forms.checkbox canGate="update" :canResource="$server" instantSave
type="checkbox" id="isSwarmWorker"
helper="For more information, please read the documentation <a class='dark:text-white' href='https://coolify.io/docs/knowledge-base/docker/swarm' target='_blank'>here</a>."
label="Is it a Swarm Worker?" />
@endif
</div>
</div>
</div>
</div>

View file

@ -56,7 +56,9 @@
use App\Livewire\Server\Resources as ResourcesShow;
use App\Livewire\Server\Security\Patches;
use App\Livewire\Server\Security\TerminalAccess;
use App\Livewire\Server\Sentinel as ServerSentinel;
use App\Livewire\Server\Show as ServerShow;
use App\Livewire\Server\Swarm as ServerSwarm;
use App\Livewire\Settings\Advanced as SettingsAdvanced;
use App\Livewire\Settings\Index as SettingsIndex;
use App\Livewire\Settings\Updates as SettingsUpdates;
@ -251,6 +253,8 @@
Route::prefix('server/{server_uuid}')->group(function () {
Route::get('/', ServerShow::class)->name('server.show');
Route::get('/advanced', ServerAdvanced::class)->name('server.advanced');
Route::get('/swarm', ServerSwarm::class)->name('server.swarm');
Route::get('/sentinel', ServerSentinel::class)->name('server.sentinel');
Route::get('/private-key', PrivateKeyShow::class)->name('server.private-key');
Route::get('/cloud-provider-token', CloudProviderTokenShow::class)->name('server.cloud-provider-token');
Route::get('/ca-certificate', CaCertificateShow::class)->name('server.ca-certificate');