feat: container metrics
This commit is contained in:
		
							parent
							
								
									439bee1203
								
							
						
					
					
						commit
						c81ad5cd03
					
				| @ -21,6 +21,6 @@ class StartSentinel | |||||||
|             "docker run --rm --pull always -d -e \"TOKEN={$token}\" -e \"SCHEDULER=true\" -e \"METRICS_HISTORY={$metrics_history}\" -e \"REFRESH_RATE={$refresh_rate}\" --name coolify-sentinel -v /var/run/docker.sock:/var/run/docker.sock -v /data/coolify/metrics:/app/metrics -v /data/coolify/logs:/app/logs --pid host --health-cmd \"curl --fail http://127.0.0.1:8888/api/health || exit 1\" --health-interval 10s --health-retries 3 ghcr.io/coollabsio/sentinel:$version", |             "docker run --rm --pull always -d -e \"TOKEN={$token}\" -e \"SCHEDULER=true\" -e \"METRICS_HISTORY={$metrics_history}\" -e \"REFRESH_RATE={$refresh_rate}\" --name coolify-sentinel -v /var/run/docker.sock:/var/run/docker.sock -v /data/coolify/metrics:/app/metrics -v /data/coolify/logs:/app/logs --pid host --health-cmd \"curl --fail http://127.0.0.1:8888/api/health || exit 1\" --health-interval 10s --health-retries 3 ghcr.io/coollabsio/sentinel:$version", | ||||||
|             'chown -R 9999:root /data/coolify/metrics /data/coolify/logs', |             'chown -R 9999:root /data/coolify/metrics /data/coolify/logs', | ||||||
|             'chmod -R 700 /data/coolify/metrics /data/coolify/logs', |             'chmod -R 700 /data/coolify/metrics /data/coolify/logs', | ||||||
|         ], $server, false); |         ], $server, true); | ||||||
|     } |     } | ||||||
| } | } | ||||||
|  | |||||||
| @ -61,7 +61,7 @@ class Kernel extends ConsoleKernel | |||||||
|     { |     { | ||||||
|         $servers = $this->all_servers->where('settings.is_usable', true)->where('settings.is_reachable', true)->where('ip', '!=', '1.2.3.4'); |         $servers = $this->all_servers->where('settings.is_usable', true)->where('settings.is_reachable', true)->where('ip', '!=', '1.2.3.4'); | ||||||
|         foreach ($servers as $server) { |         foreach ($servers as $server) { | ||||||
|             if ($server->isMetricsEnabled()) { |             if ($server->isSentinelEnabled()) { | ||||||
|                 $schedule->job(new PullSentinelImageJob($server))->everyFiveMinutes()->onOneServer(); |                 $schedule->job(new PullSentinelImageJob($server))->everyFiveMinutes()->onOneServer(); | ||||||
|             } |             } | ||||||
|             $schedule->job(new PullHelperImageJob($server))->everyFiveMinutes()->onOneServer(); |             $schedule->job(new PullHelperImageJob($server))->everyFiveMinutes()->onOneServer(); | ||||||
|  | |||||||
| @ -22,7 +22,9 @@ class DockerCleanupJob implements ShouldBeEncrypted, ShouldQueue | |||||||
| 
 | 
 | ||||||
|     public ?int $usageBefore = null; |     public ?int $usageBefore = null; | ||||||
| 
 | 
 | ||||||
|     public function __construct(public Server $server) {} |     public function __construct(public Server $server) | ||||||
|  |     { | ||||||
|  |     } | ||||||
| 
 | 
 | ||||||
|     public function handle(): void |     public function handle(): void | ||||||
|     { |     { | ||||||
|  | |||||||
| @ -28,7 +28,9 @@ class PullSentinelImageJob implements ShouldBeEncrypted, ShouldQueue | |||||||
|         return $this->server->uuid; |         return $this->server->uuid; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     public function __construct(public Server $server) {} |     public function __construct(public Server $server) | ||||||
|  |     { | ||||||
|  |     } | ||||||
| 
 | 
 | ||||||
|     public function handle(): void |     public function handle(): void | ||||||
|     { |     { | ||||||
| @ -50,7 +52,7 @@ class PullSentinelImageJob implements ShouldBeEncrypted, ShouldQueue | |||||||
|             } |             } | ||||||
|             ray('Sentinel image is up to date'); |             ray('Sentinel image is up to date'); | ||||||
|         } catch (\Throwable $e) { |         } catch (\Throwable $e) { | ||||||
|             send_internal_notification('PullSentinelImageJob failed with: '.$e->getMessage()); |             // send_internal_notification('PullSentinelImageJob failed with: '.$e->getMessage());
 | ||||||
|             ray($e->getMessage()); |             ray($e->getMessage()); | ||||||
|             throw $e; |             throw $e; | ||||||
|         } |         } | ||||||
|  | |||||||
| @ -25,7 +25,9 @@ class ServerStatusJob implements ShouldBeEncrypted, ShouldQueue | |||||||
|         return isDev() ? 1 : 3; |         return isDev() ? 1 : 3; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     public function __construct(public Server $server) {} |     public function __construct(public Server $server) | ||||||
|  |     { | ||||||
|  |     } | ||||||
| 
 | 
 | ||||||
|     public function middleware(): array |     public function middleware(): array | ||||||
|     { |     { | ||||||
| @ -46,7 +48,7 @@ class ServerStatusJob implements ShouldBeEncrypted, ShouldQueue | |||||||
|             if ($this->server->isFunctional()) { |             if ($this->server->isFunctional()) { | ||||||
|                 $this->cleanup(notify: false); |                 $this->cleanup(notify: false); | ||||||
|                 $this->remove_unnecessary_coolify_yaml(); |                 $this->remove_unnecessary_coolify_yaml(); | ||||||
|                 if ($this->server->isMetricsEnabled()) { |                 if ($this->server->isSentinelEnabled()) { | ||||||
|                     $this->server->checkSentinel(); |                     $this->server->checkSentinel(); | ||||||
|                 } |                 } | ||||||
|             } |             } | ||||||
|  | |||||||
							
								
								
									
										64
									
								
								app/Livewire/Project/Shared/Metrics.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										64
									
								
								app/Livewire/Project/Shared/Metrics.php
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,64 @@ | |||||||
|  | <?php | ||||||
|  | 
 | ||||||
|  | namespace App\Livewire\Project\Shared; | ||||||
|  | 
 | ||||||
|  | use Livewire\Component; | ||||||
|  | 
 | ||||||
|  | class Metrics extends Component | ||||||
|  | { | ||||||
|  |     public $resource; | ||||||
|  | 
 | ||||||
|  |     public $chartId = 'container-cpu'; | ||||||
|  | 
 | ||||||
|  |     public $data; | ||||||
|  | 
 | ||||||
|  |     public $categories; | ||||||
|  | 
 | ||||||
|  |     public int $interval = 5; | ||||||
|  | 
 | ||||||
|  |     public bool $poll = true; | ||||||
|  | 
 | ||||||
|  |     public function pollData() | ||||||
|  |     { | ||||||
|  |         if ($this->poll || $this->interval <= 10) { | ||||||
|  |             $this->loadData(); | ||||||
|  |             if ($this->interval > 10) { | ||||||
|  |                 $this->poll = false; | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     public function loadData() | ||||||
|  |     { | ||||||
|  |         try { | ||||||
|  |             $metrics = $this->resource->getMetrics($this->interval); | ||||||
|  |             $cpuMetrics = collect($metrics)->map(function ($metric) { | ||||||
|  |                 return [$metric[0], $metric[1]]; | ||||||
|  |             }); | ||||||
|  |             $memoryMetrics = collect($metrics)->map(function ($metric) { | ||||||
|  |                 return [$metric[0], $metric[2]]; | ||||||
|  |             }); | ||||||
|  |             $this->dispatch("refreshChartData-{$this->chartId}-cpu", [ | ||||||
|  |                 'seriesData' => $cpuMetrics, | ||||||
|  |             ]); | ||||||
|  |             $this->dispatch("refreshChartData-{$this->chartId}-memory", [ | ||||||
|  |                 'seriesData' => $memoryMetrics, | ||||||
|  |             ]); | ||||||
|  |         } catch (\Throwable $e) { | ||||||
|  |             return handleError($e, $this); | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     public function setInterval() | ||||||
|  |     { | ||||||
|  |         if ($this->interval <= 10) { | ||||||
|  |             $this->poll = true; | ||||||
|  |         } | ||||||
|  |         $this->loadData(); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     public function render() | ||||||
|  |     { | ||||||
|  |         return view('livewire.project.shared.metrics'); | ||||||
|  |     } | ||||||
|  | } | ||||||
| @ -44,6 +44,7 @@ class Form extends Component | |||||||
|         'server.settings.metrics_refresh_rate_seconds' => 'required|integer|min:1', |         'server.settings.metrics_refresh_rate_seconds' => 'required|integer|min:1', | ||||||
|         'server.settings.metrics_history_days' => 'required|integer|min:1', |         'server.settings.metrics_history_days' => 'required|integer|min:1', | ||||||
|         'wildcard_domain' => 'nullable|url', |         'wildcard_domain' => 'nullable|url', | ||||||
|  |         'server.settings.is_server_api_enabled' => 'required|boolean', | ||||||
|     ]; |     ]; | ||||||
| 
 | 
 | ||||||
|     protected $validationAttributes = [ |     protected $validationAttributes = [ | ||||||
| @ -63,7 +64,7 @@ class Form extends Component | |||||||
|         'server.settings.metrics_token' => 'Metrics Token', |         'server.settings.metrics_token' => 'Metrics Token', | ||||||
|         'server.settings.metrics_refresh_rate_seconds' => 'Metrics Interval', |         'server.settings.metrics_refresh_rate_seconds' => 'Metrics Interval', | ||||||
|         'server.settings.metrics_history_days' => 'Metrics History', |         'server.settings.metrics_history_days' => 'Metrics History', | ||||||
| 
 |         'server.settings.is_server_api_enabled' => 'Server API', | ||||||
|     ]; |     ]; | ||||||
| 
 | 
 | ||||||
|     public function mount() |     public function mount() | ||||||
| @ -85,6 +86,18 @@ class Form extends Component | |||||||
|         $this->dispatch('proxyStatusUpdated'); |         $this->dispatch('proxyStatusUpdated'); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|  |     public function checkPortForServerApi() | ||||||
|  |     { | ||||||
|  |         try { | ||||||
|  |             if ($this->server->settings->is_server_api_enabled === true) { | ||||||
|  |                 $this->server->checkServerApi(); | ||||||
|  |                 $this->dispatch('success', 'Server API is reachable.'); | ||||||
|  |             } | ||||||
|  |         } catch (\Throwable $e) { | ||||||
|  |             return handleError($e, $this); | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|     public function instantSave() |     public function instantSave() | ||||||
|     { |     { | ||||||
|         try { |         try { | ||||||
| @ -94,12 +107,22 @@ class Form extends Component | |||||||
|             $this->server->save(); |             $this->server->save(); | ||||||
|             $this->dispatch('success', 'Server updated.'); |             $this->dispatch('success', 'Server updated.'); | ||||||
|             $this->dispatch('refreshServerShow'); |             $this->dispatch('refreshServerShow'); | ||||||
|             if ($this->server->isMetricsEnabled()) { |             if ($this->server->isSentinelEnabled()) { | ||||||
|                 PullSentinelImageJob::dispatchSync($this->server); |                 PullSentinelImageJob::dispatchSync($this->server); | ||||||
|  |                 ray('Sentinel is enabled'); | ||||||
|  |                 if ($this->server->settings->isDirty('is_metrics_enabled')) { | ||||||
|                     $this->dispatch('reloadWindow'); |                     $this->dispatch('reloadWindow'); | ||||||
|  |                 } | ||||||
|  |                 if ($this->server->settings->isDirty('is_server_api_enabled') && $this->server->settings->is_server_api_enabled === true) { | ||||||
|  |                     ray('Starting sentinel'); | ||||||
|  | 
 | ||||||
|  |                 } | ||||||
|             } else { |             } else { | ||||||
|  |                 ray('Sentinel is not enabled'); | ||||||
|                 StopSentinel::dispatch($this->server); |                 StopSentinel::dispatch($this->server); | ||||||
|             } |             } | ||||||
|  |             // $this->checkPortForServerApi();
 | ||||||
|  | 
 | ||||||
|         } catch (\Throwable $e) { |         } catch (\Throwable $e) { | ||||||
|             return handleError($e, $this); |             return handleError($e, $this); | ||||||
|         } |         } | ||||||
|  | |||||||
| @ -1167,4 +1167,33 @@ class Application extends BaseModel | |||||||
| 
 | 
 | ||||||
|         return $preview; |         return $preview; | ||||||
|     } |     } | ||||||
|  | 
 | ||||||
|  |     public function getMetrics(int $mins = 5) | ||||||
|  |     { | ||||||
|  |         $server = $this->destination->server; | ||||||
|  |         $container_name = generateApplicationContainerName($this); | ||||||
|  |         if ($server->isMetricsEnabled()) { | ||||||
|  |             $from = now()->subMinutes($mins)->toIso8601ZuluString(); | ||||||
|  |             $metrics = instant_remote_process(["docker exec coolify-sentinel sh -c 'curl -H \"Authorization: Bearer {$server->settings->metrics_token}\" http://localhost:8888/api/container/{$container_name}/metrics/history?from=$from'"], $server, false); | ||||||
|  |             if (str($metrics)->contains('error')) { | ||||||
|  |                 $error = json_decode($metrics, true); | ||||||
|  |                 $error = data_get($error, 'error', 'Something is not okay, are you okay?'); | ||||||
|  |                 if ($error == 'Unauthorized') { | ||||||
|  |                     $error = 'Unauthorized, please check your metrics token or restart Sentinel to set a new token.'; | ||||||
|  |                 } | ||||||
|  |                 throw new \Exception($error); | ||||||
|  |             } | ||||||
|  |             $metrics = str($metrics)->explode("\n")->skip(1)->all(); | ||||||
|  |             $parsedCollection = collect($metrics)->flatMap(function ($item) { | ||||||
|  |                 return collect(explode("\n", trim($item)))->map(function ($line) { | ||||||
|  |                     [$time, $cpu_usage_percent, $memory_usage, $memory_usage_percent] = explode(',', trim($line)); | ||||||
|  |                     $cpu_usage_percent = number_format($cpu_usage_percent, 2); | ||||||
|  | 
 | ||||||
|  |                     return [(int) $time, (float) $cpu_usage_percent, (int) $memory_usage]; | ||||||
|  |                 }); | ||||||
|  |             }); | ||||||
|  | 
 | ||||||
|  |             return $parsedCollection->toArray(); | ||||||
|  |         } | ||||||
|  |     } | ||||||
| } | } | ||||||
|  | |||||||
| @ -9,6 +9,7 @@ use Illuminate\Database\Eloquent\Builder; | |||||||
| use Illuminate\Database\Eloquent\Casts\Attribute; | use Illuminate\Database\Eloquent\Casts\Attribute; | ||||||
| use Illuminate\Support\Collection; | use Illuminate\Support\Collection; | ||||||
| use Illuminate\Support\Facades\DB; | use Illuminate\Support\Facades\DB; | ||||||
|  | use Illuminate\Support\Facades\Process; | ||||||
| use Illuminate\Support\Facades\Storage; | use Illuminate\Support\Facades\Storage; | ||||||
| use Illuminate\Support\Str; | use Illuminate\Support\Str; | ||||||
| use Illuminate\Support\Stringable; | use Illuminate\Support\Stringable; | ||||||
| @ -460,15 +461,44 @@ $schema://$host { | |||||||
|         Storage::disk('ssh-mux')->delete($this->muxFilename()); |         Storage::disk('ssh-mux')->delete($this->muxFilename()); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|  |     public function isSentinelEnabled() | ||||||
|  |     { | ||||||
|  |         return $this->isMetricsEnabled() || $this->isServerApiEnabled(); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|     public function isMetricsEnabled() |     public function isMetricsEnabled() | ||||||
|     { |     { | ||||||
|         return $this->settings->is_metrics_enabled; |         return $this->settings->is_metrics_enabled; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|  |     public function isServerApiEnabled() | ||||||
|  |     { | ||||||
|  |         return $this->settings->is_server_api_enabled; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     public function checkServerApi() | ||||||
|  |     { | ||||||
|  |         if ($this->isServerApiEnabled()) { | ||||||
|  |             $server_ip = $this->ip; | ||||||
|  |             if (isDev()) { | ||||||
|  |                 if ($this->id === 0) { | ||||||
|  |                     $server_ip = 'localhost'; | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|  |             $command = "curl -s http://{$server_ip}:12172/api/health"; | ||||||
|  |             $process = Process::timeout(5)->run($command); | ||||||
|  |             if ($process->failed()) { | ||||||
|  |                 ray($process->exitCode(), $process->output(), $process->errorOutput()); | ||||||
|  |                 throw new \Exception("Server API is not reachable on http://{$server_ip}:12172"); | ||||||
|  |             } | ||||||
|  | 
 | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|     public function checkSentinel() |     public function checkSentinel() | ||||||
|     { |     { | ||||||
|         ray("Checking sentinel on server: {$this->name}"); |         ray("Checking sentinel on server: {$this->name}"); | ||||||
|         if ($this->isMetricsEnabled()) { |         if ($this->isSentinelEnabled()) { | ||||||
|             $sentinel_found = instant_remote_process(['docker inspect coolify-sentinel'], $this, false); |             $sentinel_found = instant_remote_process(['docker inspect coolify-sentinel'], $this, false); | ||||||
|             $sentinel_found = json_decode($sentinel_found, true); |             $sentinel_found = json_decode($sentinel_found, true); | ||||||
|             $status = data_get($sentinel_found, '0.State.Status', 'exited'); |             $status = data_get($sentinel_found, '0.State.Status', 'exited'); | ||||||
| @ -497,10 +527,10 @@ $schema://$host { | |||||||
|             $cpu = str($cpu)->explode("\n")->skip(1)->all(); |             $cpu = str($cpu)->explode("\n")->skip(1)->all(); | ||||||
|             $parsedCollection = collect($cpu)->flatMap(function ($item) { |             $parsedCollection = collect($cpu)->flatMap(function ($item) { | ||||||
|                 return collect(explode("\n", trim($item)))->map(function ($line) { |                 return collect(explode("\n", trim($item)))->map(function ($line) { | ||||||
|                     [$time, $value] = explode(',', trim($line)); |                     [$time, $cpu_usage_percent] = explode(',', trim($line)); | ||||||
|                     $value = number_format($value, 0); |                     $cpu_usage_percent = number_format($cpu_usage_percent, 0); | ||||||
| 
 | 
 | ||||||
|                     return [(int) $time, (float) $value]; |                     return [(int) $time, (float) $cpu_usage_percent]; | ||||||
|                 }); |                 }); | ||||||
|             }); |             }); | ||||||
| 
 | 
 | ||||||
|  | |||||||
| @ -226,4 +226,33 @@ class StandaloneClickhouse extends BaseModel | |||||||
|     { |     { | ||||||
|         return $this->morphMany(ScheduledDatabaseBackup::class, 'database'); |         return $this->morphMany(ScheduledDatabaseBackup::class, 'database'); | ||||||
|     } |     } | ||||||
|  | 
 | ||||||
|  |     public function getMetrics(int $mins = 5) | ||||||
|  |     { | ||||||
|  |         $server = $this->destination->server; | ||||||
|  |         $container_name = $this->uuid; | ||||||
|  |         if ($server->isMetricsEnabled()) { | ||||||
|  |             $from = now()->subMinutes($mins)->toIso8601ZuluString(); | ||||||
|  |             $metrics = instant_remote_process(["docker exec coolify-sentinel sh -c 'curl -H \"Authorization: Bearer {$server->settings->metrics_token}\" http://localhost:8888/api/container/{$container_name}/metrics/history?from=$from'"], $server, false); | ||||||
|  |             if (str($metrics)->contains('error')) { | ||||||
|  |                 $error = json_decode($metrics, true); | ||||||
|  |                 $error = data_get($error, 'error', 'Something is not okay, are you okay?'); | ||||||
|  |                 if ($error == 'Unauthorized') { | ||||||
|  |                     $error = 'Unauthorized, please check your metrics token or restart Sentinel to set a new token.'; | ||||||
|  |                 } | ||||||
|  |                 throw new \Exception($error); | ||||||
|  |             } | ||||||
|  |             $metrics = str($metrics)->explode("\n")->skip(1)->all(); | ||||||
|  |             $parsedCollection = collect($metrics)->flatMap(function ($item) { | ||||||
|  |                 return collect(explode("\n", trim($item)))->map(function ($line) { | ||||||
|  |                     [$time, $cpu_usage_percent, $memory_usage, $memory_usage_percent] = explode(',', trim($line)); | ||||||
|  |                     $cpu_usage_percent = number_format($cpu_usage_percent, 2); | ||||||
|  | 
 | ||||||
|  |                     return [(int) $time, (float) $cpu_usage_percent, (int) $memory_usage]; | ||||||
|  |                 }); | ||||||
|  |             }); | ||||||
|  | 
 | ||||||
|  |             return $parsedCollection->toArray(); | ||||||
|  |         } | ||||||
|  |     } | ||||||
| } | } | ||||||
|  | |||||||
| @ -226,4 +226,33 @@ class StandaloneDragonfly extends BaseModel | |||||||
|     { |     { | ||||||
|         return $this->morphMany(ScheduledDatabaseBackup::class, 'database'); |         return $this->morphMany(ScheduledDatabaseBackup::class, 'database'); | ||||||
|     } |     } | ||||||
|  | 
 | ||||||
|  |     public function getMetrics(int $mins = 5) | ||||||
|  |     { | ||||||
|  |         $server = $this->destination->server; | ||||||
|  |         $container_name = $this->uuid; | ||||||
|  |         if ($server->isMetricsEnabled()) { | ||||||
|  |             $from = now()->subMinutes($mins)->toIso8601ZuluString(); | ||||||
|  |             $metrics = instant_remote_process(["docker exec coolify-sentinel sh -c 'curl -H \"Authorization: Bearer {$server->settings->metrics_token}\" http://localhost:8888/api/container/{$container_name}/metrics/history?from=$from'"], $server, false); | ||||||
|  |             if (str($metrics)->contains('error')) { | ||||||
|  |                 $error = json_decode($metrics, true); | ||||||
|  |                 $error = data_get($error, 'error', 'Something is not okay, are you okay?'); | ||||||
|  |                 if ($error == 'Unauthorized') { | ||||||
|  |                     $error = 'Unauthorized, please check your metrics token or restart Sentinel to set a new token.'; | ||||||
|  |                 } | ||||||
|  |                 throw new \Exception($error); | ||||||
|  |             } | ||||||
|  |             $metrics = str($metrics)->explode("\n")->skip(1)->all(); | ||||||
|  |             $parsedCollection = collect($metrics)->flatMap(function ($item) { | ||||||
|  |                 return collect(explode("\n", trim($item)))->map(function ($line) { | ||||||
|  |                     [$time, $cpu_usage_percent, $memory_usage, $memory_usage_percent] = explode(',', trim($line)); | ||||||
|  |                     $cpu_usage_percent = number_format($cpu_usage_percent, 2); | ||||||
|  | 
 | ||||||
|  |                     return [(int) $time, (float) $cpu_usage_percent, (int) $memory_usage]; | ||||||
|  |                 }); | ||||||
|  |             }); | ||||||
|  | 
 | ||||||
|  |             return $parsedCollection->toArray(); | ||||||
|  |         } | ||||||
|  |     } | ||||||
| } | } | ||||||
|  | |||||||
| @ -226,4 +226,33 @@ class StandaloneKeydb extends BaseModel | |||||||
|     { |     { | ||||||
|         return $this->morphMany(ScheduledDatabaseBackup::class, 'database'); |         return $this->morphMany(ScheduledDatabaseBackup::class, 'database'); | ||||||
|     } |     } | ||||||
|  | 
 | ||||||
|  |     public function getMetrics(int $mins = 5) | ||||||
|  |     { | ||||||
|  |         $server = $this->destination->server; | ||||||
|  |         $container_name = $this->uuid; | ||||||
|  |         if ($server->isMetricsEnabled()) { | ||||||
|  |             $from = now()->subMinutes($mins)->toIso8601ZuluString(); | ||||||
|  |             $metrics = instant_remote_process(["docker exec coolify-sentinel sh -c 'curl -H \"Authorization: Bearer {$server->settings->metrics_token}\" http://localhost:8888/api/container/{$container_name}/metrics/history?from=$from'"], $server, false); | ||||||
|  |             if (str($metrics)->contains('error')) { | ||||||
|  |                 $error = json_decode($metrics, true); | ||||||
|  |                 $error = data_get($error, 'error', 'Something is not okay, are you okay?'); | ||||||
|  |                 if ($error == 'Unauthorized') { | ||||||
|  |                     $error = 'Unauthorized, please check your metrics token or restart Sentinel to set a new token.'; | ||||||
|  |                 } | ||||||
|  |                 throw new \Exception($error); | ||||||
|  |             } | ||||||
|  |             $metrics = str($metrics)->explode("\n")->skip(1)->all(); | ||||||
|  |             $parsedCollection = collect($metrics)->flatMap(function ($item) { | ||||||
|  |                 return collect(explode("\n", trim($item)))->map(function ($line) { | ||||||
|  |                     [$time, $cpu_usage_percent, $memory_usage, $memory_usage_percent] = explode(',', trim($line)); | ||||||
|  |                     $cpu_usage_percent = number_format($cpu_usage_percent, 2); | ||||||
|  | 
 | ||||||
|  |                     return [(int) $time, (float) $cpu_usage_percent, (int) $memory_usage]; | ||||||
|  |                 }); | ||||||
|  |             }); | ||||||
|  | 
 | ||||||
|  |             return $parsedCollection->toArray(); | ||||||
|  |         } | ||||||
|  |     } | ||||||
| } | } | ||||||
|  | |||||||
| @ -226,4 +226,33 @@ class StandaloneMariadb extends BaseModel | |||||||
|     { |     { | ||||||
|         return $this->morphMany(ScheduledDatabaseBackup::class, 'database'); |         return $this->morphMany(ScheduledDatabaseBackup::class, 'database'); | ||||||
|     } |     } | ||||||
|  | 
 | ||||||
|  |     public function getMetrics(int $mins = 5) | ||||||
|  |     { | ||||||
|  |         $server = $this->destination->server; | ||||||
|  |         $container_name = $this->uuid; | ||||||
|  |         if ($server->isMetricsEnabled()) { | ||||||
|  |             $from = now()->subMinutes($mins)->toIso8601ZuluString(); | ||||||
|  |             $metrics = instant_remote_process(["docker exec coolify-sentinel sh -c 'curl -H \"Authorization: Bearer {$server->settings->metrics_token}\" http://localhost:8888/api/container/{$container_name}/metrics/history?from=$from'"], $server, false); | ||||||
|  |             if (str($metrics)->contains('error')) { | ||||||
|  |                 $error = json_decode($metrics, true); | ||||||
|  |                 $error = data_get($error, 'error', 'Something is not okay, are you okay?'); | ||||||
|  |                 if ($error == 'Unauthorized') { | ||||||
|  |                     $error = 'Unauthorized, please check your metrics token or restart Sentinel to set a new token.'; | ||||||
|  |                 } | ||||||
|  |                 throw new \Exception($error); | ||||||
|  |             } | ||||||
|  |             $metrics = str($metrics)->explode("\n")->skip(1)->all(); | ||||||
|  |             $parsedCollection = collect($metrics)->flatMap(function ($item) { | ||||||
|  |                 return collect(explode("\n", trim($item)))->map(function ($line) { | ||||||
|  |                     [$time, $cpu_usage_percent, $memory_usage, $memory_usage_percent] = explode(',', trim($line)); | ||||||
|  |                     $cpu_usage_percent = number_format($cpu_usage_percent, 2); | ||||||
|  | 
 | ||||||
|  |                     return [(int) $time, (float) $cpu_usage_percent, (int) $memory_usage]; | ||||||
|  |                 }); | ||||||
|  |             }); | ||||||
|  | 
 | ||||||
|  |             return $parsedCollection->toArray(); | ||||||
|  |         } | ||||||
|  |     } | ||||||
| } | } | ||||||
|  | |||||||
| @ -246,4 +246,33 @@ class StandaloneMongodb extends BaseModel | |||||||
|     { |     { | ||||||
|         return $this->morphMany(ScheduledDatabaseBackup::class, 'database'); |         return $this->morphMany(ScheduledDatabaseBackup::class, 'database'); | ||||||
|     } |     } | ||||||
|  | 
 | ||||||
|  |     public function getMetrics(int $mins = 5) | ||||||
|  |     { | ||||||
|  |         $server = $this->destination->server; | ||||||
|  |         $container_name = $this->uuid; | ||||||
|  |         if ($server->isMetricsEnabled()) { | ||||||
|  |             $from = now()->subMinutes($mins)->toIso8601ZuluString(); | ||||||
|  |             $metrics = instant_remote_process(["docker exec coolify-sentinel sh -c 'curl -H \"Authorization: Bearer {$server->settings->metrics_token}\" http://localhost:8888/api/container/{$container_name}/metrics/history?from=$from'"], $server, false); | ||||||
|  |             if (str($metrics)->contains('error')) { | ||||||
|  |                 $error = json_decode($metrics, true); | ||||||
|  |                 $error = data_get($error, 'error', 'Something is not okay, are you okay?'); | ||||||
|  |                 if ($error == 'Unauthorized') { | ||||||
|  |                     $error = 'Unauthorized, please check your metrics token or restart Sentinel to set a new token.'; | ||||||
|  |                 } | ||||||
|  |                 throw new \Exception($error); | ||||||
|  |             } | ||||||
|  |             $metrics = str($metrics)->explode("\n")->skip(1)->all(); | ||||||
|  |             $parsedCollection = collect($metrics)->flatMap(function ($item) { | ||||||
|  |                 return collect(explode("\n", trim($item)))->map(function ($line) { | ||||||
|  |                     [$time, $cpu_usage_percent, $memory_usage, $memory_usage_percent] = explode(',', trim($line)); | ||||||
|  |                     $cpu_usage_percent = number_format($cpu_usage_percent, 2); | ||||||
|  | 
 | ||||||
|  |                     return [(int) $time, (float) $cpu_usage_percent, (int) $memory_usage]; | ||||||
|  |                 }); | ||||||
|  |             }); | ||||||
|  | 
 | ||||||
|  |             return $parsedCollection->toArray(); | ||||||
|  |         } | ||||||
|  |     } | ||||||
| } | } | ||||||
|  | |||||||
| @ -227,4 +227,33 @@ class StandaloneMysql extends BaseModel | |||||||
|     { |     { | ||||||
|         return $this->morphMany(ScheduledDatabaseBackup::class, 'database'); |         return $this->morphMany(ScheduledDatabaseBackup::class, 'database'); | ||||||
|     } |     } | ||||||
|  | 
 | ||||||
|  |     public function getMetrics(int $mins = 5) | ||||||
|  |     { | ||||||
|  |         $server = $this->destination->server; | ||||||
|  |         $container_name = $this->uuid; | ||||||
|  |         if ($server->isMetricsEnabled()) { | ||||||
|  |             $from = now()->subMinutes($mins)->toIso8601ZuluString(); | ||||||
|  |             $metrics = instant_remote_process(["docker exec coolify-sentinel sh -c 'curl -H \"Authorization: Bearer {$server->settings->metrics_token}\" http://localhost:8888/api/container/{$container_name}/metrics/history?from=$from'"], $server, false); | ||||||
|  |             if (str($metrics)->contains('error')) { | ||||||
|  |                 $error = json_decode($metrics, true); | ||||||
|  |                 $error = data_get($error, 'error', 'Something is not okay, are you okay?'); | ||||||
|  |                 if ($error == 'Unauthorized') { | ||||||
|  |                     $error = 'Unauthorized, please check your metrics token or restart Sentinel to set a new token.'; | ||||||
|  |                 } | ||||||
|  |                 throw new \Exception($error); | ||||||
|  |             } | ||||||
|  |             $metrics = str($metrics)->explode("\n")->skip(1)->all(); | ||||||
|  |             $parsedCollection = collect($metrics)->flatMap(function ($item) { | ||||||
|  |                 return collect(explode("\n", trim($item)))->map(function ($line) { | ||||||
|  |                     [$time, $cpu_usage_percent, $memory_usage, $memory_usage_percent] = explode(',', trim($line)); | ||||||
|  |                     $cpu_usage_percent = number_format($cpu_usage_percent, 2); | ||||||
|  | 
 | ||||||
|  |                     return [(int) $time, (float) $cpu_usage_percent, (int) $memory_usage]; | ||||||
|  |                 }); | ||||||
|  |             }); | ||||||
|  | 
 | ||||||
|  |             return $parsedCollection->toArray(); | ||||||
|  |         } | ||||||
|  |     } | ||||||
| } | } | ||||||
|  | |||||||
| @ -227,4 +227,33 @@ class StandalonePostgresql extends BaseModel | |||||||
|     { |     { | ||||||
|         return $this->morphMany(ScheduledDatabaseBackup::class, 'database'); |         return $this->morphMany(ScheduledDatabaseBackup::class, 'database'); | ||||||
|     } |     } | ||||||
|  | 
 | ||||||
|  |     public function getMetrics(int $mins = 5) | ||||||
|  |     { | ||||||
|  |         $server = $this->destination->server; | ||||||
|  |         $container_name = $this->uuid; | ||||||
|  |         if ($server->isMetricsEnabled()) { | ||||||
|  |             $from = now()->subMinutes($mins)->toIso8601ZuluString(); | ||||||
|  |             $metrics = instant_remote_process(["docker exec coolify-sentinel sh -c 'curl -H \"Authorization: Bearer {$server->settings->metrics_token}\" http://localhost:8888/api/container/{$container_name}/metrics/history?from=$from'"], $server, false); | ||||||
|  |             if (str($metrics)->contains('error')) { | ||||||
|  |                 $error = json_decode($metrics, true); | ||||||
|  |                 $error = data_get($error, 'error', 'Something is not okay, are you okay?'); | ||||||
|  |                 if ($error == 'Unauthorized') { | ||||||
|  |                     $error = 'Unauthorized, please check your metrics token or restart Sentinel to set a new token.'; | ||||||
|  |                 } | ||||||
|  |                 throw new \Exception($error); | ||||||
|  |             } | ||||||
|  |             $metrics = str($metrics)->explode("\n")->skip(1)->all(); | ||||||
|  |             $parsedCollection = collect($metrics)->flatMap(function ($item) { | ||||||
|  |                 return collect(explode("\n", trim($item)))->map(function ($line) { | ||||||
|  |                     [$time, $cpu_usage_percent, $memory_usage, $memory_usage_percent] = explode(',', trim($line)); | ||||||
|  |                     $cpu_usage_percent = number_format($cpu_usage_percent, 2); | ||||||
|  | 
 | ||||||
|  |                     return [(int) $time, (float) $cpu_usage_percent, (int) $memory_usage]; | ||||||
|  |                 }); | ||||||
|  |             }); | ||||||
|  | 
 | ||||||
|  |             return $parsedCollection->toArray(); | ||||||
|  |         } | ||||||
|  |     } | ||||||
| } | } | ||||||
|  | |||||||
| @ -222,4 +222,33 @@ class StandaloneRedis extends BaseModel | |||||||
|     { |     { | ||||||
|         return $this->morphMany(ScheduledDatabaseBackup::class, 'database'); |         return $this->morphMany(ScheduledDatabaseBackup::class, 'database'); | ||||||
|     } |     } | ||||||
|  | 
 | ||||||
|  |     public function getMetrics(int $mins = 5) | ||||||
|  |     { | ||||||
|  |         $server = $this->destination->server; | ||||||
|  |         $container_name = $this->uuid; | ||||||
|  |         if ($server->isMetricsEnabled()) { | ||||||
|  |             $from = now()->subMinutes($mins)->toIso8601ZuluString(); | ||||||
|  |             $metrics = instant_remote_process(["docker exec coolify-sentinel sh -c 'curl -H \"Authorization: Bearer {$server->settings->metrics_token}\" http://localhost:8888/api/container/{$container_name}/metrics/history?from=$from'"], $server, false); | ||||||
|  |             if (str($metrics)->contains('error')) { | ||||||
|  |                 $error = json_decode($metrics, true); | ||||||
|  |                 $error = data_get($error, 'error', 'Something is not okay, are you okay?'); | ||||||
|  |                 if ($error == 'Unauthorized') { | ||||||
|  |                     $error = 'Unauthorized, please check your metrics token or restart Sentinel to set a new token.'; | ||||||
|  |                 } | ||||||
|  |                 throw new \Exception($error); | ||||||
|  |             } | ||||||
|  |             $metrics = str($metrics)->explode("\n")->skip(1)->all(); | ||||||
|  |             $parsedCollection = collect($metrics)->flatMap(function ($item) { | ||||||
|  |                 return collect(explode("\n", trim($item)))->map(function ($line) { | ||||||
|  |                     [$time, $cpu_usage_percent, $memory_usage, $memory_usage_percent] = explode(',', trim($line)); | ||||||
|  |                     $cpu_usage_percent = number_format($cpu_usage_percent, 2); | ||||||
|  | 
 | ||||||
|  |                     return [(int) $time, (float) $cpu_usage_percent, (int) $memory_usage]; | ||||||
|  |                 }); | ||||||
|  |             }); | ||||||
|  | 
 | ||||||
|  |             return $parsedCollection->toArray(); | ||||||
|  |         } | ||||||
|  |     } | ||||||
| } | } | ||||||
|  | |||||||
| @ -161,9 +161,6 @@ function get_route_parameters(): array | |||||||
| 
 | 
 | ||||||
| function get_latest_sentinel_version(): string | function get_latest_sentinel_version(): string | ||||||
| { | { | ||||||
|     if (isDev()) { |  | ||||||
|         return '0.0.8'; |  | ||||||
|     } |  | ||||||
|     try { |     try { | ||||||
|         $response = Http::get('https://cdn.coollabs.io/sentinel/versions.json'); |         $response = Http::get('https://cdn.coollabs.io/sentinel/versions.json'); | ||||||
|         $versions = $response->json(); |         $versions = $response->json(); | ||||||
|  | |||||||
| @ -0,0 +1,28 @@ | |||||||
|  | <?php | ||||||
|  | 
 | ||||||
|  | use Illuminate\Database\Migrations\Migration; | ||||||
|  | use Illuminate\Database\Schema\Blueprint; | ||||||
|  | use Illuminate\Support\Facades\Schema; | ||||||
|  | 
 | ||||||
|  | return new class extends Migration | ||||||
|  | { | ||||||
|  |     /** | ||||||
|  |      * Run the migrations. | ||||||
|  |      */ | ||||||
|  |     public function up(): void | ||||||
|  |     { | ||||||
|  |         Schema::table('server_settings', function (Blueprint $table) { | ||||||
|  |             $table->boolean('is_server_api_enabled')->default(false); | ||||||
|  |         }); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     /** | ||||||
|  |      * Reverse the migrations. | ||||||
|  |      */ | ||||||
|  |     public function down(): void | ||||||
|  |     { | ||||||
|  |         Schema::table('server_settings', function (Blueprint $table) { | ||||||
|  |             $table->dropColumn('is_server_api_enabled'); | ||||||
|  |         }); | ||||||
|  |     } | ||||||
|  | }; | ||||||
| @ -60,6 +60,20 @@ | |||||||
|                     document.documentElement.classList.remove('dark') |                     document.documentElement.classList.remove('dark') | ||||||
|                 } |                 } | ||||||
|             } |             } | ||||||
|  |             let theme = localStorage.theme | ||||||
|  |             let baseColor = '#FCD452' | ||||||
|  |             let textColor = '#ffffff' | ||||||
|  | 
 | ||||||
|  |             function checkTheme() { | ||||||
|  |                 theme = localStorage.theme | ||||||
|  |                 if (theme == 'dark') { | ||||||
|  |                     baseColor = '#FCD452' | ||||||
|  |                     textColor = '#ffffff' | ||||||
|  |                 } else { | ||||||
|  |                     baseColor = 'black' | ||||||
|  |                     textColor = '#000000' | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|             @auth |             @auth | ||||||
|             window.Pusher = Pusher; |             window.Pusher = Pusher; | ||||||
|             window.Echo = new Echo({ |             window.Echo = new Echo({ | ||||||
|  | |||||||
| @ -1,5 +1,5 @@ | |||||||
| <div @if ($poll) wire:poll.5000ms='pollData' @endif x-init="$wire.loadData()"> | <div @if ($poll) wire:poll.5000ms='pollData' @endif x-init="$wire.loadData()"> | ||||||
|     <h3>CPU</h3> |     <h3>CPU (%)</h3> | ||||||
|     <x-forms.select label="Interval" wire:change="setInterval" id="interval"> |     <x-forms.select label="Interval" wire:change="setInterval" id="interval"> | ||||||
|         <option value="5">5 minutes (live)</option> |         <option value="5">5 minutes (live)</option> | ||||||
|         <option value="10">10 minutes (live)</option> |         <option value="10">10 minutes (live)</option> | ||||||
|  | |||||||
| @ -1,5 +1,5 @@ | |||||||
| <div @if ($poll) wire:poll.5000ms='pollData' @endif x-init="$wire.loadData()"> | <div @if ($poll) wire:poll.5000ms='pollData' @endif x-init="$wire.loadData()"> | ||||||
|     <h3>Memory</h3> |     <h3>Memory (MB)</h3> | ||||||
|     <x-forms.select label="Interval" wire:change="setInterval" id="interval"> |     <x-forms.select label="Interval" wire:change="setInterval" id="interval"> | ||||||
|         <option value="5">5 minutes (live)</option> |         <option value="5">5 minutes (live)</option> | ||||||
|         <option value="10">10 minutes (live)</option> |         <option value="10">10 minutes (live)</option> | ||||||
|  | |||||||
| @ -74,6 +74,9 @@ | |||||||
|                 @click.prevent="activeTab = 'resource-operations'; window.location.hash = 'resource-operations'" |                 @click.prevent="activeTab = 'resource-operations'; window.location.hash = 'resource-operations'" | ||||||
|                 href="#">Resource Operations |                 href="#">Resource Operations | ||||||
|             </a> |             </a> | ||||||
|  |             <a class="menu-item" :class="activeTab === 'metrics' && 'menu-item-active'" | ||||||
|  |                 @click.prevent="activeTab = 'metrics'; window.location.hash = 'metrics'" href="#">Metrics | ||||||
|  |             </a> | ||||||
|             <a class="menu-item" :class="activeTab === 'tags' && 'menu-item-active'" |             <a class="menu-item" :class="activeTab === 'tags' && 'menu-item-active'" | ||||||
|                 @click.prevent="activeTab = 'tags'; window.location.hash = 'tags'" href="#">Tags |                 @click.prevent="activeTab = 'tags'; window.location.hash = 'tags'" href="#">Tags | ||||||
|             </a> |             </a> | ||||||
| @ -126,6 +129,9 @@ | |||||||
|             <div x-cloak x-show="activeTab === 'resource-operations'"> |             <div x-cloak x-show="activeTab === 'resource-operations'"> | ||||||
|                 <livewire:project.shared.resource-operations :resource="$application" /> |                 <livewire:project.shared.resource-operations :resource="$application" /> | ||||||
|             </div> |             </div> | ||||||
|  |             <div x-cloak x-show="activeTab === 'metrics'"> | ||||||
|  |                 <livewire:project.shared.metrics :resource="$application" /> | ||||||
|  |             </div> | ||||||
|             <div x-cloak x-show="activeTab === 'tags'"> |             <div x-cloak x-show="activeTab === 'tags'"> | ||||||
|                 <livewire:project.shared.tags :resource="$application" /> |                 <livewire:project.shared.tags :resource="$application" /> | ||||||
|             </div> |             </div> | ||||||
|  | |||||||
| @ -42,6 +42,9 @@ | |||||||
|                 @click.prevent="activeTab = 'resource-operations'; window.location.hash = 'resource-operations'" |                 @click.prevent="activeTab = 'resource-operations'; window.location.hash = 'resource-operations'" | ||||||
|                 href="#">Resource Operations |                 href="#">Resource Operations | ||||||
|             </a> |             </a> | ||||||
|  |             <a class="menu-item" :class="activeTab === 'metrics' && 'menu-item-active'" | ||||||
|  |                 @click.prevent="activeTab = 'metrics'; window.location.hash = 'metrics'" href="#">Metrics | ||||||
|  |             </a> | ||||||
|             <a class="menu-item" :class="activeTab === 'tags' && 'menu-item-active'" |             <a class="menu-item" :class="activeTab === 'tags' && 'menu-item-active'" | ||||||
|                 @click.prevent="activeTab = 'tags'; window.location.hash = 'tags'" href="#">Tags |                 @click.prevent="activeTab = 'tags'; window.location.hash = 'tags'" href="#">Tags | ||||||
|             </a> |             </a> | ||||||
| @ -92,6 +95,9 @@ | |||||||
|             <div x-cloak x-show="activeTab === 'resource-operations'"> |             <div x-cloak x-show="activeTab === 'resource-operations'"> | ||||||
|                 <livewire:project.shared.resource-operations :resource="$database" /> |                 <livewire:project.shared.resource-operations :resource="$database" /> | ||||||
|             </div> |             </div> | ||||||
|  |             <div x-cloak x-show="activeTab === 'metrics'"> | ||||||
|  |                 <livewire:project.shared.metrics :resource="$database" /> | ||||||
|  |             </div> | ||||||
|             <div x-cloak x-show="activeTab === 'tags'"> |             <div x-cloak x-show="activeTab === 'tags'"> | ||||||
|                 <livewire:project.shared.tags :resource="$database" /> |                 <livewire:project.shared.tags :resource="$database" /> | ||||||
|             </div> |             </div> | ||||||
|  | |||||||
							
								
								
									
										243
									
								
								resources/views/livewire/project/shared/metrics.blade.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										243
									
								
								resources/views/livewire/project/shared/metrics.blade.php
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,243 @@ | |||||||
|  | <div> | ||||||
|  |     <div class="flex items-center gap-2 "> | ||||||
|  |         <h2>Metrics</h2> | ||||||
|  |     </div> | ||||||
|  |     <div class="pb-4">Basic metrics for your container.</div> | ||||||
|  |     @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> | ||||||
|  |     @else | ||||||
|  |         @if (!str($resource->status)->contains('running')) | ||||||
|  |             <div class="alert alert-warning">Metrics are only available when the application is running!</div> | ||||||
|  |         @else | ||||||
|  |             <x-forms.select label="Interval" wire:change="setInterval" id="interval"> | ||||||
|  |                 <option value="5">5 minutes (live)</option> | ||||||
|  |                 <option value="10">10 minutes (live)</option> | ||||||
|  |                 <option value="30">30 minutes</option> | ||||||
|  |                 <option value="60">1 hour</option> | ||||||
|  |                 <option value="720">12 hours</option> | ||||||
|  |                 <option value="10080">1 week</option> | ||||||
|  |                 <option value="43200">30 days</option> | ||||||
|  |             </x-forms.select> | ||||||
|  |             <div @if ($poll) wire:poll.5000ms='pollData' @endif x-init="$wire.loadData()" | ||||||
|  |                 class="pt-5"> | ||||||
|  |                 <h4>CPU (%)</h4> | ||||||
|  |                 <div wire:ignore id="{!! $chartId !!}-cpu"></div> | ||||||
|  | 
 | ||||||
|  |                 <script> | ||||||
|  |                     checkTheme(); | ||||||
|  |                     const optionsServerCpu = { | ||||||
|  |                         stroke: { | ||||||
|  |                             curve: 'straight', | ||||||
|  |                         }, | ||||||
|  |                         chart: { | ||||||
|  |                             height: '150px', | ||||||
|  |                             id: '{!! $chartId !!}-cpu', | ||||||
|  |                             type: 'area', | ||||||
|  |                             toolbar: { | ||||||
|  |                                 show: false, | ||||||
|  |                                 tools: { | ||||||
|  |                                     download: true, | ||||||
|  |                                     selection: false, | ||||||
|  |                                     zoom: false, | ||||||
|  |                                     zoomin: false, | ||||||
|  |                                     zoomout: false, | ||||||
|  |                                     pan: false, | ||||||
|  |                                     reset: false | ||||||
|  |                                 }, | ||||||
|  |                             }, | ||||||
|  |                             animations: { | ||||||
|  |                                 enabled: false, | ||||||
|  |                             }, | ||||||
|  |                         }, | ||||||
|  |                         fill: { | ||||||
|  |                             type: 'gradient', | ||||||
|  |                         }, | ||||||
|  |                         dataLabels: { | ||||||
|  |                             enabled: false, | ||||||
|  |                             offsetY: -10, | ||||||
|  |                             style: { | ||||||
|  |                                 colors: ['#FCD452'], | ||||||
|  |                             }, | ||||||
|  |                             background: { | ||||||
|  |                                 enabled: false, | ||||||
|  |                             } | ||||||
|  |                         }, | ||||||
|  |                         grid: { | ||||||
|  |                             show: true, | ||||||
|  |                             borderColor: '', | ||||||
|  |                         }, | ||||||
|  |                         colors: [baseColor], | ||||||
|  |                         xaxis: { | ||||||
|  |                             type: 'datetime', | ||||||
|  |                         }, | ||||||
|  |                         series: [{ | ||||||
|  |                             data: [] | ||||||
|  |                         }], | ||||||
|  |                         noData: { | ||||||
|  |                             text: 'Loading...', | ||||||
|  |                             style: { | ||||||
|  |                                 color: textColor, | ||||||
|  |                             } | ||||||
|  |                         }, | ||||||
|  |                         tooltip: { | ||||||
|  |                             enabled: false, | ||||||
|  |                         }, | ||||||
|  |                         legend: { | ||||||
|  |                             show: false | ||||||
|  |                         } | ||||||
|  |                     } | ||||||
|  |                     const serverCpuChart = new ApexCharts(document.getElementById(`{!! $chartId !!}-cpu`), optionsServerCpu); | ||||||
|  |                     serverCpuChart.render(); | ||||||
|  |                     document.addEventListener('livewire:init', () => { | ||||||
|  |                         Livewire.on('refreshChartData-{!! $chartId !!}-cpu', (chartData) => { | ||||||
|  |                             checkTheme(); | ||||||
|  |                             serverCpuChart.updateOptions({ | ||||||
|  |                                 series: [{ | ||||||
|  |                                     data: chartData[0].seriesData, | ||||||
|  |                                 }], | ||||||
|  |                                 colors: [baseColor], | ||||||
|  |                                 xaxis: { | ||||||
|  |                                     type: 'datetime', | ||||||
|  |                                     labels: { | ||||||
|  |                                         show: true, | ||||||
|  |                                         style: { | ||||||
|  |                                             colors: textColor, | ||||||
|  |                                         } | ||||||
|  |                                     } | ||||||
|  |                                 }, | ||||||
|  |                                 yaxis: { | ||||||
|  |                                     show: true, | ||||||
|  |                                     labels: { | ||||||
|  |                                         show: true, | ||||||
|  |                                         style: { | ||||||
|  |                                             colors: textColor, | ||||||
|  |                                         } | ||||||
|  |                                     } | ||||||
|  |                                 }, | ||||||
|  |                                 noData: { | ||||||
|  |                                     text: 'Loading...', | ||||||
|  |                                     style: { | ||||||
|  |                                         color: textColor, | ||||||
|  |                                     } | ||||||
|  |                                 } | ||||||
|  |                             }); | ||||||
|  |                         }); | ||||||
|  |                     }); | ||||||
|  |                 </script> | ||||||
|  | 
 | ||||||
|  |                 <h3>Memory (MB)</h3> | ||||||
|  |                 <div wire:ignore id="{!! $chartId !!}-memory"></div> | ||||||
|  | 
 | ||||||
|  |                 <script> | ||||||
|  |                     checkTheme(); | ||||||
|  |                     const optionsServerMemory = { | ||||||
|  |                         stroke: { | ||||||
|  |                             curve: 'straight', | ||||||
|  |                         }, | ||||||
|  |                         chart: { | ||||||
|  |                             height: '150px', | ||||||
|  |                             id: '{!! $chartId !!}-memory', | ||||||
|  |                             type: 'area', | ||||||
|  |                             toolbar: { | ||||||
|  |                                 show: false, | ||||||
|  |                                 tools: { | ||||||
|  |                                     download: true, | ||||||
|  |                                     selection: false, | ||||||
|  |                                     zoom: false, | ||||||
|  |                                     zoomin: false, | ||||||
|  |                                     zoomout: false, | ||||||
|  |                                     pan: false, | ||||||
|  |                                     reset: false | ||||||
|  |                                 }, | ||||||
|  |                             }, | ||||||
|  |                             animations: { | ||||||
|  |                                 enabled: false, | ||||||
|  |                             }, | ||||||
|  |                         }, | ||||||
|  |                         fill: { | ||||||
|  |                             type: 'gradient', | ||||||
|  |                         }, | ||||||
|  |                         dataLabels: { | ||||||
|  |                             enabled: false, | ||||||
|  |                             offsetY: -10, | ||||||
|  |                             style: { | ||||||
|  |                                 colors: ['#FCD452'], | ||||||
|  |                             }, | ||||||
|  |                             background: { | ||||||
|  |                                 enabled: false, | ||||||
|  |                             } | ||||||
|  |                         }, | ||||||
|  |                         grid: { | ||||||
|  |                             show: true, | ||||||
|  |                             borderColor: '', | ||||||
|  |                         }, | ||||||
|  |                         colors: [baseColor], | ||||||
|  |                         xaxis: { | ||||||
|  |                             type: 'datetime', | ||||||
|  |                             labels: { | ||||||
|  |                                 show: true, | ||||||
|  |                                 style: { | ||||||
|  |                                     colors: textColor, | ||||||
|  |                                 } | ||||||
|  |                             } | ||||||
|  |                         }, | ||||||
|  |                         series: [{ | ||||||
|  |                             data: [] | ||||||
|  |                         }], | ||||||
|  |                         noData: { | ||||||
|  |                             text: 'Loading...', | ||||||
|  |                             style: { | ||||||
|  |                                 color: textColor, | ||||||
|  |                             } | ||||||
|  |                         }, | ||||||
|  |                         tooltip: { | ||||||
|  |                             enabled: false, | ||||||
|  |                         }, | ||||||
|  |                         legend: { | ||||||
|  |                             show: false | ||||||
|  |                         } | ||||||
|  |                     } | ||||||
|  |                     const serverMemoryChart = new ApexCharts(document.getElementById(`{!! $chartId !!}-memory`), | ||||||
|  |                         optionsServerMemory); | ||||||
|  |                     serverMemoryChart.render(); | ||||||
|  |                     document.addEventListener('livewire:init', () => { | ||||||
|  |                         Livewire.on('refreshChartData-{!! $chartId !!}-memory', (chartData) => { | ||||||
|  |                             checkTheme(); | ||||||
|  |                             serverMemoryChart.updateOptions({ | ||||||
|  |                                 series: [{ | ||||||
|  |                                     data: chartData[0].seriesData, | ||||||
|  |                                 }], | ||||||
|  |                                 colors: [baseColor], | ||||||
|  |                                 xaxis: { | ||||||
|  |                                     type: 'datetime', | ||||||
|  |                                     labels: { | ||||||
|  |                                         show: true, | ||||||
|  |                                         style: { | ||||||
|  |                                             colors: textColor, | ||||||
|  |                                         } | ||||||
|  |                                     } | ||||||
|  |                                 }, | ||||||
|  |                                 yaxis: { | ||||||
|  |                                     min: 0, | ||||||
|  |                                     show: true, | ||||||
|  |                                     labels: { | ||||||
|  |                                         show: true, | ||||||
|  |                                         style: { | ||||||
|  |                                             colors: textColor, | ||||||
|  |                                         } | ||||||
|  |                                     } | ||||||
|  |                                 }, | ||||||
|  |                                 noData: { | ||||||
|  |                                     text: 'Loading...', | ||||||
|  |                                     style: { | ||||||
|  |                                         color: textColor, | ||||||
|  |                                     } | ||||||
|  |                                 } | ||||||
|  |                             }); | ||||||
|  |                         }); | ||||||
|  |                     }); | ||||||
|  |                 </script> | ||||||
|  |             </div> | ||||||
|  |         @endif | ||||||
|  |     @endif | ||||||
|  | </div> | ||||||
| @ -145,13 +145,16 @@ | |||||||
|                     helper="You can define the maximum duration for a deployment to run before timing it out." /> |                     helper="You can define the maximum duration for a deployment to run before timing it out." /> | ||||||
|             </div> |             </div> | ||||||
|             <div class="flex items-center gap-2"> |             <div class="flex items-center gap-2"> | ||||||
|                 <h3 class="py-4">Metrics</h3> |                 <h3 class="py-4">Sentinel</h3> | ||||||
|                 @if ($server->isMetricsEnabled()) |                 @if ($server->isSentinelEnabled()) | ||||||
|                     <x-forms.button wire:click='restartSentinel'>Restart Collector</x-forms.button> |                     <x-forms.button wire:click='restartSentinel'>Restart</x-forms.button> | ||||||
|                 @endif |                 @endif | ||||||
|             </div> |             </div> | ||||||
|             <div class="w-64"> |             <div class="w-64"> | ||||||
|                 <x-forms.checkbox instantSave id="server.settings.is_metrics_enabled" label="Enable metrics" /> |                 <x-forms.checkbox instantSave id="server.settings.is_metrics_enabled" label="Enable Metrics" /> | ||||||
|  |                 {{-- <x-forms.checkbox instantSave id="server.settings.is_server_api_enabled" label="Enable Server API" | ||||||
|  |                     helper="You need to open port 12172 on your firewall. This API will be used to gather data from your server, which makes Coolify a lot faster than relying on SSH connections." /> | ||||||
|  |                 <x-forms.button wire:click='checkPortForServerApi'>Check Port for Server API</x-forms.button> --}} | ||||||
|             </div> |             </div> | ||||||
|             <div class="pt-4"> |             <div class="pt-4"> | ||||||
|                 <div class="flex flex-wrap gap-2 sm:flex-nowrap"> |                 <div class="flex flex-wrap gap-2 sm:flex-nowrap"> | ||||||
|  | |||||||
| @ -7,22 +7,6 @@ | |||||||
|     <livewire:server.delete :server="$server" /> |     <livewire:server.delete :server="$server" /> | ||||||
|     @if ($server->isFunctional() && $server->isMetricsEnabled()) |     @if ($server->isFunctional() && $server->isMetricsEnabled()) | ||||||
|         <div class="pt-10"> |         <div class="pt-10"> | ||||||
|             <script> |  | ||||||
|                 let theme = localStorage.theme |  | ||||||
|                 let baseColor = '#FCD452' |  | ||||||
|                 let textColor = '#ffffff' |  | ||||||
| 
 |  | ||||||
|                 function checkTheme() { |  | ||||||
|                     theme = localStorage.theme |  | ||||||
|                     if (theme == 'dark') { |  | ||||||
|                         baseColor = '#FCD452' |  | ||||||
|                         textColor = '#ffffff' |  | ||||||
|                     } else { |  | ||||||
|                         baseColor = 'black' |  | ||||||
|                         textColor = '#000000' |  | ||||||
|                     } |  | ||||||
|                 } |  | ||||||
|             </script> |  | ||||||
|             <livewire:charts.server-cpu :server="$server" /> |             <livewire:charts.server-cpu :server="$server" /> | ||||||
|             <livewire:charts.server-memory :server="$server" /> |             <livewire:charts.server-memory :server="$server" /> | ||||||
|         </div> |         </div> | ||||||
|  | |||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user