remote monitor (#167)

This commit is contained in:
Saeed Vaziry 2024-04-17 16:03:06 +02:00 committed by GitHub
parent 0cd815cce6
commit d07e9bcad2
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
20 changed files with 250 additions and 7 deletions

View File

@ -0,0 +1,31 @@
<?php
namespace App\Console\Commands;
use App\Models\Server;
use Illuminate\Console\Command;
use Illuminate\Database\Eloquent\Builder;
class GetMetricsCommand extends Command
{
protected $signature = 'metrics:get';
protected $description = 'Get server metrics';
public function handle(): void
{
$checkedMetrics = 0;
Server::query()->whereHas('services', function (Builder $query) {
$query->where('type', 'monitoring')
->where('name', 'remote-monitor');
})->chunk(10, function ($servers) use (&$checkedMetrics) {
/** @var Server $server */
foreach ($servers as $server) {
$info = $server->os()->resourceInfo();
$server->metrics()->create(array_merge($info, ['server_id' => $server->id]));
$checkedMetrics++;
}
});
$this->info("Checked $checkedMetrics metrics");
}
}

View File

@ -17,6 +17,7 @@ protected function schedule(Schedule $schedule): void
$schedule->command('backups:run "0 0 * * 0"')->weekly();
$schedule->command('backups:run "0 0 1 * *"')->monthly();
$schedule->command('metrics:delete-older-metrics')->daily();
$schedule->command('metrics:get')->everyMinute();
}
/**

View File

@ -166,4 +166,21 @@ public function cleanup(): void
'cleanup'
);
}
public function resourceInfo(): array
{
$info = $this->server->ssh()->exec(
$this->getScript('resource-info.sh'),
);
return [
'load' => str($info)->after('load:')->before(PHP_EOL)->toString(),
'memory_total' => str($info)->after('memory_total:')->before(PHP_EOL)->toString(),
'memory_used' => str($info)->after('memory_used:')->before(PHP_EOL)->toString(),
'memory_free' => str($info)->after('memory_free:')->before(PHP_EOL)->toString(),
'disk_total' => str($info)->after('disk_total:')->before(PHP_EOL)->toString(),
'disk_used' => str($info)->after('disk_used:')->before(PHP_EOL)->toString(),
'disk_free' => str($info)->after('disk_free:')->before(PHP_EOL)->toString(),
];
}
}

View File

@ -0,0 +1,7 @@
echo "load:$(uptime | awk -F'load average:' '{print $2}' | awk -F, '{print $1}' | tr -d ' ')"
echo "memory_total:$(free -k | awk 'NR==2{print $2}')"
echo "memory_used:$(free -k | awk 'NR==2{print $3}')"
echo "memory_free:$(free -k | awk 'NR==2{print $7}')"
echo "disk_total:$(df -BM / | awk 'NR==2{print $2}' | sed 's/M//')"
echo "disk_used:$(df -BM / | awk 'NR==2{print $3}' | sed 's/M//')"
echo "disk_free:$(df -BM / | awk 'NR==2{print $4}' | sed 's/M//')"

View File

@ -0,0 +1,53 @@
<?php
namespace App\SSH\Services\Monitoring\RemoteMonitor;
use App\Models\Metric;
use App\SSH\Services\AbstractService;
use Closure;
use Illuminate\Validation\Rule;
class RemoteMonitor extends AbstractService
{
public function creationRules(array $input): array
{
return [
'type' => [
function (string $attribute, mixed $value, Closure $fail) {
$monitoringExists = $this->service->server->monitoring();
if ($monitoringExists) {
$fail('You already have a monitoring service on the server.');
}
},
],
'version' => [
'required',
Rule::in(['latest']),
],
];
}
public function creationData(array $input): array
{
return [
'data_retention' => 10,
];
}
public function data(): array
{
return [
'data_retention' => $this->service->type_data['data_retention'] ?? 10,
];
}
public function install(): void
{
//
}
public function uninstall(): void
{
Metric::where('server_id', $this->service->server_id)->delete();
}
}

View File

@ -1,6 +1,6 @@
<?php
namespace App\SSH\Services\VitoAgent;
namespace App\SSH\Services\Monitoring\VitoAgent;
use App\Models\Metric;
use App\SSH\HasScripts;

View File

@ -142,6 +142,7 @@
'ufw' => 'firewall',
'supervisor' => 'process_manager',
'vito-agent' => 'monitoring',
'remote-monitor' => 'monitoring',
],
'service_handlers' => [
'nginx' => \App\SSH\Services\Webserver\Nginx::class,
@ -152,7 +153,8 @@
'php' => \App\SSH\Services\PHP\PHP::class,
'ufw' => \App\SSH\Services\Firewall\Ufw::class,
'supervisor' => \App\SSH\Services\ProcessManager\Supervisor::class,
'vito-agent' => \App\SSH\Services\VitoAgent\VitoAgent::class,
'vito-agent' => \App\SSH\Services\Monitoring\VitoAgent\VitoAgent::class,
'remote-monitor' => \App\SSH\Services\Monitoring\RemoteMonitor\RemoteMonitor::class,
],
'service_units' => [
'nginx' => [

View File

@ -0,0 +1,29 @@
<?xml version="1.0" encoding="utf-8"?><!-- Uploaded to: SVG Repo, www.svgrepo.com, Generator: SVG Repo Mixer Tools -->
<svg width="800px" height="800px" viewBox="0 -5.23 70 70" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:cc="http://creativecommons.org/ns#" xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" xmlns="http://www.w3.org/2000/svg">
<metadata>
<rdf:RDF>
<cc:Work>
<dc:subject>
Miscellaneous
</dc:subject>
<dc:identifier>
health-monitoring
</dc:identifier>
<dc:title>
Health Monitoring
</dc:title>
<dc:format>
image/svg+xml
</dc:format>
<dc:publisher>
Amido Limited
</dc:publisher>
<dc:creator>
Richard Slater
</dc:creator>
<dc:type rdf:resource="http://purl.org/dc/dcmitype/StillImage"/>
</cc:Work>
</rdf:RDF>
</metadata>
<path d="m -633.94123,753.25889 c -6.59942,-3.2916 -17.80605,-13.7307 -24.90952,-23.2035 l -0.38611,-0.5149 4.90905,0 c 3.3284,0 5.08031,-0.051 5.44092,-0.1594 0.95525,-0.2862 1.50799,-0.9179 2.58607,-2.9554 1.97619,-3.735 2.24879,-4.2224 2.2879,-4.0904 0.0218,0.074 0.44604,4.3009 0.94276,9.3939 0.87326,8.9538 0.91529,9.2823 1.27154,9.9368 0.62081,1.1407 1.47439,1.6301 2.85312,1.6359 1.01617,0 1.76269,-0.3415 2.41627,-1.1191 0.25355,-0.3016 1.82033,-3.2056 3.48173,-6.4532 l 3.02073,-5.9047 10.36659,-0.039 c 10.32236,-0.039 10.36894,-0.041 10.91581,-0.3356 1.1802,-0.6369 1.77594,-1.6202 1.77528,-2.9304 -6.9e-4,-1.3721 -0.67396,-2.4208 -1.91258,-2.9791 -0.5125,-0.231 -1.30161,-0.2501 -11.80218,-0.2858 -7.69785,-0.026 -11.47959,0.01 -11.97032,0.1108 -1.27206,0.264 -1.77303,0.7868 -3.0106,3.1416 l -1.08999,2.0739 -0.1043,-0.5158 c -0.0574,-0.2837 -0.47667,-4.3775 -0.9318,-9.0974 -0.45513,-4.7199 -0.88563,-8.7992 -0.95668,-9.0652 -0.36496,-1.3662 -1.62876,-2.2659 -3.16688,-2.2544 -1.04822,0.01 -1.94772,0.4395 -2.48617,1.1931 -0.17485,0.2447 -1.92936,3.5346 -3.8989,7.311 l -3.581,6.866 -5.76782,0.036 -5.76783,0.036 -0.83086,-1.6834 c -2.06318,-4.1804 -2.89449,-7.6097 -2.738,-11.2949 0.12425,-2.9261 0.69392,-5.0125 2.04328,-7.4832 1.10812,-2.029 3.06519,-4.3559 4.69277,-5.5795 1.78333,-1.3407 4.15216,-2.2461 6.64618,-2.5403 2.10735,-0.2485 4.60651,0.089 7.37391,0.9964 1.2153,0.3984 4.21499,1.9073 5.62954,2.8318 2.45012,1.6012 5.68511,4.4633 7.84072,6.9369 l 0.80955,0.929 0.94007,-1.2397 c 1.88483,-2.4857 4.78785,-5.1075 7.55221,-6.8208 5.19337,-3.2187 11.05786,-4.2791 15.6703,-2.8335 3.74959,1.1752 6.7744,3.9944 8.98105,8.3706 2.19828,4.3596 2.39398,9.8576 0.53892,15.1404 -1.06649,3.0372 -2.39805,5.6594 -4.46756,8.7979 -2.55838,3.88 -4.87538,6.6471 -9.08862,10.8542 -5.31708,5.3093 -11.00984,9.9038 -16.48777,13.3068 -1.60577,0.9976 -3.84246,2.2037 -4.0818,2.201 -0.0583,0 -0.75536,-0.325 -1.54898,-0.7208 z" fill="#00bcf2" transform="translate(667.003 -694.43)"/>
</svg>

After

Width:  |  Height:  |  Size: 3.0 KiB

View File

@ -22,7 +22,12 @@ class="p-6"
<x-input-label for="data_retention" value="Delete metrics older than" />
<x-select-input id="data_retention" name="data_retention" class="mt-1 w-full">
@foreach (config("core.metrics_data_retention") as $item)
<option value="{{ $item }}">{{ $item }} Days</option>
<option
value="{{ $item }}"
@if($server->monitoring()->handler()->data()['data_retention'] == $item) selected @endif
>
{{ $item }} Days
</option>
@endforeach
</x-select-input>
@error("data_retention")

View File

@ -0,0 +1,6 @@
@include("services.partials.unit-actions.restart", ["disabled" => true])
@include("services.partials.unit-actions.start", ["disabled" => true])
@include("services.partials.unit-actions.stop", ["disabled" => true])
@include("services.partials.unit-actions.enable", ["disabled" => true])
@include("services.partials.unit-actions.disable", ["disabled" => true])
@include("services.partials.unit-actions.uninstall")

View File

@ -0,0 +1,37 @@
<x-secondary-button class="!w-full" x-on:click="$dispatch('open-modal', 'install-remote-monitor')">
Install
</x-secondary-button>
@push("modals")
<x-modal name="install-remote-monitor">
<form
id="install-remote-monitor-form"
hx-post="{{ route("servers.services.install", ["server" => $server]) }}"
hx-swap="outerHTML"
hx-select="#install-remote-monitor-form"
class="p-6"
>
@csrf
<input type="hidden" name="name" value="remote-monitor" />
<input type="hidden" name="type" value="monitoring" />
<input type="hidden" name="version" value="latest" />
<h2 class="text-lg font-medium text-gray-900 dark:text-gray-100">
{{ __("Install Remote Monitor") }}
</h2>
@error("type")
<x-input-error class="mt-2" :messages="$message" />
@enderror
<div class="mt-6 flex justify-end">
<x-secondary-button type="button" x-on:click="$dispatch('close')">
{{ __("Cancel") }}
</x-secondary-button>
<x-primary-button id="btn-remote-monitor" hx-disable class="ml-3">
{{ __("Install") }}
</x-primary-button>
</div>
</form>
</x-modal>
@endpush

View File

@ -21,7 +21,8 @@ class="p-6"
<div class="mt-6">
<x-alert-warning>
Vito Agent is only works if you are running your Vito instance on a cloud not local!
Vito Agent is only works if you are running your Vito instance on a cloud not local! Consider
installing remote-monitor instead.
</x-alert-warning>
</div>

View File

@ -1,4 +1,5 @@
<x-icon-button
:disabled="isset($disabled) ? $disabled : false"
data-tooltip="Disable Service"
class="cursor-pointer"
href="{{ route('servers.services.disable', ['server' => $server, 'service' => $service]) }}"

View File

@ -1,5 +1,5 @@
<x-icon-button
:disabled="$service->status != \App\Enums\ServiceStatus::DISABLED"
:disabled="isset($disabled) ? $disabled : $service->status != \App\Enums\ServiceStatus::DISABLED"
data-tooltip="Enable Service"
class="cursor-pointer"
href="{{ route('servers.services.enable', ['server' => $server, 'service' => $service]) }}"

View File

@ -1,4 +1,5 @@
<x-icon-button
:disabled="isset($disabled) ? $disabled : false"
data-tooltip="Restart Service"
class="cursor-pointer"
href="{{ route('servers.services.restart', ['server' => $server, 'service' => $service]) }}"

View File

@ -1,5 +1,5 @@
<x-icon-button
:disabled="$service->status != \App\Enums\ServiceStatus::STOPPED"
:disabled="isset($disabled) ? $disabled : $service->status != \App\Enums\ServiceStatus::STOPPED"
data-tooltip="Start Service"
class="cursor-pointer"
href="{{ route('servers.services.start', ['server' => $server, 'service' => $service]) }}"

View File

@ -1,6 +1,6 @@
<x-icon-button
:disabled="isset($disabled) ? $disabled : $service->status != \App\Enums\ServiceStatus::READY"
data-tooltip="Stop Service"
:disabled="$service->status != \App\Enums\ServiceStatus::READY"
class="cursor-pointer"
href="{{ route('servers.services.stop', ['server' => $server, 'service' => $service]) }}"
>

View File

@ -0,0 +1,52 @@
<?php
namespace Tests\Unit\Commands;
use App\Enums\ServiceStatus;
use App\Facades\SSH;
use App\Models\Service;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Tests\TestCase;
class GetMetricsCommandTest extends TestCase
{
use RefreshDatabase;
public function test_get_metrics(): void
{
SSH::fake(<<<'EOF'
load:1
memory_total:1
memory_used:1
memory_free:1
disk_total:1
disk_used:1
disk_free:1
EOF);
Service::factory()->create([
'server_id' => $this->server->id,
'name' => 'remote-monitor',
'type' => 'monitoring',
'type_data' => [
'data_retention' => 7,
],
'version' => 'latest',
'status' => ServiceStatus::READY,
]);
$this->artisan('metrics:get')
->expectsOutput('Checked 1 metrics');
$this->assertDatabaseHas('metrics', [
'server_id' => $this->server->id,
'load' => 1,
'memory_total' => 1,
'memory_used' => 1,
'memory_free' => 1,
'disk_total' => 1,
'disk_used' => 1,
'disk_free' => 1,
]);
}
}