diff --git a/api/endpoints/network.php b/api/endpoints/network.php index d09eae1..0fa2066 100644 --- a/api/endpoints/network.php +++ b/api/endpoints/network.php @@ -149,7 +149,28 @@ if ($action === 'scan') { ]; } - // 3. External services we can actually ping from DO + // 3. Netscan-discovered devices (PVE1 nmap push — status from last scan) + $discovered = JarvisDB::query( + 'SELECT ip, mac, hostname, device_type, status, last_seen FROM network_devices + WHERE (alias IS NULL OR alias = "") AND last_seen > DATE_SUB(NOW(), INTERVAL 15 MINUTE) + ORDER BY ip' + ); + foreach ($discovered as $dev) { + if (in_array($dev['ip'], $agentIPs)) continue; + $devices[] = [ + 'ip' => $dev['ip'], + 'name' => $dev['hostname'] ?: ($dev['device_type'] ?: $dev['ip']), + 'mac' => $dev['mac'], + 'type' => $dev['device_type'] ?: 'device', + 'alive' => $dev['status'] === 'online', + 'status' => $dev['status'], + 'last_seen' => $dev['last_seen'], + 'source' => 'netscan', + 'deletable' => false, + ]; + } + + // 4. External services we can actually ping from DO $external = [ ['ip' => '134.209.72.226', 'name' => 'FusionPBX DO', 'type' => 'server'], ]; diff --git a/public_html/admin/index.php b/public_html/admin/index.php index 6799f2a..a458f05 100644 --- a/public_html/admin/index.php +++ b/public_html/admin/index.php @@ -88,23 +88,20 @@ if ($action) { 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++; } + // Queue shell command to PVE1 agent — it runs jarvis-netscan.sh and pushes results back + $pve1 = JarvisDB::single('SELECT agent_id FROM registered_agents WHERE ip_address="10.48.200.90" AND status="online" LIMIT 1'); + if (!$pve1) { + $pve1 = JarvisDB::single('SELECT agent_id FROM registered_agents WHERE hostname LIKE "%pve%" AND status="online" LIMIT 1'); + } + if ($pve1) { + JarvisDB::execute( + 'INSERT INTO agent_commands (agent_id, command_type, command_data, status) VALUES (?,?,?,?)', + [$pve1['agent_id'], 'shell', json_encode(['command'=>'/usr/local/bin/jarvis-netscan.sh','allowed'=>true]), 'pending'] + ); + j(['ok' => true, 'queued' => true, 'note' => 'Scan command sent to PVE1 agent — results in ~40 seconds']); + } else { + j(['ok' => false, 'note' => 'PVE1 agent offline — scan will run automatically via cron in < 3 minutes']); } - j(['ok' => true, 'found' => $count]); case 'network_save': $id = (int)($_POST['id'] ?? 0); @@ -762,15 +759,19 @@ function renderNetwork() { async function scanNow() { const btn = document.getElementById('scanBtn'); - btn.textContent = 'SCANNING...'; btn.disabled = true; + btn.textContent = 'QUEUING...'; btn.disabled = true; const fd = new FormData(); fd.append('action','network_scan'); try { const r = await fetch(location.href,{method:'POST',body:fd}); const d = await r.json(); - if (d.ok) { toast(`Scan complete — ${d.found} devices found`,'ok'); loadNetwork(); } - else toast(d.error||'Scan failed','err'); - } catch(e){ toast('Scan failed','err'); } - btn.textContent = 'SCAN NOW'; btn.disabled = false; + if (d.ok && d.queued) { + toast('Scan queued — refreshing in 45s...','ok'); + setTimeout(()=>{ loadNetwork(); }, 45000); + } else { + toast(d.note || 'Scan scheduled via cron','ok'); + } + } catch(e){ toast('Request failed','err'); } + setTimeout(()=>{ btn.textContent='SCAN NOW'; btn.disabled=false; }, 5000); } function netModal(id=0, ip='', alias='', type='') {