diff --git a/api/endpoints/facts_collector.php b/api/endpoints/facts_collector.php index 7af1404..a5c0eb4 100644 --- a/api/endpoints/facts_collector.php +++ b/api/endpoints/facts_collector.php @@ -311,6 +311,62 @@ function collect_all(): array { $results['sites'] = 'error: ' . $e->getMessage(); } + + // ── Network Device Scan (nmap via PVE1) ─────────────────────────────── + try { + $nmapRaw = shell_exec( + "sshpass -p 'Joker1974!!!' ssh -o StrictHostKeyChecking=no -o ConnectTimeout=10 " . + "root@10.48.200.90 'nmap -sn --send-ip 10.48.200.0/24 2>/dev/null' 2>/dev/null" + ); + if ($nmapRaw) { + $discovered = []; + $cur = null; + foreach (explode("\n", $nmapRaw) as $line) { + $line = trim($line); + if (preg_match('/Nmap scan report for (?:(\S+) \()?(\d+\.\d+\.\d+\.\d+)\)?/', $line, $m)) { + if ($cur) $discovered[] = $cur; + $cur = ['hostname' => ($m[1] && $m[1] !== $m[2]) ? $m[1] : null, 'ip' => $m[2], 'mac' => null, 'vendor' => null]; + } elseif ($cur && preg_match('/MAC Address: ([0-9A-Fa-f:]{17}) \(([^)]+)\)/i', $line, $m)) { + $cur['mac'] = strtolower($m[1]); + $cur['vendor'] = $m[2] !== 'Unknown' ? $m[2] : null; + } + } + if ($cur) $discovered[] = $cur; + + $discoveredIPs = []; + foreach ($discovered as $d) { + $discoveredIPs[] = $d['ip']; + JarvisDB::execute( + 'INSERT INTO network_devices (ip, mac, hostname, status, last_seen) + VALUES (?,?,?,"online",NOW()) + ON DUPLICATE KEY UPDATE mac=VALUES(mac), hostname=COALESCE(VALUES(hostname),hostname), + status="online", last_seen=NOW()', + [$d['ip'], $d['mac'], $d['hostname'] ?? $d['vendor']] + ); + if ($d['vendor']) { + JarvisDB::execute( + 'UPDATE network_devices SET device_type=? WHERE ip=? AND (device_type IS NULL OR device_type="")', + [$d['vendor'], $d['ip']] + ); + } + } + + if (!empty($discoveredIPs)) { + $ph = implode(',', array_fill(0, count($discoveredIPs), '?')); + JarvisDB::execute( + "UPDATE network_devices SET status='offline' + WHERE ip NOT IN ($ph) AND last_seen < DATE_SUB(NOW(), INTERVAL 10 MINUTE)", + $discoveredIPs + ); + } + $results['nmap_scan'] = 'ok (' . count($discovered) . ' devices found)'; + } else { + $results['nmap_scan'] = 'skipped (PVE1 unreachable)'; + } + } catch (Exception $e) { + $results['nmap_scan'] = 'error: ' . $e->getMessage(); + } + return $results; } diff --git a/public_html/admin/index.php b/public_html/admin/index.php index d79d6a9..6799f2a 100644 --- a/public_html/admin/index.php +++ b/public_html/admin/index.php @@ -10,6 +10,17 @@ function loggedIn(): bool { return !empty($_SESSION['admin_user']); } function j(mixed $d): never { header('Content-Type: application/json'); echo json_encode($d); exit; } function bad(string $msg, int $code = 400): never { http_response_code($code); j(['error' => $msg]); } +function self_upsert_device(array $d): void { + JarvisDB::execute( + 'INSERT INTO network_devices (ip,mac,hostname,status,last_seen) VALUES (?,?,?,"online",NOW()) + ON DUPLICATE KEY UPDATE mac=VALUES(mac), hostname=COALESCE(VALUES(hostname),hostname), status="online", last_seen=NOW()', + [$d['ip'], $d['mac'], $d['hostname'] ?? $d['vendor']] + ); + if (!empty($d['vendor'])) { + JarvisDB::execute('UPDATE network_devices SET device_type=? WHERE ip=? AND (device_type IS NULL OR device_type="")', [$d['vendor'], $d['ip']]); + } +} + // ── BACKEND API ─────────────────────────────────────────────────────────────── $action = $_GET['action'] ?? $_POST['action'] ?? ''; if ($action) { @@ -74,7 +85,26 @@ if ($action) { // ── NETWORK ────────────────────────────────────────────────────────── case 'network_list': - j(JarvisDB::query('SELECT id,ip,mac,hostname,alias,device_type,status,last_seen FROM network_devices WHERE alias IS NOT NULL ORDER BY alias')); + j(JarvisDB::query('SELECT id,ip,mac,hostname,alias,device_type,status,last_seen FROM network_devices ORDER BY status="online" DESC, COALESCE(alias,hostname,ip)')); + + case 'network_scan': + // Trigger immediate nmap scan via PVE1 + $out = shell_exec("sshpass -p 'Joker1974!!!' ssh -o StrictHostKeyChecking=no -o ConnectTimeout=10 root@10.48.200.90 'nmap -sn --send-ip 10.48.200.0/24 2>/dev/null' 2>/dev/null"); + $count = 0; + if ($out) { + $cur = null; + foreach (explode("\n", $out) as $line) { + $line = trim($line); + if (preg_match('/Nmap scan report for (?:(\S+) \()?(\d+\.\d+\.\d+\.\d+)\)?/', $line, $m)) { + if ($cur) { self_upsert_device($cur); $count++; } + $cur = ['hostname' => ($m[1] && $m[1] !== $m[2]) ? $m[1] : null, 'ip' => $m[2], 'mac' => null, 'vendor' => null]; + } elseif ($cur && preg_match('/MAC Address: ([0-9A-Fa-f:]{17}) \(([^)]+)\)/i', $line, $m)) { + $cur['mac'] = strtolower($m[1]); $cur['vendor'] = $m[2] !== 'Unknown' ? $m[2] : null; + } + } + if ($cur) { self_upsert_device($cur); $count++; } + } + j(['ok' => true, 'found' => $count]); case 'network_save': $id = (int)($_POST['id'] ?? 0); @@ -406,9 +436,18 @@ select.filter-sel:focus{border-color:var(--cyan)}
| NAME | IP | TYPE | STATUS | LAST SEEN | ACTIONS | |
|---|---|---|---|---|---|---|
| NAME | IP | MAC | VENDOR / TYPE | STATUS | LAST SEEN | ACTIONS |