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