[ 'name' => $r['label'] ?? ucfirst($r['name']), 'type' => $r['hint'] ?? '', 'status' => ($r['ok'] ?? false) ? 'online' : 'offline', ], $rows); [$cpu, $cpuCores, $cpuMhz] = $this->cpu(); [$ramPercent, $ramUsed, $ramTotal] = $this->ram(); [$load1, $load5, $load15] = $this->load(); [$uptimeDays, $uptimeHours] = $this->uptime(); [$diskUsedPercent, $diskUsedGb, $diskFreeGb, $diskTotalGb] = $this->disk(); $servicesActive = count(array_filter($services, fn($s) => $s['status'] === 'online')); $sslConfigured = SettingModel::get('ssl_configured', '1') === '1'; return view('livewire.ui.nx.dashboard', [ 'sslConfigured' => $sslConfigured, 'domainCount' => Domain::where('is_system', false)->where('is_server', false)->count(), 'mailboxCount' => MailUser::where('is_system', false)->where('is_active', true)->count(), 'servicesActive' => $servicesActive, 'servicesTotal' => count($services), 'alertCount' => 0, 'backup' => $this->backupData(), 'mailHostname' => gethostname() ?: 'mailserver', 'services' => $services, 'cpu' => $cpu, 'cpuCores' => $cpuCores, 'cpuMhz' => $cpuMhz, 'ramPercent' => $ramPercent, 'ramUsed' => $ramUsed, 'ramTotal' => $ramTotal, 'load1' => $load1, 'load5' => $load5, 'load15' => $load15, 'uptimeDays' => $uptimeDays, 'uptimeHours' => $uptimeHours, 'diskUsedPercent' => $diskUsedPercent, 'diskUsedGb' => $diskUsedGb, 'diskFreeGb' => $diskFreeGb, 'diskTotalGb' => $diskTotalGb, ...$this->mailSecurity(), 'ports' => $this->ports(), ]); } private function ports(): array { $check = [25, 465, 587, 110, 143, 993, 995, 80, 443]; $out = trim(@shell_exec('ss -tlnH 2>/dev/null') ?? ''); $listening = []; foreach (explode("\n", $out) as $line) { if (preg_match('/:(\d+)\s/', $line, $m)) { $listening[(int)$m[1]] = true; } } $result = []; foreach ($check as $port) { $result[$port] = isset($listening[$port]); } return $result; } private function mailSecurity(): array { // rspamd metrics from cache (populated by spamav:collect every 5 min) $av = Cache::get('dash.spamav') ?? SettingModel::get('spamav.metrics', []); $spam = (int)($av['spam'] ?? 0); $reject = (int)($av['reject'] ?? 0); $ham = (int)($av['ham'] ?? 0); $clamVer = $av['clamVer'] ?? '—'; // Postfix queue counts (active + deferred) $queueOut = trim(@shell_exec('postqueue -p 2>/dev/null') ?? ''); $qActive = preg_match_all('/^[A-F0-9]{9,}\*?\s+/mi', $queueOut); $qDeferred = substr_count($queueOut, '(deferred)'); $qTotal = $qActive + $qDeferred; return [ 'spamBlocked' => $spam + $reject, 'spamTagged' => $spam, 'spamRejected'=> $reject, 'hamCount' => $ham, 'clamVer' => $clamVer, 'queueTotal' => $qTotal, 'queueDeferred' => $qDeferred, ]; } private function cpu(): array { $cores = (int)(shell_exec("nproc 2>/dev/null") ?: 1); $mhz = round((float)shell_exec("awk '/^cpu MHz/{s+=$4;n++}END{if(n)print s/n}' /proc/cpuinfo 2>/dev/null") / 1000, 1); $s1 = $this->stat(); usleep(400000); $s2 = $this->stat(); $idle1 = $s1[3] + ($s1[4] ?? 0); $idle2 = $s2[3] + ($s2[4] ?? 0); $dt = array_sum($s2) - array_sum($s1); $cpu = $dt > 0 ? max(0, min(100, round(($dt - ($idle2 - $idle1)) / $dt * 100))) : 0; return [$cpu, $cores, $mhz ?: '—']; } private function stat(): array { $p = preg_split('/\s+/', trim(shell_exec("head -1 /proc/stat 2>/dev/null") ?: '')); array_shift($p); return array_map('intval', $p); } private function ram(): array { preg_match('/MemTotal:\s+(\d+)/', shell_exec("cat /proc/meminfo") ?: '', $mt); preg_match('/MemAvailable:\s+(\d+)/', shell_exec("cat /proc/meminfo") ?: '', $ma); $total = (int)($mt[1] ?? 0); $avail = (int)($ma[1] ?? 0); $used = $total - $avail; return [$total > 0 ? round($used / $total * 100) : 0, round($used / 1048576, 1), round($total / 1048576, 1)]; } private function backupData(): array { $policy = BackupPolicy::first(); if (!$policy) { return ['status' => 'unconfigured', 'last_at' => null, 'last_at_full' => null, 'size' => null, 'duration' => null, 'next_at' => null, 'enabled' => false]; } $running = BackupJob::whereIn('status', ['queued', 'running'])->exists(); if ($running) { $status = 'running'; } elseif ($policy->last_run_at === null) { $status = 'pending'; } else { $status = $policy->last_status ?? 'unknown'; } $size = ($policy->last_size_bytes ?? 0) > 0 ? $this->fmtBytes($policy->last_size_bytes) : null; $duration = null; $lastJob = BackupJob::where('status', 'ok')->latest('finished_at')->first(); if ($lastJob && $lastJob->started_at && $lastJob->finished_at) { $secs = $lastJob->started_at->diffInSeconds($lastJob->finished_at); $duration = $secs >= 60 ? round($secs / 60) . ' min' : $secs . 's'; } $next = null; if ($policy->enabled && $policy->schedule_cron) { try { $cron = new \Cron\CronExpression($policy->schedule_cron); $next = $cron->getNextRunDate()->format('d.m.Y H:i'); } catch (\Throwable) {} } return [ 'status' => $status, 'last_at' => $policy->last_run_at?->diffForHumans(), 'last_at_full' => $policy->last_run_at?->format('d.m.Y H:i'), 'size' => $size, 'duration' => $duration, 'next_at' => $next, 'enabled' => (bool) $policy->enabled, ]; } private function fmtBytes(int $bytes): string { $units = ['B', 'KB', 'MB', 'GB', 'TB']; $i = 0; $v = (float) $bytes; while ($v >= 1024 && $i < 4) { $v /= 1024; $i++; } return number_format($v, $i <= 1 ? 0 : 1) . ' ' . $units[$i]; } private function load(): array { $p = explode(' ', trim(shell_exec("cat /proc/loadavg") ?: '')); return [$p[0] ?? '0.00', $p[1] ?? '0.00', $p[2] ?? '0.00']; } private function uptime(): array { $s = (int)(float)(shell_exec("awk '{print $1}' /proc/uptime") ?: 0); return [intdiv($s, 86400), intdiv($s % 86400, 3600)]; } private function disk(): array { $p = preg_split('/\s+/', trim(shell_exec("df -BG / 2>/dev/null | tail -1") ?: '')); $total = (int)($p[1] ?? 0); $used = (int)($p[2] ?? 0); $free = (int)($p[3] ?? 0); return [$total > 0 ? round($used / $total * 100) : 0, $used, $free, $total]; } }