mirror of
https://github.com/myronblair/jarvis
synced 2026-06-30 17:50:23 -05:00
189 lines
7.5 KiB
PHP
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')]);
|
|
}
|