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

477 lines
17 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 Livewire\Component;
use Illuminate\Support\Facades\Artisan;
use App\Models\Setting;
class StorageCard extends Component
{
public string $target = '/';
// Summen für die Zahlenanzeige (GB)
public ?int $diskTotalGb = null;
public ?int $diskUsedGb = null;
public ?int $diskFreeGb = null;
// Donut
public array $diskSegments = []; // [{angle,class}]
public int $diskSegOuterRadius = 92;
public int $diskInnerSize = 160;
public array $diskCenterText = ['percent' => '', 'label' => 'SPEICHER BELEGT'];
// Legende/Bar (GENAU wie dein Blade es erwartet)
// [{label,color,gb,percent}]
public array $barSegments = [];
public ?string $measuredAt = null;
protected int $segCount = 100; // Donut-Segmente
private int $legendMinBytes = 104858; // 0,1 MiB darunter blenden wir aus
public function mount(string $target = '/'): void
{
$this->target = $target ?: '/';
$this->loadFromSettings();
}
public function render()
{
return view('livewire.ui.system.storage-card');
}
public function refresh(): void
{
Artisan::call('health:probe-disk', ['target' => $this->target]);
$this->loadFromSettings();
}
// ─────────────────────────────────────────────────────────────
private function humanBytes(float|int $bytes): string
{
$b = max(0, (float)$bytes);
if ($b >= 1024 ** 3) return number_format($b / (1024 ** 3), 1) . ' GB';
if ($b >= 1024 ** 2) return number_format($b / (1024 ** 2), 2) . ' MiB';
if ($b >= 1024) return number_format($b / 1024, 0) . ' KiB';
return (string)((int)$b) . ' B';
}
protected function loadFromSettings(): void
{
$disk = Setting::get('health.disk', []);
if (!is_array($disk) || empty($disk)) {
$this->resetUi();
return;
}
// Summen (GB) so wie dein Blade sie zeigt
$this->diskTotalGb = self::intOrNull($disk['total_gb'] ?? null);
$this->diskUsedGb = self::intOrNull($disk['used_gb'] ?? null);
$this->diskFreeGb = self::intOrNull($disk['free_gb'] ?? ($disk['free_plus_reserve_gb'] ?? null));
$percent = $disk['percent_used_total'] ?? null;
$this->diskCenterText = [
'percent' => is_numeric($percent) ? (string)round($percent) . '%' : '',
'label' => 'SPEICHER BELEGT',
];
// Breakdown in BYTES (kommt aus StorageProbe)
$bdBytes = $disk['breakdown_bytes'] ?? ['system' => 0, 'mails' => 0, 'backup' => 0];
// Total/Free in BYTES ableiten
$totalBytes = (int)round(((int)($disk['total_gb'] ?? 0)) * 1024 ** 3);
$freeGb = $disk['free_gb'] ?? ($disk['free_plus_reserve_gb'] ?? 0);
$freeBytes = (int)round(((float)$freeGb) * 1024 ** 3);
// reportetes Used (Bytes)
$usedReportedBytes = max(0, $totalBytes - $freeBytes);
// Summe Breakdown angleichen (Rest → System), damit Donut/Prozente stimmig sind
$sumUsedBreakdown = max(0, (int)$bdBytes['system'] + (int)$bdBytes['mails'] + (int)$bdBytes['backup']);
if ($usedReportedBytes > $sumUsedBreakdown) {
$bdBytes['system'] += ($usedReportedBytes - $sumUsedBreakdown);
}
// Donut färben
$this->diskSegments = $this->buildDonutSegmentsFromBytes($bdBytes, $totalBytes, $freeBytes);
// Legende/Bar (GB + Prozent; KEIN „text“-Feld)
$this->barSegments = $this->buildLegendGbFromBytes($bdBytes, $totalBytes, $freeBytes);
$this->measuredAt = Setting::get('health.disk_updated_at', null);
}
protected function buildDonutSegmentsFromBytes(array $bdBytes, int $totalBytes, int $freeBytes): array
{
if ($totalBytes <= 0) return [];
$order = [
['key' => 'system', 'class' => 'bg-emerald-400'],
['key' => 'mails', 'class' => 'bg-rose-400'],
['key' => 'backup', 'class' => 'bg-sky-400'],
];
$counts = [];
foreach ($order as $d) {
$bytes = max(0, (int)($bdBytes[$d['key']] ?? 0));
$cnt = (int)round($this->segCount * ($bytes / $totalBytes));
if ($bytes > 0 && $cnt === 0) $cnt = 1; // min. 1 Segment, wenn vorhanden
$counts[$d['key']] = $cnt;
}
$usedCount = array_sum($counts);
$freeCount = max(0, $this->segCount - $usedCount);
$segments = [];
foreach ($order as $d) {
for ($i = 0; $i < $counts[$d['key']]; $i++) {
$segments[] = ['class' => $d['class']];
}
}
for ($i = 0; $i < $freeCount; $i++) {
$segments[] = ['class' => 'bg-white/15'];
}
while (count($segments) < $this->segCount) {
$segments[] = ['class' => 'bg-white/15'];
}
$out = [];
for ($i = 0; $i < $this->segCount; $i++) {
$angle = (360 / $this->segCount) * $i - 90;
$out[] = ['angle' => $angle, 'class' => $segments[$i]['class']];
}
return $out;
}
// protected function buildLegendGbFromBytes(array $bdBytes, int $totalBytes, int $freeBytes): array
// {
// $defs = [
// ['key' => 'system', 'label' => 'System', 'class' => 'bg-emerald-400'],
// ['key' => 'mails', 'label' => 'Mails', 'class' => 'bg-rose-400'],
// ['key' => 'backup', 'label' => 'Backups', 'class' => 'bg-sky-400'],
// ];
//
// $toPercent = function (int $bytes) use ($totalBytes): int {
// if ($totalBytes <= 0) return 0;
// return (int)max(0, min(100, round($bytes * 100 / $totalBytes)));
// };
// $toGb = fn(int $bytes) => round($bytes / (1024 ** 3), 1);
//
// $out = [];
// foreach ($defs as $d) {
// $val = max(0, (int)($bdBytes[$d['key']] ?? 0));
// if ($val <= 0) continue; // zu klein → nicht anzeigen
// $out[] = [
// 'label' => $d['label'],
// 'color' => $d['class'],
// 'gb' => $toGb($val), // ← genau das Feld, das dein Blade nutzt
// 'percent' => $toPercent($val),
// ];
// }
//
// // „Frei“ immer anzeigen
// $out[] = [
// 'label' => 'Frei',
// 'color' => 'bg-white/20',
// 'gb' => $toGb($freeBytes),
// 'percent' => $toPercent($freeBytes),
// ];
//
// return $out;
// }
protected function buildLegendGbFromBytes(array $bdBytes, int $totalBytes, int $freeBytes): array
{
$defs = [
['key' => 'system', 'label' => 'System', 'class' => 'bg-emerald-400'],
['key' => 'mails', 'label' => 'Mails', 'class' => 'bg-rose-400'],
['key' => 'backup', 'label' => 'Backups', 'class' => 'bg-sky-400'],
];
$toPercent = function (int $bytes) use ($totalBytes): int {
if ($totalBytes <= 0) return 0;
return (int)max(0, min(100, round($bytes * 100 / $totalBytes)));
};
$toGbNum = fn(int $bytes) => round($bytes / (1024 ** 3), 1);
$out = [];
foreach ($defs as $d) {
$val = max(0, (int)($bdBytes[$d['key']] ?? 0));
// < 1 MiB NICHT anzeigen
if ($val < $this->legendMinBytes) continue;
$out[] = [
'label' => $d['label'],
'color' => $d['class'],
// numerisch in GB lassen (falls du später rechnen willst)
'gb' => $toGbNum($val),
'percent' => $toPercent($val),
// FERTIG formatiert mit Einheit fürs Blade
'human' => $this->humanBytes($val),
];
}
// „Frei“ immer anzeigen
$out[] = [
'label' => 'Frei',
'color' => 'bg-white/20',
'gb' => $toGbNum($freeBytes),
'percent' => $toPercent($freeBytes),
'human' => $this->humanBytes($freeBytes),
];
return $out;
}
protected function resetUi(): void
{
$this->diskTotalGb = null;
$this->diskUsedGb = null;
$this->diskFreeGb = null;
$this->diskSegments = [];
$this->barSegments = [];
$this->diskCenterText = ['percent' => '', 'label' => 'SPEICHER BELEGT'];
$this->measuredAt = null;
}
protected static function intOrNull($v): ?int
{
return is_numeric($v) ? (int)round($v) : null;
}
}
//
//
//namespace App\Livewire\Ui\System;
//
//use Livewire\Component;
//use Illuminate\Support\Facades\Artisan;
//use App\Models\Setting;
//
//class StorageCard extends Component
//{
// public string $target = '/';
//
// // Summen
// public ?int $diskTotalGb = null;
// public ?int $diskUsedGb = null;
// public ?int $diskFreeGb = null;
//
// // Donut
// public array $diskSegments = [];
// public int $diskSegOuterRadius = 92;
// public int $diskInnerSize = 160;
// public array $diskCenterText = ['percent' => '', 'label' => 'SPEICHER BELEGT'];
//
// private int $legendMinBytes = 1 * 1024 * 1024; // 1 MiB Schwelle
//
// // Stacked-Bar + Legende
// public array $barSegments = []; // [{label,color,gb,percent}]
// public ?string $measuredAt = null;
//
// protected int $segCount = 100;
//
// public function mount(string $target = '/'): void
// {
// $this->target = $target ?: '/';
// $this->loadFromSettings();
// }
//
// public function render()
// {
// return view('livewire.ui.system.storage-card');
// }
//
// public function refresh(): void
// {
// Artisan::call('health:probe-disk', ['target' => $this->target]);
// $this->loadFromSettings();
// }
//
// private function humanSize(int $bytes): string
// {
// if ($bytes >= 1024**3) return number_format($bytes / 1024**3, 1) . ' GB';
// if ($bytes >= 1024**2) return number_format($bytes / 1024**2, 2) . ' MiB';
// if ($bytes >= 1024) return number_format($bytes / 1024, 0) . ' KiB';
// return $bytes . ' B';
// }
//
// protected function loadFromSettings(): void
// {
// $disk = Setting::get('health.disk', []);
// if (!is_array($disk) || empty($disk)) { $this->resetUi(); return; }
//
// $this->diskTotalGb = self::intOrNull($disk['total_gb'] ?? null);
// $this->diskUsedGb = self::intOrNull($disk['used_gb'] ?? null);
// $this->diskFreeGb = self::intOrNull($disk['free_gb'] ?? ($disk['free_plus_reserve_gb'] ?? null));
//
// $percent = $disk['percent_used_total'] ?? null;
// $this->diskCenterText = [
// 'percent' => is_numeric($percent) ? (string)round($percent).'%' : '',
// 'label' => 'SPEICHER BELEGT',
// ];
//
// // Donut farbig aus Breakdown
// $this->diskSegments = $this->buildDonutSegmentsFromBreakdown($disk);
//
// // Legende unten (gleiche Farben wie Donut)
// $total = max(1, (float)($disk['total_gb'] ?? 1));
// $bd = $disk['breakdown'] ?? [];
// $sys = (float)($bd['system_gb'] ?? 0);
// $mails = (float)($bd['mails_gb'] ?? 0);
// $backup = (float)($bd['backup_gb'] ?? 0);
// $free = (float)($disk['free_gb'] ?? ($disk['free_plus_reserve_gb'] ?? 0));
// $p = fn(float $gb) => max(0, min(100, round($gb * 100 / $total)));
//
// $this->barSegments = [
// ['label' => 'System', 'gb' => round($sys,1), 'percent' => $p($sys), 'color' => 'bg-emerald-400'],
// ['label' => 'Mails', 'gb' => round($mails,1), 'percent' => $p($mails), 'color' => 'bg-rose-400'],
// ['label' => 'Backups', 'gb' => round($backup,1),'percent' => $p($backup), 'color' => 'bg-sky-400'],
// ['label' => 'Frei', 'gb' => round($free,1), 'percent' => $p($free), 'color' => 'bg-white/20'],
// ];
//
// $this->barSegments = array_values(array_filter(
// $this->barSegments,
// fn($b) => ($b['gb'] ?? 0) > 0 // nur Einträge mit realer Größe
// ));
//
// $this->measuredAt = Setting::get('health.disk_updated_at', null);
// }
//
// // NEU: ersetzt buildSegments() + nutzt Breakdown
// protected function buildDonutSegmentsFromBreakdown(array $disk): array
// {
// $total = (float)($disk['total_gb'] ?? 0);
// if ($total <= 0) return [];
//
// // Breakdown lesen
// $bd = $disk['breakdown'] ?? [];
// $sys = (float)($bd['system_gb'] ?? 0);
// $mails = (float)($bd['mails_gb'] ?? 0);
// $backup = (float)($bd['backup_gb'] ?? 0);
// $free = (float)($disk['free_gb'] ?? ($disk['free_plus_reserve_gb'] ?? 0));
//
// // Robust machen: falls used aus Settings größer ist als Breakdown-Summe → auf System draufschlagen
// $usedReported = (float)($disk['used_gb'] ?? ($total - $free));
// $sumUsedBd = $sys + $mails + $backup;
// if ($usedReported > $sumUsedBd && $usedReported <= $total) {
// $sys += ($usedReported - $sumUsedBd);
// }
// // Grenzen
// $sys = max(0, min($sys, $total));
// $mails = max(0, min($mails, $total));
// $backup = max(0, min($backup, $total));
// $free = max(0, min($free, $total));
//
// // Segmente verteilen
// $mkCount = function (float $gb) use ($total) {
// return (int) round($this->segCount * $gb / $total);
// };
//
// $segments = [];
// $add = function (int $count, string $class) use (&$segments) {
// for ($i = 0; $i < $count; $i++) {
// $segments[] = ['class' => $class];
// }
// };
//
// $add($mkCount($sys), 'bg-emerald-400'); // System
// $add($mkCount($mails), 'bg-rose-400'); // Mails
// $add($mkCount($backup), 'bg-sky-400'); // Backups
//
// // Rest = Frei (grau)
// while (count($segments) < $this->segCount) {
// $segments[] = ['class' => 'bg-white/15'];
// }
//
// // Winkel setzen (gleich wie vorher)
// $out = [];
// for ($i = 0; $i < $this->segCount; $i++) {
// $angle = (360 / $this->segCount) * $i - 90;
// $out[] = ['angle' => $angle, 'class' => $segments[$i]['class']];
// }
// return $out;
// }
//
//// protected function buildSegments(?int $percent): array
//// {
//// $segments = [];
//// $active = is_int($percent) ? (int)round($this->segCount * $percent / 100) : 0;
////
//// $activeClass = match (true) {
//// !is_int($percent) => 'bg-white/15',
//// $percent >= 90 => 'bg-rose-400',
//// $percent >= 70 => 'bg-amber-300',
//// default => 'bg-emerald-400',
//// };
////
//// for ($i = 0; $i < $this->segCount; $i++) {
//// $angle = (360 / $this->segCount) * $i - 90;
//// $segments[] = [
//// 'angle' => $angle,
//// 'class' => $i < $active ? $activeClass : 'bg-white/15'
//// ];
//// }
//// return $segments;
//// }
//
// protected function buildBar(array $disk): array
// {
// $total = (float)($disk['total_gb'] ?? 0);
// if ($total <= 0) return [];
//
// // Breakdown lesen + normalisieren
// [$sys, $mails, $backups] = $this->readBreakdown($disk);
//
// $free = max(0.0, (float)($disk['free_gb'] ?? ($disk['free_plus_reserve_gb'] ?? 0)));
// $used = min($total, $sys + $mails + $backups); // robust bei Messrauschen
//
// // Falls Breakdown kleiner ist als used_gb: Rest als “System” draufschlagen,
// // damit die Prozent-Summe 100 ergibt.
// $usedReported = (float)($disk['used_gb'] ?? ($total - $free));
// if ($usedReported > 0 && $used < $usedReported) {
// $sys += ($usedReported - $used);
// $used = $usedReported;
// }
//
// // Prozent berechnen
// $p = fn(float $gb) => max(0, min(100, round($gb * 100 / $total)));
//
// return [
// ['label' => 'System', 'gb' => round($sys, 1), 'percent' => $p($sys), 'color' => 'bg-emerald-400'],
// ['label' => 'Mails', 'gb' => round($mails, 1), 'percent' => $p($mails), 'color' => 'bg-rose-400'],
// ['label' => 'Backups', 'gb' => round($backups, 1), 'percent' => $p($backups), 'color' => 'bg-sky-400'],
// ['label' => 'Frei', 'gb' => round($free, 1), 'percent' => $p($free), 'color' => 'bg-white/20'],
// ];
// }
//
// protected function readBreakdown(array $disk): array
// {
// // Deine Keys ggf. hier mappen
// $bd = $disk['breakdown'] ?? [];
// $sys = (float)($bd['system_gb'] ?? 0);
// $mails = (float)($bd['mails_gb'] ?? 0);
// $backup = (float)($bd['backup_gb'] ?? 0);
//
// return [$sys, $mails, $backup];
// }
//
// protected function resetUi(): void
// {
// $this->diskTotalGb = null;
// $this->diskUsedGb = null;
// $this->diskFreeGb = null;
// $this->diskSegments = [];
// $this->barSegments = [];
// $this->diskCenterText = ['percent' => '', 'label' => 'SPEICHER BELEGT'];
// $this->measuredAt = null;
// }
//
// protected static function intOrNull($v): ?int
// {
// return is_numeric($v) ? (int)round($v) : null;
// }
//}