477 lines
17 KiB
PHP
477 lines
17 KiB
PHP
<?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;
|
||
// }
|
||
//}
|