parent
dd3f413e6a
commit
3108d521a5
|
|
@ -8,25 +8,27 @@ use App\Models\Setting;
|
|||
class StorageProbe extends Command
|
||||
{
|
||||
protected $signature = 'health:probe-disk {target=/}';
|
||||
protected $description = 'Speichert Storage-Werte (inkl. Frei+5%) in settings:health.disk';
|
||||
protected $description = 'Speichert Storage-Werte (inkl. Breakdown) in settings:health.disk';
|
||||
|
||||
public function handle(): int
|
||||
{
|
||||
$target = $this->argument('target') ?: '/';
|
||||
$data = $this->probe($target);
|
||||
$data = $this->probe($target);
|
||||
|
||||
// Persistiert (DB + Redis) über dein Settings-Model
|
||||
Setting::set('health.disk', $data);
|
||||
Setting::set('health.disk_updated_at', now()->toIso8601String());
|
||||
|
||||
$this->info(sprintf(
|
||||
'Storage %s → total:%dGB used:%dGB free_user:%dGB free+5%%:%dGB (%%used:%d)',
|
||||
'Storage %s → total:%dGB used:%dGB free_user:%dGB free+5%%:%dGB (%%used:%d) breakdown: system=%.1fGB mails=%.1fGB backups=%.1fGB',
|
||||
$data['mount'],
|
||||
$data['total_gb'],
|
||||
$data['used_gb'],
|
||||
$data['free_gb'],
|
||||
$data['free_plus_reserve_gb'],
|
||||
$data['percent_used_total'],
|
||||
$data['breakdown']['system_gb'],
|
||||
$data['breakdown']['mails_gb'],
|
||||
$data['breakdown']['backup_gb'],
|
||||
));
|
||||
|
||||
return self::SUCCESS;
|
||||
|
|
@ -34,7 +36,8 @@ class StorageProbe extends Command
|
|||
|
||||
protected function probe(string $target): array
|
||||
{
|
||||
$line = trim((string) @shell_exec('df -kP ' . escapeshellarg($target) . ' 2>/dev/null | tail -n1'));
|
||||
// --- df: Gesamtdaten des Filesystems (inkl. Reserve) -----------------
|
||||
$line = trim((string)@shell_exec('LC_ALL=C df -kP ' . escapeshellarg($target) . ' 2>/dev/null | tail -n1'));
|
||||
|
||||
$device = $mount = '';
|
||||
$totalKb = $usedKb = $availKb = 0;
|
||||
|
|
@ -42,35 +45,140 @@ class StorageProbe extends Command
|
|||
if ($line !== '') {
|
||||
$p = preg_split('/\s+/', $line);
|
||||
if (count($p) >= 6) {
|
||||
$device = $p[0];
|
||||
$totalKb = (int) $p[1]; // TOTAL (inkl. Reserve)
|
||||
$usedKb = (int) $p[2]; // Used
|
||||
$availKb = (int) $p[3]; // Avail (User-sicht)
|
||||
$mount = $p[5];
|
||||
$device = $p[0];
|
||||
$totalKb = (int)$p[1]; // TOTAL (inkl. Reserve)
|
||||
$usedKb = (int)$p[2]; // Used
|
||||
$availKb = (int)$p[3]; // Avail (User-sicht)
|
||||
$mount = $p[5];
|
||||
}
|
||||
}
|
||||
|
||||
$toGiB = static fn($kb) => (int) round(max(0, (int)$kb) / (1024*1024));
|
||||
$toGiB_i = static fn($kb) => (int)round(max(0, (int)$kb) / (1024 * 1024)); // ganzzahlig (UI: Gesamt/Genutzt/Frei)
|
||||
$toGiB_f = static fn($kb) => round(max(0, (int)$kb) / (1024 * 1024), 1); // eine Nachkommastelle (Breakdown/Legende)
|
||||
|
||||
$totalGb = $toGiB($totalKb);
|
||||
$freeGb = $toGiB($availKb); // user-verfügbar
|
||||
$usedGb = max(0, $totalGb - $freeGb); // belegt inkl. Reserve
|
||||
$res5Gb = (int) round($totalGb * 0.05); // 5% von Gesamt
|
||||
$totalGb = $toGiB_i($totalKb);
|
||||
$freeGb = $toGiB_i($availKb); // user-verfügbar
|
||||
$usedGb = max(0, $totalGb - $freeGb); // belegt inkl. Reserve
|
||||
$res5Gb = (int)round($totalGb * 0.05); // 5% von Gesamt
|
||||
$freePlusReserveGb = min($totalGb, $freeGb + $res5Gb);
|
||||
$percentUsed = $totalGb > 0 ? (int)round($usedGb * 100 / $totalGb) : 0;
|
||||
|
||||
$percentUsed = $totalGb > 0 ? (int) round($usedGb * 100 / $totalGb) : 0;
|
||||
// --- du: reale Verbräuche bestimmter Bäume (KB) -----------------------
|
||||
$duKb = function (string $path): int {
|
||||
if (!is_dir($path)) return 0;
|
||||
$kb = (int)trim((string)@shell_exec('LC_ALL=C du -sk --apparent-size ' . escapeshellarg($path) . ' 2>/dev/null | cut -f1'));
|
||||
return max(0, $kb);
|
||||
};
|
||||
|
||||
$kbMails = $duKb('/var/mail/vhosts');
|
||||
$kbBackup = $duKb('/var/backups/mailwolt');
|
||||
|
||||
// „System“ = alles übrige, was nicht Mails/Backups ist (OS, App, Logs, DB-Daten, …)
|
||||
$gbMails = $toGiB_f($kbMails);
|
||||
$gbBackup = $toGiB_f($kbBackup);
|
||||
|
||||
// system_gb aus „usedGb – (mails+backup)“, nie negativ
|
||||
$gbSystem = max(0, round($usedGb - ($gbMails + $gbBackup), 1));
|
||||
|
||||
return [
|
||||
'device' => $device ?: 'unknown',
|
||||
'mount' => $mount ?: $target,
|
||||
'device' => $device ?: 'unknown',
|
||||
'mount' => $mount ?: $target,
|
||||
|
||||
'total_gb' => $totalGb,
|
||||
'used_gb' => $usedGb, // inkl. Reserve
|
||||
'free_gb' => $freeGb, // User-sicht
|
||||
'reserve5_gb' => $res5Gb, // Info
|
||||
'free_plus_reserve_gb' => $freePlusReserveGb, // ← das willst du anzeigen
|
||||
'total_gb' => $totalGb,
|
||||
'used_gb' => $usedGb, // inkl. Reserve
|
||||
'free_gb' => $freeGb, // User-sicht
|
||||
'reserve5_gb' => $res5Gb, // Info
|
||||
'free_plus_reserve_gb' => $freePlusReserveGb, // Anzeige „Frei“
|
||||
|
||||
'percent_used_total' => $percentUsed, // fürs Donut (~15%)
|
||||
'percent_used_total' => $percentUsed,
|
||||
|
||||
// Reale Breakdown-Werte
|
||||
'breakdown' => [
|
||||
'system_gb' => $gbSystem,
|
||||
'mails_gb' => $gbMails,
|
||||
'backup_gb' => $gbBackup,
|
||||
],
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
//namespace App\Console\Commands;
|
||||
//
|
||||
//use Illuminate\Console\Command;
|
||||
//use App\Models\Setting;
|
||||
//
|
||||
//class StorageProbe extends Command
|
||||
//{
|
||||
// protected $signature = 'health:probe-disk {target=/}';
|
||||
// protected $description = 'Speichert Storage-Werte (inkl. Frei+5%) in settings:health.disk';
|
||||
//
|
||||
// public function handle(): int
|
||||
// {
|
||||
// $target = $this->argument('target') ?: '/';
|
||||
// $data = $this->probe($target);
|
||||
//
|
||||
// // Persistiert (DB + Redis) über dein Settings-Model
|
||||
// Setting::set('health.disk', $data);
|
||||
// Setting::set('health.disk_updated_at', now()->toIso8601String());
|
||||
//
|
||||
// $this->info(sprintf(
|
||||
// 'Storage %s → total:%dGB used:%dGB free_user:%dGB free+5%%:%dGB (%%used:%d)',
|
||||
// $data['mount'],
|
||||
// $data['total_gb'],
|
||||
// $data['used_gb'],
|
||||
// $data['free_gb'],
|
||||
// $data['free_plus_reserve_gb'],
|
||||
// $data['percent_used_total'],
|
||||
// ));
|
||||
//
|
||||
// return self::SUCCESS;
|
||||
// }
|
||||
//
|
||||
// protected function probe(string $target): array
|
||||
// {
|
||||
// $line = trim((string) @shell_exec('df -kP ' . escapeshellarg($target) . ' 2>/dev/null | tail -n1'));
|
||||
//
|
||||
// $device = $mount = '';
|
||||
// $totalKb = $usedKb = $availKb = 0;
|
||||
//
|
||||
// if ($line !== '') {
|
||||
// $p = preg_split('/\s+/', $line);
|
||||
// if (count($p) >= 6) {
|
||||
// $device = $p[0];
|
||||
// $totalKb = (int) $p[1]; // TOTAL (inkl. Reserve)
|
||||
// $usedKb = (int) $p[2]; // Used
|
||||
// $availKb = (int) $p[3]; // Avail (User-sicht)
|
||||
// $mount = $p[5];
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// $toGiB = static fn($kb) => (int) round(max(0, (int)$kb) / (1024*1024));
|
||||
//
|
||||
// $totalGb = $toGiB($totalKb);
|
||||
// $freeGb = $toGiB($availKb); // user-verfügbar
|
||||
// $usedGb = max(0, $totalGb - $freeGb); // belegt inkl. Reserve
|
||||
// $res5Gb = (int) round($totalGb * 0.05); // 5% von Gesamt
|
||||
// $freePlusReserveGb = min($totalGb, $freeGb + $res5Gb);
|
||||
//
|
||||
// $percentUsed = $totalGb > 0 ? (int) round($usedGb * 100 / $totalGb) : 0;
|
||||
//
|
||||
// return [
|
||||
// 'device' => $device ?: 'unknown',
|
||||
// 'mount' => $mount ?: $target,
|
||||
//
|
||||
// 'total_gb' => $totalGb,
|
||||
// 'used_gb' => $usedGb, // inkl. Reserve
|
||||
// 'free_gb' => $freeGb, // User-sicht
|
||||
// 'reserve5_gb' => $res5Gb, // Info
|
||||
// 'free_plus_reserve_gb' => $freePlusReserveGb, // ← das willst du anzeigen
|
||||
//
|
||||
// 'percent_used_total' => $percentUsed, // fürs Donut (~15%)
|
||||
// 'breakdown' => [
|
||||
// 'system_gb' => 5.2, // OS, App, Logs …
|
||||
// 'mails_gb' => 2.8, // /var/mail/vhosts
|
||||
// 'backup_gb' => 1.0, // /var/backups/mailwolt (oder wohin du sicherst)
|
||||
// ],
|
||||
// ];
|
||||
// }
|
||||
//}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
<?php
|
||||
|
||||
|
||||
namespace App\Livewire\Ui\System;
|
||||
|
||||
use Livewire\Component;
|
||||
|
|
@ -10,17 +11,22 @@ class StorageCard extends Component
|
|||
{
|
||||
public string $target = '/';
|
||||
|
||||
// Summen
|
||||
public ?int $diskTotalGb = null;
|
||||
public ?int $diskUsedGb = null; // inkl. Reserve (passt zum Donut)
|
||||
public ?int $diskFreeGb = null; // wird unten auf free_plus_reserve_gb gesetzt
|
||||
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'];
|
||||
public int $diskSegOuterRadius = 92;
|
||||
public int $diskInnerSize = 160;
|
||||
public array $diskCenterText = ['percent' => '–', 'label' => 'SPEICHER BELEGT'];
|
||||
|
||||
// Stacked-Bar + Legende
|
||||
public array $barSegments = []; // [{label,color,gb,percent}]
|
||||
public ?string $measuredAt = null;
|
||||
|
||||
protected int $segCount = 48;
|
||||
protected int $segCount = 100;
|
||||
|
||||
public function mount(string $target = '/'): void
|
||||
{
|
||||
|
|
@ -28,7 +34,10 @@ class StorageCard extends Component
|
|||
$this->loadFromSettings();
|
||||
}
|
||||
|
||||
public function render() { return view('livewire.ui.system.storage-card'); }
|
||||
public function render()
|
||||
{
|
||||
return view('livewire.ui.system.storage-card');
|
||||
}
|
||||
|
||||
public function refresh(): void
|
||||
{
|
||||
|
|
@ -36,41 +45,277 @@ class StorageCard extends Component
|
|||
$this->loadFromSettings();
|
||||
}
|
||||
|
||||
// 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',
|
||||
// ];
|
||||
// $this->diskSegments = $this->buildSegments(is_numeric($percent) ? (int)$percent : null);
|
||||
//
|
||||
// $this->barSegments = $this->buildBar($disk);
|
||||
// $this->measuredAt = Setting::get('health.disk_updated_at', null);
|
||||
// }
|
||||
|
||||
protected function loadFromSettings(): void
|
||||
{
|
||||
$disk = Setting::get('health.disk', []);
|
||||
if (!is_array($disk) || empty($disk)) return;
|
||||
if (!is_array($disk) || empty($disk)) { $this->resetUi(); return; }
|
||||
|
||||
$this->diskTotalGb = $disk['total_gb'] ?? null;
|
||||
$this->diskUsedGb = $disk['used_gb'] ?? null;
|
||||
$this->diskFreeGb = $disk['free_gb'] ?? ($disk['free_plus_reserve_gb'] ?? null);
|
||||
$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) ? $percent.'%' : '–',
|
||||
'percent' => is_numeric($percent) ? (string)round($percent).'%' : '–',
|
||||
'label' => 'SPEICHER BELEGT',
|
||||
];
|
||||
$this->diskSegments = $this->buildSegments($percent);
|
||||
|
||||
// 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);
|
||||
}
|
||||
|
||||
protected function buildSegments(?int $percent): array
|
||||
// NEU: ersetzt buildSegments() + nutzt Breakdown
|
||||
protected function buildDonutSegmentsFromBreakdown(array $disk): array
|
||||
{
|
||||
$segments = [];
|
||||
$active = is_int($percent) ? (int) round($this->segCount * $percent / 100) : 0;
|
||||
$total = (float)($disk['total_gb'] ?? 0);
|
||||
if ($total <= 0) return [];
|
||||
|
||||
$activeClass = match (true) {
|
||||
!is_int($percent) => 'bg-white/15',
|
||||
$percent >= 90 => 'bg-rose-400',
|
||||
$percent >= 70 => 'bg-amber-300',
|
||||
default => 'bg-emerald-400',
|
||||
// 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;
|
||||
$segments[] = ['angle' => $angle, 'class' => $i < $active ? $activeClass : 'bg-white/15'];
|
||||
$out[] = ['angle' => $angle, 'class' => $segments[$i]['class']];
|
||||
}
|
||||
return $segments;
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
//namespace App\Livewire\Ui\System;
|
||||
//
|
||||
//use Livewire\Component;
|
||||
//use Illuminate\Support\Facades\Artisan;
|
||||
//use App\Models\Setting;
|
||||
//
|
||||
//class StorageCard extends Component
|
||||
//{
|
||||
// public string $target = '/';
|
||||
//
|
||||
// public ?int $diskTotalGb = null;
|
||||
// public ?int $diskUsedGb = null; // inkl. Reserve (passt zum Donut)
|
||||
// public ?int $diskFreeGb = null; // wird unten auf free_plus_reserve_gb gesetzt
|
||||
//
|
||||
// public array $diskSegments = [];
|
||||
// public int $diskSegOuterRadius = 92;
|
||||
// public int $diskInnerSize = 160;
|
||||
// public array $diskCenterText = ['percent' => '–', 'label' => 'SPEICHER BELEGT'];
|
||||
// public ?string $measuredAt = null;
|
||||
//
|
||||
// protected int $segCount = 48;
|
||||
//
|
||||
// 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();
|
||||
// }
|
||||
//
|
||||
// protected function loadFromSettings(): void
|
||||
// {
|
||||
// $disk = Setting::get('health.disk', []);
|
||||
// if (!is_array($disk) || empty($disk)) return;
|
||||
//
|
||||
// $this->diskTotalGb = $disk['total_gb'] ?? null;
|
||||
// $this->diskUsedGb = $disk['used_gb'] ?? null;
|
||||
// $this->diskFreeGb = $disk['free_gb'] ?? ($disk['free_plus_reserve_gb'] ?? null);
|
||||
//
|
||||
// $percent = $disk['percent_used_total'] ?? null;
|
||||
// $this->diskCenterText = [
|
||||
// 'percent' => is_numeric($percent) ? $percent.'%' : '–',
|
||||
// 'label' => 'SPEICHER BELEGT',
|
||||
// ];
|
||||
// $this->diskSegments = $this->buildSegments($percent);
|
||||
//
|
||||
// $this->measuredAt = Setting::get('health.disk_updated_at', null);
|
||||
// }
|
||||
//
|
||||
// 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;
|
||||
// }
|
||||
//}
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
<div class="glass-card relative p-4 max-h-fit border border-white/10 bg-white/5 rounded-2xl">
|
||||
<div class="glass-card relative p-4 max-h-fit border border-white/10 bg-white/5 rounded-2xl" wire:poll.30s="refresh">
|
||||
{{-- Kopf --}}
|
||||
<div class="flex items-center justify-between -mb-3">
|
||||
<div class="inline-flex items-center gap-2 rounded-full bg-white/5 border border-white/10 px-2.5 py-1">
|
||||
|
|
@ -12,14 +12,12 @@
|
|||
</div>
|
||||
|
||||
{{-- Inhalt --}}
|
||||
<div class="grid grid-cols-1 items-center">
|
||||
<div class="grid grid-cols-1 items-center gap-4">
|
||||
{{-- Donut --}}
|
||||
<div class="flex items-center justify-center">
|
||||
<div class="flex items-center justify-center mt-2">
|
||||
<div class="relative" style="width: {{ $diskInnerSize + 80 }}px; height: {{ $diskInnerSize + 80 }}px;">
|
||||
{{-- Innerer grauer Kreis --}}
|
||||
<div class="absolute inset-[36px] rounded-full bg-white/[0.04] backdrop-blur-sm ring-1 ring-white/10"></div>
|
||||
|
||||
{{-- Prozentanzeige im Zentrum --}}
|
||||
<div class="absolute inset-0 flex flex-col items-center justify-center">
|
||||
<div class="text-2xl md:text-3xl font-semibold leading-none tracking-tight">
|
||||
{{ $diskCenterText['percent'] }}
|
||||
|
|
@ -27,41 +25,131 @@
|
|||
<div class="text-[10px] md:text-[11px] text-white/60 mt-1 uppercase tracking-wide">
|
||||
{{ $diskCenterText['label'] }}
|
||||
</div>
|
||||
@if($measuredAt)
|
||||
<div class="absolute bottom-12 mt-2 text-[10px] text-white/45 text-center">
|
||||
zuletzt aktualisiert: <br> {{ \Carbon\Carbon::parse($measuredAt)->diffForHumans() }}
|
||||
</div>
|
||||
@endif
|
||||
|
||||
{{-- @if($measuredAt)--}}
|
||||
{{-- <div class="absolute bottom-12 mt-2 text-[10px] text-white/45 text-center">--}}
|
||||
{{-- zuletzt aktualisiert:<br>{{ \Carbon\Carbon::parse($measuredAt)->diffForHumans() }}--}}
|
||||
{{-- </div>--}}
|
||||
{{-- @endif--}}
|
||||
</div>
|
||||
|
||||
{{-- Segment-Ring --}}
|
||||
@foreach($diskSegments as $seg)
|
||||
<span class="absolute top-1/2 left-1/2 block"
|
||||
style="
|
||||
transform: rotate({{ $seg['angle'] }}deg) translateX({{ $diskSegOuterRadius + 14 }}px);
|
||||
width: 12px; height: 6px; margin:-3px 0 0 -6px;">
|
||||
style="transform: rotate({{ $seg['angle'] }}deg) translateX({{ $diskSegOuterRadius + 14 }}px);
|
||||
width: 12px; height: 3px; margin:-3px 0 0 -6px;">
|
||||
<span class="block w-full h-full rounded-full {{ $seg['class'] }}"></span>
|
||||
</span>
|
||||
@endforeach
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{{-- Zahlen --}}
|
||||
<div class="md:pl-2">
|
||||
<dl class="space-y-2">
|
||||
<div class="flex items-center justify-between">
|
||||
<dt class="text-white/60 text-sm">Gesamt</dt>
|
||||
<dd class="font-medium tabular-nums text-base">{{ is_numeric($diskTotalGb) ? $diskTotalGb.' GB' : '–' }}</dd>
|
||||
{{-- Zahlen + Stacked Bar --}}
|
||||
<div class="md:pl-2 space-y-4">
|
||||
{{-- Stacked-Bar (horizontale Leiste) --}}
|
||||
@if(!empty($barSegments))
|
||||
<div class="space-y-2">
|
||||
{{-- <div class="w-full h-3 rounded-full bg-white/10 overflow-hidden ring-1 ring-white/10">--}}
|
||||
{{-- <div class="flex h-full">--}}
|
||||
{{-- @foreach($barSegments as $b)--}}
|
||||
{{-- <div class="{{ $b['color'] }} h-full" style="width: {{ $b['percent'] }}%"></div>--}}
|
||||
{{-- @endforeach--}}
|
||||
{{-- </div>--}}
|
||||
{{-- </div>--}}
|
||||
|
||||
{{-- Legende --}}
|
||||
<div class="flex flex-wrap gap-3 text-[12px] text-white/70">
|
||||
@foreach($barSegments as $b)
|
||||
<div class="inline-flex items-center gap-2">
|
||||
<span class="inline-block w-2.5 h-2.5 rounded-full {{ $b['color'] }}"></span>
|
||||
<span>{{ $b['label'] }} {{ $b['gb'] }} GB ({{ $b['percent'] }}%)</span>
|
||||
</div>
|
||||
@endforeach
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex items-center justify-between">
|
||||
<dt class="text-white/60 text-sm">Genutzt</dt>
|
||||
<dd class="font-medium tabular-nums text-base">{{ is_numeric($diskUsedGb) ? $diskUsedGb.' GB' : '–' }}</dd>
|
||||
</div>
|
||||
<div class="flex items-center justify-between">
|
||||
<dt class="text-white/60 text-sm">Frei</dt>
|
||||
<dd class="font-medium tabular-nums text-base">{{ is_numeric($diskFreeGb) ? $diskFreeGb.' GB' : '–' }}</dd>
|
||||
</div>
|
||||
</dl>
|
||||
@endif
|
||||
|
||||
{{-- Zahlen unten rechts --}}
|
||||
{{-- <dl class="space-y-2">--}}
|
||||
{{-- <div class="flex items-center justify-between">--}}
|
||||
{{-- <dt class="text-white/60 text-sm">Gesamt</dt>--}}
|
||||
{{-- <dd class="font-medium tabular-nums text-base">{{ is_numeric($diskTotalGb) ? $diskTotalGb.' GB' : '–' }}</dd>--}}
|
||||
{{-- </div>--}}
|
||||
{{-- <div class="flex items-center justify-between">--}}
|
||||
{{-- <dt class="text-white/60 text-sm">Genutzt</dt>--}}
|
||||
{{-- <dd class="font-medium tabular-nums text-base">{{ is_numeric($diskUsedGb) ? $diskUsedGb.' GB' : '–' }}</dd>--}}
|
||||
{{-- </div>--}}
|
||||
{{-- <div class="flex items-center justify-between">--}}
|
||||
{{-- <dt class="text-white/60 text-sm">Frei</dt>--}}
|
||||
{{-- <dd class="font-medium tabular-nums text-base">{{ is_numeric($diskFreeGb) ? $diskFreeGb.' GB' : '–' }}</dd>--}}
|
||||
{{-- </div>--}}
|
||||
{{-- </dl>--}}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{{--<div class="glass-card relative p-4 max-h-fit border border-white/10 bg-white/5 rounded-2xl">--}}
|
||||
{{-- --}}{{-- Kopf --}}
|
||||
{{-- <div class="flex items-center justify-between -mb-3">--}}
|
||||
{{-- <div class="inline-flex items-center gap-2 rounded-full bg-white/5 border border-white/10 px-2.5 py-1">--}}
|
||||
{{-- <i class="ph ph-hard-drives text-white/70 text-[13px]"></i>--}}
|
||||
{{-- <span class="text-[11px] tracking-wide uppercase text-white/70">Storage</span>--}}
|
||||
{{-- </div>--}}
|
||||
{{-- <button wire:click="refresh"--}}
|
||||
{{-- class="inline-flex items-center gap-1 rounded-full border border-white/10 bg-white/5 px-2 py-0.5 text-[10px] text-white/70 hover:text-white hover:border-white/20 transition">--}}
|
||||
{{-- Update <i class="ph ph-arrows-clockwise text-[12px]"></i>--}}
|
||||
{{-- </button>--}}
|
||||
{{-- </div>--}}
|
||||
|
||||
{{-- --}}{{-- Inhalt --}}
|
||||
{{-- <div class="grid grid-cols-1 items-center">--}}
|
||||
{{-- --}}{{-- Donut --}}
|
||||
{{-- <div class="flex items-center justify-center">--}}
|
||||
{{-- <div class="relative" style="width: {{ $diskInnerSize + 80 }}px; height: {{ $diskInnerSize + 80 }}px;">--}}
|
||||
{{-- --}}{{-- Innerer grauer Kreis --}}
|
||||
{{-- <div class="absolute inset-[36px] rounded-full bg-white/[0.04] backdrop-blur-sm ring-1 ring-white/10"></div>--}}
|
||||
|
||||
{{-- --}}{{-- Prozentanzeige im Zentrum --}}
|
||||
{{-- <div class="absolute inset-0 flex flex-col items-center justify-center">--}}
|
||||
{{-- <div class="text-2xl md:text-3xl font-semibold leading-none tracking-tight">--}}
|
||||
{{-- {{ $diskCenterText['percent'] }}--}}
|
||||
{{-- </div>--}}
|
||||
{{-- <div class="text-[10px] md:text-[11px] text-white/60 mt-1 uppercase tracking-wide">--}}
|
||||
{{-- {{ $diskCenterText['label'] }}--}}
|
||||
{{-- </div>--}}
|
||||
{{-- @if($measuredAt)--}}
|
||||
{{-- <div class="absolute bottom-12 mt-2 text-[10px] text-white/45 text-center">--}}
|
||||
{{-- zuletzt aktualisiert: <br> {{ \Carbon\Carbon::parse($measuredAt)->diffForHumans() }}--}}
|
||||
{{-- </div>--}}
|
||||
{{-- @endif--}}
|
||||
{{-- </div>--}}
|
||||
|
||||
{{-- --}}{{-- Segment-Ring --}}
|
||||
{{-- @foreach($diskSegments as $seg)--}}
|
||||
{{-- <span class="absolute top-1/2 left-1/2 block"--}}
|
||||
{{-- style="--}}
|
||||
{{-- transform: rotate({{ $seg['angle'] }}deg) translateX({{ $diskSegOuterRadius + 14 }}px);--}}
|
||||
{{-- width: 12px; height: 6px; margin:-3px 0 0 -6px;">--}}
|
||||
{{-- <span class="block w-full h-full rounded-full {{ $seg['class'] }}"></span>--}}
|
||||
{{-- </span>--}}
|
||||
{{-- @endforeach--}}
|
||||
{{-- </div>--}}
|
||||
{{-- </div>--}}
|
||||
|
||||
{{-- --}}{{-- Zahlen --}}
|
||||
{{-- <div class="md:pl-2">--}}
|
||||
{{-- <dl class="space-y-2">--}}
|
||||
{{-- <div class="flex items-center justify-between">--}}
|
||||
{{-- <dt class="text-white/60 text-sm">Gesamt</dt>--}}
|
||||
{{-- <dd class="font-medium tabular-nums text-base">{{ is_numeric($diskTotalGb) ? $diskTotalGb.' GB' : '–' }}</dd>--}}
|
||||
{{-- </div>--}}
|
||||
{{-- <div class="flex items-center justify-between">--}}
|
||||
{{-- <dt class="text-white/60 text-sm">Genutzt</dt>--}}
|
||||
{{-- <dd class="font-medium tabular-nums text-base">{{ is_numeric($diskUsedGb) ? $diskUsedGb.' GB' : '–' }}</dd>--}}
|
||||
{{-- </div>--}}
|
||||
{{-- <div class="flex items-center justify-between">--}}
|
||||
{{-- <dt class="text-white/60 text-sm">Frei</dt>--}}
|
||||
{{-- <dd class="font-medium tabular-nums text-base">{{ is_numeric($diskFreeGb) ? $diskFreeGb.' GB' : '–' }}</dd>--}}
|
||||
{{-- </div>--}}
|
||||
{{-- </dl>--}}
|
||||
{{-- </div>--}}
|
||||
{{-- </div>--}}
|
||||
{{--</div>--}}
|
||||
|
|
|
|||
Loading…
Reference in New Issue