Files
jarvis/api/endpoints/network.php
T
myron dc55e6c45b Initial commit: JARVIS AI dashboard v2.3
- 4-tier chat: HA control → Ollama → Groq → Claude
- Push-based agent system with heartbeat/metrics
- Network monitoring, alerts, Proxmox, Home Assistant
- Windows + Linux agent installers
- Stats cache cron, facts collector, KB engine
2026-05-25 13:22:57 +00:00

170 lines
6.7 KiB
PHP

<?php
// Network monitoring endpoint
function pingHost(string $ip): array {
$cmd = 'ping -c 1 -W 1 ' . escapeshellarg($ip) . ' 2>/dev/null';
$out = shell_exec($cmd);
$alive = $out && strpos($out, '1 received') !== false;
$latency = null;
if ($alive && preg_match('/time=([\d.]+)/', $out, $m)) {
$latency = (float)$m[1];
}
return ['alive' => $alive, 'latency_ms' => $latency];
}
function scanSubnet(string $prefix, int $timeout = 10): array {
$cmd = 'nmap -sn --host-timeout 1s ' . escapeshellarg($prefix . '.0/24') .
' -oG - 2>/dev/null | grep "Up$" | awk \'{print $2}\'';
$out = shell_exec($cmd) ?? '';
$hosts = array_filter(explode("\n", trim($out)));
return array_values($hosts);
}
function getArpTable(): array {
$out = shell_exec('arp -n 2>/dev/null') ?? '';
$devices = [];
foreach (explode("\n", trim($out)) as $line) {
if (preg_match('/^([\d.]+)\s+\w+\s+([\w:]+)/', $line, $m)) {
$devices[$m[1]] = strtolower($m[2]);
}
}
return $devices;
}
$action = $action ?? 'status';
$method = $_SERVER['REQUEST_METHOD'] ?? 'GET';
if ($action === 'scan') {
$liveHosts = scanSubnet(LOCAL_SUBNET, 8);
$arp = getArpTable();
$known = JarvisDB::query('SELECT * FROM network_devices');
$knownMap = [];
foreach ($known as $d) $knownMap[$d['ip']] = $d;
$devices = [];
foreach ($liveHosts as $ip) {
$mac = $arp[$ip] ?? null;
$known_dev = $knownMap[$ip] ?? null;
$nsOut = shell_exec('timeout 1 nslookup ' . escapeshellarg($ip) . ' 2>/dev/null | grep "name ="');
$hostname = null;
if ($nsOut && preg_match('/name = (.+)\./', $nsOut, $nm)) {
$hostname = rtrim($nm[1], '.');
}
$devices[] = [
'ip' => $ip,
'mac' => $mac,
'hostname' => $hostname,
'alias' => $known_dev['alias'] ?? null,
'type' => $known_dev['device_type'] ?? 'unknown',
'status' => 'online',
];
JarvisDB::execute(
'INSERT INTO network_devices (ip, mac, hostname, status, last_seen) VALUES (?,?,?,\'online\',NOW())
ON DUPLICATE KEY UPDATE mac=VALUES(mac), hostname=VALUES(hostname), status=\'online\', last_seen=NOW()',
[$ip, $mac, $hostname]
);
}
foreach ($knownMap as $ip => $dev) {
if (!in_array($ip, $liveHosts)) {
JarvisDB::execute('UPDATE network_devices SET status=\'offline\' WHERE ip=?', [$ip]);
}
}
echo json_encode(['devices' => $devices, 'count' => count($devices), 'scanned_at' => date('c')]);
} elseif ($action === 'add' && $method === 'POST') {
$ip = filter_var($data['ip'] ?? '', FILTER_VALIDATE_IP);
$alias = substr(trim($data['alias'] ?? ''), 0, 100);
$type = preg_replace('/[^a-z0-9_\-]/', '', strtolower($data['type'] ?? 'device'));
if (!$ip) { echo json_encode(['error' => 'Invalid IP address']); exit; }
if (!$alias) { echo json_encode(['error' => 'Name is required']); exit; }
JarvisDB::execute(
'INSERT INTO network_devices (ip, alias, device_type, status) VALUES (?,?,?,\'unknown\')
ON DUPLICATE KEY UPDATE alias=VALUES(alias), device_type=VALUES(device_type)',
[$ip, $alias, $type]
);
echo json_encode(['success' => true]);
} elseif ($action === 'delete' && $method === 'POST') {
$ip = filter_var($data['ip'] ?? '', FILTER_VALIDATE_IP);
if (!$ip) { echo json_encode(['error' => 'Invalid IP']); exit; }
// Don't allow deleting agent-managed entries
$isAgent = JarvisDB::query('SELECT id FROM registered_agents WHERE ip_address=? LIMIT 1', [$ip]);
if (!empty($isAgent)) { echo json_encode(['error' => 'Cannot delete agent-managed device']); exit; }
JarvisDB::execute('DELETE FROM network_devices WHERE ip=?', [$ip]);
echo json_encode(['success' => true]);
} else {
// Status: unified device list from agents + user-managed DB entries + external services
$devices = [];
// Mark agents offline if not heard from in 2 minutes
JarvisDB::execute(
'UPDATE registered_agents SET status="offline" WHERE last_seen < DATE_SUB(NOW(), INTERVAL 2 MINUTE) AND status = "online"'
);
// 1. Agent-based devices — status from heartbeat, no ping from DO needed
$agents = JarvisDB::query(
'SELECT agent_id, hostname, ip_address, status, last_seen, agent_type FROM registered_agents ORDER BY hostname'
);
$agentIPs = [];
foreach ($agents as $ag) {
$agentIPs[] = $ag['ip_address'];
$devices[] = [
'ip' => $ag['ip_address'],
'name' => $ag['hostname'],
'type' => 'agent',
'agent_id' => $ag['agent_id'],
'agent_type' => $ag['agent_type'],
'alive' => $ag['status'] === 'online',
'status' => $ag['status'],
'last_seen' => $ag['last_seen'],
'source' => 'agent',
'deletable' => false,
];
}
// 2. User-managed devices from DB (named/aliased entries not covered by agents)
$pinned = JarvisDB::query(
'SELECT ip, alias, device_type, status, last_seen FROM network_devices
WHERE alias IS NOT NULL AND alias != "" ORDER BY alias'
);
foreach ($pinned as $dev) {
if (in_array($dev['ip'], $agentIPs)) continue; // agent already covers this IP
$ping = pingHost($dev['ip']);
$newStatus = $ping['alive'] ? 'online' : 'offline';
JarvisDB::execute(
'UPDATE network_devices SET status=?, last_seen=NOW() WHERE ip=?',
[$newStatus, $dev['ip']]
);
$devices[] = [
'ip' => $dev['ip'],
'name' => $dev['alias'],
'type' => $dev['device_type'] ?: 'device',
'alive' => $ping['alive'],
'latency_ms' => $ping['latency_ms'],
'status' => $newStatus,
'last_seen' => $dev['last_seen'],
'source' => 'db',
'deletable' => true,
];
}
// 3. External services we can actually ping from DO
$external = [
['ip' => '134.209.72.226', 'name' => 'FusionPBX DO', 'type' => 'server'],
];
foreach ($external as $host) {
if (in_array($host['ip'], $agentIPs)) continue;
$ping = pingHost($host['ip']);
$devices[] = array_merge($host, [
'alive' => $ping['alive'],
'latency_ms' => $ping['latency_ms'],
'status' => $ping['alive'] ? 'online' : 'offline',
'source' => 'static',
'deletable' => false,
]);
}
echo json_encode(['devices' => $devices, 'timestamp' => date('c')]);
}