Files
jarvis/api/endpoints/network.php
T

189 lines
7.5 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') {
// JARVIS runs on DigitalOcean — cannot reach 10.48.200.x directly.
// Scan is delegated to the PVE1 agent (jarvis-netscan.sh runs nmap locally).
// This endpoint: queues the scan command to PVE1, returns current DB state immediately.
// Queue netscan to PVE1 agent
$pve1 = JarvisDB::single(
"SELECT agent_id FROM registered_agents WHERE ip_address='10.48.200.90' AND status='online' LIMIT 1"
);
$queued = false;
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']
);
$queued = true;
}
// Return current online devices from DB (populated by PVE1 netscan every 3 min)
$devices = JarvisDB::query(
"SELECT ip, mac, hostname, alias, device_type as type, status, last_seen
FROM network_devices WHERE status='online' AND last_seen > DATE_SUB(NOW(), INTERVAL 15 MINUTE)
ORDER BY COALESCE(alias,hostname,ip)"
);
echo json_encode([
'devices' => $devices,
'count' => count($devices),
'queued' => $queued,
'scanned_at' => date('c'),
'note' => $queued
? 'Scan dispatched to PVE1 — results update in ~40 seconds.'
: 'Returning cached scan data. PVE1 auto-scans every 3 minutes.',
]);
} 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. 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'],
];
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')]);
}