ams-ai-monitoring-tehnichesk/server.js

222 lines
8.2 KiB
JavaScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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);
}
}