const express = require("express"); const app = express(); const path = require("path"); const { Pool } = require("pg"); const fs = require("fs"); const handlebars = require("handlebars"); require("dotenv").config(); const multer = require("multer"); const http = require("http"); const Client = require('ssh2').Client; const axios = require('axios'); const moment = require('moment'); const bodyParser = require('body-parser'); const _ = require('lodash'); const storage = multer.diskStorage({ destination: function (req, file, cb) { cb(null, "uploads"); }, filename: function (req, file, cb) { cb(null, Date.now() + "-" + file.originalname); }, }); const upload = multer({ storage: storage }); app.use(express.static(path.join(__dirname, "static"))); app.use(express.json()); app.use(express.urlencoded({ extended: false })); app.use(bodyParser.json()); app.get("/", index); app.get("/login", login); app.get("/register", register); app.get("/live", live); app.get("/reports", reports); app.get("/devices", devices); app.get("/devices/drivers", drivers); app.get("/devices/update", update); app.get("/videos", videos); app.get("/videos/export",videoExport); app.get("/settings", settings); const connectionProperties = { host: process.env.SSH_HOST, port: process.env.SSH_PORT, username: process.env.SSH_USERNAME, password: process.env.SSH_PASSWORD, }; const commandToExecute = 'docker ps | grep connectionserver-video-server'; const conn = new Client(); app.post("/videos/restart", async (req, res) => { conn.on('ready', function() { console.log('Подключение по SSH успешно'); conn.exec(commandToExecute, function(err, stream) { if (err) throw err; let containerId = ''; stream .on('data', function(data) { const lines = data.toString().split('\n'); for (const line of lines) { if (line.includes('connectionserver-video-server')) { containerId = line.trim().split(/\s+/)[0]; break; } } if (containerId) { console.log('Найден CONTAINER ID:', containerId); const restartCommand = `docker restart ${containerId}`; conn.exec(restartCommand, function(err, stream) { if (err); console.log('Команда для рестарта выполнена.'); conn.end(); // Закрываем соединение SSH res.status(200).json({ message: "Команда для рестарта выполнена." }); return; }); } else { console.log('Контейнер connectionserver-video-server не найден.'); conn.end(); // Закрываем соединение SSH } }) .stderr.on('data', function(data) { console.error('Ошибка выполнения команды:', data.toString()); conn.end(); // Закрываем соединение SSH res.status(500).json({ error: "Ошибка сервера" }); return; }); }); }).connect(connectionProperties); conn.on('error', function(err) { console.error('Ошибка подключения: ' + err.message); }); }); // const DB_User = process.env.DB_USER; // const DB_Password = process.env.DB_PASSWORD; // const DB_Host = process.env.DB_HOST; // const DB_Port = process.env.DB_PORT; // const DB_Name = process.env.DB_NAME; const DB_User = "postgres"; const DB_Password = process.env.POSTGRES_PASSWORD; const DB_Host = "postgres"; const DB_Port = "5432"; const DB_Name = "postgres"; async function index(req, res) { var templateData = { Organisation: "Название организации", User: "Тестовое Имя", ifDBError: false, Count: "", AlarmsLast11Days: new Array(11).fill(0), Alarms11DaysBefore: new Array(11).fill(0), Dates: [], PositionsLast11Days: new Array(11).fill(0), }; try { const pool = new Pool({ user: DB_User, host: DB_Host, database: DB_Name, password: DB_Password, port: DB_Port, }); const client = await pool.connect(); const query = ` SELECT COUNT(*) AS count FROM registrars `; const registrars = await client.query(query); templateData.Count = registrars.rows[0].count; const last11DaysQuery = ` WITH date_sequence AS ( SELECT DATE_TRUNC('day', NOW() - INTERVAL '10 days' - INTERVAL '3 hours') + (generate_series(0, 10) || ' days')::interval AS day ) SELECT date_sequence.day AS day, COALESCE(COUNT(DISTINCT a.evtuuid), 0) AS count FROM date_sequence LEFT JOIN ( SELECT DISTINCT ON (evtuuid) evtuuid, time FROM alarms WHERE alarmtype = 56 AND time >= NOW() - INTERVAL '11 days' + INTERVAL '3 hours' AND time <= NOW() + INTERVAL '1 day' + INTERVAL '3 hours' ORDER BY evtuuid, time DESC LIMIT 100 ) AS a ON DATE_TRUNC('day', a.time) = date_sequence.day GROUP BY date_sequence.day ORDER BY date_sequence.day DESC; `; const last11DaysAlarms = await client.query(last11DaysQuery); const daysBeforeQuery = ` WITH date_sequence AS ( SELECT DATE_TRUNC('day', NOW() - INTERVAL '21 days' - INTERVAL '3 hours') + (generate_series(0, 10) || ' days')::interval AS day ) SELECT date_sequence.day AS day, COALESCE(COUNT(DISTINCT a.evtuuid), 0) AS count FROM date_sequence LEFT JOIN ( SELECT DISTINCT ON (evtuuid) evtuuid, time FROM alarms WHERE alarmtype = 56 AND time >= NOW() - INTERVAL '21 days' + INTERVAL '3 hours' AND time <= NOW() + INTERVAL '10 day' + INTERVAL '3 hours' ORDER BY evtuuid, time DESC LIMIT 100 ) AS a ON DATE_TRUNC('day', a.time) = date_sequence.day GROUP BY date_sequence.day ORDER BY date_sequence.day DESC; `; const daysBeforeAlarms = await client.query(daysBeforeQuery); const currentDate = new Date(); const dates = []; const dates11DaysAgo = []; for (let i = 10; i >= 0; i--) { const date = new Date(currentDate - i * 24 * 60 * 60 * 1000); const formattedDate = date.toLocaleDateString('ru-RU', { day: '2-digit', month: '2-digit' }); dates.push(formattedDate); const date11DaysAgo = new Date(currentDate - i * 24 * 60 * 60 * 1000 - 11 * 24 * 60 * 60 * 1000); const formattedDate11DaysAgo = date11DaysAgo.toLocaleDateString('ru-RU', { day: '2-digit', month: '2-digit' }); dates11DaysAgo.push(formattedDate11DaysAgo); } templateData.Dates = dates; dates11DaysAgo.reverse(); const positionsLast11DaysQuery = ` SELECT COUNT(*) AS count, DATE_TRUNC('day', time) AS day, CASE WHEN COUNT(*) = 0 THEN 0 ELSE 1 END AS sort_value FROM geo WHERE time >= NOW() - INTERVAL '10 days' + INTERVAL '3 hours' AND time <= NOW() + INTERVAL '1 day' + INTERVAL '3 hours' GROUP BY DATE_TRUNC('day', time) ORDER BY sort_value DESC, day DESC `; const positionsLast11Days = await client.query(positionsLast11DaysQuery); templateData.Dates.reverse(); const last11DaysMap = new Map(last11DaysAlarms.rows.map(row => [row.day.toLocaleDateString('ru-RU', { day: '2-digit', month: '2-digit' }), parseInt(row.count, 10)])); for (let i = 0; i < dates.length; i++) { const dateKey = dates[i]; templateData.AlarmsLast11Days[i] = last11DaysMap.has(dateKey) ? last11DaysMap.get(dateKey) : 0; } const beforeDaysMap = new Map(daysBeforeAlarms.rows.map(row => [row.day.toLocaleDateString('ru-RU', { day: '2-digit', month: '2-digit' }), parseInt(row.count, 10)])); for (let i = 0; i < dates.length; i++) { const dateKey = dates11DaysAgo[i]; templateData.Alarms11DaysBefore[i] = beforeDaysMap.has(dateKey) ? beforeDaysMap.get(dateKey) : 0; } const positionsMap = new Map(positionsLast11Days.rows.map(row => [row.day.toLocaleDateString('ru-RU', { day: '2-digit', month: '2-digit' }), parseInt(row.count, 10)])); for (let i = 0; i < dates.length; i++) { const dateKey = dates[i]; templateData.PositionsLast11Days[i] = positionsMap.has(dateKey) ? positionsMap.get(dateKey) : 0; } // console.log(templateData); const source = fs.readFileSync("static/templates/index.html", "utf8"); const template = handlebars.compile(source); const resultT = template(templateData); res.send(resultT); } catch (error) { console.error(error); templateData.ifDBError = true; const source = fs.readFileSync("static/templates/index.html", "utf8"); const template = handlebars.compile(source); const resultT = template(templateData); res.send(resultT); } } function login(req, res) { res.sendFile(path.join(__dirname, "static/templates/login.html")); } function register(req, res) { res.sendFile(path.join(__dirname, "static/templates/register.html")); } async function live(req, res) { let templateData = { Organisation: "Название организации", User: "Тестовое Имя", ifDBError: false, Registrars: [], Alarms: [], Count: 0, Groups: [], }; try { const pool = new Pool({ user: DB_User, host: DB_Host, database: DB_Name, password: DB_Password, port: DB_Port, }); const client = await pool.connect(); const minuteInMillis = 90 * 1000; const query = ` SELECT id, serial, channels, lastkeepalive FROM registrars ORDER BY id ASC `; const registrars = await client.query(query); templateData.Registrars = registrars.rows.map((row) => ({ id: row.id, serial: row.serial, channels: row.channels, status: Date.now() - Date.parse(row.lastkeepalive) <= minuteInMillis, })); const subquery = ` SELECT a.evtuuid, a.id, a.cmdno, a.time, a.serial, a.st, r.plate, g.latitude, g.longitude FROM ( SELECT DISTINCT ON (evtuuid) evtuuid, id, cmdno, time, serial, st FROM alarms WHERE alarmtype = 56 ORDER BY evtuuid, time DESC LIMIT 100 ) AS a LEFT JOIN registrars AS r ON a.serial = r.serial LEFT JOIN ( SELECT *, ROW_NUMBER() OVER (PARTITION BY serial ORDER BY ABS(EXTRACT(EPOCH FROM (time - $1)))) AS row_num FROM geo ) AS g ON a.serial = g.serial AND g.row_num = 1 ORDER BY a.time DESC; `; const alarms = await client.query(subquery, [new Date()]); function formatDate(date) { const options = { year: "2-digit", month: "2-digit", day: "2-digit", hour: "2-digit", minute: "2-digit", }; const adjustedDate = new Date(date); adjustedDate.setHours(adjustedDate.getHours() - 3); const formattedDate = adjustedDate.toLocaleString("ru-RU", options); return formattedDate.replace(",", ""); } (templateData.Alarms = alarms.rows.map((alarm) => { let type; switch (alarm.st) { case "0": type = "Усталость"; break; case "1": type = "Водитель пропал"; break; case "2": type = "Разговор по телефону"; break; case "3": type = "Курение за рулём"; break; case "4": type = "Водитель отвлекся"; break; case "5": type = "Выезд с полосы движения"; break; case "6": type = "!!! Лобовое столкновение"; break; case "7": type = "Скорость превышена"; break; case "8": type = "Распознавание номерных знаков"; break; case "9": type = "!! Маленькое расстояние спереди"; break; case "10": type = "Водитель зевает"; break; case "11": type = "!!! Столкновение с пешеходом"; break; case "12": type = "Проходы переполнены"; break; case "13": type = "!! Посадка/высадка вне остановки"; break; case "14": type = "!! Смена полосы с нарушением ПДД"; break; case "15": type = "! Включенный телефон у водителя"; break; case "16": type = "!!! Ремень безопасности"; break; case "17": type = "Проверка не удалась"; break; case "18": type = "Слепые зоны справа"; break; case "19": type = "!!! Заднее столкновение"; break; case "20": type = "!!! Управление без рук"; break; case "21": type = "!! Управление одной рукой"; break; case "22": type = "Очки, блокирующие инфракрасное излучение"; break; case "23": type = "Слепые зоны слева"; break; case "24": type = "Помехи для пассажиров"; break; case "25": type = "На перекрестке ограничена скорость"; break; case "26": type = "Обнаружен перекресток"; break; case "27": type = "Пешеходы на переходе"; break; case "28": type = "! Неучтивое отношение к пешеходам"; break; case "29": type = "Обнаружен пешеходный переход"; break; case "30": type = "Водитель матерится"; break; default: type = "Неизвестный тип"; } return { id: alarm.id, cmdno: alarm.cmdno, time: formatDate(alarm.time), serial: alarm.serial, st: alarm.st, type: type, plate: alarm.plate, latitude: (alarm.latitude).toFixed(6), longitude: (alarm.longitude).toFixed(6), geo: alarm.latitude + "," + alarm.longitude, }; })) templateData.Count = templateData.Alarms.length; // Выполняем запрос, чтобы получить все данные из таблицы registrars const queryRegistrars = ` SELECT id, serial, channels, lastkeepalive, name, "group", plate, sim, ip, port FROM registrars ORDER BY id `; const registrarsResult = await client.query(queryRegistrars); const allRegistrars = registrarsResult.rows; // Группируем устройства по группам и сохраняем данные каждого устройства const groupedRegistrars = {}; allRegistrars.forEach((registrar) => { if (!groupedRegistrars[registrar.group]) { groupedRegistrars[registrar.group] = []; } groupedRegistrars[registrar.group].push({ id: registrar.id, serial: registrar.serial, channels: registrar.channels, status: Date.now() - Date.parse(registrar.lastkeepalive) <= minuteInMillis, name: registrar.name, group: registrar.group, plate: registrar.plate, sim: registrar.sim, ip: registrar.ip, port: registrar.port, }); }); // Заполняем массив групп данными устройств в каждой группе for (const groupName in groupedRegistrars) { templateData.Groups.push({ name: groupName, devices: groupedRegistrars[groupName], }); } const source = fs.readFileSync("static/templates/live.html", "utf8"); const template = handlebars.compile(source); const resultHTML = template(templateData); res.send(resultHTML); client.release(); } catch (error) { console.error(error); templateData.ifDBError = true; const source = fs.readFileSync("static/templates/live.html", "utf8"); const template = handlebars.compile(source); const resultT = template(templateData); res.send(resultT); } } app.post("/devices-geo", async (req, res) => { const selectedDevices = req.body.devices; const pool = new Pool({ user: DB_User, host: DB_Host, database: DB_Name, password: DB_Password, port: DB_Port, }); const placeholders = selectedDevices .map((_, index) => `$${index + 1}`) .join(","); const subquery = ` SELECT g.serial, g.longitude, g.latitude, g.direction, g.speed, r.lastkeepalive, r.plate, r.group FROM geo g INNER JOIN ( SELECT serial, MAX(time) AS time FROM geo WHERE serial IN ( SELECT serial FROM registrars WHERE id IN (${placeholders}) ) GROUP BY serial ) s ON g.serial = s.serial AND g.time = s.time INNER JOIN registrars r ON g.serial = r.serial `; pool.query(subquery, selectedDevices, (err, result) => { if (err) { console.error("Ошибка выполнения запроса:", err); res.status(500).json({ error: "Ошибка сервера" }); return; } const minuteInMillis = 60000; const devicesData = result.rows.map((row) => { if (row.speed > 150) { row.speed /= 100; } return { serial: row.serial, longitude: row.longitude, latitude: row.latitude, direction: row.direction, speed: row.speed, status: Date.now() - Date.parse(row.lastkeepalive) <= minuteInMillis, plate: row.plate, group: row.group, }; }); // console.log(devicesData) res.json({ devicesData }); }); }); async function reports(req, res) { let templateData = { Organisation: "Название организации", User: "Тестовое Имя", ifDBError: false, Registrars: [], Groups: [], }; try { const pool = new Pool({ user: DB_User, host: DB_Host, database: DB_Name, password: DB_Password, port: DB_Port, }); const client = await pool.connect(); const query = ` SELECT a.evtuuid, a.id, a.cmdno, a.time, a.serial, a.st, r.plate, g.latitude, g.longitude FROM ( SELECT DISTINCT ON (evtuuid) evtuuid, id, cmdno, time, serial, st FROM alarms WHERE alarmtype = 56 ORDER BY evtuuid, time DESC LIMIT 100 ) AS a LEFT JOIN registrars AS r ON a.serial = r.serial LEFT JOIN ( SELECT *, ROW_NUMBER() OVER (PARTITION BY serial ORDER BY ABS(EXTRACT(EPOCH FROM (time - $1)))) AS row_num FROM geo ) AS g ON a.serial = g.serial AND g.row_num = 1 ORDER BY a.time DESC; `; const alarms = await client.query(query, [new Date()]); function formatDate(date) { const options = { year: "2-digit", month: "2-digit", day: "2-digit", hour: "2-digit", minute: "2-digit", }; const adjustedDate = new Date(date); adjustedDate.setHours(adjustedDate.getHours() - 3); const formattedDate = adjustedDate.toLocaleString("ru-RU", options); return formattedDate.replace(",", ""); } (templateData.Alarms = alarms.rows.map((alarm) => { let type; switch (alarm.st) { case "0": type = "Усталость"; break; case "1": type = "Водитель пропал"; break; case "2": type = "Разговор по телефону"; break; case "3": type = "Курение за рулём"; break; case "4": type = "Водитель отвлекся"; break; case "5": type = "Выезд с полосы движения"; break; case "6": type = "!!! Лобовое столкновение"; break; case "7": type = "Скорость превышена"; break; case "8": type = "Распознавание номерных знаков"; break; case "9": type = "!! Маленькое расстояние спереди"; break; case "10": type = "Водитель зевает"; break; case "11": type = "!!! Столкновение с пешеходом"; break; case "12": type = "Проходы переполнены"; break; case "13": type = "!! Посадка/высадка вне остановки"; break; case "14": type = "!! Смена полосы с нарушением ПДД"; break; case "15": type = "! Включенный телефон у водителя"; break; case "16": type = "!!! Ремень безопасности"; break; case "17": type = "Проверка не удалась"; break; case "18": type = "Слепые зоны справа"; break; case "19": type = "!!! Заднее столкновение"; break; case "20": type = "!!! Управление без рук"; break; case "21": type = "!! Управление одной рукой"; break; case "22": type = "Очки, блокирующие инфракрасное излучение"; break; case "23": type = "Слепые зоны слева"; break; case "24": type = "Помехи для пассажиров"; break; case "25": type = "На перекрестке ограничена скорость"; break; case "26": type = "Обнаружен перекресток"; break; case "27": type = "Пешеходы на переходе"; break; case "28": type = "! Неучтивое отношение к пешеходам"; break; case "29": type = "Обнаружен пешеходный переход"; break; case "30": type = "Водитель матерится"; break; default: type = "Неизвестный тип"; } return { id: alarm.id, cmdno: alarm.cmdno, time: formatDate(alarm.time), serial: alarm.serial, st: alarm.st, type: type, plate: alarm.plate, latitude: (alarm.latitude).toFixed(6), longitude: (alarm.longitude).toFixed(6), geo: (alarm.latitude).toFixed(6) + "," + (alarm.longitude).toFixed(6), }; })) // Выполняем запрос, чтобы получить все данные из таблицы registrars const queryRegistrars = ` SELECT id, serial, lastkeepalive, name, "group", plate, sim, ip, port FROM registrars ORDER BY id `; const registrarsResult = await client.query(queryRegistrars); const allRegistrars = registrarsResult.rows; // Группируем устройства по группам const groupedRegistrars = {}; allRegistrars.forEach((registrar) => { if (!groupedRegistrars[registrar.group]) { groupedRegistrars[registrar.group] = []; } groupedRegistrars[registrar.group].push(registrar.serial); }); // Заполняем массив групп и серийными номерами устройств в каждой группе for (const groupName in groupedRegistrars) { templateData.Groups.push({ name: groupName, serials: groupedRegistrars[groupName], }); } const source = fs.readFileSync( "static/templates/reports/index.html", "utf8" ); const template = handlebars.compile(source); const resultT = template(templateData); res.send(resultT); } catch (error) { console.error(error); templateData.ifDBError = true; const source = fs.readFileSync( "static/templates/reports/index.html", "utf8" ); const template = handlebars.compile(source); const resultT = template(templateData); res.send(resultT); } } app.get("/api/devices", async (req, res) => { try { const pool = new Pool({ user: DB_User, host: DB_Host, database: DB_Name, password: DB_Password, port: DB_Port, }); const { page = 1, limit = 10 } = req.query; const offset = (page - 1) * limit; const query = ` SELECT a.id, a.cmdno, a.time, a.serial, a.st, r.plate, g.latitude, g.longitude FROM ( SELECT id, cmdno, time, serial, st FROM alarms ORDER BY time DESC LIMIT $1 OFFSET $2 ) AS a LEFT JOIN registrars AS r ON a.serial = r.serial LEFT JOIN ( SELECT *, ROW_NUMBER() OVER (PARTITION BY serial ORDER BY ABS(EXTRACT(EPOCH FROM (time - $3)))) AS row_num FROM geo ) AS g ON a.serial = g.serial AND g.row_num = 1 ORDER BY a.time DESC; `; const alarms = await pool.query(query, [limit, offset, new Date()]); function formatDate(date) { const options = { year: "2-digit", month: "2-digit", day: "2-digit", hour: "2-digit", minute: "2-digit", }; const adjustedDate = new Date(date); adjustedDate.setHours(adjustedDate.getHours() - 3); const formattedDate = adjustedDate.toLocaleString("ru-RU", options); return formattedDate.replace(",", ""); } const alarmsData = alarms.rows.map((alarm) => { let type; switch (alarm.st) { case "0": type = "Усталость"; break; case "1": type = "Отсутствие водителя"; break; case "2": type = "Разговор по телефону"; break; default: type = "Неизвестный тип"; } return { id: alarm.id, cmdno: alarm.cmdno, time: formatDate(alarm.time), serial: alarm.serial, st: alarm.st, type: type, plate: alarm.plate, latitude: alarm.latitude, longitude: alarm.longitude, geo: alarm.latitude + "," + alarm.longitude, }; }); const totalCountQuery = "SELECT COUNT(*) FROM alarms;"; const totalCount = await pool.query(totalCountQuery); const totalPages = Math.ceil(totalCount.rows[0].count / limit); res.json({ data: alarmsData, totalPages: totalPages, }); } catch (error) { console.error("Error executing query:", error); res.status(500).json({ error: "Internal server error" }); } }); app.get('/reports/:id', async (req, res) => { const id = req.params.id; let templateData = { Organisation: "Название организации", User: "Тестовое Имя", ifDBError: false, Id: id, Type: "", Speed: "", Date: "", Serial: "", Geo: "", Latitude: "", Longitude: "", DriverName: "", DriverPhone: "", DriverEmail: "", DriverLicense: "", PrevLatitude: "", PrevLongitude: "", NextLatitude: "", NextLongitude: "", Speeds: "", }; try { const pool = new Pool({ user: DB_User, host: DB_Host, database: DB_Name, password: DB_Password, port: DB_Port, }); const client = await pool.connect(); const minuteInMillis = 90 * 1000; const query = ` WITH PrevNextGeo AS ( SELECT a.serial, a.st, a.time, a.geoid, (SELECT g1.latitude FROM alarms a1 LEFT JOIN geo g1 ON a1.geoid = g1.id WHERE a1.evtuuid = a.evtuuid ORDER BY a1.time ASC LIMIT 1) AS prev_latitude, (SELECT g2.longitude FROM alarms a2 LEFT JOIN geo g2 ON a2.geoid = g2.id WHERE a2.evtuuid = a.evtuuid ORDER BY a2.time ASC LIMIT 1) AS prev_longitude, (SELECT g3.latitude FROM alarms a3 LEFT JOIN geo g3 ON a3.geoid = g3.id WHERE a3.evtuuid = a.evtuuid ORDER BY a3.time DESC LIMIT 1) AS next_latitude, (SELECT g4.longitude FROM alarms a4 LEFT JOIN geo g4 ON a4.geoid = g4.id WHERE a4.evtuuid = a.evtuuid ORDER BY a4.time DESC LIMIT 1) AS next_longitude, g.longitude, g.latitude, g.speed, d.name, d.surname, d.card, d.phone, d.email FROM alarms a LEFT JOIN geo g ON a.geoid = g.id LEFT JOIN drivers d ON a.serial = d.transport WHERE a.id = ${id} LIMIT 1 ), Speeds AS ( SELECT g.speed, ROW_NUMBER() OVER (ORDER BY ABS(EXTRACT(EPOCH FROM (a.time - (SELECT time FROM PrevNextGeo)))) ASC) AS row_number FROM alarms a LEFT JOIN geo g ON a.geoid = g.id WHERE g.serial = (SELECT serial FROM PrevNextGeo) -- Ограничиваем результаты только записями с тем же serial ) SELECT *, ( SELECT array_agg(speed) FROM Speeds WHERE row_number <= 11 ) AS nearest_speeds FROM PrevNextGeo; `; const alarm = (await client.query(query)).rows[0]; // console.log(alarm); function formatDate(date) { const options = { year: "2-digit", month: "2-digit", day: "2-digit", hour: "2-digit", minute: "2-digit", }; const adjustedDate = new Date(date); adjustedDate.setHours(adjustedDate.getHours() - 3); const formattedDate = adjustedDate.toLocaleString("ru-RU", options); return formattedDate.replace(",", ""); } let type; switch (alarm.st) { case "0": type = "Усталость"; break; case "1": type = "Водитель пропал"; break; case "2": type = "Разговор по телефону"; break; case "3": type = "Курение за рулём"; break; case "4": type = "Водитель отвлекся"; break; case "5": type = "Выезд с полосы движения"; break; case "6": type = "!!! Лобовое столкновение"; break; case "7": type = "Скорость превышена"; break; case "8": type = "Распознавание номерных знаков"; break; case "9": type = "!! Маленькое расстояние спереди"; break; case "10": type = "Водитель зевает"; break; case "11": type = "!!! Столкновение с пешеходом"; break; case "12": type = "Проходы переполнены"; break; case "13": type = "!! Посадка/высадка вне остановки"; break; case "14": type = "!! Смена полосы с нарушением ПДД"; break; case "15": type = "! Включенный телефон у водителя"; break; case "16": type = "!!! Ремень безопасности"; break; case "17": type = "Проверка не удалась"; break; case "18": type = "Слепые зоны справа"; break; case "19": type = "!!! Заднее столкновение"; break; case "20": type = "!!! Управление без рук"; break; case "21": type = "!! Управление одной рукой"; break; case "22": type = "Очки, блокирующие инфракрасное излучение"; break; case "23": type = "Слепые зоны слева"; break; case "24": type = "Помехи для пассажиров"; break; case "25": type = "На перекрестке ограничена скорость"; break; case "26": type = "Обнаружен перекресток"; break; case "27": type = "Пешеходы на переходе"; break; case "28": type = "! Неучтивое отношение к пешеходам"; break; case "29": type = "Обнаружен пешеходный переход"; break; case "30": type = "Водитель матерится"; break; default: type = "Неизвестный тип"; } var actualSpeed; if (alarm.speed > 150) { actualSpeed = alarm.speed / 100 } else { actualSpeed = alarm.speed } templateData.Type = type; templateData.Speed = actualSpeed; templateData.Date = formatDate(alarm.time); templateData.Serial = alarm.serial; templateData.Geo = alarm.latitude + "," + alarm.longitude; templateData.Latitude = alarm.latitude templateData.Longitude = alarm.longitude templateData.DriverName = alarm.name + " " + alarm.surname; templateData.DriverPhone = alarm.phone; templateData.DriverEmail = alarm.email; templateData.DriverLicense = alarm.card; templateData.PrevLatitude = alarm.prev_latitude; templateData.PrevLongitude = alarm.prev_longitude; templateData.NextLatitude = alarm.next_latitude; templateData.NextLongitude = alarm.next_longitude; templateData.Speeds = alarm.nearest_speeds templateData.Speeds = templateData.Speeds.map(speed => { if (speed > 150) { return speed / 100; } else { return speed; } }); // console.log(templateData); const source = fs.readFileSync("static/templates/reports/report.html", "utf8"); const template = handlebars.compile(source); const resultT = template(templateData); res.send(resultT); client.release(); } catch (error) { console.error(error); templateData.ifDBError = true; const source = fs.readFileSync( "static/templates/reports/report.html", "utf8" ); const template = handlebars.compile(source); const resultT = template(templateData); res.send(resultT); } }); async function devices(req, res) { let templateData = { Organisation: "Название организации", User: "Тестовое Имя", ifDBError: false, Registrars: [], Groups: [], }; try { const pool = new Pool({ user: DB_User, host: DB_Host, database: DB_Name, password: DB_Password, port: DB_Port, }); const client = await pool.connect(); const minuteInMillis = 90 * 1000; // Выполняем запрос, чтобы получить все данные из таблицы registrars const queryRegistrars = ` SELECT id, serial, lastkeepalive, name, "group", plate, sim, ip, port FROM registrars ORDER BY id `; const registrarsResult = await client.query(queryRegistrars); const allRegistrars = registrarsResult.rows; // Определяем статус connected на основе lastkeepalive templateData.Registrars = allRegistrars.map((registrar) => ({ id: registrar.id, serial: registrar.serial, status: Date.now() - Date.parse(registrar.lastkeepalive) <= minuteInMillis, name: registrar.name, group: registrar.group, plate: registrar.plate, sim: registrar.sim, ip: registrar.ip, port: registrar.port, })); // Группируем устройства по группам const groupedRegistrars = {}; allRegistrars.forEach((registrar) => { if (!groupedRegistrars[registrar.group]) { groupedRegistrars[registrar.group] = []; } groupedRegistrars[registrar.group].push(registrar.serial); }); // Заполняем массив групп и серийными номерами устройств в каждой группе for (const groupName in groupedRegistrars) { templateData.Groups.push({ name: groupName, serials: groupedRegistrars[groupName], }); } // console.log(templateData); const source = fs.readFileSync("static/templates/devices/index.html", "utf8"); const template = handlebars.compile(source); const resultT = template(templateData); res.send(resultT); client.release(); } catch (error) { console.error(error); templateData.ifDBError = true; const source = fs.readFileSync( "static/templates/devices/index.html", "utf8" ); const template = handlebars.compile(source); const resultT = template(templateData); res.send(resultT); } } async function getParameterByName(serial, fieldName) { const requestPayload = { FIELDS: [fieldName], }; const requestResponse = await axios.get(`http://krbl.ru:8080/http/parameters/request?serial=${serial}`, { headers: { 'Content-Type': 'application/json', }, data: JSON.stringify(requestPayload), }); // console.log(requestResponse.data); await new Promise(resolve => setTimeout(resolve, 300)); const getResponse = await axios.get(`http://krbl.ru:8080/http/parameters/get?serial=${serial}`); // console.log(getResponse.data); const fieldPath = findPathForField(fieldName); if (fieldPath) { const value = _.get(getResponse.data, fieldPath); return { [fieldName]: value }; } return null; } app.post('/device-parameters', async (req, res) => { try { const { serial, FIELDS } = req.body; // console.log(serial, FIELDS); const responseData = {}; // Используем асинхронный цикл для выполнения GET-запросов по очереди for (const field of FIELDS) { const parameter = await getParameterByName(serial, field); if (parameter) { Object.assign(responseData, parameter); } } // console.log(responseData); res.json(responseData); } catch (error) { console.error(error); res.status(500).json({ error: 'Internal server error' }); } }); function findPathForField(fieldName) { const fieldPathMap = { "VEHICLEID": "DATA.RIP.BN", "DATEMOD": "DATA.TIMEP.DATEM", "TIMEFORMAT": "DATA.TIMEP.TIMEM", "LANGUAGE": "DATA.GSP.LANT", "VIDEOFORMAT": "DATA.GSP.VGA", "GEOMOD": "DATA.GSP.GM", "SUBSTREAMMODE": "DATA.SUBSTRNET.SM", "NETWORK": "DATA.SUBSTRNET.NEC", "DISPLAYMENU": "DATA.DOSD", "RECORDINGPARAMS": "DATA.RP.RCP", "FUTURERECORDINGSTYLE": "DATA.MP.OT", "FUTURERECORDING": "DATA.MP.OT", "STREAMPARAMS": "DATA.AR.VEC", "VIDEOIMAGEMOD": "DATA.SVIP", }; return fieldPathMap[fieldName] || null; } app.put('/device-parameters', async (req, res) => { // Получаем данные из PUT запроса const requestData = req.body; const { serial } = req.query; // Извлекаем необходимые параметры const { DATEMOD, TIMEFORMAT, LANGUAGE, VIDEOFORMAT, GEOMOD, SUBSTREAMMODE, NE, TE, VE, SE, GE } = requestData; // Создаем JSON для GET запроса const requestBody = { TIMEP: { DATEM: parseInt(DATEMOD, 10) || 1, TIMEM: parseInt(TIMEFORMAT, 10) || 0 }, GSP: { LANT: parseInt(LANGUAGE, 10) || 12, VGA: parseInt(VIDEOFORMAT, 10) || 0, GM: parseInt(GEOMOD, 10) || 0 }, SUBSTRNET: { SM: parseInt(SUBSTREAMMODE, 10) || 1 }, DOSD: { NE: parseInt(NE, 10) || 1, TE: parseInt(TE, 10) || 1, VE: parseInt(VE, 10) || 0, SE: parseInt(SE, 10) || 0, GE: parseInt(GE, 10) || 0 } }; // Отправляем GET запрос с JSON BODY try { const response = await axios.get(`http://krbl.ru:8080/http/parameters/set?serial=${serial}`, { data: JSON.stringify(requestBody), headers: { 'Content-Type': 'application/json' } }); res.status(response.status).send(response.data); } catch (error) { res.status(500).send('Произошла ошибка при отправке GET запроса.'); } }); app.post("/devicedata", async (req, res) => { const id = req.body.id; const pool = new Pool({ user: DB_User, host: DB_Host, database: DB_Name, password: DB_Password, port: DB_Port, }); const client = await pool.connect(); try { const query = "SELECT * FROM registrars WHERE id = $1;"; const devicedata = await client.query(query, [id]); const response = devicedata.rows[0]; res.json(response); } finally { client.release(); } }); app.post("/updatedevice", async (req, res) => { const pool = new Pool({ user: DB_User, host: DB_Host, database: DB_Name, password: DB_Password, port: DB_Port, }); const client = await pool.connect(); var { plateNumber, plateColor, serialNumber, channelsAmount, connectionProtocol, IPAddress, deviceGroup, serverPort, sumNumber, simIMEI, simIMSI, simModule, transportType, transportFactory, transportStrength, transportEngine, transportStanina, transportFuel, transportCertificate, transportCategory, transportExpire, transportConsumption, transportProvince, transportCity, equipmentName, equipmentPassword, equipmentNumber, equipmentReleased, equipmentInstaller, equipmentInstalled, equipmentDescription, } = req.body; try { const query = ` UPDATE registrars SET plate = $1, plate_color = $2, channels = $3, protocol = $4, ip = $5, "group" = $6, port = $7, sim = $8, imei = $9, imsi = $10, module = $11, auto = $12, factory = $13, capacity = $14, engine = $15, stanina = $16, fuel = $17, certificate = $18, category = $19, certificate_exp = $20, consumption = $21, region = $22, city = $23, name = $24, password = $25, batch = $26, release = $27, installer = $28, installation = $29, description = $30 WHERE serial = $31 RETURNING *; `; const values = [ plateNumber, plateColor, channelsAmount, connectionProtocol, IPAddress, deviceGroup, serverPort, sumNumber, simIMEI, simIMSI, simModule, transportType, transportFactory, transportStrength, transportEngine, transportStanina, transportFuel, transportCertificate, transportCategory, transportExpire, transportConsumption, transportProvince, transportCity, equipmentName, equipmentPassword, equipmentNumber, equipmentReleased, equipmentInstaller, equipmentInstalled, equipmentDescription, serialNumber, ]; const result = await client.query(query, values); const updatedRow = result.rows[0]; // console.log("Updated row:", updatedRow); res.send("Data updated successfully"); } catch (error) { console.error("Error updating data:", error); res.status(500).send("An error occurred while updating data"); } finally { client.release(); } }); app.post("/updatedriver", upload.single("upload-file"), async (req, res) => { const pool = new Pool({ user: DB_User, host: DB_Host, database: DB_Name, password: DB_Password, port: DB_Port, }); const client = await pool.connect(); var { driverName, driverSurname, driverCard, driverGender, driverLicense, driverPassport, driverPhone, driverEmail, driverTransport, driverDescription, driverID, } = req.body; try { // Вставка новой строки в таблицу drivers const query = ` UPDATE drivers SET name = $1, surname = $2, card = $3, gender = $4, license = $5, passport = $6, phone = $7, email = $8, transport = $9, description = $10 WHERE id = $11 RETURNING *; `; const values = [ driverName, driverSurname, driverCard, driverGender, driverLicense, driverPassport, driverPhone, driverEmail, driverTransport, driverDescription, driverID, ]; const result = await client.query(query, values); const newRow = result.rows[0]; // console.log("New driver added:", newRow); res.send("Data added successfully"); } catch (error) { console.error("Error adding data:", error); res.status(500).send("An error occurred while adding data"); } finally { client.release(); } }); app.post("/adddriver", upload.single("upload-file"), async (req, res) => { const pool = new Pool({ user: DB_User, host: DB_Host, database: DB_Name, password: DB_Password, port: DB_Port, }); const client = await pool.connect(); var { driverName, driverSurname, driverCard, driverGender, driverLicense, driverPassport, driverPhone, driverEmail, driverTransport, driverDescription, } = req.body; try { // Вставка новой строки в таблицу drivers const query = ` INSERT INTO drivers ( name, surname, card, gender, license, passport, phone, email, transport, description ) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10) RETURNING *; `; const values = [ driverName, driverSurname, driverCard, driverGender, driverLicense, driverPassport, driverPhone, driverEmail, driverTransport, driverDescription, ]; const result = await client.query(query, values); const newRow = result.rows[0]; // console.log("New driver added:", newRow); res.send("Data added successfully"); } catch (error) { console.error("Error adding data:", error); res.status(500).send("An error occurred while adding data"); } finally { client.release(); } }); app.post("/driverdata", async (req, res) => { const id = req.body.id; const pool = new Pool({ user: DB_User, host: DB_Host, database: DB_Name, password: DB_Password, port: DB_Port, }); const client = await pool.connect(); try { // Выполняем запрос и получаем результат const query = "SELECT * FROM drivers WHERE id = $1;"; const driverdata = await client.query(query, [id]); // Формирование и отправка ответа const response = driverdata.rows[0]; res.json(response); } finally { client.release(); } }); app.post("/deletedriver", async (req, res) => { const id = req.body.id; const pool = new Pool({ user: DB_User, host: DB_Host, database: DB_Name, password: DB_Password, port: DB_Port, }); const client = await pool.connect(); try { // Выполняем запрос и получаем результат const query = "DELETE FROM drivers WHERE id = $1;"; const driverdata = await client.query(query, [id]); // Формирование и отправка ответа res.send("Data deleted successfully"); } finally { client.release(); } }); async function drivers(req, res) { let templateData = { Organisation: "Название организации", User: "Тестовое Имя", ifDBError: false, Drivers: [], Registrars: [], }; try { const pool = new Pool({ user: DB_User, host: DB_Host, database: DB_Name, password: DB_Password, port: DB_Port, }); const client = await pool.connect(); // Выполняем запрос для объединения данных из таблиц drivers и registrars const queryDrivers = ` SELECT d.id, d.name, d.surname, d.transport, d.phone, d.email, d.card, r.connected FROM drivers d LEFT JOIN registrars r ON d.transport = r.serial ORDER BY r.connected DESC NULLS LAST, CASE WHEN r.connected = true THEN 0 ELSE 1 END, d.id `; const driversResult = await client.query(queryDrivers); templateData.Drivers = driversResult.rows.map((driver) => ({ id: driver.id, name: driver.name, surname: driver.surname, transport: driver.transport, phone: driver.phone, email: driver.email, card: driver.card, })); const queryRegistrars = ` SELECT serial FROM registrars `; const registrarsResult = await client.query(queryRegistrars); templateData.Registrars = registrarsResult.rows.map( (registrar) => registrar.serial ); // console.log(templateData); const source = fs.readFileSync( "static/templates/devices/drivers.html", "utf8" ); const template = handlebars.compile(source); const resultT = template(templateData); res.send(resultT); client.release(); } catch (error) { console.error(error); templateData.ifDBError = true; const source = fs.readFileSync( "static/templates/devices/drivers.html", "utf8" ); const template = handlebars.compile(source); const resultT = template(templateData); res.send(resultT); } } function update(req, res) { res.sendFile(path.join(__dirname, "static/templates/devices/update.html")); } function settings(req, res) { res.sendFile(path.join(__dirname, "static/templates/settings.html")); } async function videos(req, res) { let templateData = { Organisation: "Название организации", User: "Тестовое Имя", ifDBError: false, Registrars: [], Groups: [], }; try { const pool = new Pool({ user: DB_User, host: DB_Host, database: DB_Name, password: DB_Password, port: DB_Port, }); const client = await pool.connect(); const minuteInMillis = 60 * 1000; const query = ` SELECT id, serial, lastkeepalive FROM registrars ORDER BY id ASC `; const registrars = await client.query(query); templateData.Registrars = registrars.rows.map((row) => ({ id: row.id, serial: row.serial, status: Date.now() - Date.parse(row.lastkeepalive) <= minuteInMillis, })); // Выполняем запрос, чтобы получить все данные из таблицы registrars const queryRegistrars = ` SELECT id, serial, channels, lastkeepalive, name, "group", plate, sim, ip, port FROM registrars ORDER BY id `; const registrarsResult = await client.query(queryRegistrars); const allRegistrars = registrarsResult.rows; // Группируем устройства по группам и сохраняем данные каждого устройства const groupedRegistrars = {}; allRegistrars.forEach((registrar) => { if (!groupedRegistrars[registrar.group]) { groupedRegistrars[registrar.group] = []; } groupedRegistrars[registrar.group].push({ id: registrar.id, serial: registrar.serial, channels: registrar.channels, status: Date.now() - Date.parse(registrar.lastkeepalive) <= minuteInMillis, name: registrar.name, group: registrar.group, plate: registrar.plate, sim: registrar.sim, ip: registrar.ip, port: registrar.port, }); }); // Заполняем массив групп данными устройств в каждой группе for (const groupName in groupedRegistrars) { templateData.Groups.push({ name: groupName, devices: groupedRegistrars[groupName], }); } // console.log(templateData); const source = fs.readFileSync("static/templates/videos/playback.html", "utf8"); const template = handlebars.compile(source); const resultHTML = template(templateData); res.send(resultHTML); client.release(); } catch (error) { console.error(error); templateData.ifDBError = true; const source = fs.readFileSync("static/templates/videos/playback.html", "utf8"); const template = handlebars.compile(source); const resultT = template(templateData); res.send(resultT); } } async function videoExport(req, res) { let templateData = { Organisation: "Название организации", User: "Тестовое Имя", ifDBError: false, Registrars: [], Groups: [], }; try { const pool = new Pool({ user: DB_User, host: DB_Host, database: DB_Name, password: DB_Password, port: DB_Port, }); const client = await pool.connect(); const minuteInMillis = 60 * 1000; const query = ` SELECT id, serial, lastkeepalive FROM registrars ORDER BY id ASC `; const registrars = await client.query(query); templateData.Registrars = registrars.rows.map((row) => ({ id: row.id, serial: row.serial, status: Date.now() - Date.parse(row.lastkeepalive) <= minuteInMillis, })); // Выполняем запрос, чтобы получить все данные из таблицы registrars const queryRegistrars = ` SELECT id, serial, channels, lastkeepalive, name, "group", plate, sim, ip, port FROM registrars ORDER BY id `; const registrarsResult = await client.query(queryRegistrars); const allRegistrars = registrarsResult.rows; // Группируем устройства по группам и сохраняем данные каждого устройства const groupedRegistrars = {}; allRegistrars.forEach((registrar) => { if (!groupedRegistrars[registrar.group]) { groupedRegistrars[registrar.group] = []; } groupedRegistrars[registrar.group].push({ id: registrar.id, serial: registrar.serial, channels: registrar.channels, status: Date.now() - Date.parse(registrar.lastkeepalive) <= minuteInMillis, name: registrar.name, group: registrar.group, plate: registrar.plate, sim: registrar.sim, ip: registrar.ip, port: registrar.port, }); }); // Заполняем массив групп данными устройств в каждой группе for (const groupName in groupedRegistrars) { templateData.Groups.push({ name: groupName, devices: groupedRegistrars[groupName], }); } // console.log(templateData); const source = fs.readFileSync("static/templates/videos/export.html", "utf8"); const template = handlebars.compile(source); const resultHTML = template(templateData); res.send(resultHTML); client.release(); } catch (error) { console.error(error); templateData.ifDBError = true; const source = fs.readFileSync("static/templates/videos/export.html", "utf8"); const template = handlebars.compile(source); const resultT = template(templateData); res.send(resultT); } } app.get('/getData', async (req, res) => { const selectedSerial = req.query.serial; const selectedDate = req.query.selectedDate; const selectedTime = req.query.selectedTime; const selectedChannel = req.query.selectedChannel; try { const successResponse = await axios.get(`http://krbl.ru:8080/http/filelist/request?serial=${selectedSerial}&querytime=${selectedDate}&channel=${selectedChannel}`); if (successResponse.data.SUCCESS) { await new Promise(resolve => setTimeout(resolve, 5000)); const dataResponse = await axios.get(`http://krbl.ru:8080/http/filelist/get?serial=${selectedSerial}&querytime=${selectedDate}&channel=${selectedChannel}`); if (successResponse.data.SUCCESS) { const dataId = dataResponse.data.DATAID; const dateRanges = dataResponse.data.DATA; let dataFound = false; let selectedDataId = null; const selectedDateTime = moment(selectedDate + selectedTime, 'YYYYMMDDHHmmss').valueOf(); if (dateRanges.length === 1) { // Если в массиве DATA только одно значение const [startRange, endRange] = dateRanges[0].split('-'); const startDateTime = moment(startRange, 'YYYYMMDDHHmmss').valueOf(); const endDateTime = moment(endRange, 'YYYYMMDDHHmmss').valueOf(); if (!isNaN(selectedDateTime) && !isNaN(startDateTime) && !isNaN(endDateTime)) { if (selectedDateTime >= startDateTime && selectedDateTime <= endDateTime) { dataFound = true; selectedDataId = dataId[0]; } } else { console.error("Неверный формат данных для сравнения."); } } else { // Если в массиве DATA больше одного значения for (let i = 0; i < dateRanges.length; i++) { const [startRange, endRange] = dateRanges[i].split('-'); const startDateTime = moment(startRange, 'YYYYMMDDHHmmss').valueOf(); const endDateTime = moment(endRange, 'YYYYMMDDHHmmss').valueOf(); if (!isNaN(selectedDateTime) && !isNaN(startDateTime) && !isNaN(endDateTime)) { if (selectedDateTime >= startDateTime && selectedDateTime <= endDateTime) { dataFound = true; selectedDataId = dataId[i]; break; } } else { console.error("Неверный формат данных для сравнения."); } } } if (dataFound) { // Здесь можно отправить запрос скоростей и отрисовать график res.json({ success: true, dataId: selectedDataId }); } else { res.json({ success: false, message: 'Данных для выбранного периода нет' }); } }} else { res.json({ success: false, message: 'Ошибка при получении данных' }); } } catch (error) { console.error('Ошибка при отправке запроса:', error); res.json({ success: false, message: 'Ошибка при отправке запроса' }); } }); app.post("/getspeedarchive", async (req, res) => { const { serial, datetime } = req.body; const formattedDateTime = new Date(datetime).toISOString(); const pool = new Pool({ user: DB_User, host: DB_Host, database: DB_Name, password: DB_Password, port: DB_Port, }); const client = await pool.connect(); const sqlQuery = ` SELECT speed, latitude, longitude FROM geo WHERE serial = $1 AND time >= $2 AND time < $3; `; const startTime = new Date(formattedDateTime); startTime.setMinutes(0, 0, 0); // Округление до начала часа const endTime = new Date(startTime); endTime.setHours(endTime.getHours() + 1); pool.query(sqlQuery, [serial, startTime, endTime], (error, results) => { if (error) { console.error("Ошибка при выполнении SQL-запроса:", error); res.status(500).json({ error: "Ошибка на сервере" }); } else { const speeds = results.rows.map((row) => row.speed); const transformedSpeeds = speeds.map((speed) => { if (speed > 150) { return speed / 100; } else { return speed; } }); const geoData = results.rows.map((row) => ({ latitude: row.latitude, longitude: row.longitude, })); res.json({ speeds: transformedSpeeds, geo: geoData }); } }); }); const port = 8081; app.listen(port, () => { console.log(`Server is running on port ${port}`); }); // app.use((req, res, next) => { // res.sendFile(path.join(__dirname, "static/templates/404.html")); // });