fix: prevent metric charts from freezing on page navigation (#7848)
This commit is contained in:
commit
b448b08058
16 changed files with 568 additions and 943 deletions
|
|
@ -6,6 +6,7 @@
|
|||
use App\Services\ConfigurationGenerator;
|
||||
use App\Traits\ClearsGlobalSearchCache;
|
||||
use App\Traits\HasConfiguration;
|
||||
use App\Traits\HasMetrics;
|
||||
use App\Traits\HasSafeStringAttribute;
|
||||
use Illuminate\Database\Eloquent\Casts\Attribute;
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
|
|
@ -111,7 +112,7 @@
|
|||
|
||||
class Application extends BaseModel
|
||||
{
|
||||
use ClearsGlobalSearchCache, HasConfiguration, HasFactory, HasSafeStringAttribute, SoftDeletes;
|
||||
use ClearsGlobalSearchCache, HasConfiguration, HasFactory, HasMetrics, HasSafeStringAttribute, SoftDeletes;
|
||||
|
||||
private static $parserVersion = '5';
|
||||
|
||||
|
|
@ -1977,54 +1978,6 @@ public static function getDomainsByUuid(string $uuid): array
|
|||
return [];
|
||||
}
|
||||
|
||||
public function getCpuMetrics(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->sentinel_token}\" http://localhost:8888/api/container/{$container_name}/cpu/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 = json_decode($metrics, true);
|
||||
$parsedCollection = collect($metrics)->map(function ($metric) {
|
||||
return [(int) $metric['time'], (float) $metric['percent']];
|
||||
});
|
||||
|
||||
return $parsedCollection->toArray();
|
||||
}
|
||||
}
|
||||
|
||||
public function getMemoryMetrics(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->sentinel_token}\" http://localhost:8888/api/container/{$container_name}/memory/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 = json_decode($metrics, true);
|
||||
$parsedCollection = collect($metrics)->map(function ($metric) {
|
||||
return [(int) $metric['time'], (float) $metric['used']];
|
||||
});
|
||||
|
||||
return $parsedCollection->toArray();
|
||||
}
|
||||
}
|
||||
|
||||
public function getLimits(): array
|
||||
{
|
||||
return [
|
||||
|
|
|
|||
|
|
@ -16,6 +16,7 @@
|
|||
use App\Notifications\Server\Unreachable;
|
||||
use App\Services\ConfigurationRepository;
|
||||
use App\Traits\ClearsGlobalSearchCache;
|
||||
use App\Traits\HasMetrics;
|
||||
use App\Traits\HasSafeStringAttribute;
|
||||
use Illuminate\Database\Eloquent\Builder;
|
||||
use Illuminate\Database\Eloquent\Casts\Attribute;
|
||||
|
|
@ -103,7 +104,7 @@
|
|||
|
||||
class Server extends BaseModel
|
||||
{
|
||||
use ClearsGlobalSearchCache, HasFactory, SchemalessAttributesTrait, SoftDeletes;
|
||||
use ClearsGlobalSearchCache, HasFactory, HasMetrics, SchemalessAttributesTrait, SoftDeletes;
|
||||
|
||||
public static $batch_counter = 0;
|
||||
|
||||
|
|
@ -667,141 +668,6 @@ public function checkSentinel()
|
|||
CheckAndStartSentinelJob::dispatch($this);
|
||||
}
|
||||
|
||||
public function getCpuMetrics(int $mins = 5)
|
||||
{
|
||||
if ($this->isMetricsEnabled()) {
|
||||
$from = now()->subMinutes($mins)->toIso8601ZuluString();
|
||||
$cpu = instant_remote_process(["docker exec coolify-sentinel sh -c 'curl -H \"Authorization: Bearer {$this->settings->sentinel_token}\" http://localhost:8888/api/cpu/history?from=$from'"], $this, false);
|
||||
if (str($cpu)->contains('error')) {
|
||||
$error = json_decode($cpu, 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);
|
||||
}
|
||||
$cpu = json_decode($cpu, true);
|
||||
|
||||
$metrics = collect($cpu)->map(function ($metric) {
|
||||
return [(int) $metric['time'], (float) $metric['percent']];
|
||||
})->toArray();
|
||||
|
||||
// Downsample for intervals > 60 minutes to prevent browser freeze
|
||||
if ($mins > 60 && count($metrics) > 1000) {
|
||||
$metrics = $this->downsampleLTTB($metrics, 1000);
|
||||
}
|
||||
|
||||
return collect($metrics);
|
||||
}
|
||||
}
|
||||
|
||||
public function getMemoryMetrics(int $mins = 5)
|
||||
{
|
||||
if ($this->isMetricsEnabled()) {
|
||||
$from = now()->subMinutes($mins)->toIso8601ZuluString();
|
||||
$memory = instant_remote_process(["docker exec coolify-sentinel sh -c 'curl -H \"Authorization: Bearer {$this->settings->sentinel_token}\" http://localhost:8888/api/memory/history?from=$from'"], $this, false);
|
||||
if (str($memory)->contains('error')) {
|
||||
$error = json_decode($memory, 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);
|
||||
}
|
||||
$memory = json_decode($memory, true);
|
||||
$metrics = collect($memory)->map(function ($metric) {
|
||||
$usedPercent = $metric['usedPercent'] ?? 0.0;
|
||||
|
||||
return [(int) $metric['time'], (float) $usedPercent];
|
||||
})->toArray();
|
||||
|
||||
// Downsample for intervals > 60 minutes to prevent browser freeze
|
||||
if ($mins > 60 && count($metrics) > 1000) {
|
||||
$metrics = $this->downsampleLTTB($metrics, 1000);
|
||||
}
|
||||
|
||||
return collect($metrics);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Downsample metrics using the Largest-Triangle-Three-Buckets (LTTB) algorithm.
|
||||
* This preserves the visual shape of the data better than simple averaging.
|
||||
*
|
||||
* @param array $data Array of [timestamp, value] pairs
|
||||
* @param int $threshold Target number of points
|
||||
* @return array Downsampled data
|
||||
*/
|
||||
private function downsampleLTTB(array $data, int $threshold): array
|
||||
{
|
||||
$dataLength = count($data);
|
||||
|
||||
// Return unchanged if threshold >= data length, or if threshold <= 2
|
||||
// (threshold <= 2 would cause division by zero in bucket calculation)
|
||||
if ($threshold >= $dataLength || $threshold <= 2) {
|
||||
return $data;
|
||||
}
|
||||
|
||||
$sampled = [];
|
||||
$sampled[] = $data[0]; // Always keep first point
|
||||
|
||||
$bucketSize = ($dataLength - 2) / ($threshold - 2);
|
||||
|
||||
$a = 0; // Index of previous selected point
|
||||
|
||||
for ($i = 0; $i < $threshold - 2; $i++) {
|
||||
// Calculate bucket range
|
||||
$bucketStart = (int) floor(($i + 1) * $bucketSize) + 1;
|
||||
$bucketEnd = (int) floor(($i + 2) * $bucketSize) + 1;
|
||||
$bucketEnd = min($bucketEnd, $dataLength - 1);
|
||||
|
||||
// Calculate average point for next bucket (used as reference)
|
||||
$nextBucketStart = (int) floor(($i + 2) * $bucketSize) + 1;
|
||||
$nextBucketEnd = (int) floor(($i + 3) * $bucketSize) + 1;
|
||||
$nextBucketEnd = min($nextBucketEnd, $dataLength - 1);
|
||||
|
||||
$avgX = 0;
|
||||
$avgY = 0;
|
||||
$nextBucketCount = $nextBucketEnd - $nextBucketStart + 1;
|
||||
|
||||
if ($nextBucketCount > 0) {
|
||||
for ($j = $nextBucketStart; $j <= $nextBucketEnd; $j++) {
|
||||
$avgX += $data[$j][0];
|
||||
$avgY += $data[$j][1];
|
||||
}
|
||||
$avgX /= $nextBucketCount;
|
||||
$avgY /= $nextBucketCount;
|
||||
}
|
||||
|
||||
// Find point in current bucket with largest triangle area
|
||||
$maxArea = -1;
|
||||
$maxAreaIndex = $bucketStart;
|
||||
|
||||
$pointAX = $data[$a][0];
|
||||
$pointAY = $data[$a][1];
|
||||
|
||||
for ($j = $bucketStart; $j <= $bucketEnd; $j++) {
|
||||
// Triangle area calculation
|
||||
$area = abs(
|
||||
($pointAX - $avgX) * ($data[$j][1] - $pointAY) -
|
||||
($pointAX - $data[$j][0]) * ($avgY - $pointAY)
|
||||
) * 0.5;
|
||||
|
||||
if ($area > $maxArea) {
|
||||
$maxArea = $area;
|
||||
$maxAreaIndex = $j;
|
||||
}
|
||||
}
|
||||
|
||||
$sampled[] = $data[$maxAreaIndex];
|
||||
$a = $maxAreaIndex;
|
||||
}
|
||||
|
||||
$sampled[] = $data[$dataLength - 1]; // Always keep last point
|
||||
|
||||
return $sampled;
|
||||
}
|
||||
|
||||
public function getDiskUsage(): ?string
|
||||
{
|
||||
return instant_remote_process(['df / --output=pcent | tr -cd 0-9'], $this, false);
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@
|
|||
namespace App\Models;
|
||||
|
||||
use App\Traits\ClearsGlobalSearchCache;
|
||||
use App\Traits\HasMetrics;
|
||||
use App\Traits\HasSafeStringAttribute;
|
||||
use Illuminate\Database\Eloquent\Casts\Attribute;
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
|
|
@ -10,7 +11,7 @@
|
|||
|
||||
class StandaloneClickhouse extends BaseModel
|
||||
{
|
||||
use ClearsGlobalSearchCache, HasFactory, HasSafeStringAttribute, SoftDeletes;
|
||||
use ClearsGlobalSearchCache, HasFactory, HasMetrics, HasSafeStringAttribute, SoftDeletes;
|
||||
|
||||
protected $guarded = [];
|
||||
|
||||
|
|
@ -320,50 +321,6 @@ public function scheduledBackups()
|
|||
return $this->morphMany(ScheduledDatabaseBackup::class, 'database');
|
||||
}
|
||||
|
||||
public function getCpuMetrics(int $mins = 5)
|
||||
{
|
||||
$server = $this->destination->server;
|
||||
$container_name = $this->uuid;
|
||||
$from = now()->subMinutes($mins)->toIso8601ZuluString();
|
||||
$metrics = instant_remote_process(["docker exec coolify-sentinel sh -c 'curl -H \"Authorization: Bearer {$server->settings->sentinel_token}\" http://localhost:8888/api/container/{$container_name}/cpu/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 = json_decode($metrics, true);
|
||||
$parsedCollection = collect($metrics)->map(function ($metric) {
|
||||
return [(int) $metric['time'], (float) $metric['percent']];
|
||||
});
|
||||
|
||||
return $parsedCollection->toArray();
|
||||
}
|
||||
|
||||
public function getMemoryMetrics(int $mins = 5)
|
||||
{
|
||||
$server = $this->destination->server;
|
||||
$container_name = $this->uuid;
|
||||
$from = now()->subMinutes($mins)->toIso8601ZuluString();
|
||||
$metrics = instant_remote_process(["docker exec coolify-sentinel sh -c 'curl -H \"Authorization: Bearer {$server->settings->sentinel_token}\" http://localhost:8888/api/container/{$container_name}/memory/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 = json_decode($metrics, true);
|
||||
$parsedCollection = collect($metrics)->map(function ($metric) {
|
||||
return [(int) $metric['time'], (float) $metric['used']];
|
||||
});
|
||||
|
||||
return $parsedCollection->toArray();
|
||||
}
|
||||
|
||||
public function isBackupSolutionAvailable()
|
||||
{
|
||||
return false;
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@
|
|||
namespace App\Models;
|
||||
|
||||
use App\Traits\ClearsGlobalSearchCache;
|
||||
use App\Traits\HasMetrics;
|
||||
use App\Traits\HasSafeStringAttribute;
|
||||
use Illuminate\Database\Eloquent\Casts\Attribute;
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
|
|
@ -10,7 +11,7 @@
|
|||
|
||||
class StandaloneDragonfly extends BaseModel
|
||||
{
|
||||
use ClearsGlobalSearchCache, HasFactory, HasSafeStringAttribute, SoftDeletes;
|
||||
use ClearsGlobalSearchCache, HasFactory, HasMetrics, HasSafeStringAttribute, SoftDeletes;
|
||||
|
||||
protected $guarded = [];
|
||||
|
||||
|
|
@ -316,50 +317,6 @@ public function scheduledBackups()
|
|||
return $this->morphMany(ScheduledDatabaseBackup::class, 'database');
|
||||
}
|
||||
|
||||
public function getCpuMetrics(int $mins = 5)
|
||||
{
|
||||
$server = $this->destination->server;
|
||||
$container_name = $this->uuid;
|
||||
$from = now()->subMinutes($mins)->toIso8601ZuluString();
|
||||
$metrics = instant_remote_process(["docker exec coolify-sentinel sh -c 'curl -H \"Authorization: Bearer {$server->settings->sentinel_token}\" http://localhost:8888/api/container/{$container_name}/cpu/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 = json_decode($metrics, true);
|
||||
$parsedCollection = collect($metrics)->map(function ($metric) {
|
||||
return [(int) $metric['time'], (float) $metric['percent']];
|
||||
});
|
||||
|
||||
return $parsedCollection->toArray();
|
||||
}
|
||||
|
||||
public function getMemoryMetrics(int $mins = 5)
|
||||
{
|
||||
$server = $this->destination->server;
|
||||
$container_name = $this->uuid;
|
||||
$from = now()->subMinutes($mins)->toIso8601ZuluString();
|
||||
$metrics = instant_remote_process(["docker exec coolify-sentinel sh -c 'curl -H \"Authorization: Bearer {$server->settings->sentinel_token}\" http://localhost:8888/api/container/{$container_name}/memory/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 = json_decode($metrics, true);
|
||||
$parsedCollection = collect($metrics)->map(function ($metric) {
|
||||
return [(int) $metric['time'], (float) $metric['used']];
|
||||
});
|
||||
|
||||
return $parsedCollection->toArray();
|
||||
}
|
||||
|
||||
public function isBackupSolutionAvailable()
|
||||
{
|
||||
return false;
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@
|
|||
namespace App\Models;
|
||||
|
||||
use App\Traits\ClearsGlobalSearchCache;
|
||||
use App\Traits\HasMetrics;
|
||||
use App\Traits\HasSafeStringAttribute;
|
||||
use Illuminate\Database\Eloquent\Casts\Attribute;
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
|
|
@ -10,7 +11,7 @@
|
|||
|
||||
class StandaloneKeydb extends BaseModel
|
||||
{
|
||||
use ClearsGlobalSearchCache, HasFactory, HasSafeStringAttribute, SoftDeletes;
|
||||
use ClearsGlobalSearchCache, HasFactory, HasMetrics, HasSafeStringAttribute, SoftDeletes;
|
||||
|
||||
protected $guarded = [];
|
||||
|
||||
|
|
@ -316,50 +317,6 @@ public function scheduledBackups()
|
|||
return $this->morphMany(ScheduledDatabaseBackup::class, 'database');
|
||||
}
|
||||
|
||||
public function getCpuMetrics(int $mins = 5)
|
||||
{
|
||||
$server = $this->destination->server;
|
||||
$container_name = $this->uuid;
|
||||
$from = now()->subMinutes($mins)->toIso8601ZuluString();
|
||||
$metrics = instant_remote_process(["docker exec coolify-sentinel sh -c 'curl -H \"Authorization: Bearer {$server->settings->sentinel_token}\" http://localhost:8888/api/container/{$container_name}/cpu/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 = json_decode($metrics, true);
|
||||
$parsedCollection = collect($metrics)->map(function ($metric) {
|
||||
return [(int) $metric['time'], (float) $metric['percent']];
|
||||
});
|
||||
|
||||
return $parsedCollection->toArray();
|
||||
}
|
||||
|
||||
public function getMemoryMetrics(int $mins = 5)
|
||||
{
|
||||
$server = $this->destination->server;
|
||||
$container_name = $this->uuid;
|
||||
$from = now()->subMinutes($mins)->toIso8601ZuluString();
|
||||
$metrics = instant_remote_process(["docker exec coolify-sentinel sh -c 'curl -H \"Authorization: Bearer {$server->settings->sentinel_token}\" http://localhost:8888/api/container/{$container_name}/memory/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 = json_decode($metrics, true);
|
||||
$parsedCollection = collect($metrics)->map(function ($metric) {
|
||||
return [(int) $metric['time'], (float) $metric['used']];
|
||||
});
|
||||
|
||||
return $parsedCollection->toArray();
|
||||
}
|
||||
|
||||
public function isBackupSolutionAvailable()
|
||||
{
|
||||
return false;
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@
|
|||
namespace App\Models;
|
||||
|
||||
use App\Traits\ClearsGlobalSearchCache;
|
||||
use App\Traits\HasMetrics;
|
||||
use App\Traits\HasSafeStringAttribute;
|
||||
use Illuminate\Database\Eloquent\Casts\Attribute;
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
|
|
@ -11,7 +12,7 @@
|
|||
|
||||
class StandaloneMariadb extends BaseModel
|
||||
{
|
||||
use ClearsGlobalSearchCache, HasFactory, HasSafeStringAttribute, SoftDeletes;
|
||||
use ClearsGlobalSearchCache, HasFactory, HasMetrics, HasSafeStringAttribute, SoftDeletes;
|
||||
|
||||
protected $guarded = [];
|
||||
|
||||
|
|
@ -319,50 +320,6 @@ public function sslCertificates()
|
|||
return $this->morphMany(SslCertificate::class, 'resource');
|
||||
}
|
||||
|
||||
public function getCpuMetrics(int $mins = 5)
|
||||
{
|
||||
$server = $this->destination->server;
|
||||
$container_name = $this->uuid;
|
||||
$from = now()->subMinutes($mins)->toIso8601ZuluString();
|
||||
$metrics = instant_remote_process(["docker exec coolify-sentinel sh -c 'curl -H \"Authorization: Bearer {$server->settings->sentinel_token}\" http://localhost:8888/api/container/{$container_name}/cpu/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 = json_decode($metrics, true);
|
||||
$parsedCollection = collect($metrics)->map(function ($metric) {
|
||||
return [(int) $metric['time'], (float) $metric['percent']];
|
||||
});
|
||||
|
||||
return $parsedCollection->toArray();
|
||||
}
|
||||
|
||||
public function getMemoryMetrics(int $mins = 5)
|
||||
{
|
||||
$server = $this->destination->server;
|
||||
$container_name = $this->uuid;
|
||||
$from = now()->subMinutes($mins)->toIso8601ZuluString();
|
||||
$metrics = instant_remote_process(["docker exec coolify-sentinel sh -c 'curl -H \"Authorization: Bearer {$server->settings->sentinel_token}\" http://localhost:8888/api/container/{$container_name}/memory/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 = json_decode($metrics, true);
|
||||
$parsedCollection = collect($metrics)->map(function ($metric) {
|
||||
return [(int) $metric['time'], (float) $metric['used']];
|
||||
});
|
||||
|
||||
return $parsedCollection->toArray();
|
||||
}
|
||||
|
||||
public function isBackupSolutionAvailable()
|
||||
{
|
||||
return true;
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@
|
|||
namespace App\Models;
|
||||
|
||||
use App\Traits\ClearsGlobalSearchCache;
|
||||
use App\Traits\HasMetrics;
|
||||
use App\Traits\HasSafeStringAttribute;
|
||||
use Illuminate\Database\Eloquent\Casts\Attribute;
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
|
|
@ -10,7 +11,7 @@
|
|||
|
||||
class StandaloneMongodb extends BaseModel
|
||||
{
|
||||
use ClearsGlobalSearchCache, HasFactory, HasSafeStringAttribute, SoftDeletes;
|
||||
use ClearsGlobalSearchCache, HasFactory, HasMetrics, HasSafeStringAttribute, SoftDeletes;
|
||||
|
||||
protected $guarded = [];
|
||||
|
||||
|
|
@ -341,50 +342,6 @@ public function scheduledBackups()
|
|||
return $this->morphMany(ScheduledDatabaseBackup::class, 'database');
|
||||
}
|
||||
|
||||
public function getCpuMetrics(int $mins = 5)
|
||||
{
|
||||
$server = $this->destination->server;
|
||||
$container_name = $this->uuid;
|
||||
$from = now()->subMinutes($mins)->toIso8601ZuluString();
|
||||
$metrics = instant_remote_process(["docker exec coolify-sentinel sh -c 'curl -H \"Authorization: Bearer {$server->settings->sentinel_token}\" http://localhost:8888/api/container/{$container_name}/cpu/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 = json_decode($metrics, true);
|
||||
$parsedCollection = collect($metrics)->map(function ($metric) {
|
||||
return [(int) $metric['time'], (float) $metric['percent']];
|
||||
});
|
||||
|
||||
return $parsedCollection->toArray();
|
||||
}
|
||||
|
||||
public function getMemoryMetrics(int $mins = 5)
|
||||
{
|
||||
$server = $this->destination->server;
|
||||
$container_name = $this->uuid;
|
||||
$from = now()->subMinutes($mins)->toIso8601ZuluString();
|
||||
$metrics = instant_remote_process(["docker exec coolify-sentinel sh -c 'curl -H \"Authorization: Bearer {$server->settings->sentinel_token}\" http://localhost:8888/api/container/{$container_name}/memory/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 = json_decode($metrics, true);
|
||||
$parsedCollection = collect($metrics)->map(function ($metric) {
|
||||
return [(int) $metric['time'], (float) $metric['used']];
|
||||
});
|
||||
|
||||
return $parsedCollection->toArray();
|
||||
}
|
||||
|
||||
public function isBackupSolutionAvailable()
|
||||
{
|
||||
return true;
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@
|
|||
namespace App\Models;
|
||||
|
||||
use App\Traits\ClearsGlobalSearchCache;
|
||||
use App\Traits\HasMetrics;
|
||||
use App\Traits\HasSafeStringAttribute;
|
||||
use Illuminate\Database\Eloquent\Casts\Attribute;
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
|
|
@ -10,7 +11,7 @@
|
|||
|
||||
class StandaloneMysql extends BaseModel
|
||||
{
|
||||
use ClearsGlobalSearchCache, HasFactory, HasSafeStringAttribute, SoftDeletes;
|
||||
use ClearsGlobalSearchCache, HasFactory, HasMetrics, HasSafeStringAttribute, SoftDeletes;
|
||||
|
||||
protected $guarded = [];
|
||||
|
||||
|
|
@ -320,50 +321,6 @@ public function scheduledBackups()
|
|||
return $this->morphMany(ScheduledDatabaseBackup::class, 'database');
|
||||
}
|
||||
|
||||
public function getCpuMetrics(int $mins = 5)
|
||||
{
|
||||
$server = $this->destination->server;
|
||||
$container_name = $this->uuid;
|
||||
$from = now()->subMinutes($mins)->toIso8601ZuluString();
|
||||
$metrics = instant_remote_process(["docker exec coolify-sentinel sh -c 'curl -H \"Authorization: Bearer {$server->settings->sentinel_token}\" http://localhost:8888/api/container/{$container_name}/cpu/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 = json_decode($metrics, true);
|
||||
$parsedCollection = collect($metrics)->map(function ($metric) {
|
||||
return [(int) $metric['time'], (float) $metric['percent']];
|
||||
});
|
||||
|
||||
return $parsedCollection->toArray();
|
||||
}
|
||||
|
||||
public function getMemoryMetrics(int $mins = 5)
|
||||
{
|
||||
$server = $this->destination->server;
|
||||
$container_name = $this->uuid;
|
||||
$from = now()->subMinutes($mins)->toIso8601ZuluString();
|
||||
$metrics = instant_remote_process(["docker exec coolify-sentinel sh -c 'curl -H \"Authorization: Bearer {$server->settings->sentinel_token}\" http://localhost:8888/api/container/{$container_name}/memory/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 = json_decode($metrics, true);
|
||||
$parsedCollection = collect($metrics)->map(function ($metric) {
|
||||
return [(int) $metric['time'], (float) $metric['used']];
|
||||
});
|
||||
|
||||
return $parsedCollection->toArray();
|
||||
}
|
||||
|
||||
public function isBackupSolutionAvailable()
|
||||
{
|
||||
return true;
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@
|
|||
namespace App\Models;
|
||||
|
||||
use App\Traits\ClearsGlobalSearchCache;
|
||||
use App\Traits\HasMetrics;
|
||||
use App\Traits\HasSafeStringAttribute;
|
||||
use Illuminate\Database\Eloquent\Casts\Attribute;
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
|
|
@ -10,7 +11,7 @@
|
|||
|
||||
class StandalonePostgresql extends BaseModel
|
||||
{
|
||||
use ClearsGlobalSearchCache, HasFactory, HasSafeStringAttribute, SoftDeletes;
|
||||
use ClearsGlobalSearchCache, HasFactory, HasMetrics, HasSafeStringAttribute, SoftDeletes;
|
||||
|
||||
protected $guarded = [];
|
||||
|
||||
|
|
@ -337,51 +338,4 @@ public function isBackupSolutionAvailable()
|
|||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
public function getCpuMetrics(int $mins = 5)
|
||||
{
|
||||
$server = $this->destination->server;
|
||||
$container_name = $this->uuid;
|
||||
$from = now()->subMinutes($mins)->toIso8601ZuluString();
|
||||
$metrics = instant_remote_process(["docker exec coolify-sentinel sh -c 'curl -H \"Authorization: Bearer {$server->settings->sentinel_token}\" http://localhost:8888/api/container/{$container_name}/cpu/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 = json_decode($metrics, true);
|
||||
$parsedCollection = collect($metrics)->map(function ($metric) {
|
||||
return [
|
||||
(int) $metric['time'],
|
||||
(float) ($metric['percent'] ?? 0.0),
|
||||
];
|
||||
});
|
||||
|
||||
return $parsedCollection->toArray();
|
||||
}
|
||||
|
||||
public function getMemoryMetrics(int $mins = 5)
|
||||
{
|
||||
$server = $this->destination->server;
|
||||
$container_name = $this->uuid;
|
||||
$from = now()->subMinutes($mins)->toIso8601ZuluString();
|
||||
$metrics = instant_remote_process(["docker exec coolify-sentinel sh -c 'curl -H \"Authorization: Bearer {$server->settings->sentinel_token}\" http://localhost:8888/api/container/{$container_name}/memory/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 = json_decode($metrics, true);
|
||||
$parsedCollection = collect($metrics)->map(function ($metric) {
|
||||
return [(int) $metric['time'], (float) $metric['used']];
|
||||
});
|
||||
|
||||
return $parsedCollection->toArray();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@
|
|||
namespace App\Models;
|
||||
|
||||
use App\Traits\ClearsGlobalSearchCache;
|
||||
use App\Traits\HasMetrics;
|
||||
use App\Traits\HasSafeStringAttribute;
|
||||
use Illuminate\Database\Eloquent\Casts\Attribute;
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
|
|
@ -10,7 +11,7 @@
|
|||
|
||||
class StandaloneRedis extends BaseModel
|
||||
{
|
||||
use ClearsGlobalSearchCache, HasFactory, HasSafeStringAttribute, SoftDeletes;
|
||||
use ClearsGlobalSearchCache, HasFactory, HasMetrics, HasSafeStringAttribute, SoftDeletes;
|
||||
|
||||
protected $guarded = [];
|
||||
|
||||
|
|
@ -332,50 +333,6 @@ public function scheduledBackups()
|
|||
return $this->morphMany(ScheduledDatabaseBackup::class, 'database');
|
||||
}
|
||||
|
||||
public function getCpuMetrics(int $mins = 5)
|
||||
{
|
||||
$server = $this->destination->server;
|
||||
$container_name = $this->uuid;
|
||||
$from = now()->subMinutes($mins)->toIso8601ZuluString();
|
||||
$metrics = instant_remote_process(["docker exec coolify-sentinel sh -c 'curl -H \"Authorization: Bearer {$server->settings->sentinel_token}\" http://localhost:8888/api/container/{$container_name}/cpu/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 = json_decode($metrics, true);
|
||||
$parsedCollection = collect($metrics)->map(function ($metric) {
|
||||
return [(int) $metric['time'], (float) $metric['percent']];
|
||||
});
|
||||
|
||||
return $parsedCollection->toArray();
|
||||
}
|
||||
|
||||
public function getMemoryMetrics(int $mins = 5)
|
||||
{
|
||||
$server = $this->destination->server;
|
||||
$container_name = $this->uuid;
|
||||
$from = now()->subMinutes($mins)->toIso8601ZuluString();
|
||||
$metrics = instant_remote_process(["docker exec coolify-sentinel sh -c 'curl -H \"Authorization: Bearer {$server->settings->sentinel_token}\" http://localhost:8888/api/container/{$container_name}/memory/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 = json_decode($metrics, true);
|
||||
$parsedCollection = collect($metrics)->map(function ($metric) {
|
||||
return [(int) $metric['time'], (float) $metric['used']];
|
||||
});
|
||||
|
||||
return $parsedCollection->toArray();
|
||||
}
|
||||
|
||||
public function isBackupSolutionAvailable()
|
||||
{
|
||||
return false;
|
||||
|
|
|
|||
74
app/Traits/HasMetrics.php
Normal file
74
app/Traits/HasMetrics.php
Normal file
|
|
@ -0,0 +1,74 @@
|
|||
<?php
|
||||
|
||||
namespace App\Traits;
|
||||
|
||||
trait HasMetrics
|
||||
{
|
||||
public function getCpuMetrics(int $mins = 5): ?array
|
||||
{
|
||||
return $this->getMetrics('cpu', $mins, 'percent');
|
||||
}
|
||||
|
||||
public function getMemoryMetrics(int $mins = 5): ?array
|
||||
{
|
||||
$field = $this->isServerMetrics() ? 'usedPercent' : 'used';
|
||||
|
||||
return $this->getMetrics('memory', $mins, $field);
|
||||
}
|
||||
|
||||
private function getMetrics(string $type, int $mins, string $valueField): ?array
|
||||
{
|
||||
$server = $this->getMetricsServer();
|
||||
if (! $server->isMetricsEnabled()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$from = now()->subMinutes($mins)->toIso8601ZuluString();
|
||||
$endpoint = $this->getMetricsEndpoint($type, $from);
|
||||
|
||||
$response = instant_remote_process(
|
||||
["docker exec coolify-sentinel sh -c 'curl -H \"Authorization: Bearer {$server->settings->sentinel_token}\" {$endpoint}'"],
|
||||
$server,
|
||||
false
|
||||
);
|
||||
|
||||
if (str($response)->contains('error')) {
|
||||
$error = json_decode($response, 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 = collect(json_decode($response, true))->map(function ($metric) use ($valueField) {
|
||||
return [(int) $metric['time'], (float) ($metric[$valueField] ?? 0.0)];
|
||||
})->toArray();
|
||||
|
||||
if ($mins > 60 && count($metrics) > 1000) {
|
||||
$metrics = downsampleLTTB($metrics, 1000);
|
||||
}
|
||||
|
||||
return $metrics;
|
||||
}
|
||||
|
||||
private function isServerMetrics(): bool
|
||||
{
|
||||
return $this instanceof \App\Models\Server;
|
||||
}
|
||||
|
||||
private function getMetricsServer(): \App\Models\Server
|
||||
{
|
||||
return $this->isServerMetrics() ? $this : $this->destination->server;
|
||||
}
|
||||
|
||||
private function getMetricsEndpoint(string $type, string $from): string
|
||||
{
|
||||
$base = 'http://localhost:8888/api';
|
||||
if ($this->isServerMetrics()) {
|
||||
return "{$base}/{$type}/history?from={$from}";
|
||||
}
|
||||
|
||||
return "{$base}/container/{$this->uuid}/{$type}/history?from={$from}";
|
||||
}
|
||||
}
|
||||
|
|
@ -3416,3 +3416,81 @@ function verifyPasswordConfirmation(mixed $password, ?Livewire\Component $compon
|
|||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Downsample metrics using the Largest-Triangle-Three-Buckets (LTTB) algorithm.
|
||||
* This preserves the visual shape of the data better than simple averaging.
|
||||
*
|
||||
* @param array $data Array of [timestamp, value] pairs
|
||||
* @param int $threshold Target number of points
|
||||
* @return array Downsampled data
|
||||
*/
|
||||
function downsampleLTTB(array $data, int $threshold): array
|
||||
{
|
||||
$dataLength = count($data);
|
||||
|
||||
// Return unchanged if threshold >= data length, or if threshold <= 2
|
||||
// (threshold <= 2 would cause division by zero in bucket calculation)
|
||||
if ($threshold >= $dataLength || $threshold <= 2) {
|
||||
return $data;
|
||||
}
|
||||
|
||||
$sampled = [];
|
||||
$sampled[] = $data[0]; // Always keep first point
|
||||
|
||||
$bucketSize = ($dataLength - 2) / ($threshold - 2);
|
||||
|
||||
$a = 0; // Index of previous selected point
|
||||
|
||||
for ($i = 0; $i < $threshold - 2; $i++) {
|
||||
// Calculate bucket range
|
||||
$bucketStart = (int) floor(($i + 1) * $bucketSize) + 1;
|
||||
$bucketEnd = (int) floor(($i + 2) * $bucketSize) + 1;
|
||||
$bucketEnd = min($bucketEnd, $dataLength - 1);
|
||||
|
||||
// Calculate average point for next bucket (used as reference)
|
||||
$nextBucketStart = (int) floor(($i + 2) * $bucketSize) + 1;
|
||||
$nextBucketEnd = (int) floor(($i + 3) * $bucketSize) + 1;
|
||||
$nextBucketEnd = min($nextBucketEnd, $dataLength - 1);
|
||||
|
||||
$avgX = 0;
|
||||
$avgY = 0;
|
||||
$nextBucketCount = $nextBucketEnd - $nextBucketStart + 1;
|
||||
|
||||
if ($nextBucketCount > 0) {
|
||||
for ($j = $nextBucketStart; $j <= $nextBucketEnd; $j++) {
|
||||
$avgX += $data[$j][0];
|
||||
$avgY += $data[$j][1];
|
||||
}
|
||||
$avgX /= $nextBucketCount;
|
||||
$avgY /= $nextBucketCount;
|
||||
}
|
||||
|
||||
// Find point in current bucket with largest triangle area
|
||||
$maxArea = -1;
|
||||
$maxAreaIndex = $bucketStart;
|
||||
|
||||
$pointAX = $data[$a][0];
|
||||
$pointAY = $data[$a][1];
|
||||
|
||||
for ($j = $bucketStart; $j <= $bucketEnd; $j++) {
|
||||
// Triangle area calculation
|
||||
$area = abs(
|
||||
($pointAX - $avgX) * ($data[$j][1] - $pointAY) -
|
||||
($pointAX - $data[$j][0]) * ($avgY - $pointAY)
|
||||
) * 0.5;
|
||||
|
||||
if ($area > $maxArea) {
|
||||
$maxArea = $area;
|
||||
$maxAreaIndex = $j;
|
||||
}
|
||||
}
|
||||
|
||||
$sampled[] = $data[$maxAreaIndex];
|
||||
$a = $maxAreaIndex;
|
||||
}
|
||||
|
||||
$sampled[] = $data[$dataLength - 1]; // Always keep last point
|
||||
|
||||
return $sampled;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -29,230 +29,179 @@ class="pt-5">
|
|||
<div wire:ignore id="{!! $chartId !!}-cpu"></div>
|
||||
|
||||
<script>
|
||||
checkTheme();
|
||||
const optionsServerCpu = {
|
||||
stroke: {
|
||||
curve: 'straight',
|
||||
width: 2,
|
||||
},
|
||||
chart: {
|
||||
height: '150px',
|
||||
id: '{!! $chartId !!}-cpu',
|
||||
type: 'area',
|
||||
toolbar: {
|
||||
show: true,
|
||||
tools: {
|
||||
download: false,
|
||||
selection: false,
|
||||
zoom: true,
|
||||
zoomin: false,
|
||||
zoomout: false,
|
||||
pan: false,
|
||||
reset: true
|
||||
(function() {
|
||||
checkTheme();
|
||||
const optionsServerCpu = {
|
||||
stroke: {
|
||||
curve: 'straight',
|
||||
width: 2,
|
||||
},
|
||||
chart: {
|
||||
height: '150px',
|
||||
id: '{!! $chartId !!}-cpu',
|
||||
type: 'area',
|
||||
toolbar: {
|
||||
show: true,
|
||||
tools: {
|
||||
download: false,
|
||||
selection: false,
|
||||
zoom: true,
|
||||
zoomin: false,
|
||||
zoomout: false,
|
||||
pan: false,
|
||||
reset: true
|
||||
},
|
||||
},
|
||||
animations: {
|
||||
enabled: true,
|
||||
},
|
||||
},
|
||||
animations: {
|
||||
enabled: true,
|
||||
fill: {
|
||||
type: 'gradient',
|
||||
},
|
||||
},
|
||||
fill: {
|
||||
type: 'gradient',
|
||||
},
|
||||
dataLabels: {
|
||||
enabled: false,
|
||||
offsetY: -10,
|
||||
style: {
|
||||
colors: ['#FCD452'],
|
||||
},
|
||||
background: {
|
||||
dataLabels: {
|
||||
enabled: false,
|
||||
}
|
||||
},
|
||||
grid: {
|
||||
show: true,
|
||||
borderColor: '',
|
||||
},
|
||||
colors: [cpuColor],
|
||||
xaxis: {
|
||||
type: 'datetime',
|
||||
},
|
||||
series: [{
|
||||
name: "CPU %",
|
||||
data: []
|
||||
}],
|
||||
noData: {
|
||||
text: 'Loading...',
|
||||
style: {
|
||||
color: textColor,
|
||||
}
|
||||
},
|
||||
tooltip: {
|
||||
enabled: true,
|
||||
marker: {
|
||||
show: false,
|
||||
offsetY: -10,
|
||||
style: {
|
||||
colors: ['#FCD452'],
|
||||
},
|
||||
background: {
|
||||
enabled: false,
|
||||
}
|
||||
},
|
||||
grid: {
|
||||
show: true,
|
||||
borderColor: '',
|
||||
},
|
||||
custom: function({ series, seriesIndex, dataPointIndex, w }) {
|
||||
const value = series[seriesIndex][dataPointIndex];
|
||||
const timestamp = w.globals.seriesX[seriesIndex][dataPointIndex];
|
||||
const date = new Date(timestamp);
|
||||
const timeString = String(date.getUTCHours()).padStart(2, '0') + ':' +
|
||||
String(date.getUTCMinutes()).padStart(2, '0') + ':' +
|
||||
String(date.getUTCSeconds()).padStart(2, '0') + ', ' +
|
||||
date.getUTCFullYear() + '-' +
|
||||
String(date.getUTCMonth() + 1).padStart(2, '0') + '-' +
|
||||
String(date.getUTCDate()).padStart(2, '0');
|
||||
return '<div class="apexcharts-tooltip-custom">' +
|
||||
'<div class="apexcharts-tooltip-custom-value">CPU: <span class="apexcharts-tooltip-value-bold">' + value + '%</span></div>' +
|
||||
'<div class="apexcharts-tooltip-custom-title">' + timeString + '</div>' +
|
||||
'</div>';
|
||||
}
|
||||
},
|
||||
legend: {
|
||||
show: false
|
||||
}
|
||||
}
|
||||
const serverCpuChart = new ApexCharts(document.getElementById(`{!! $chartId !!}-cpu`), optionsServerCpu);
|
||||
serverCpuChart.render();
|
||||
Livewire.on('refreshChartData-{!! $chartId !!}-cpu', (chartData) => {
|
||||
checkTheme();
|
||||
serverCpuChart.updateOptions({
|
||||
series: [{
|
||||
data: chartData[0].seriesData,
|
||||
}],
|
||||
colors: [cpuColor],
|
||||
colors: [cpuColor],
|
||||
xaxis: {
|
||||
type: 'datetime',
|
||||
labels: {
|
||||
show: true,
|
||||
style: {
|
||||
colors: textColor,
|
||||
}
|
||||
}
|
||||
},
|
||||
yaxis: {
|
||||
show: true,
|
||||
labels: {
|
||||
show: true,
|
||||
style: {
|
||||
colors: textColor,
|
||||
},
|
||||
formatter: function(value) {
|
||||
return Math.round(value) + ' %';
|
||||
}
|
||||
}
|
||||
},
|
||||
series: [{
|
||||
name: "CPU %",
|
||||
data: []
|
||||
}],
|
||||
noData: {
|
||||
text: 'Loading...',
|
||||
style: {
|
||||
color: textColor,
|
||||
}
|
||||
},
|
||||
tooltip: {
|
||||
enabled: true,
|
||||
marker: {
|
||||
show: false,
|
||||
},
|
||||
custom: function({ series, seriesIndex, dataPointIndex, w }) {
|
||||
const value = series[seriesIndex][dataPointIndex];
|
||||
const timestamp = w.globals.seriesX[seriesIndex][dataPointIndex];
|
||||
const date = new Date(timestamp);
|
||||
const timeString = String(date.getUTCHours()).padStart(2, '0') + ':' +
|
||||
String(date.getUTCMinutes()).padStart(2, '0') + ':' +
|
||||
String(date.getUTCSeconds()).padStart(2, '0') + ', ' +
|
||||
date.getUTCFullYear() + '-' +
|
||||
String(date.getUTCMonth() + 1).padStart(2, '0') + '-' +
|
||||
String(date.getUTCDate()).padStart(2, '0');
|
||||
return '<div class="apexcharts-tooltip-custom">' +
|
||||
'<div class="apexcharts-tooltip-custom-value">CPU: <span class="apexcharts-tooltip-value-bold">' + value + '%</span></div>' +
|
||||
'<div class="apexcharts-tooltip-custom-title">' + timeString + '</div>' +
|
||||
'</div>';
|
||||
}
|
||||
},
|
||||
legend: {
|
||||
show: false
|
||||
}
|
||||
}
|
||||
const serverCpuChart = new ApexCharts(document.getElementById(`{!! $chartId !!}-cpu`), optionsServerCpu);
|
||||
serverCpuChart.render();
|
||||
Livewire.on('refreshChartData-{!! $chartId !!}-cpu', (chartData) => {
|
||||
checkTheme();
|
||||
serverCpuChart.updateOptions({
|
||||
series: [{
|
||||
data: chartData[0].seriesData,
|
||||
}],
|
||||
colors: [cpuColor],
|
||||
xaxis: {
|
||||
type: 'datetime',
|
||||
labels: {
|
||||
show: true,
|
||||
style: {
|
||||
colors: textColor,
|
||||
}
|
||||
}
|
||||
},
|
||||
yaxis: {
|
||||
show: true,
|
||||
labels: {
|
||||
show: true,
|
||||
style: {
|
||||
colors: textColor,
|
||||
},
|
||||
formatter: function(value) {
|
||||
return Math.round(value) + ' %';
|
||||
}
|
||||
}
|
||||
},
|
||||
noData: {
|
||||
text: 'Loading...',
|
||||
style: {
|
||||
color: textColor,
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
})();
|
||||
</script>
|
||||
|
||||
<h4>Memory Usage</h4>
|
||||
<div wire:ignore id="{!! $chartId !!}-memory"></div>
|
||||
|
||||
<script>
|
||||
checkTheme();
|
||||
const optionsServerMemory = {
|
||||
stroke: {
|
||||
curve: 'straight',
|
||||
width: 2,
|
||||
},
|
||||
chart: {
|
||||
height: '150px',
|
||||
id: '{!! $chartId !!}-memory',
|
||||
type: 'area',
|
||||
toolbar: {
|
||||
show: true,
|
||||
tools: {
|
||||
download: false,
|
||||
selection: false,
|
||||
zoom: true,
|
||||
zoomin: false,
|
||||
zoomout: false,
|
||||
pan: false,
|
||||
reset: true
|
||||
(function() {
|
||||
checkTheme();
|
||||
const optionsServerMemory = {
|
||||
stroke: {
|
||||
curve: 'straight',
|
||||
width: 2,
|
||||
},
|
||||
chart: {
|
||||
height: '150px',
|
||||
id: '{!! $chartId !!}-memory',
|
||||
type: 'area',
|
||||
toolbar: {
|
||||
show: true,
|
||||
tools: {
|
||||
download: false,
|
||||
selection: false,
|
||||
zoom: true,
|
||||
zoomin: false,
|
||||
zoomout: false,
|
||||
pan: false,
|
||||
reset: true
|
||||
},
|
||||
},
|
||||
animations: {
|
||||
enabled: true,
|
||||
},
|
||||
},
|
||||
animations: {
|
||||
enabled: true,
|
||||
fill: {
|
||||
type: 'gradient',
|
||||
},
|
||||
},
|
||||
fill: {
|
||||
type: 'gradient',
|
||||
},
|
||||
dataLabels: {
|
||||
enabled: false,
|
||||
offsetY: -10,
|
||||
style: {
|
||||
colors: ['#FCD452'],
|
||||
},
|
||||
background: {
|
||||
dataLabels: {
|
||||
enabled: false,
|
||||
}
|
||||
},
|
||||
grid: {
|
||||
show: true,
|
||||
borderColor: '',
|
||||
},
|
||||
colors: [ramColor],
|
||||
xaxis: {
|
||||
type: 'datetime',
|
||||
labels: {
|
||||
offsetY: -10,
|
||||
style: {
|
||||
colors: ['#FCD452'],
|
||||
},
|
||||
background: {
|
||||
enabled: false,
|
||||
}
|
||||
},
|
||||
grid: {
|
||||
show: true,
|
||||
style: {
|
||||
colors: textColor,
|
||||
}
|
||||
}
|
||||
},
|
||||
series: [{
|
||||
name: "Memory (MB)",
|
||||
data: []
|
||||
}],
|
||||
noData: {
|
||||
text: 'Loading...',
|
||||
style: {
|
||||
color: textColor,
|
||||
}
|
||||
},
|
||||
tooltip: {
|
||||
enabled: true,
|
||||
marker: {
|
||||
show: false,
|
||||
borderColor: '',
|
||||
},
|
||||
custom: function({ series, seriesIndex, dataPointIndex, w }) {
|
||||
const value = series[seriesIndex][dataPointIndex];
|
||||
const timestamp = w.globals.seriesX[seriesIndex][dataPointIndex];
|
||||
const date = new Date(timestamp);
|
||||
const timeString = String(date.getUTCHours()).padStart(2, '0') + ':' +
|
||||
String(date.getUTCMinutes()).padStart(2, '0') + ':' +
|
||||
String(date.getUTCSeconds()).padStart(2, '0') + ', ' +
|
||||
date.getUTCFullYear() + '-' +
|
||||
String(date.getUTCMonth() + 1).padStart(2, '0') + '-' +
|
||||
String(date.getUTCDate()).padStart(2, '0');
|
||||
return '<div class="apexcharts-tooltip-custom">' +
|
||||
'<div class="apexcharts-tooltip-custom-value">Memory: <span class="apexcharts-tooltip-value-bold">' + value + ' MB</span></div>' +
|
||||
'<div class="apexcharts-tooltip-custom-title">' + timeString + '</div>' +
|
||||
'</div>';
|
||||
}
|
||||
},
|
||||
legend: {
|
||||
show: false
|
||||
}
|
||||
}
|
||||
const serverMemoryChart = new ApexCharts(document.getElementById(`{!! $chartId !!}-memory`),
|
||||
optionsServerMemory);
|
||||
serverMemoryChart.render();
|
||||
Livewire.on('refreshChartData-{!! $chartId !!}-memory', (chartData) => {
|
||||
checkTheme();
|
||||
serverMemoryChart.updateOptions({
|
||||
series: [{
|
||||
data: chartData[0].seriesData,
|
||||
}],
|
||||
colors: [ramColor],
|
||||
colors: [ramColor],
|
||||
xaxis: {
|
||||
type: 'datetime',
|
||||
labels: {
|
||||
|
|
@ -262,27 +211,82 @@ class="pt-5">
|
|||
}
|
||||
}
|
||||
},
|
||||
yaxis: {
|
||||
min: 0,
|
||||
show: true,
|
||||
labels: {
|
||||
show: true,
|
||||
style: {
|
||||
colors: textColor,
|
||||
},
|
||||
formatter: function(value) {
|
||||
return Math.round(value) + ' MB';
|
||||
}
|
||||
}
|
||||
},
|
||||
series: [{
|
||||
name: "Memory (MB)",
|
||||
data: []
|
||||
}],
|
||||
noData: {
|
||||
text: 'Loading...',
|
||||
style: {
|
||||
color: textColor,
|
||||
}
|
||||
},
|
||||
tooltip: {
|
||||
enabled: true,
|
||||
marker: {
|
||||
show: false,
|
||||
},
|
||||
custom: function({ series, seriesIndex, dataPointIndex, w }) {
|
||||
const value = series[seriesIndex][dataPointIndex];
|
||||
const timestamp = w.globals.seriesX[seriesIndex][dataPointIndex];
|
||||
const date = new Date(timestamp);
|
||||
const timeString = String(date.getUTCHours()).padStart(2, '0') + ':' +
|
||||
String(date.getUTCMinutes()).padStart(2, '0') + ':' +
|
||||
String(date.getUTCSeconds()).padStart(2, '0') + ', ' +
|
||||
date.getUTCFullYear() + '-' +
|
||||
String(date.getUTCMonth() + 1).padStart(2, '0') + '-' +
|
||||
String(date.getUTCDate()).padStart(2, '0');
|
||||
return '<div class="apexcharts-tooltip-custom">' +
|
||||
'<div class="apexcharts-tooltip-custom-value">Memory: <span class="apexcharts-tooltip-value-bold">' + value + ' MB</span></div>' +
|
||||
'<div class="apexcharts-tooltip-custom-title">' + timeString + '</div>' +
|
||||
'</div>';
|
||||
}
|
||||
},
|
||||
legend: {
|
||||
show: false
|
||||
}
|
||||
}
|
||||
const serverMemoryChart = new ApexCharts(document.getElementById(`{!! $chartId !!}-memory`),
|
||||
optionsServerMemory);
|
||||
serverMemoryChart.render();
|
||||
Livewire.on('refreshChartData-{!! $chartId !!}-memory', (chartData) => {
|
||||
checkTheme();
|
||||
serverMemoryChart.updateOptions({
|
||||
series: [{
|
||||
data: chartData[0].seriesData,
|
||||
}],
|
||||
colors: [ramColor],
|
||||
xaxis: {
|
||||
type: 'datetime',
|
||||
labels: {
|
||||
show: true,
|
||||
style: {
|
||||
colors: textColor,
|
||||
}
|
||||
}
|
||||
},
|
||||
yaxis: {
|
||||
min: 0,
|
||||
show: true,
|
||||
labels: {
|
||||
show: true,
|
||||
style: {
|
||||
colors: textColor,
|
||||
},
|
||||
formatter: function(value) {
|
||||
return Math.round(value) + ' MB';
|
||||
}
|
||||
}
|
||||
},
|
||||
noData: {
|
||||
text: 'Loading...',
|
||||
style: {
|
||||
color: textColor,
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
})();
|
||||
</script>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -23,145 +23,16 @@
|
|||
<div wire:ignore id="{!! $chartId !!}-cpu"></div>
|
||||
|
||||
<script>
|
||||
checkTheme();
|
||||
const optionsServerCpu = {
|
||||
stroke: {
|
||||
curve: 'straight',
|
||||
width: 2,
|
||||
},
|
||||
chart: {
|
||||
height: '150px',
|
||||
id: '{!! $chartId !!}-cpu',
|
||||
type: 'area',
|
||||
toolbar: {
|
||||
show: true,
|
||||
tools: {
|
||||
download: false,
|
||||
selection: false,
|
||||
zoom: true,
|
||||
zoomin: false,
|
||||
zoomout: false,
|
||||
pan: false,
|
||||
reset: true
|
||||
},
|
||||
},
|
||||
animations: {
|
||||
enabled: true,
|
||||
},
|
||||
},
|
||||
fill: {
|
||||
type: 'gradient',
|
||||
},
|
||||
dataLabels: {
|
||||
enabled: false,
|
||||
offsetY: -10,
|
||||
style: {
|
||||
colors: ['#FCD452'],
|
||||
},
|
||||
background: {
|
||||
enabled: false,
|
||||
}
|
||||
},
|
||||
grid: {
|
||||
show: true,
|
||||
borderColor: '',
|
||||
},
|
||||
colors: [cpuColor],
|
||||
xaxis: {
|
||||
type: 'datetime',
|
||||
},
|
||||
series: [{
|
||||
name: 'CPU %',
|
||||
data: []
|
||||
}],
|
||||
noData: {
|
||||
text: 'Loading...',
|
||||
style: {
|
||||
color: textColor,
|
||||
}
|
||||
},
|
||||
tooltip: {
|
||||
enabled: true,
|
||||
marker: {
|
||||
show: false,
|
||||
},
|
||||
custom: function({ series, seriesIndex, dataPointIndex, w }) {
|
||||
const value = series[seriesIndex][dataPointIndex];
|
||||
const timestamp = w.globals.seriesX[seriesIndex][dataPointIndex];
|
||||
const date = new Date(timestamp);
|
||||
const timeString = String(date.getUTCHours()).padStart(2, '0') + ':' +
|
||||
String(date.getUTCMinutes()).padStart(2, '0') + ':' +
|
||||
String(date.getUTCSeconds()).padStart(2, '0') + ', ' +
|
||||
date.getUTCFullYear() + '-' +
|
||||
String(date.getUTCMonth() + 1).padStart(2, '0') + '-' +
|
||||
String(date.getUTCDate()).padStart(2, '0');
|
||||
return '<div class="apexcharts-tooltip-custom">' +
|
||||
'<div class="apexcharts-tooltip-custom-value">CPU: <span class="apexcharts-tooltip-value-bold">' + value + '%</span></div>' +
|
||||
'<div class="apexcharts-tooltip-custom-title">' + timeString + '</div>' +
|
||||
'</div>';
|
||||
}
|
||||
},
|
||||
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: [cpuColor],
|
||||
xaxis: {
|
||||
type: 'datetime',
|
||||
labels: {
|
||||
show: true,
|
||||
style: {
|
||||
colors: textColor,
|
||||
}
|
||||
}
|
||||
},
|
||||
yaxis: {
|
||||
show: true,
|
||||
labels: {
|
||||
show: true,
|
||||
style: {
|
||||
colors: textColor,
|
||||
},
|
||||
formatter: function(value) {
|
||||
return Math.round(value) + ' %';
|
||||
}
|
||||
}
|
||||
},
|
||||
noData: {
|
||||
text: 'Loading...',
|
||||
style: {
|
||||
color: textColor,
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
</script>
|
||||
|
||||
<div>
|
||||
<h4>Memory Usage</h4>
|
||||
<div wire:ignore id="{!! $chartId !!}-memory"></div>
|
||||
|
||||
<script>
|
||||
(function() {
|
||||
checkTheme();
|
||||
const optionsServerMemory = {
|
||||
const optionsServerCpu = {
|
||||
stroke: {
|
||||
curve: 'straight',
|
||||
width: 2,
|
||||
},
|
||||
chart: {
|
||||
height: '150px',
|
||||
id: '{!! $chartId !!}-memory',
|
||||
id: '{!! $chartId !!}-cpu',
|
||||
type: 'area',
|
||||
toolbar: {
|
||||
show: true,
|
||||
|
|
@ -196,18 +67,12 @@
|
|||
show: true,
|
||||
borderColor: '',
|
||||
},
|
||||
colors: [ramColor],
|
||||
colors: [cpuColor],
|
||||
xaxis: {
|
||||
type: 'datetime',
|
||||
labels: {
|
||||
show: true,
|
||||
style: {
|
||||
colors: textColor,
|
||||
}
|
||||
}
|
||||
},
|
||||
series: [{
|
||||
name: "Memory (%)",
|
||||
},
|
||||
series: [{
|
||||
name: 'CPU %',
|
||||
data: []
|
||||
}],
|
||||
noData: {
|
||||
|
|
@ -232,7 +97,7 @@
|
|||
String(date.getUTCMonth() + 1).padStart(2, '0') + '-' +
|
||||
String(date.getUTCDate()).padStart(2, '0');
|
||||
return '<div class="apexcharts-tooltip-custom">' +
|
||||
'<div class="apexcharts-tooltip-custom-value">Memory: <span class="apexcharts-tooltip-value-bold">' + value + '%</span></div>' +
|
||||
'<div class="apexcharts-tooltip-custom-value">CPU: <span class="apexcharts-tooltip-value-bold">' + value + '%</span></div>' +
|
||||
'<div class="apexcharts-tooltip-custom-title">' + timeString + '</div>' +
|
||||
'</div>';
|
||||
}
|
||||
|
|
@ -241,17 +106,16 @@
|
|||
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({
|
||||
const serverCpuChart = new ApexCharts(document.getElementById(`{!! $chartId !!}-cpu`),
|
||||
optionsServerCpu);
|
||||
serverCpuChart.render();
|
||||
Livewire.on('refreshChartData-{!! $chartId !!}-cpu', (chartData) => {
|
||||
checkTheme();
|
||||
serverCpuChart.updateOptions({
|
||||
series: [{
|
||||
data: chartData[0].seriesData,
|
||||
}],
|
||||
colors: [ramColor],
|
||||
colors: [cpuColor],
|
||||
xaxis: {
|
||||
type: 'datetime',
|
||||
labels: {
|
||||
|
|
@ -262,16 +126,15 @@
|
|||
}
|
||||
},
|
||||
yaxis: {
|
||||
min: 0,
|
||||
show: true,
|
||||
labels: {
|
||||
show: true,
|
||||
style: {
|
||||
colors: textColor,
|
||||
},
|
||||
formatter: function(value) {
|
||||
return Math.round(value) + ' %';
|
||||
}
|
||||
formatter: function(value) {
|
||||
return Math.round(value) + ' %';
|
||||
}
|
||||
}
|
||||
},
|
||||
noData: {
|
||||
|
|
@ -282,7 +145,144 @@
|
|||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
})();
|
||||
</script>
|
||||
|
||||
<div>
|
||||
<h4>Memory Usage</h4>
|
||||
<div wire:ignore id="{!! $chartId !!}-memory"></div>
|
||||
|
||||
<script>
|
||||
(function() {
|
||||
checkTheme();
|
||||
const optionsServerMemory = {
|
||||
stroke: {
|
||||
curve: 'straight',
|
||||
width: 2,
|
||||
},
|
||||
chart: {
|
||||
height: '150px',
|
||||
id: '{!! $chartId !!}-memory',
|
||||
type: 'area',
|
||||
toolbar: {
|
||||
show: true,
|
||||
tools: {
|
||||
download: false,
|
||||
selection: false,
|
||||
zoom: true,
|
||||
zoomin: false,
|
||||
zoomout: false,
|
||||
pan: false,
|
||||
reset: true
|
||||
},
|
||||
},
|
||||
animations: {
|
||||
enabled: true,
|
||||
},
|
||||
},
|
||||
fill: {
|
||||
type: 'gradient',
|
||||
},
|
||||
dataLabels: {
|
||||
enabled: false,
|
||||
offsetY: -10,
|
||||
style: {
|
||||
colors: ['#FCD452'],
|
||||
},
|
||||
background: {
|
||||
enabled: false,
|
||||
}
|
||||
},
|
||||
grid: {
|
||||
show: true,
|
||||
borderColor: '',
|
||||
},
|
||||
colors: [ramColor],
|
||||
xaxis: {
|
||||
type: 'datetime',
|
||||
labels: {
|
||||
show: true,
|
||||
style: {
|
||||
colors: textColor,
|
||||
}
|
||||
}
|
||||
},
|
||||
series: [{
|
||||
name: "Memory (%)",
|
||||
data: []
|
||||
}],
|
||||
noData: {
|
||||
text: 'Loading...',
|
||||
style: {
|
||||
color: textColor,
|
||||
}
|
||||
},
|
||||
tooltip: {
|
||||
enabled: true,
|
||||
marker: {
|
||||
show: false,
|
||||
},
|
||||
custom: function({ series, seriesIndex, dataPointIndex, w }) {
|
||||
const value = series[seriesIndex][dataPointIndex];
|
||||
const timestamp = w.globals.seriesX[seriesIndex][dataPointIndex];
|
||||
const date = new Date(timestamp);
|
||||
const timeString = String(date.getUTCHours()).padStart(2, '0') + ':' +
|
||||
String(date.getUTCMinutes()).padStart(2, '0') + ':' +
|
||||
String(date.getUTCSeconds()).padStart(2, '0') + ', ' +
|
||||
date.getUTCFullYear() + '-' +
|
||||
String(date.getUTCMonth() + 1).padStart(2, '0') + '-' +
|
||||
String(date.getUTCDate()).padStart(2, '0');
|
||||
return '<div class="apexcharts-tooltip-custom">' +
|
||||
'<div class="apexcharts-tooltip-custom-value">Memory: <span class="apexcharts-tooltip-value-bold">' + value + '%</span></div>' +
|
||||
'<div class="apexcharts-tooltip-custom-title">' + timeString + '</div>' +
|
||||
'</div>';
|
||||
}
|
||||
},
|
||||
legend: {
|
||||
show: false
|
||||
}
|
||||
}
|
||||
const serverMemoryChart = new ApexCharts(document.getElementById(`{!! $chartId !!}-memory`),
|
||||
optionsServerMemory);
|
||||
serverMemoryChart.render();
|
||||
Livewire.on('refreshChartData-{!! $chartId !!}-memory', (chartData) => {
|
||||
checkTheme();
|
||||
serverMemoryChart.updateOptions({
|
||||
series: [{
|
||||
data: chartData[0].seriesData,
|
||||
}],
|
||||
colors: [ramColor],
|
||||
xaxis: {
|
||||
type: 'datetime',
|
||||
labels: {
|
||||
show: true,
|
||||
style: {
|
||||
colors: textColor,
|
||||
}
|
||||
}
|
||||
},
|
||||
yaxis: {
|
||||
min: 0,
|
||||
show: true,
|
||||
labels: {
|
||||
show: true,
|
||||
style: {
|
||||
colors: textColor,
|
||||
},
|
||||
formatter: function(value) {
|
||||
return Math.round(value) + ' %';
|
||||
}
|
||||
}
|
||||
},
|
||||
noData: {
|
||||
text: 'Loading...',
|
||||
style: {
|
||||
color: textColor,
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
})();
|
||||
</script>
|
||||
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@
|
|||
use App\Models\StandaloneClickhouse;
|
||||
|
||||
test('clickhouse uses clickhouse_db field in internal connection string', function () {
|
||||
$clickhouse = new StandaloneClickhouse();
|
||||
$clickhouse = new StandaloneClickhouse;
|
||||
$clickhouse->clickhouse_admin_user = 'testuser';
|
||||
$clickhouse->clickhouse_admin_password = 'testpass';
|
||||
$clickhouse->clickhouse_db = 'mydb';
|
||||
|
|
@ -18,7 +18,7 @@
|
|||
});
|
||||
|
||||
test('clickhouse defaults to default database when clickhouse_db is null', function () {
|
||||
$clickhouse = new StandaloneClickhouse();
|
||||
$clickhouse = new StandaloneClickhouse;
|
||||
$clickhouse->clickhouse_admin_user = 'testuser';
|
||||
$clickhouse->clickhouse_admin_password = 'testpass';
|
||||
$clickhouse->clickhouse_db = null;
|
||||
|
|
@ -30,7 +30,7 @@
|
|||
});
|
||||
|
||||
test('clickhouse external url uses correct database', function () {
|
||||
$clickhouse = new StandaloneClickhouse();
|
||||
$clickhouse = new StandaloneClickhouse;
|
||||
$clickhouse->clickhouse_admin_user = 'admin';
|
||||
$clickhouse->clickhouse_admin_password = 'secret';
|
||||
$clickhouse->clickhouse_db = 'production';
|
||||
|
|
@ -38,12 +38,19 @@
|
|||
$clickhouse->is_public = true;
|
||||
$clickhouse->public_port = 8123;
|
||||
|
||||
$clickhouse->destination = new class {
|
||||
$clickhouse->destination = new class
|
||||
{
|
||||
public $server;
|
||||
public function __construct() {
|
||||
$this->server = new class {
|
||||
public function __get($name) {
|
||||
if ($name === 'getIp') return '1.2.3.4';
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this->server = new class
|
||||
{
|
||||
public function __get($name)
|
||||
{
|
||||
if ($name === 'getIp') {
|
||||
return '1.2.3.4';
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,19 +1,9 @@
|
|||
<?php
|
||||
|
||||
use App\Models\Server;
|
||||
|
||||
/**
|
||||
* Helper to call the private downsampleLTTB method on Server model via reflection.
|
||||
* Tests for the downsampleLTTB helper function used for metrics downsampling.
|
||||
* This function implements the Largest-Triangle-Three-Buckets algorithm.
|
||||
*/
|
||||
function callDownsampleLTTB(array $data, int $threshold): array
|
||||
{
|
||||
$server = new Server;
|
||||
$reflection = new ReflectionClass($server);
|
||||
$method = $reflection->getMethod('downsampleLTTB');
|
||||
|
||||
return $method->invoke($server, $data, $threshold);
|
||||
}
|
||||
|
||||
it('returns data unchanged when below threshold', function () {
|
||||
$data = [
|
||||
[1000, 10.5],
|
||||
|
|
@ -21,7 +11,7 @@ function callDownsampleLTTB(array $data, int $threshold): array
|
|||
[3000, 15.7],
|
||||
];
|
||||
|
||||
$result = callDownsampleLTTB($data, 1000);
|
||||
$result = downsampleLTTB($data, 1000);
|
||||
|
||||
expect($result)->toBe($data);
|
||||
});
|
||||
|
|
@ -35,10 +25,10 @@ function callDownsampleLTTB(array $data, int $threshold): array
|
|||
[5000, 12.0],
|
||||
];
|
||||
|
||||
$result = callDownsampleLTTB($data, 2);
|
||||
$result = downsampleLTTB($data, 2);
|
||||
expect($result)->toBe($data);
|
||||
|
||||
$result = callDownsampleLTTB($data, 1);
|
||||
$result = downsampleLTTB($data, 1);
|
||||
expect($result)->toBe($data);
|
||||
});
|
||||
|
||||
|
|
@ -52,7 +42,7 @@ function callDownsampleLTTB(array $data, int $threshold): array
|
|||
$data[] = [$i * 1000, mt_rand(0, 100) / 10];
|
||||
}
|
||||
|
||||
$result = callDownsampleLTTB($data, 10);
|
||||
$result = downsampleLTTB($data, 10);
|
||||
|
||||
expect(count($result))->toBe(10);
|
||||
});
|
||||
|
|
@ -63,7 +53,7 @@ function callDownsampleLTTB(array $data, int $threshold): array
|
|||
$data[] = [$i * 1000, $i * 1.5];
|
||||
}
|
||||
|
||||
$result = callDownsampleLTTB($data, 20);
|
||||
$result = downsampleLTTB($data, 20);
|
||||
|
||||
// First point should be preserved
|
||||
expect($result[0])->toBe($data[0]);
|
||||
|
|
@ -78,7 +68,7 @@ function callDownsampleLTTB(array $data, int $threshold): array
|
|||
$data[] = [$i * 60000, sin($i / 10) * 50 + 50]; // Sine wave pattern
|
||||
}
|
||||
|
||||
$result = callDownsampleLTTB($data, 50);
|
||||
$result = downsampleLTTB($data, 50);
|
||||
|
||||
// Verify all timestamps are in non-decreasing order
|
||||
$previousTimestamp = -1;
|
||||
|
|
@ -100,7 +90,7 @@ function callDownsampleLTTB(array $data, int $threshold): array
|
|||
}
|
||||
|
||||
$startTime = microtime(true);
|
||||
$result = callDownsampleLTTB($data, 1000);
|
||||
$result = downsampleLTTB($data, 1000);
|
||||
$executionTime = microtime(true) - $startTime;
|
||||
|
||||
expect(count($result))->toBe(1000);
|
||||
|
|
@ -121,7 +111,7 @@ function callDownsampleLTTB(array $data, int $threshold): array
|
|||
$data[] = [$i * 1000, $value];
|
||||
}
|
||||
|
||||
$result = callDownsampleLTTB($data, 20);
|
||||
$result = downsampleLTTB($data, 20);
|
||||
|
||||
// The peak (100) and valley (0) should be preserved due to LTTB algorithm
|
||||
$values = array_column($result, 1);
|
||||
Loading…
Reference in a new issue