diff --git a/public_html/admin/index.php b/public_html/admin/index.php new file mode 100644 index 0000000..d79d6a9 --- /dev/null +++ b/public_html/admin/index.php @@ -0,0 +1,934 @@ + $msg]); } + +// ── BACKEND API ─────────────────────────────────────────────────────────────── +$action = $_GET['action'] ?? $_POST['action'] ?? ''; +if ($action) { + // Login doesn't require session + if ($action === 'login') { + $u = trim($_POST['username'] ?? ''); + $p = $_POST['password'] ?? ''; + $row = JarvisDB::single('SELECT * FROM users WHERE username = ?', [$u]); + if ($row && password_verify($p, $row['password_hash'])) { + $_SESSION['admin_user'] = $row['username']; + $_SESSION['admin_name'] = $row['display_name']; + j(['ok' => true, 'name' => $row['display_name']]); + } + bad('Invalid credentials', 401); + } + if ($action === 'logout') { session_destroy(); j(['ok' => true]); } + if (!loggedIn()) bad('Not authenticated', 401); + + switch ($action) { + + // ── DASHBOARD ───────────────────────────────────────────────────────── + case 'dashboard': + $mi = []; + foreach (file('/proc/meminfo') as $l) { + [$k,$v] = explode(':', $l, 2) + [null,null]; + if ($k) $mi[trim($k)] = (int)trim($v); + } + $mt = $mi['MemTotal'] ?? 0; $mf = $mi['MemAvailable'] ?? 0; + $up = (int)explode(' ', file_get_contents('/proc/uptime'))[0]; + $la = explode(' ', file_get_contents('/proc/loadavg')); + $disk = trim(shell_exec("df / | tail -1 | awk '{print $5}'") ?? ''); + j([ + 'sys' => [ + 'mem_pct' => $mt > 0 ? round(($mt-$mf)/$mt*100,1) : 0, + 'mem_used_mb' => round(($mt-$mf)/1024), + 'mem_total_mb' => round($mt/1024), + 'uptime_s' => $up, + 'load_1m' => (float)$la[0], + 'disk_pct' => $disk, + ], + 'agents' => JarvisDB::single('SELECT COUNT(*) total, SUM(status="online") online FROM registered_agents'), + 'alerts' => JarvisDB::single('SELECT COUNT(*) total, SUM(resolved=0) active FROM alerts'), + 'devices' => JarvisDB::single('SELECT COUNT(*) total, SUM(status="online") online FROM network_devices WHERE alias IS NOT NULL'), + 'facts' => JarvisDB::single('SELECT COUNT(*) total FROM kb_facts'), + 'intents' => JarvisDB::single('SELECT COUNT(*) total, SUM(active=1) active FROM kb_intents'), + ]); + + // ── AGENTS ─────────────────────────────────────────────────────────── + case 'agents_list': + $agents = JarvisDB::query('SELECT agent_id, hostname, agent_type, ip_address, status, last_seen, created_at FROM registered_agents ORDER BY status="online" DESC, hostname'); + $metrics = JarvisDB::query('SELECT agent_id, cpu_pct, mem_pct, disk_pct FROM agent_metrics WHERE recorded_at > DATE_SUB(NOW(), INTERVAL 5 MINUTE) GROUP BY agent_id'); + $mm = array_column($metrics, null, 'agent_id'); + foreach ($agents as &$a) $a['metrics'] = $mm[$a['agent_id']] ?? null; + j($agents); + + case 'agents_delete': + $id = $_POST['agent_id'] ?? ''; if (!$id) bad('Missing agent_id'); + JarvisDB::execute('DELETE FROM registered_agents WHERE agent_id=?', [$id]); + JarvisDB::execute('DELETE FROM agent_metrics WHERE agent_id=?', [$id]); + JarvisDB::execute('DELETE FROM agent_commands WHERE agent_id=?', [$id]); + j(['ok' => true]); + + // ── NETWORK ────────────────────────────────────────────────────────── + case 'network_list': + j(JarvisDB::query('SELECT id,ip,mac,hostname,alias,device_type,status,last_seen FROM network_devices WHERE alias IS NOT NULL ORDER BY alias')); + + case 'network_save': + $id = (int)($_POST['id'] ?? 0); + $ip = trim($_POST['ip'] ?? ''); $alias = trim($_POST['alias'] ?? ''); + $type = trim($_POST['device_type'] ?? 'device'); + if (!$ip || !$alias) bad('IP and alias required'); + if ($id) { + JarvisDB::execute('UPDATE network_devices SET ip=?,alias=?,device_type=? WHERE id=?', [$ip,$alias,$type,$id]); + } else { + JarvisDB::execute('INSERT INTO network_devices (ip,alias,device_type,status) VALUES (?,?,?,"unknown") ON DUPLICATE KEY UPDATE alias=?,device_type=?', [$ip,$alias,$type,$alias,$type]); + } + j(['ok' => true]); + + case 'network_delete': + $id = (int)($_POST['id'] ?? 0); if (!$id) bad('Missing id'); + JarvisDB::execute('DELETE FROM network_devices WHERE id=?', [$id]); + j(['ok' => true]); + + case 'network_ping': + $ip = trim($_POST['ip'] ?? ''); if (!$ip) bad('Missing IP'); + $out = shell_exec('ping -c 2 -W 2 '.escapeshellarg($ip).' 2>/dev/null'); + $alive = $out && (strpos($out,'2 received')!==false || strpos($out,'1 received')!==false); + $lat = null; + if ($alive && preg_match('/time=([\d.]+)/', $out, $m)) $lat = (float)$m[1]; + j(['alive'=>$alive,'latency_ms'=>$lat]); + + // ── ALERTS ─────────────────────────────────────────────────────────── + case 'alerts_list': + $f = $_GET['filter'] ?? 'all'; + $w = $f === 'active' ? 'WHERE resolved=0' : ($f === 'resolved' ? 'WHERE resolved=1' : ''); + j(JarvisDB::query("SELECT id,alert_type,title,message,severity,resolved,created_at,resolved_at,source_key FROM alerts $w ORDER BY created_at DESC LIMIT 300")); + + case 'alerts_resolve': + $id = (int)($_POST['id'] ?? 0); if (!$id) bad('Missing id'); + JarvisDB::execute('UPDATE alerts SET resolved=1,resolved_at=NOW() WHERE id=?', [$id]); + j(['ok' => true]); + + case 'alerts_resolve_all': + JarvisDB::execute('UPDATE alerts SET resolved=1,resolved_at=NOW() WHERE resolved=0'); + j(['ok' => true]); + + case 'alerts_delete': + $id = (int)($_POST['id'] ?? 0); if (!$id) bad('Missing id'); + JarvisDB::execute('DELETE FROM alerts WHERE id=?', [$id]); + j(['ok' => true]); + + case 'alerts_purge_resolved': + JarvisDB::execute('DELETE FROM alerts WHERE resolved=1'); + j(['ok' => true]); + + case 'alerts_save': + $id = (int)($_POST['id'] ?? 0); + $t = trim($_POST['title'] ?? ''); if (!$t) bad('Title required'); + $typ = trim($_POST['alert_type'] ?? 'manual'); + $msg = trim($_POST['message'] ?? ''); + $sev = trim($_POST['severity'] ?? 'info'); + if ($id) { + JarvisDB::execute('UPDATE alerts SET alert_type=?,title=?,message=?,severity=? WHERE id=?', [$typ,$t,$msg,$sev,$id]); + } else { + JarvisDB::execute('INSERT INTO alerts (alert_type,title,message,severity,resolved) VALUES (?,?,?,?,0)', [$typ,$t,$msg,$sev]); + } + j(['ok' => true]); + + // ── KB FACTS ───────────────────────────────────────────────────────── + case 'facts_categories': + j(JarvisDB::query('SELECT category, COUNT(*) cnt FROM kb_facts GROUP BY category ORDER BY cnt DESC')); + + case 'facts_list': + $cat = $_GET['category'] ?? ''; + if ($cat === '__all__') { + j(JarvisDB::query('SELECT id,category,fact_key,fact_value,host,updated_at FROM kb_facts ORDER BY category,fact_key LIMIT 1000')); + } + j(JarvisDB::query('SELECT id,category,fact_key,fact_value,host,updated_at FROM kb_facts WHERE category=? ORDER BY fact_key', [$cat])); + + case 'facts_save': + $id = (int)($_POST['id'] ?? 0); + $cat = trim($_POST['category'] ?? ''); $key = trim($_POST['fact_key'] ?? ''); $val = trim($_POST['fact_value'] ?? ''); + if (!$cat||!$key) bad('Category and key required'); + if ($id) { + JarvisDB::execute('UPDATE kb_facts SET category=?,fact_key=?,fact_value=? WHERE id=?', [$cat,$key,$val,$id]); + } else { + JarvisDB::execute('INSERT INTO kb_facts (category,fact_key,fact_value) VALUES (?,?,?)', [$cat,$key,$val]); + } + j(['ok' => true]); + + case 'facts_delete': + $id = (int)($_POST['id'] ?? 0); if (!$id) bad('Missing id'); + JarvisDB::execute('DELETE FROM kb_facts WHERE id=?', [$id]); + j(['ok' => true]); + + // ── KB INTENTS ─────────────────────────────────────────────────────── + case 'intents_list': + j(JarvisDB::query('SELECT id,intent_name,pattern,response_template,action_type,priority,active FROM kb_intents ORDER BY priority DESC,intent_name')); + + case 'intents_save': + $id = (int)($_POST['id'] ?? 0); + $name = trim($_POST['intent_name'] ?? ''); $pat = trim($_POST['pattern'] ?? ''); + $resp = trim($_POST['response_template'] ?? ''); + $typ = trim($_POST['action_type'] ?? 'response'); + $pri = (int)($_POST['priority'] ?? 5); $act = (int)($_POST['active'] ?? 1); + if (!$name||!$pat) bad('Name and pattern required'); + if ($id) { + JarvisDB::execute('UPDATE kb_intents SET intent_name=?,pattern=?,response_template=?,action_type=?,priority=?,active=? WHERE id=?', [$name,$pat,$resp,$typ,$pri,$act,$id]); + } else { + JarvisDB::execute('INSERT INTO kb_intents (intent_name,pattern,response_template,action_type,priority,active) VALUES (?,?,?,?,?,?)', [$name,$pat,$resp,$typ,$pri,$act]); + } + j(['ok' => true]); + + case 'intents_delete': + $id = (int)($_POST['id'] ?? 0); if (!$id) bad('Missing id'); + JarvisDB::execute('DELETE FROM kb_intents WHERE id=?', [$id]); + j(['ok' => true]); + + case 'intents_toggle': + $id = (int)($_POST['id'] ?? 0); if (!$id) bad('Missing id'); + JarvisDB::execute('UPDATE kb_intents SET active=NOT active WHERE id=?', [$id]); + j(['ok' => true]); + + // ── SITES ──────────────────────────────────────────────────────────── + case 'sites_list': + j(JarvisDB::query("SELECT fact_key,fact_value,updated_at FROM kb_facts WHERE category='sites' ORDER BY fact_key")); + + // ── USERS ──────────────────────────────────────────────────────────── + case 'users_list': + j(JarvisDB::query('SELECT id,username,display_name,last_seen,created_at FROM users ORDER BY username')); + + case 'users_save': + $id = (int)($_POST['id'] ?? 0); if (!$id) bad('Missing id'); + $dn = trim($_POST['display_name'] ?? ''); + $pw = trim($_POST['password'] ?? ''); + if ($pw) { + JarvisDB::execute('UPDATE users SET display_name=?,password_hash=? WHERE id=?', [$dn, password_hash($pw, PASSWORD_BCRYPT), $id]); + } else { + JarvisDB::execute('UPDATE users SET display_name=? WHERE id=?', [$dn, $id]); + } + j(['ok' => true]); + + default: bad('Unknown action'); + } +} +?> + + +
+ + +ADMIN PORTAL
+ + + + +