VPS and dedicated server ordering, managed panels, SSO, billing, and launcher integration — forked from hosting infrastructure with server-focused routes and dashboard. Co-authored-by: Cursor <cursoragent@cursor.com>
163 lines
5.4 KiB
PHP
163 lines
5.4 KiB
PHP
<?php
|
|
|
|
namespace App\Console\Commands;
|
|
|
|
use App\Models\HostingNode;
|
|
use App\Services\Hosting\NodeMonitoringService;
|
|
use Illuminate\Console\Command;
|
|
|
|
class CheckNodeHealthCommand extends Command
|
|
{
|
|
protected $signature = 'hosting:check-node-health
|
|
{--node= : Check a specific node by ID}
|
|
{--all : Check all active nodes}';
|
|
|
|
protected $description = 'Run health checks on hosting nodes to collect resource metrics';
|
|
|
|
public function handle(NodeMonitoringService $monitoringService): int
|
|
{
|
|
$nodeId = $this->option('node');
|
|
|
|
if ($nodeId) {
|
|
return $this->checkSingleNode($monitoringService, (int) $nodeId);
|
|
}
|
|
|
|
return $this->checkAllNodes($monitoringService);
|
|
}
|
|
|
|
private function checkSingleNode(NodeMonitoringService $monitoringService, int $nodeId): int
|
|
{
|
|
$node = HostingNode::find($nodeId);
|
|
|
|
if (!$node) {
|
|
$this->error("Node with ID {$nodeId} not found.");
|
|
return self::FAILURE;
|
|
}
|
|
|
|
$this->info("Checking node: {$node->name} ({$node->ip_address})...");
|
|
|
|
$result = $monitoringService->checkNode($node);
|
|
|
|
$this->displayNodeResult($node, $result);
|
|
|
|
return self::SUCCESS;
|
|
}
|
|
|
|
private function checkAllNodes(NodeMonitoringService $monitoringService): int
|
|
{
|
|
$nodes = HostingNode::whereIn('status', [
|
|
HostingNode::STATUS_ACTIVE,
|
|
HostingNode::STATUS_FULL,
|
|
HostingNode::STATUS_MAINTENANCE,
|
|
])->get();
|
|
|
|
if ($nodes->isEmpty()) {
|
|
$this->warn('No active nodes found to check.');
|
|
return self::SUCCESS;
|
|
}
|
|
|
|
$this->info("Checking {$nodes->count()} node(s)...");
|
|
$this->newLine();
|
|
|
|
$results = [];
|
|
$bar = $this->output->createProgressBar($nodes->count());
|
|
$bar->start();
|
|
|
|
foreach ($nodes as $node) {
|
|
$results[$node->id] = $monitoringService->checkNode($node);
|
|
$bar->advance();
|
|
}
|
|
|
|
$bar->finish();
|
|
$this->newLine(2);
|
|
|
|
$this->displaySummaryTable($nodes, $results);
|
|
|
|
$unhealthy = collect($results)->filter(fn($r) => ($r['health'] ?? '') !== NodeMonitoringService::HEALTH_OK);
|
|
if ($unhealthy->isNotEmpty()) {
|
|
$this->newLine();
|
|
$this->warn("⚠️ {$unhealthy->count()} node(s) require attention.");
|
|
}
|
|
|
|
return self::SUCCESS;
|
|
}
|
|
|
|
private function displayNodeResult(HostingNode $node, array $result): void
|
|
{
|
|
$health = $result['health'] ?? 'unknown';
|
|
$healthIcon = match ($health) {
|
|
NodeMonitoringService::HEALTH_OK => '✅',
|
|
NodeMonitoringService::HEALTH_WARNING => '⚠️',
|
|
NodeMonitoringService::HEALTH_CRITICAL => '🔴',
|
|
NodeMonitoringService::HEALTH_UNREACHABLE => '❌',
|
|
default => '❓',
|
|
};
|
|
|
|
$this->newLine();
|
|
$this->info("{$healthIcon} Health: " . strtoupper($health));
|
|
$this->newLine();
|
|
|
|
if (isset($result['error'])) {
|
|
$this->error("Error: {$result['error']}");
|
|
return;
|
|
}
|
|
|
|
$this->table(
|
|
['Metric', 'Value'],
|
|
[
|
|
['CPU Usage', ($result['cpu']['usage_percent'] ?? 'N/A') . '%'],
|
|
['RAM Usage', ($result['memory']['usage_percent'] ?? 'N/A') . '%'],
|
|
['Disk Usage (root)', ($result['disk']['root']['usage_percent'] ?? 'N/A') . '%'],
|
|
['Load (1m)', $result['load']['load_1'] ?? 'N/A'],
|
|
['Load per Core', $result['load']['load_per_core'] ?? 'N/A'],
|
|
['Uptime', $result['uptime']['formatted'] ?? 'N/A'],
|
|
['Processes', $result['processes']['total'] ?? 'N/A'],
|
|
['Response Time', ($result['response_time_ms'] ?? 'N/A') . 'ms'],
|
|
]
|
|
);
|
|
|
|
if (!empty($result['services'])) {
|
|
$this->newLine();
|
|
$this->info('Services:');
|
|
foreach ($result['services'] as $service => $running) {
|
|
$icon = $running ? '✅' : '❌';
|
|
$this->line(" {$icon} {$service}");
|
|
}
|
|
}
|
|
}
|
|
|
|
private function displaySummaryTable($nodes, array $results): void
|
|
{
|
|
$rows = [];
|
|
|
|
foreach ($nodes as $node) {
|
|
$result = $results[$node->id] ?? [];
|
|
$health = $result['health'] ?? 'unknown';
|
|
|
|
$healthIcon = match ($health) {
|
|
NodeMonitoringService::HEALTH_OK => '✅',
|
|
NodeMonitoringService::HEALTH_WARNING => '⚠️',
|
|
NodeMonitoringService::HEALTH_CRITICAL => '🔴',
|
|
NodeMonitoringService::HEALTH_UNREACHABLE => '❌',
|
|
default => '❓',
|
|
};
|
|
|
|
$rows[] = [
|
|
$node->name,
|
|
$node->ip_address,
|
|
"{$healthIcon} " . strtoupper($health),
|
|
isset($result['cpu']['usage_percent']) ? $result['cpu']['usage_percent'] . '%' : 'N/A',
|
|
isset($result['memory']['usage_percent']) ? $result['memory']['usage_percent'] . '%' : 'N/A',
|
|
isset($result['disk']['root']['usage_percent']) ? $result['disk']['root']['usage_percent'] . '%' : 'N/A',
|
|
$result['load']['load_1'] ?? 'N/A',
|
|
"{$node->current_accounts}/{$node->max_accounts}",
|
|
];
|
|
}
|
|
|
|
$this->table(
|
|
['Node', 'IP', 'Health', 'CPU', 'RAM', 'Disk', 'Load', 'Accounts'],
|
|
$rows
|
|
);
|
|
}
|
|
}
|