222 lines
8.2 KiB
JavaScript
222 lines
8.2 KiB
JavaScript
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);
|
||
}
|
||
}
|