const http = require("http"); const fs = require("fs"); const path = require("path"); const PORT = process.env.PORT || 3000; const DATA = path.join(__dirname, "data.json"); function readData() { try { return JSON.parse(fs.readFileSync(DATA, "utf8")); } catch (_) { return { ams: [], events: [], alerts: [], edges: [] }; } } function writeData(data) { fs.writeFileSync(DATA, JSON.stringify(data, null, 2)); } function body(req) { return new Promise((resolve) => { let s = ""; req.on("data", (c) => (s += c)); req.on("end", () => { try { resolve(JSON.parse(s || "{}")); } catch (_) { resolve({}); } }); }); } http.createServer(async (req, res) => { const url = req.url.split("?")[0]; // API: Получить все АМС if (req.method === "GET" && url === "/api/ams") { const data = readData(); res.writeHead(200, { "Content-Type": "application/json" }); return res.end(JSON.stringify(data.ams)); } // API: Добавить АМС if (req.method === "POST" && url === "/api/ams") { const ams = await body(req); const data = readData(); ams.id = Date.now(); ams.status = "online"; ams.created_at = new Date().toISOString(); data.ams.push(ams); writeData(data); res.writeHead(200, { "Content-Type": "application/json" }); return res.end(JSON.stringify({ ok: true, ams })); } // API: Получить все события if (req.method === "GET" && url === "/api/events") { const data = readData(); res.writeHead(200, { "Content-Type": "application/json" }); return res.end(JSON.stringify(data.events.sort((a, b) => new Date(b.detected_at) - new Date(a.detected_at)))); } // API: Получить событие по ID if (req.method === "GET" && url.startsWith("/api/events/")) { const eventId = url.split("/").pop(); const data = readData(); const event = data.events.find(e => e.event_id === eventId); if (!event) { res.writeHead(404, { "Content-Type": "application/json" }); return res.end(JSON.stringify({ error: "Not found" })); } res.writeHead(200, { "Content-Type": "application/json" }); return res.end(JSON.stringify(event)); } // API: Получить все тревоги if (req.method === "GET" && url === "/api/alerts") { const data = readData(); res.writeHead(200, { "Content-Type": "application/json" }); return res.end(JSON.stringify(data.alerts.sort((a, b) => new Date(b.created_at) - new Date(a.created_at)))); } // API: Обновить статус тревоги if (req.method === "PATCH" && url.startsWith("/api/alerts/")) { const alertId = parseInt(url.split("/").pop()); const update = await body(req); const data = readData(); const alert = data.alerts.find(a => a.id === alertId); if (!alert) { res.writeHead(404, { "Content-Type": "application/json" }); return res.end(JSON.stringify({ error: "Not found" })); } if (update.status) alert.status = update.status; if (update.confirmed_by) alert.confirmed_by = update.confirmed_by; alert.updated_at = new Date().toISOString(); writeData(data); res.writeHead(200, { "Content-Type": "application/json" }); return res.end(JSON.stringify({ ok: true, alert })); } // API: Получить все edge-устройства if (req.method === "GET" && url === "/api/edges") { const data = readData(); res.writeHead(200, { "Content-Type": "application/json" }); return res.end(JSON.stringify(data.edges)); } // API: Получить статистику if (req.method === "GET" && url === "/api/stats") { const data = readData(); const now = new Date(); const today = now.toISOString().slice(0, 10); const alertsToday = data.alerts.filter(a => a.created_at.startsWith(today)).length; const eventsToday = data.events.filter(e => e.detected_at.startsWith(today)).length; const onlineEdges = data.edges.filter(e => e.status === "online").length; const onlineAms = data.ams.filter(a => a.status === "online").length; res.writeHead(200, { "Content-Type": "application/json" }); return res.end(JSON.stringify({ total_ams: data.ams.length, online_ams: onlineAms, total_edges: data.edges.length, online_edges: onlineEdges, alerts_today: alertsToday, events_today: eventsToday, total_alerts: data.alerts.length })); } // API: Получить событие от edge-устройства (имитация NATS) if (req.method === "POST" && url === "/api/events") { const event = await body(req); const data = readData(); // Добавить событие event.event_id = event.event_id || String(Date.now()); event.received_at = new Date().toISOString(); data.events.push(event); // Проверить есть ли наряд-допуск для этой АМС const ams = data.ams.find(a => a.id === event.amc_id || a.name === event.amc_name); const hasPermit = ams && ams.has_permit; // Если нет наряда - создать тревогу if (!hasPermit && event.event_type === "person_detected") { const alert = { id: Date.now(), event_id: event.event_id, amc_id: event.amc_id, amc_name: event.amc_name, camera_id: event.camera_id, camera_role: event.camera_role, event_type: event.event_type, confidence: event.confidence, status: "new", created_at: new Date().toISOString(), snapshot_url: event.snapshot_url, clip_url: event.clip_url }; data.alerts.push(alert); // Отправить уведомление в Telegram (если настроен) if (process.env.BOT_TOKEN && process.env.TELEGRAM_CHAT_ID) { sendTelegramAlert(alert).catch(console.error); } console.log(`🚨 ТРЕВОГА: АМС ${event.amc_name}, камера ${event.camera_role}`); } writeData(data); res.writeHead(200, { "Content-Type": "application/json" }); return res.end(JSON.stringify({ ok: true, alert_created: !hasPermit })); } // API: Обновить heartbeat от edge if (req.method === "POST" && url === "/api/heartbeat") { const hb = await body(req); const data = readData(); let edge = data.edges.find(e => e.edge_id === hb.edge_id); if (!edge) { edge = { edge_id: hb.edge_id, amc_id: hb.amc_id, amc_name: hb.amc_name, status: "online", last_heartbeat: new Date().toISOString() }; data.edges.push(edge); } else { edge.status = "online"; edge.last_heartbeat = new Date().toISOString(); } writeData(data); res.writeHead(200, { "Content-Type": "application/json" }); return res.end(JSON.stringify({ ok: true })); } // Статика: index.html / style.css / script.js let file = url === "/" ? "/index.html" : url; const full = path.join(__dirname, file); if (full.startsWith(__dirname) && fs.existsSync(full) && fs.statSync(full).isFile()) { const ext = path.extname(full); const type = ext === ".css" ? "text/css" : ext === ".js" ? "application/javascript" : "text/html"; res.writeHead(200, { "Content-Type": type + "; charset=utf-8" }); return res.end(fs.readFileSync(full)); } res.writeHead(404, { "Content-Type": "text/plain" }); res.end("Not found"); }).listen(PORT, () => console.log("🚀 АМС AI-Мониторинг сервер на порту " + PORT)); async function sendTelegramAlert(alert) { try { const text = `🚨 *ТРЕВОГА: Несанкционированное проникновение*\n\n` + `📍 АМС: ${alert.amc_name}\n` + `📹 Камера: ${alert.camera_role}\n` + `⏰ Время: ${new Date(alert.created_at).toLocaleString("kk-KZ")}\n` + `🎯 Тип: ${alert.event_type}\n` + `📊 Confidence: ${(alert.confidence * 100).toFixed(1)}%`; await fetch(`https://api.telegram.org/bot${process.env.BOT_TOKEN}/sendMessage`, { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ chat_id: process.env.TELEGRAM_CHAT_ID, text: text, parse_mode: "Markdown" }) }); } catch (e) { console.error("Telegram error:", e.message); } }