feat(sentinel): make sentinel mandatory on regular servers
Remove the enable/disable toggle from the server UI, logs page, and Sentinel API so is_sentinel_enabled is derived and read-only. Enable existing regular servers via migration, start Sentinel after validate- and-install, and drop the daily ServerManagerJob restart.
This commit is contained in:
parent
d71a72a45d
commit
424dbd36ff
23 changed files with 214 additions and 293 deletions
|
|
@ -9,6 +9,7 @@
|
|||
use App\Models\Team;
|
||||
use Illuminate\Console\Command;
|
||||
use Illuminate\Support\Carbon;
|
||||
use Illuminate\Support\Collection;
|
||||
use Illuminate\Support\Facades\Cache;
|
||||
|
||||
class ScheduledJobDiagnostics extends Command
|
||||
|
|
@ -203,7 +204,6 @@ private function inspectServerJobs(?string $serverFilter): void
|
|||
}
|
||||
|
||||
$dedupKeys = [
|
||||
"sentinel-restart:{$server->id}" => '0 0 * * *',
|
||||
"server-patch-check:{$server->id}" => '0 0 * * 0',
|
||||
"server-check:{$server->id}" => isCloud() ? '*/5 * * * *' : '* * * * *',
|
||||
"server-storage-check:{$server->id}" => data_get($server->settings, 'server_disk_usage_check_frequency', '0 23 * * *'),
|
||||
|
|
@ -235,7 +235,7 @@ private function inspectServerJobs(?string $serverFilter): void
|
|||
$this->newLine();
|
||||
}
|
||||
|
||||
private function getServers(?string $serverFilter): \Illuminate\Support\Collection
|
||||
private function getServers(?string $serverFilter): Collection
|
||||
{
|
||||
$query = Server::with('settings')->where('ip', '!=', '1.2.3.4');
|
||||
|
||||
|
|
|
|||
|
|
@ -12,7 +12,6 @@
|
|||
class ServerSentinelController extends Controller
|
||||
{
|
||||
private const ALLOWED_FIELDS = [
|
||||
'is_sentinel_enabled',
|
||||
'is_metrics_enabled',
|
||||
'is_sentinel_debug_enabled',
|
||||
'sentinel_token',
|
||||
|
|
@ -36,7 +35,7 @@ private function transform(Server $server): array
|
|||
{
|
||||
$settings = $server->settings;
|
||||
$payload = [
|
||||
'is_sentinel_enabled' => (bool) $settings->is_sentinel_enabled,
|
||||
'is_sentinel_enabled' => $server->isSentinelEnabled(),
|
||||
'is_metrics_enabled' => (bool) $settings->is_metrics_enabled,
|
||||
'is_sentinel_debug_enabled' => (bool) $settings->is_sentinel_debug_enabled,
|
||||
'sentinel_metrics_refresh_rate_seconds' => (int) $settings->sentinel_metrics_refresh_rate_seconds,
|
||||
|
|
@ -69,7 +68,7 @@ private function transform(Server $server): array
|
|||
description: 'Sentinel settings.',
|
||||
content: new OA\JsonContent(
|
||||
properties: [
|
||||
new OA\Property(property: 'is_sentinel_enabled', type: 'boolean'),
|
||||
new OA\Property(property: 'is_sentinel_enabled', type: 'boolean', readOnly: true, description: 'Sentinel is mandatory on regular managed servers.'),
|
||||
new OA\Property(property: 'is_metrics_enabled', type: 'boolean'),
|
||||
new OA\Property(property: 'is_sentinel_debug_enabled', type: 'boolean'),
|
||||
new OA\Property(property: 'sentinel_token', type: 'string', description: 'Only present with read:sensitive.'),
|
||||
|
|
@ -118,7 +117,6 @@ public function show(Request $request): JsonResponse
|
|||
required: true,
|
||||
content: new OA\JsonContent(
|
||||
properties: [
|
||||
new OA\Property(property: 'is_sentinel_enabled', type: 'boolean'),
|
||||
new OA\Property(property: 'is_metrics_enabled', type: 'boolean'),
|
||||
new OA\Property(property: 'is_sentinel_debug_enabled', type: 'boolean'),
|
||||
new OA\Property(property: 'sentinel_token', type: 'string'),
|
||||
|
|
@ -158,7 +156,6 @@ public function update(Request $request): JsonResponse
|
|||
$this->authorize('update', $server);
|
||||
|
||||
$validator = customApiValidator($request->all(), [
|
||||
'is_sentinel_enabled' => 'boolean',
|
||||
'is_metrics_enabled' => 'boolean',
|
||||
'is_sentinel_debug_enabled' => 'boolean',
|
||||
'sentinel_token' => ['string', 'max:500', 'regex:/\A[a-zA-Z0-9._\-+=\/]+\z/'],
|
||||
|
|
@ -189,29 +186,12 @@ public function update(Request $request): JsonResponse
|
|||
}
|
||||
|
||||
$settings = $server->settings;
|
||||
$enablingSentinel = $request->has('is_sentinel_enabled')
|
||||
&& $request->boolean('is_sentinel_enabled')
|
||||
&& ! $settings->is_sentinel_enabled;
|
||||
|
||||
if ($enablingSentinel && $server->isBuildServer()) {
|
||||
return response()->json([
|
||||
'message' => 'Validation failed.',
|
||||
'errors' => ['is_sentinel_enabled' => ['Sentinel cannot be enabled on build servers.']],
|
||||
], 422);
|
||||
}
|
||||
|
||||
foreach (self::ALLOWED_FIELDS as $field) {
|
||||
if ($request->has($field)) {
|
||||
$settings->{$field} = $request->input($field);
|
||||
}
|
||||
}
|
||||
|
||||
// Disabling Sentinel also clears related toggles (matches Livewire toggleSentinel).
|
||||
if ($request->has('is_sentinel_enabled') && ! $request->boolean('is_sentinel_enabled')) {
|
||||
$settings->is_metrics_enabled = false;
|
||||
$settings->is_sentinel_debug_enabled = false;
|
||||
}
|
||||
|
||||
$settings->save();
|
||||
|
||||
auditLog('api.server.sentinel.updated', [
|
||||
|
|
|
|||
|
|
@ -166,14 +166,6 @@ private function processServerTasks(Server $server): void
|
|||
}
|
||||
}
|
||||
|
||||
$isSentinelEnabled = $server->isSentinelEnabled();
|
||||
$shouldRestartSentinel = $isSentinelEnabled && shouldRunCronNow('0 0 * * *', $serverTimezone, "sentinel-restart:{$server->id}", $this->executionTime);
|
||||
// Dispatch Sentinel restart if due (daily for Sentinel-enabled servers)
|
||||
|
||||
if ($shouldRestartSentinel) {
|
||||
CheckAndStartSentinelJob::dispatch($server);
|
||||
}
|
||||
|
||||
// Dispatch ServerStorageCheckJob if due (only when Sentinel is out of sync or disabled)
|
||||
// When Sentinel is active, PushServerUpdateJob handles storage checks with real-time data
|
||||
if ($sentinelOutOfSync) {
|
||||
|
|
@ -195,7 +187,6 @@ private function processServerTasks(Server $server): void
|
|||
ServerPatchCheckJob::dispatch($server);
|
||||
}
|
||||
|
||||
// Note: CheckAndStartSentinelJob is only dispatched daily (line above) for version updates.
|
||||
// Crash recovery is handled by sentinelOutOfSync → ServerCheckJob → CheckAndStartSentinelJob.
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -202,6 +202,9 @@ public function handle(): void
|
|||
// Broadcast events to update UI
|
||||
ServerValidated::dispatch($this->server->team_id, $this->server->uuid);
|
||||
ServerReachabilityChanged::dispatch($this->server);
|
||||
if ($this->server->isSentinelEnabled()) {
|
||||
CheckAndStartSentinelJob::dispatch($this->server);
|
||||
}
|
||||
|
||||
} catch (\Throwable $e) {
|
||||
Log::error('ValidateAndInstallServer: Exception occurred', [
|
||||
|
|
|
|||
|
|
@ -2,8 +2,6 @@
|
|||
|
||||
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;
|
||||
|
|
@ -34,8 +32,6 @@ class Sentinel extends Component
|
|||
#[Validate(['nullable', 'url'])]
|
||||
public ?string $sentinelCustomUrl = null;
|
||||
|
||||
public bool $isSentinelEnabled;
|
||||
|
||||
public bool $isSentinelDebugEnabled;
|
||||
|
||||
public ?string $sentinelCustomDockerImage = null;
|
||||
|
|
@ -64,7 +60,6 @@ private function syncData(bool $toModel = false): void
|
|||
$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 {
|
||||
|
|
@ -74,7 +69,6 @@ private function syncData(bool $toModel = false): void
|
|||
$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;
|
||||
}
|
||||
|
|
@ -103,33 +97,6 @@ public function restartSentinel()
|
|||
}
|
||||
}
|
||||
|
||||
public function toggleSentinel(): void
|
||||
{
|
||||
try {
|
||||
$this->authorize('manageSentinel', $this->server);
|
||||
if (! $this->isSentinelEnabled) {
|
||||
if ($this->server->isBuildServer()) {
|
||||
$this->dispatch('error', 'Sentinel cannot be enabled on build servers.');
|
||||
|
||||
return;
|
||||
}
|
||||
$customImage = isDev() ? $this->sentinelCustomDockerImage : null;
|
||||
StartSentinel::run($this->server, true, null, $customImage);
|
||||
$this->sentinelCustomUrl = $this->server->settings->sentinel_custom_url;
|
||||
$this->isSentinelEnabled = true;
|
||||
} else {
|
||||
$this->isSentinelEnabled = false;
|
||||
$this->isMetricsEnabled = false;
|
||||
$this->isSentinelDebugEnabled = false;
|
||||
StopSentinel::dispatch($this->server);
|
||||
}
|
||||
$this->submit();
|
||||
$this->dispatch('refreshServerShow');
|
||||
} catch (\Throwable $e) {
|
||||
handleError($e, $this);
|
||||
}
|
||||
}
|
||||
|
||||
public function regenerateSentinelToken()
|
||||
{
|
||||
try {
|
||||
|
|
|
|||
|
|
@ -2,7 +2,6 @@
|
|||
|
||||
namespace App\Livewire\Server\Sentinel;
|
||||
|
||||
use App\Actions\Server\StartSentinel;
|
||||
use App\Models\Server;
|
||||
use Illuminate\Foundation\Auth\Access\AuthorizesRequests;
|
||||
use Illuminate\View\View;
|
||||
|
|
@ -30,35 +29,6 @@ public function mount(): void
|
|||
$this->authorize('viewSentinel', $this->server);
|
||||
}
|
||||
|
||||
public function enableSentinel(): void
|
||||
{
|
||||
$this->authorize('manageSentinel', $this->server);
|
||||
|
||||
try {
|
||||
$this->server->refresh();
|
||||
if ($this->server->isBuildServer()) {
|
||||
$this->dispatch('error', 'Sentinel cannot be enabled on build servers.');
|
||||
|
||||
return;
|
||||
}
|
||||
if ($this->server->isSwarm()) {
|
||||
$this->dispatch('error', 'Sentinel cannot be enabled on Swarm servers.');
|
||||
|
||||
return;
|
||||
}
|
||||
if ($this->server->isSentinelEnabled()) {
|
||||
return;
|
||||
}
|
||||
|
||||
StartSentinel::run($this->server, true);
|
||||
$this->server->refresh();
|
||||
$this->dispatch('refreshServerShow');
|
||||
$this->dispatch('success', 'Sentinel has been enabled.');
|
||||
} catch (\Throwable $e) {
|
||||
handleError($e, $this);
|
||||
}
|
||||
}
|
||||
|
||||
public function render(): View
|
||||
{
|
||||
return view('livewire.server.sentinel.logs');
|
||||
|
|
|
|||
|
|
@ -2,7 +2,6 @@
|
|||
|
||||
namespace App\Livewire\Server;
|
||||
|
||||
use App\Actions\Server\StartSentinel;
|
||||
use App\Actions\Server\StopSentinel;
|
||||
use App\Events\ServerReachabilityChanged;
|
||||
use App\Models\CloudProviderToken;
|
||||
|
|
@ -67,8 +66,6 @@ class Show extends Component
|
|||
|
||||
public ?string $sentinelCustomUrl = null;
|
||||
|
||||
public bool $isSentinelEnabled;
|
||||
|
||||
public bool $isSentinelDebugEnabled;
|
||||
|
||||
public ?string $sentinelCustomDockerImage = null;
|
||||
|
|
@ -161,7 +158,6 @@ protected function rules(): array
|
|||
'sentinelMetricsHistoryDays' => 'required|integer|min:1',
|
||||
'sentinelPushIntervalSeconds' => 'required|integer|min:10',
|
||||
'sentinelCustomUrl' => 'nullable|url',
|
||||
'isSentinelEnabled' => 'required',
|
||||
'isSentinelDebugEnabled' => 'required',
|
||||
'serverTimezone' => 'required',
|
||||
];
|
||||
|
|
@ -265,7 +261,6 @@ private function syncData(bool $toModel = false): void
|
|||
$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;
|
||||
|
||||
if (! validate_timezone($this->serverTimezone)) {
|
||||
|
|
@ -296,7 +291,6 @@ private function syncData(bool $toModel = false): void
|
|||
$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;
|
||||
$this->serverTimezone = $this->server->settings->server_timezone;
|
||||
|
|
@ -425,10 +419,10 @@ public function updatedIsBuildServer($value)
|
|||
|
||||
return;
|
||||
}
|
||||
if ($value === true && $this->isSentinelEnabled) {
|
||||
$this->isSentinelEnabled = false;
|
||||
if ($value === true && $this->server->isSentinelEnabled()) {
|
||||
$this->isMetricsEnabled = false;
|
||||
$this->isSentinelDebugEnabled = false;
|
||||
$this->server->settings->is_sentinel_enabled = false;
|
||||
StopSentinel::dispatch($this->server);
|
||||
$this->dispatch('info', 'Sentinel has been disabled as build servers cannot run Sentinel.');
|
||||
}
|
||||
|
|
@ -440,30 +434,6 @@ public function updatedIsBuildServer($value)
|
|||
}
|
||||
}
|
||||
|
||||
public function updatedIsSentinelEnabled($value)
|
||||
{
|
||||
try {
|
||||
$this->authorize('manageSentinel', $this->server);
|
||||
if ($value === true) {
|
||||
if ($this->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 {
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@
|
|||
use App\Actions\Proxy\CheckProxy;
|
||||
use App\Actions\Proxy\StartProxy;
|
||||
use App\Events\ServerValidated;
|
||||
use App\Jobs\CheckAndStartSentinelJob;
|
||||
use App\Models\Server;
|
||||
use Illuminate\Foundation\Auth\Access\AuthorizesRequests;
|
||||
use Livewire\Component;
|
||||
|
|
@ -275,6 +276,9 @@ public function validateDockerVersion()
|
|||
$this->dispatch('refreshServerShow');
|
||||
$this->dispatch('refreshBoardingIndex');
|
||||
ServerValidated::dispatch($this->server->team_id, $this->server->uuid);
|
||||
if ($this->server->isSentinelEnabled()) {
|
||||
CheckAndStartSentinelJob::dispatch($this->server);
|
||||
}
|
||||
$this->dispatch('success', 'Server validated, proxy is starting in a moment.');
|
||||
$proxyShouldRun = CheckProxy::run($this->server, true);
|
||||
if (! $proxyShouldRun) {
|
||||
|
|
|
|||
|
|
@ -972,17 +972,20 @@ public function isSentinelLive()
|
|||
return Carbon::parse($this->sentinel_updated_at)->isAfter(now()->subSeconds($this->waitBeforeDoingSshCheck()));
|
||||
}
|
||||
|
||||
public function isSentinelEnabled()
|
||||
public function isSentinelEnabled(): bool
|
||||
{
|
||||
return ($this->isMetricsEnabled() || $this->isServerApiEnabled()) && ! $this->isBuildServer();
|
||||
return ! $this->isBuildServer()
|
||||
&& ! $this->isSwarm()
|
||||
&& ! $this->isForceDisabled()
|
||||
&& ! $this->isTransferredAway();
|
||||
}
|
||||
|
||||
public function isMetricsEnabled()
|
||||
public function isMetricsEnabled(): bool
|
||||
{
|
||||
return $this->settings->is_metrics_enabled;
|
||||
}
|
||||
|
||||
public function isServerApiEnabled()
|
||||
public function isServerApiEnabled(): bool
|
||||
{
|
||||
return $this->settings->is_sentinel_enabled;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -54,7 +54,7 @@ public function claim(Server $server, bool $writeRemote = true, bool $rebindSent
|
|||
if ($rebindSentinel && $server->settings) {
|
||||
$server->settings->sentinel_custom_url = $instanceUrl;
|
||||
$server->settings->ensureValidSentinelToken();
|
||||
// Leave sentinel disabled until operator enables metrics; endpoint is ready.
|
||||
$server->settings->is_sentinel_enabled = true;
|
||||
$server->settings->save();
|
||||
$sentinelRebound = true;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,31 @@
|
|||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
|
||||
return new class extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*/
|
||||
public function up(): void
|
||||
{
|
||||
DB::table('server_settings')
|
||||
->where('is_sentinel_enabled', false)
|
||||
->where('is_build_server', false)
|
||||
->where('is_swarm_manager', false)
|
||||
->where('is_swarm_worker', false)
|
||||
->where('force_disabled', false)
|
||||
->where('is_reachable', true)
|
||||
->where('is_usable', true)
|
||||
->update(['is_sentinel_enabled' => true]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*/
|
||||
public function down(): void
|
||||
{
|
||||
// Existing values cannot be distinguished from values enabled before this migration.
|
||||
}
|
||||
};
|
||||
|
|
@ -2,6 +2,7 @@
|
|||
|
||||
namespace Database\Seeders;
|
||||
|
||||
use App\Jobs\CheckAndStartSentinelJob;
|
||||
use App\Models\Server;
|
||||
use Illuminate\Database\Seeder;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
|
|
@ -13,6 +14,10 @@ public function run()
|
|||
Server::chunk(100, function ($servers) {
|
||||
foreach ($servers as $server) {
|
||||
try {
|
||||
if ($server->isSentinelEnabled()) {
|
||||
$server->settings->is_sentinel_enabled = true;
|
||||
$server->settings->saveQuietly();
|
||||
}
|
||||
if (str($server->settings->sentinel_token)->isEmpty()) {
|
||||
$server->settings->generateSentinelToken(ignoreEvent: true);
|
||||
}
|
||||
|
|
@ -25,11 +30,10 @@ public function run()
|
|||
}
|
||||
|
||||
if (str($server->settings->sentinel_custom_url)->isEmpty()) {
|
||||
$url = $server->settings->generateSentinelUrl(ignoreEvent: true);
|
||||
if (str($url)->isEmpty()) {
|
||||
$server->settings->is_sentinel_enabled = false;
|
||||
$server->settings->save();
|
||||
}
|
||||
$server->settings->generateSentinelUrl(ignoreEvent: true);
|
||||
}
|
||||
if ($server->isFunctional() && $server->isSentinelEnabled() && filled($server->settings->sentinel_custom_url)) {
|
||||
CheckAndStartSentinelJob::dispatch($server);
|
||||
}
|
||||
} catch (\Throwable $e) {
|
||||
Log::error('Error seeding sentinel: '.$e->getMessage());
|
||||
|
|
|
|||
|
|
@ -25,7 +25,7 @@
|
|||
</a>
|
||||
</x-slot:actions>
|
||||
<x-callout type="info" title="Metrics are not enabled">
|
||||
Enable Sentinel and metrics for this server before collecting application usage data.
|
||||
Enable metrics for this server before collecting application usage data.
|
||||
</x-callout>
|
||||
</x-application.settings-section>
|
||||
@elseif (!str($resource->status)->contains('running'))
|
||||
|
|
|
|||
|
|
@ -276,14 +276,14 @@ class="server-settings-workspace application-settings-workspace mt-4 grid w-full
|
|||
@else
|
||||
<x-application.settings-section id="server-metrics-overview-section" title="Metrics"
|
||||
helper="Inspect recent CPU and memory usage reported by Sentinel.">
|
||||
<x-empty size="sm" title="Sentinel is required"
|
||||
description="Enable Sentinel before collecting CPU and memory metrics for this server."
|
||||
<x-empty size="sm" title="Metrics unavailable"
|
||||
description="Sentinel metrics are unavailable on build and Swarm servers."
|
||||
icon-name="dashboard">
|
||||
<x-slot:contents>
|
||||
<a class="button"
|
||||
href="{{ route('server.sentinel', ['server_uuid' => $server->uuid]) }}"
|
||||
{{ wireNavigate() }}>
|
||||
Configure Sentinel
|
||||
View Sentinel
|
||||
<x-external-link />
|
||||
</a>
|
||||
</x-slot:contents>
|
||||
|
|
|
|||
|
|
@ -1,44 +1,31 @@
|
|||
<div class="application-settings-form flex w-full flex-col gap-6">
|
||||
<form wire:submit.prevent="submit" class="contents">
|
||||
@if ($isSentinelEnabled)
|
||||
{{-- Scope dirty tracking to savable form fields only. Without wire:target,
|
||||
Livewire compares the entire component snapshot — so dev-only x-init
|
||||
`$wire.set('sentinelCustomDockerImage', …)` (and similar) briefly
|
||||
flashes this bar on every page open. --}}
|
||||
<x-unsaved-bar action="submit"
|
||||
targets="sentinelCustomUrl,sentinelToken,sentinelMetricsRefreshRateSeconds,sentinelMetricsHistoryDays,sentinelPushIntervalSeconds" />
|
||||
@endif
|
||||
{{-- Scope dirty tracking to savable form fields only. Without wire:target,
|
||||
Livewire compares the entire component snapshot — so dev-only x-init
|
||||
`$wire.set('sentinelCustomDockerImage', …)` (and similar) briefly
|
||||
flashes this bar on every page open. --}}
|
||||
<x-unsaved-bar action="submit"
|
||||
targets="sentinelCustomUrl,sentinelToken,sentinelMetricsRefreshRateSeconds,sentinelMetricsHistoryDays,sentinelPushIntervalSeconds" />
|
||||
|
||||
<x-application.settings-section id="server-sentinel-overview-section" title="Sentinel"
|
||||
helper="Monitor server and container health while collecting historical metrics.">
|
||||
<x-slot:actions>
|
||||
<div class="flex items-center gap-2">
|
||||
@if (!$isSentinelEnabled)
|
||||
<x-forms.button canGate="update" :canResource="$server" isHighlighted
|
||||
wire:click="toggleSentinel">
|
||||
Enable Sentinel
|
||||
</x-forms.button>
|
||||
@else
|
||||
<x-status-badge :status="$server->isSentinelLive() ? 'In sync' : 'Out of sync'"
|
||||
:type="$server->isSentinelLive() ? 'success' : 'warning'" />
|
||||
<x-forms.button wire:click="restartSentinel" canGate="update"
|
||||
:canResource="$server">
|
||||
<x-reicon name="refresh" class="size-3.5" />
|
||||
{{ $server->isSentinelLive() ? 'Restart' : 'Sync' }}
|
||||
</x-forms.button>
|
||||
<x-forms.button canGate="update" :canResource="$server"
|
||||
wire:click="toggleSentinel">
|
||||
Disable
|
||||
</x-forms.button>
|
||||
@endif
|
||||
<x-status-badge :status="$server->isSentinelLive() ? 'In sync' : 'Out of sync'"
|
||||
:type="$server->isSentinelLive() ? 'success' : 'warning'" />
|
||||
<x-forms.button wire:click="restartSentinel" canGate="update"
|
||||
:canResource="$server">
|
||||
<x-reicon name="refresh" class="size-3.5" />
|
||||
{{ $server->isSentinelLive() ? 'Restart' : 'Sync' }}
|
||||
</x-forms.button>
|
||||
</div>
|
||||
</x-slot:actions>
|
||||
|
||||
@if ($isSentinelEnabled && !$server->isSentinelLive())
|
||||
@if (!$server->isSentinelLive())
|
||||
<x-callout type="warning" title="Sentinel is out of sync">
|
||||
Sync Sentinel to apply its current configuration and restore health reporting.
|
||||
</x-callout>
|
||||
@elseif ($isSentinelEnabled)
|
||||
@else
|
||||
<div class="flex items-start gap-3">
|
||||
<div
|
||||
class="flex size-9 shrink-0 items-center justify-center rounded-lg bg-neutral-100 text-neutral-500 dark:bg-white/[0.06] dark:text-fg-dim">
|
||||
|
|
@ -51,10 +38,6 @@ class="flex size-9 shrink-0 items-center justify-center rounded-lg bg-neutral-10
|
|||
</p>
|
||||
</div>
|
||||
</div>
|
||||
@else
|
||||
<x-empty size="sm" title="Sentinel is disabled"
|
||||
description="Enable Sentinel to collect metrics and monitor server and container health."
|
||||
icon-name="dashboard" />
|
||||
@endif
|
||||
</x-application.settings-section>
|
||||
|
||||
|
|
|
|||
|
|
@ -21,14 +21,8 @@ class="logs-section-status-badge" />
|
|||
displayName="Sentinel" :collapsible="false" />
|
||||
</div>
|
||||
@else
|
||||
<x-slot:actions>
|
||||
<x-forms.button canGate="manageSentinel" :canResource="$server" isHighlighted
|
||||
wire:click="enableSentinel">
|
||||
Enable Sentinel
|
||||
</x-forms.button>
|
||||
</x-slot:actions>
|
||||
<x-empty size="sm" title="Sentinel is disabled"
|
||||
description="Enable Sentinel to view its logs."
|
||||
<x-empty size="sm" title="Sentinel is unavailable"
|
||||
description="Sentinel does not run on build or Swarm servers."
|
||||
icon-name="dashboard" />
|
||||
@endif
|
||||
</x-application.settings-section>
|
||||
|
|
|
|||
|
|
@ -12,6 +12,11 @@
|
|||
uses(RefreshDatabase::class);
|
||||
|
||||
beforeEach(function () {
|
||||
config([
|
||||
'app.maintenance.store' => 'array',
|
||||
'cache.default' => 'array',
|
||||
'cache.stores.redis.driver' => 'array',
|
||||
]);
|
||||
InstanceSettings::forceCreate(['id' => 0, 'is_api_enabled' => true]);
|
||||
|
||||
$this->team = Team::factory()->create();
|
||||
|
|
@ -234,6 +239,17 @@ function serverSubsystemsHeaders(): array
|
|||
->and((bool) $settings->is_sentinel_debug_enabled)->toBeTrue();
|
||||
});
|
||||
|
||||
test('PATCH rejects disabling mandatory Sentinel', function () {
|
||||
$this->withHeaders(serverSubsystemsHeaders())
|
||||
->patchJson("/api/v1/servers/{$this->server->uuid}/sentinel", [
|
||||
'is_sentinel_enabled' => false,
|
||||
])
|
||||
->assertUnprocessable()
|
||||
->assertJsonValidationErrors('is_sentinel_enabled');
|
||||
|
||||
expect($this->server->fresh()->isSentinelEnabled())->toBeTrue();
|
||||
});
|
||||
|
||||
test('other-team sentinel endpoints return 404', function () {
|
||||
$this->withHeaders(serverSubsystemsHeaders())
|
||||
->getJson("/api/v1/servers/{$this->otherServer->uuid}/sentinel")
|
||||
|
|
|
|||
|
|
@ -1,6 +1,5 @@
|
|||
<?php
|
||||
|
||||
use App\Jobs\CheckAndStartSentinelJob;
|
||||
use App\Models\Server;
|
||||
use App\Models\User;
|
||||
use Illuminate\Foundation\Testing\RefreshDatabase;
|
||||
|
|
@ -8,7 +7,7 @@
|
|||
|
||||
uses(RefreshDatabase::class);
|
||||
|
||||
it('does not start Sentinel after it has been disabled', function () {
|
||||
it('treats Sentinel as enabled for regular servers even when the legacy flag and metrics are disabled', function () {
|
||||
DB::table('instance_settings')->insert(['id' => 0]);
|
||||
$user = User::factory()->create();
|
||||
$server = Server::factory()->create([
|
||||
|
|
@ -19,7 +18,38 @@
|
|||
'is_sentinel_enabled' => false,
|
||||
]);
|
||||
|
||||
(new CheckAndStartSentinelJob($server))->handle();
|
||||
|
||||
expect((bool) $server->settings->fresh()->is_sentinel_enabled)->toBeFalse();
|
||||
expect($server->fresh()->isSentinelEnabled())->toBeTrue();
|
||||
});
|
||||
|
||||
it('does not enable Sentinel for excluded server types', function (array $settings, array $metadata = []) {
|
||||
DB::table('instance_settings')->insert(['id' => 0]);
|
||||
$user = User::factory()->create();
|
||||
$server = Server::factory()->create([
|
||||
'team_id' => $user->teams()->first()->id,
|
||||
'server_metadata' => $metadata,
|
||||
]);
|
||||
$server->settings->update(array_merge([
|
||||
'is_metrics_enabled' => false,
|
||||
'is_sentinel_enabled' => true,
|
||||
], $settings));
|
||||
|
||||
expect($server->fresh()->isSentinelEnabled())->toBeFalse();
|
||||
})->with([
|
||||
'build server' => [['is_build_server' => true]],
|
||||
'swarm manager' => [['is_swarm_manager' => true]],
|
||||
'swarm worker' => [['is_swarm_worker' => true]],
|
||||
'transferred server' => [[], ['transfer' => ['status' => 'transferred']]],
|
||||
'force-disabled server' => [['force_disabled' => true]],
|
||||
]);
|
||||
|
||||
it('keeps metrics optional while Sentinel remains enabled', function () {
|
||||
DB::table('instance_settings')->insert(['id' => 0]);
|
||||
$user = User::factory()->create();
|
||||
$server = Server::factory()->create([
|
||||
'team_id' => $user->teams()->first()->id,
|
||||
]);
|
||||
$server->settings->update(['is_metrics_enabled' => false]);
|
||||
|
||||
expect($server->fresh()->isSentinelEnabled())->toBeTrue()
|
||||
->and((bool) $server->settings->fresh()->is_metrics_enabled)->toBeFalse();
|
||||
});
|
||||
|
|
|
|||
|
|
@ -10,25 +10,13 @@
|
|||
->not->toContain('$this->syncData();');
|
||||
});
|
||||
|
||||
it('dispatches a server navbar refresh after toggling sentinel', function () {
|
||||
it('does not expose a Sentinel disable action', function () {
|
||||
$componentSource = file_get_contents(app_path('Livewire/Server/Sentinel.php'));
|
||||
$view = file_get_contents(resource_path('views/livewire/server/sentinel.blade.php'));
|
||||
|
||||
preg_match('/public function toggleSentinel\([^)]*\).*?\{(?<body>.*?)
|
||||
\}/s', $componentSource, $matches);
|
||||
|
||||
expect($matches['body'] ?? '')
|
||||
->toContain("\$this->dispatch('refreshServerShow');");
|
||||
});
|
||||
|
||||
it('only marks sentinel enabled after startup succeeds', function () {
|
||||
$componentSource = file_get_contents(app_path('Livewire/Server/Sentinel.php'));
|
||||
|
||||
preg_match('/public function toggleSentinel\([^)]*\).*?\{(?<body>.*?)\n \}/s', $componentSource, $matches);
|
||||
$toggleBody = $matches['body'] ?? '';
|
||||
|
||||
expect(strpos($toggleBody, 'StartSentinel::run'))->toBeLessThan(
|
||||
strpos($toggleBody, '$this->isSentinelEnabled = true;')
|
||||
);
|
||||
expect($componentSource)->not->toContain('function toggleSentinel')
|
||||
->and($view)->not->toContain('Disable')
|
||||
->and($view)->not->toContain('Enable Sentinel');
|
||||
});
|
||||
|
||||
it('does not repeat a disabled status badge in the sentinel empty state', function () {
|
||||
|
|
@ -45,3 +33,11 @@
|
|||
expect($matches['body'] ?? '')
|
||||
->toContain("\$this->dispatch('success', 'Sentinel settings updated. Restarting Sentinel.');");
|
||||
});
|
||||
|
||||
it('starts mandatory Sentinel after server validation succeeds', function () {
|
||||
$interactiveValidation = file_get_contents(app_path('Livewire/Server/ValidateAndInstall.php'));
|
||||
$queuedValidation = file_get_contents(app_path('Jobs/ValidateAndInstallServerJob.php'));
|
||||
|
||||
expect($interactiveValidation)->toContain('CheckAndStartSentinelJob::dispatch($this->server);')
|
||||
->and($queuedValidation)->toContain('CheckAndStartSentinelJob::dispatch($this->server);');
|
||||
});
|
||||
|
|
|
|||
|
|
@ -1,13 +1,11 @@
|
|||
<?php
|
||||
|
||||
use App\Actions\Server\StartSentinel;
|
||||
use App\Livewire\Project\Shared\GetLogs;
|
||||
use App\Livewire\Server\Sentinel\Logs;
|
||||
use App\Models\InstanceSettings;
|
||||
use App\Models\Server;
|
||||
use App\Models\Team;
|
||||
use App\Models\User;
|
||||
use Illuminate\Auth\Access\AuthorizationException;
|
||||
use Illuminate\Foundation\Testing\RefreshDatabase;
|
||||
use Livewire\Livewire;
|
||||
|
||||
|
|
@ -23,105 +21,36 @@
|
|||
$this->server = Server::factory()->create(['team_id' => $team->id]);
|
||||
});
|
||||
|
||||
it('does not show sync status or fetch logs when sentinel is disabled', function (bool $recentHeartbeat) {
|
||||
it('shows Sentinel status and logs when the legacy flag and metrics are disabled', function (bool $recentHeartbeat) {
|
||||
$this->server->sentinelHeartbeat(isReset: ! $recentHeartbeat);
|
||||
$this->server->settings()->update(['is_sentinel_enabled' => false, 'is_metrics_enabled' => false]);
|
||||
|
||||
Livewire::withQueryParams(['server_uuid' => $this->server->uuid])
|
||||
->test(Logs::class)
|
||||
->assertSee('Sentinel is disabled')
|
||||
->assertSeeHtml('wire:click="enableSentinel"')
|
||||
->assertDontSee('Out of sync')
|
||||
->assertDontSee('In sync')
|
||||
->assertDontSeeLivewire(GetLogs::class);
|
||||
->assertSee($recentHeartbeat ? 'In sync' : 'Out of sync')
|
||||
->assertDontSee('Enable Sentinel')
|
||||
->assertDontSee('Sentinel is disabled')
|
||||
->assertSeeLivewire(GetLogs::class);
|
||||
})->with([false, true]);
|
||||
|
||||
it('shows sync status and logs when sentinel is enabled', function (bool $metricsOnly, bool $recentHeartbeat) {
|
||||
it('shows Sentinel status independently of optional metrics', function (bool $metricsEnabled, bool $recentHeartbeat) {
|
||||
$this->server->sentinelHeartbeat(isReset: ! $recentHeartbeat);
|
||||
$this->server->settings()->update([
|
||||
'is_sentinel_enabled' => ! $metricsOnly,
|
||||
'is_metrics_enabled' => $metricsOnly,
|
||||
'is_build_server' => false,
|
||||
'is_sentinel_enabled' => false,
|
||||
'is_metrics_enabled' => $metricsEnabled,
|
||||
]);
|
||||
|
||||
Livewire::withQueryParams(['server_uuid' => $this->server->uuid])
|
||||
->test(Logs::class)
|
||||
->assertDontSee('Sentinel is disabled')
|
||||
->assertSee($recentHeartbeat ? 'In sync' : 'Out of sync')
|
||||
->assertSeeLivewire(GetLogs::class);
|
||||
})->with([false, true])->with([false, true]);
|
||||
|
||||
it('enables sentinel from the logs page', function () {
|
||||
$this->server->settings()->update(['is_sentinel_enabled' => false, 'is_metrics_enabled' => false]);
|
||||
StartSentinel::shouldRun()->once()->withArgs(function (Server $server, bool $restart): bool {
|
||||
expect($server->id)->toBe($this->server->id);
|
||||
expect($restart)->toBeTrue();
|
||||
$server->settings->update(['is_sentinel_enabled' => true]);
|
||||
|
||||
return true;
|
||||
});
|
||||
it('does not offer Sentinel controls or logs on unsupported servers', function (string $setting) {
|
||||
$this->server->settings()->update([$setting => true]);
|
||||
|
||||
Livewire::withQueryParams(['server_uuid' => $this->server->uuid])
|
||||
->test(Logs::class)
|
||||
->call('enableSentinel')
|
||||
->assertDontSee('Sentinel is disabled')
|
||||
->assertDontSee('Enable Sentinel')
|
||||
->assertSeeLivewire(GetLogs::class)
|
||||
->assertDispatched('refreshServerShow')
|
||||
->assertDispatched('success');
|
||||
|
||||
expect($this->server->fresh()->isSentinelEnabled())->toBeTrue();
|
||||
});
|
||||
|
||||
it('keeps sentinel disabled when startup fails', function () {
|
||||
$this->server->settings()->update(['is_sentinel_enabled' => false, 'is_metrics_enabled' => false]);
|
||||
StartSentinel::shouldRun()->once()->andThrow(new RuntimeException('Startup failed'));
|
||||
|
||||
Livewire::withQueryParams(['server_uuid' => $this->server->uuid])
|
||||
->test(Logs::class)
|
||||
->call('enableSentinel')
|
||||
->assertSee('Sentinel is disabled')
|
||||
->assertDontSeeLivewire(GetLogs::class)
|
||||
->assertDispatched('error')
|
||||
->assertNotDispatched('success');
|
||||
|
||||
expect($this->server->fresh()->isSentinelEnabled())->toBeFalse();
|
||||
});
|
||||
|
||||
it('does not enable sentinel on unsupported servers', function (string $setting) {
|
||||
$this->server->settings()->update(['is_sentinel_enabled' => false, 'is_metrics_enabled' => false, $setting => true]);
|
||||
StartSentinel::shouldRun()->never();
|
||||
|
||||
Livewire::withQueryParams(['server_uuid' => $this->server->uuid])
|
||||
->test(Logs::class)
|
||||
->call('enableSentinel')
|
||||
->assertSee('Sentinel is disabled')
|
||||
->assertDispatched('error');
|
||||
->assertDontSeeLivewire(GetLogs::class);
|
||||
})->with(['is_build_server', 'is_swarm_manager', 'is_swarm_worker']);
|
||||
|
||||
it('denies enabling sentinel to members and users outside the server team', function (bool $crossTeam) {
|
||||
$this->server->settings()->update(['is_sentinel_enabled' => false, 'is_metrics_enabled' => false]);
|
||||
$user = User::factory()->create();
|
||||
if (! $crossTeam) {
|
||||
$this->server->team->members()->attach($user->id, ['role' => 'member']);
|
||||
}
|
||||
$this->actingAs($user);
|
||||
StartSentinel::shouldRun()->never();
|
||||
$component = new Logs;
|
||||
$component->server = $this->server->fresh();
|
||||
|
||||
expect(fn () => $component->enableSentinel())
|
||||
->toThrow(AuthorizationException::class);
|
||||
expect($this->server->fresh()->isSentinelEnabled())->toBeFalse();
|
||||
})->with([false, true]);
|
||||
|
||||
it('does not restart sentinel when it is already enabled', function () {
|
||||
$this->server->settings()->update(['is_sentinel_enabled' => true, 'is_build_server' => false]);
|
||||
StartSentinel::shouldRun()->never();
|
||||
|
||||
Livewire::withQueryParams(['server_uuid' => $this->server->uuid])
|
||||
->test(Logs::class)
|
||||
->assertSeeLivewire(GetLogs::class)
|
||||
->call('enableSentinel')
|
||||
->assertNotDispatched('success');
|
||||
});
|
||||
|
|
|
|||
41
tests/Feature/SentinelMandatoryMigrationTest.php
Normal file
41
tests/Feature/SentinelMandatoryMigrationTest.php
Normal file
|
|
@ -0,0 +1,41 @@
|
|||
<?php
|
||||
|
||||
use App\Models\Server;
|
||||
use App\Models\User;
|
||||
use Illuminate\Foundation\Testing\RefreshDatabase;
|
||||
|
||||
uses(RefreshDatabase::class);
|
||||
|
||||
it('enables Sentinel only for existing active regular servers', function () {
|
||||
$user = User::factory()->create();
|
||||
$teamId = $user->teams()->first()->id;
|
||||
|
||||
$regularServer = Server::factory()->create(['team_id' => $teamId]);
|
||||
$regularServer->settings->update([
|
||||
'is_sentinel_enabled' => false,
|
||||
'is_reachable' => true,
|
||||
'is_usable' => true,
|
||||
]);
|
||||
|
||||
$buildServer = Server::factory()->create(['team_id' => $teamId]);
|
||||
$buildServer->settings->update([
|
||||
'is_sentinel_enabled' => false,
|
||||
'is_build_server' => true,
|
||||
'is_reachable' => true,
|
||||
'is_usable' => true,
|
||||
]);
|
||||
|
||||
$unvalidatedServer = Server::factory()->create(['team_id' => $teamId]);
|
||||
$unvalidatedServer->settings->update([
|
||||
'is_sentinel_enabled' => false,
|
||||
'is_reachable' => false,
|
||||
'is_usable' => false,
|
||||
]);
|
||||
|
||||
$migration = require database_path('migrations/2026_09_08_202212_enable_sentinel_for_existing_regular_servers.php');
|
||||
$migration->up();
|
||||
|
||||
expect((bool) $regularServer->settings->fresh()->is_sentinel_enabled)->toBeTrue()
|
||||
->and((bool) $buildServer->settings->fresh()->is_sentinel_enabled)->toBeFalse()
|
||||
->and((bool) $unvalidatedServer->settings->fresh()->is_sentinel_enabled)->toBeFalse();
|
||||
});
|
||||
|
|
@ -7,15 +7,15 @@
|
|||
Cache::flush();
|
||||
});
|
||||
|
||||
it('catches delayed sentinel restart when job runs past midnight', function () {
|
||||
Cache::put('sentinel-restart:1', Carbon::create(2026, 2, 27, 0, 0, 0, 'UTC')->toIso8601String(), 86400);
|
||||
it('catches a delayed daily job when it runs past midnight', function () {
|
||||
Cache::put('daily-job:1', Carbon::create(2026, 2, 27, 0, 0, 0, 'UTC')->toIso8601String(), 86400);
|
||||
|
||||
// Job runs 3 minutes late at 00:03
|
||||
Carbon::setTestNow(Carbon::create(2026, 2, 28, 0, 3, 0, 'UTC'));
|
||||
|
||||
// isDue() would return false at 00:03, but getPreviousRunDate() = 00:00 today
|
||||
// lastDispatched = yesterday 00:00 → today 00:00 > yesterday → fires
|
||||
$result = shouldRunCronNow('0 0 * * *', 'UTC', 'sentinel-restart:1');
|
||||
$result = shouldRunCronNow('0 0 * * *', 'UTC', 'daily-job:1');
|
||||
|
||||
expect($result)->toBeTrue();
|
||||
});
|
||||
|
|
@ -63,26 +63,26 @@
|
|||
// Step 1: 15:00 — not due for midnight cron, but seeds cache
|
||||
Carbon::setTestNow(Carbon::create(2026, 2, 28, 15, 0, 0, 'UTC'));
|
||||
|
||||
$result1 = shouldRunCronNow('0 0 * * *', 'UTC', 'sentinel-restart:seed-test');
|
||||
$result1 = shouldRunCronNow('0 0 * * *', 'UTC', 'daily-job:seed-test');
|
||||
expect($result1)->toBeFalse();
|
||||
|
||||
// Step 2: Next day at 00:05 — delayed 5 minutes past midnight
|
||||
// Catch-up: previousDue = Mar 1 00:00, lastDispatched = Feb 28 00:00 → fires
|
||||
Carbon::setTestNow(Carbon::create(2026, 3, 1, 0, 5, 0, 'UTC'));
|
||||
|
||||
$result2 = shouldRunCronNow('0 0 * * *', 'UTC', 'sentinel-restart:seed-test');
|
||||
$result2 = shouldRunCronNow('0 0 * * *', 'UTC', 'daily-job:seed-test');
|
||||
expect($result2)->toBeTrue();
|
||||
});
|
||||
|
||||
it('does not double-dispatch within same cron window', function () {
|
||||
Carbon::setTestNow(Carbon::create(2026, 2, 28, 0, 0, 0, 'UTC'));
|
||||
|
||||
$first = shouldRunCronNow('0 0 * * *', 'UTC', 'sentinel-restart:10');
|
||||
$first = shouldRunCronNow('0 0 * * *', 'UTC', 'daily-job:10');
|
||||
expect($first)->toBeTrue();
|
||||
|
||||
// Next minute — should NOT dispatch again
|
||||
Carbon::setTestNow(Carbon::create(2026, 2, 28, 0, 1, 0, 'UTC'));
|
||||
|
||||
$second = shouldRunCronNow('0 0 * * *', 'UTC', 'sentinel-restart:10');
|
||||
$second = shouldRunCronNow('0 0 * * *', 'UTC', 'daily-job:10');
|
||||
expect($second)->toBeFalse();
|
||||
});
|
||||
|
|
|
|||
|
|
@ -43,6 +43,15 @@
|
|||
Queue::assertNotPushed(CheckAndStartSentinelJob::class);
|
||||
});
|
||||
|
||||
it('does not schedule periodic Sentinel restart checks', function () {
|
||||
$root = dirname(__DIR__, 2);
|
||||
$manager = file_get_contents($root.'/app/Jobs/ServerManagerJob.php');
|
||||
$diagnostics = file_get_contents($root.'/app/Console/Commands/ScheduledJobDiagnostics.php');
|
||||
|
||||
expect($manager)->not->toContain('sentinel-restart:')
|
||||
->and($diagnostics)->not->toContain('sentinel-restart:');
|
||||
});
|
||||
|
||||
it('skips ServerConnectionCheckJob when sentinel is live', function () {
|
||||
$settings = Mockery::mock(InstanceSettings::class);
|
||||
$settings->instance_timezone = 'UTC';
|
||||
|
|
|
|||
Loading…
Reference in a new issue