'–', '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; // } //}