mailwolt/app/Livewire/Ui/System/HealthCard.php

203 lines
7.5 KiB
PHP
Raw Blame History

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

<?php
namespace App\Livewire\Ui\System;
use Carbon\Carbon;
use Illuminate\Support\Facades\Cache;
use Livewire\Component;
class HealthCard extends Component
{
// Inputquellen
public array $meta = [];
// UI: Balken/Segmente
public int $barSegments = 24;
public array $cpuSeg = [];
public array $ramSeg = [];
// Anzeige
public ?int $cpuPercent = null;
public ?int $ramPercent = null;
public ?string $ramSummary = null;
public ?string $loadText = null;
public array $loadDots = [];
public ?string $uptimeText = null;
public string $uptimeIcon = 'ph ph-clock';
public ?string $updatedAtHuman = null;
// NEW: IPs
public ?string $ip4 = null;
public ?string $ip6 = null;
public function mount(): void
{
$this->loadData();
}
public function loadData(): void
{
$this->meta = Cache::get('health:meta', []);
$this->hydrateSystem();
$this->hydrateIps();
$this->hydrateUpdatedAt();
}
public function render()
{
return view('livewire.ui.system.health-card');
}
/* --------------- Aufbereitung --------------- */
protected function hydrateSystem(): void
{
$sys = is_array($this->meta['system'] ?? null) ? $this->meta['system'] : [];
// RAM % + Summary
$this->ramPercent = $this->numInt($this->pick($sys, ['ram','ram_percent','mem_percent','memory']));
if ($this->ramPercent === null && is_array($sys['ram'] ?? null)) {
$this->ramPercent = $this->numInt($this->pick($sys['ram'], ['percent','used_percent']));
}
$used = $this->numInt($this->pick($sys['ram'] ?? [], ['used_gb','used','usedGB']));
$tot = $this->numInt($this->pick($sys['ram'] ?? [], ['total_gb','total','totalGB']));
$this->ramSummary = ($used !== null && $tot !== null) ? "{$used} GB / {$tot} GB" : null;
// Load (1/5/15)
$l1 = $this->numFloat($this->pick($sys, ['cpu_load_1','load1','load_1']));
$l5 = $this->numFloat($this->pick($sys, ['cpu_load_5','load5','load_5']));
$l15 = $this->numFloat($this->pick($sys, ['cpu_load_15','load15','load_15']));
$loadMixed = $this->pick($sys, ['load','loadavg','load_avg','load_text']);
if (is_array($loadMixed)) {
$vals = array_values($loadMixed);
$l1 ??= $this->numFloat($vals[0] ?? null);
$l5 ??= $this->numFloat($vals[1] ?? null);
$l15 ??= $this->numFloat($vals[2] ?? null);
}
$this->loadText = $this->fmtLoad($l1, $l5, $l15, is_string($loadMixed) ? $loadMixed : null);
// CPU %
$this->cpuPercent = $this->numInt($this->pick($sys, ['cpu','cpu_percent','cpuUsage','cpu_usage']));
if ($this->cpuPercent === null && $l1 !== null) {
$cores = $this->numInt($this->pick($sys, ['cores','cpu_cores','num_cores'])) ?: 1;
$this->cpuPercent = (int) round(min(100, max(0, ($l1 / max(1,$cores)) * 100)));
}
// Uptime
$this->uptimeText = $this->pick($sys, ['uptime_human','uptimeText','uptime']);
if (!$this->uptimeText) {
$uptimeSec = $this->numInt($this->pick($sys, ['uptime_seconds','uptime_sec','uptime_s']));
if ($uptimeSec !== null) $this->uptimeText = $this->secondsToHuman($uptimeSec);
}
// Segmente
$this->cpuSeg = $this->buildSegments($this->cpuPercent);
$this->ramSeg = $this->buildSegments($this->ramPercent);
// Load Dots relativ zu Kernen
$cores = max(1, (int)($this->pick($sys, ['cores','cpu_cores','num_cores']) ?? 1));
$ratio1 = $l1 !== null ? $l1 / $cores : null;
$ratio5 = $l5 !== null ? $l5 / $cores : null;
$ratio15 = $l15 !== null ? $l15 / $cores : null;
$this->loadDots = [
['label'=>'1m', 'cls'=>$this->loadDotClass($ratio1)],
['label'=>'5m', 'cls'=>$this->loadDotClass($ratio5)],
['label'=>'15m', 'cls'=>$this->loadDotClass($ratio15)],
];
}
protected function hydrateIps(): void
{
$sys = is_array($this->meta['system'] ?? null) ? $this->meta['system'] : [];
// Versuche diverse mögliche Keys
$this->ip4 = $this->pick($sys, ['ipv4','ip4','public_ipv4','server_public_ipv4','lan_ipv4']);
$this->ip6 = $this->pick($sys, ['ipv6','ip6','public_ipv6','server_public_ipv6','lan_ipv6']);
// Fallback: aus installer.env lesen
if (!$this->ip4 || !$this->ip6) {
$env = @file('/etc/mailwolt/installer.env', FILE_IGNORE_NEW_LINES) ?: [];
foreach ($env as $ln) {
if (!$this->ip4 && str_starts_with($ln, 'SERVER_PUBLIC_IPV4=')) {
$this->ip4 = trim(substr($ln, strlen('SERVER_PUBLIC_IPV4=')));
}
if (!$this->ip6 && str_starts_with($ln, 'SERVER_PUBLIC_IPV6=')) {
$this->ip6 = trim(substr($ln, strlen('SERVER_PUBLIC_IPV6=')));
}
}
}
// Auf hübsch: dünne Zwischenräume (wir lassen Blade das stylen monospaced + tracking)
$this->ip4 = $this->ip4 ?: '';
$this->ip6 = $this->ip6 ?: '';
}
protected function hydrateUpdatedAt(): void
{
$updated = $this->meta['updated_at'] ?? null;
try { $this->updatedAtHuman = $updated ? Carbon::parse($updated)->diffForHumans() : '';
} catch (\Throwable) { $this->updatedAtHuman = ''; }
}
/* --------------- Helpers --------------- */
protected function pick(array $arr, array $keys)
{
foreach ($keys as $k) {
if (array_key_exists($k, $arr) && $arr[$k] !== null && $arr[$k] !== '') {
return $arr[$k];
}
}
return null;
}
protected function numInt($v): ?int { return is_numeric($v) ? (int) round($v) : null; }
protected function numFloat($v): ?float { return is_numeric($v) ? (float)$v : null; }
protected function fmtLoad(?float $l1, ?float $l5, ?float $l15, ?string $fallback): ?string
{
if ($l1 !== null || $l5 !== null || $l15 !== null) {
$fmt = fn($x) => $x === null ? '' : number_format($x, 2);
return "{$fmt($l1)} / {$fmt($l5)} / {$fmt($l15)}";
}
return $fallback ?: null;
}
protected function secondsToHuman(int $s): string
{
$d = intdiv($s, 86400); $s %= 86400;
$h = intdiv($s, 3600); $s %= 3600;
$m = intdiv($s, 60);
if ($d > 0) return "{$d}d {$h}h";
if ($h > 0) return "{$h}h {$m}m";
return "{$m}m";
}
protected function toneByPercent(?int $p): string {
if ($p === null) return 'white';
if ($p >= 90) return 'rose';
if ($p >= 70) return 'amber';
return 'emerald';
}
protected function buildSegments(?int $percent): array {
$n = max(6, $this->barSegments);
$filled = is_numeric($percent) ? (int) round($n * max(0, min(100, $percent)) / 100) : 0;
$tone = $this->toneByPercent($percent);
$fillCls = match($tone) {
'rose' => 'bg-rose-500/80',
'amber' => 'bg-amber-400/80',
'emerald'=> 'bg-emerald-500/80',
default => 'bg-white/20',
};
return array_map(fn($i) => $i < $filled ? $fillCls : 'bg-white/10', range(0, $n-1));
}
protected function loadDotClass(?float $ratio): string {
if ($ratio === null) return 'bg-white/25';
if ($ratio >= 0.9) return 'bg-rose-500';
if ($ratio >= 0.7) return 'bg-amber-400';
return 'bg-emerald-400';
}
}