diff --git a/server.js b/server.js
index 9ad32d5..b12ac3f 100644
--- a/server.js
+++ b/server.js
@@ -30,6 +30,7 @@ app.get("/reports", reports);
app.get("/devices", devices);
app.get("/devices/drivers", drivers);
app.get("/devices/update", update);
+app.get("/videos", videos);
// const DB_User = process.env.DB_USER;
@@ -64,7 +65,7 @@ async function index(req, res) {
port: DB_Port,
});
const client = await pool.connect();
- // Выполняем запрос и получаем результат
+
const query = `
SELECT COUNT(*) AS count
FROM registrars
@@ -74,40 +75,40 @@ async function index(req, res) {
templateData.Count = registrars.rows[0].count;
const last11DaysQuery = `
- SELECT COUNT(DISTINCT evtuuid) AS count
- FROM alarms
- WHERE time >= NOW() - INTERVAL '10 days' + INTERVAL '3 hours'
- AND time <= NOW() + INTERVAL '1 day' + INTERVAL '3 hours'
- AND st IS NOT NULL
- GROUP BY DATE_TRUNC('day', time)
- ORDER BY DATE_TRUNC('day', time)
+ WITH date_sequence AS (
+ SELECT DATE_TRUNC('day', NOW() - INTERVAL '10 days') + (generate_series(0, 10) || ' days')::interval AS day
+ )
+ SELECT
+ date_sequence.day AS day,
+ COALESCE(COUNT(DISTINCT evtuuid), 0) AS count
+ FROM date_sequence
+ LEFT JOIN alarms ON DATE_TRUNC('day', alarms.time) = date_sequence.day
+ AND alarms.time >= NOW() - INTERVAL '10 days' + INTERVAL '3 hours'
+ AND alarms.time <= NOW() + INTERVAL '1 day' + INTERVAL '3 hours'
+ AND alarms.st IS NOT NULL
+ GROUP BY date_sequence.day
+ ORDER BY date_sequence.day DESC
`;
const last11DaysAlarms = await client.query(last11DaysQuery);
- const last11DaysCounts = last11DaysAlarms.rows.map(row => parseInt(row.count, 10));
- for (let i = 0; i < last11DaysCounts.length; i++) {
- if (last11DaysCounts[i] > 0) {
- templateData.AlarmsLast11Days[i] = last11DaysCounts[i];
- }
- }
const daysBeforeQuery = `
- SELECT COUNT(DISTINCT evtuuid) AS count
- FROM alarms
- WHERE time >= NOW() - INTERVAL '21 days' + INTERVAL '3 hours'
- AND time <= NOW() - INTERVAL '10 days' + INTERVAL '3 hours'
- AND st IS NOT NULL
- GROUP BY DATE_TRUNC('day', time)
- ORDER BY DATE_TRUNC('day', time)
- `;
- const daysBeforeAlarms = await client.query(daysBeforeQuery);
- const daysBeforeCounts = daysBeforeAlarms.rows.map(row => parseInt(row.count, 10));
- for (let i = 0; i < daysBeforeCounts.length; i++) {
- if (daysBeforeCounts[i] > 0) {
- templateData.Alarms11DaysBefore[i] = daysBeforeCounts[i];
- }
- }
+ WITH date_sequence AS (
+ SELECT DATE_TRUNC('day', NOW() - INTERVAL '21 days') + (generate_series(0, 10) || ' days')::interval AS day
+ )
+ SELECT
+ date_sequence.day AS day,
+ COALESCE(COUNT(DISTINCT evtuuid), 0) AS count
+ FROM date_sequence
+ LEFT JOIN alarms ON DATE_TRUNC('day', alarms.time) = date_sequence.day
+ AND alarms.time >= NOW() - INTERVAL '21 days' + INTERVAL '3 hours'
+ AND alarms.time <= NOW() - INTERVAL '10 days' + INTERVAL '3 hours'
+ AND alarms.st IS NOT NULL
+ GROUP BY date_sequence.day
+ ORDER BY date_sequence.day DESC
+`;
+
+ const daysBeforeAlarms = await client.query(daysBeforeQuery);
- // Создание массива дат в формате "dd.mm" за последние 11 дней
const currentDate = new Date();
const dates = [];
for (let i = 10; i >= 0; i--) {
@@ -118,22 +119,50 @@ async function index(req, res) {
templateData.Dates = dates;
const positionsLast11DaysQuery = `
- SELECT COUNT(*) AS count
- 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 DATE_TRUNC('day', time)
+ 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);
- const positionsLast11DaysCounts = positionsLast11Days.rows.map(row => parseInt(row.count, 10));
- for (let i = 0; i < positionsLast11DaysCounts.length; i++) {
- if (positionsLast11DaysCounts[i] > 0) {
- templateData.PositionsLast11Days[i] = positionsLast11DaysCounts[i];
- }
- }
+ // console.log(positionsLast11Days.rows)
+ // const positionsLast11DaysCounts = positionsLast11Days.rows.map(row => parseInt(row.count, 10));
+ // for (let i = 0; i < positionsLast11DaysCounts.length; i++) {
+ // if (positionsLast11DaysCounts[i] > 0) {
+ // templateData.PositionsLast11Days[i] = positionsLast11DaysCounts[i];
+ // }
+ // }
- // console.log(templateData);
+ 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 = dates[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");
@@ -1437,6 +1466,105 @@ function update(req, res) {
res.sendFile(path.join(__dirname, "static/templates/devices/update.html"));
}
+async function videos(req, res) {
+ let templateData = {
+ Organisation: "Название организации",
+ User: "Тестовое Имя",
+ ifDBError: false,
+ 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();
+
+ 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,
+ }));
+
+ console.log(templateData);
+
+ const source = fs.readFileSync("static/templates/videos/see.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/see.html", "utf8");
+ const template = handlebars.compile(source);
+ const resultT = template(templateData);
+ res.send(resultT);
+ }
+}
+
+
+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}`);
diff --git a/static/img/left.svg b/static/img/left.svg
new file mode 100644
index 0000000..a9bd69f
--- /dev/null
+++ b/static/img/left.svg
@@ -0,0 +1,11 @@
+
+
+
+
diff --git a/static/img/play-circle.svg b/static/img/play-circle.svg
new file mode 100644
index 0000000..bd9f83b
--- /dev/null
+++ b/static/img/play-circle.svg
@@ -0,0 +1,11 @@
+
+
+
+
diff --git a/static/img/right.svg b/static/img/right.svg
new file mode 100644
index 0000000..1f1ffb9
--- /dev/null
+++ b/static/img/right.svg
@@ -0,0 +1,11 @@
+
+
+
+
diff --git a/static/styles/main.css b/static/styles/main.css
index e8dd9ed..52f8b41 100644
--- a/static/styles/main.css
+++ b/static/styles/main.css
@@ -478,6 +478,7 @@ header h2 span {
border: 2px solid rgba(245, 245, 250, 1);
border-radius: 30px;
overflow: hidden;
+ position: relative;
}
.table h1 {
@@ -1401,17 +1402,6 @@ input[type="datetime-local"] {
padding: 12px 33px 26px 33px;
}
-.video-container {
- position: relative;
- width: 500px;
-}
-
-video {
- width: 450px;
- display: inline-block;
- border-right: 2px solid rgba(245, 245, 250, 1);
-}
-
.controls {
width: 100%;
display: flex;
@@ -1476,7 +1466,7 @@ video {
transition: 1s;
position: relative;
float: left;
- z-index: 10;
+ z-index: 3;
}
.signals-list.hide {
@@ -1612,12 +1602,24 @@ video {
background: rgba(0, 0, 0, 0.10);
}
-.map {
+.stream-map {
/* background-color: #ff443a2f; */
width: 100%;
height: 100%;
display: block;
- /* float: right; */
+ position: absolute;
+ bottom: 0;
+ right: 0;
+
+}
+
+.map {
+ height: 310px;
+ width: 25%;
+ display: block;
+ position: absolute;
+ bottom: 0;
+ left: 0;
}
#map {
@@ -1631,7 +1633,7 @@ video {
position: absolute;
top: 10px;
left: 50%;
- z-index: 10;
+ z-index: 3;
background-color: white;
padding: 9px 11px;
border-radius: 10px;
@@ -1674,7 +1676,7 @@ video {
font-weight: bold;
}
-.cameras {
+.stream-cameras {
background: #F5F5FA;
position: absolute;
bottom: 0;
@@ -1682,6 +1684,16 @@ video {
width: calc(100% - 345px - 2px);
z-index: 3;
}
+.cameras {
+ background: #F5F5FA;
+ position: absolute;
+ top: 0;
+ right: 0;
+ width: 100%;
+ height: calc(100% - 310px);
+ z-index: 3;
+ display: inline-flex;
+}
.cameras-swipe {
display: flex;
@@ -1700,13 +1712,13 @@ video {
background: #dcdce2;
}
-.video-container {
+.stream-video-container {
display: flex;
flex-wrap: wrap;
width: 100%;
}
-.video-container video {
+.stream-video-container video {
height: auto;
box-sizing: border-box;
width: 25%;
@@ -1714,6 +1726,79 @@ video {
border: 1px solid white;
}
+.video-container {
+ display: flex;
+ flex-wrap: wrap;
+ width: 80%;
+ float: left;
+}
+
+.video-container div {
+ height: auto;
+ box-sizing: border-box;
+ width: 25%;
+ padding: 0;
+ height: 33.4%;
+ border: 1px solid white;
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ text-align: center;
+ justify-content: center;
+}
+
+.video-container div,
+.video-container-right div {
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ text-align: center;
+ justify-content: center;
+ cursor: pointer;
+ transition: 0.1s;
+}
+
+.video-container div:hover,
+.video-container-right div:hover {
+ background-color: rgba(0, 0, 0, 0.1);
+}
+
+.video-container div img,
+.video-container-right div img {
+ height: 32px;
+ width: 32px;
+}
+
+.video-container div span,
+.video-container-right div span {
+ font-size: 16px;
+ font-weight: 500;
+ margin-top: 15px;
+}
+
+.video-container-right {
+ display: flex;
+ flex-wrap: wrap;
+ float: right;
+ width: 20%;
+ float: right;
+}
+
+.video-container-right div {
+ height: auto;
+ box-sizing: border-box;
+ width: 100%;
+ height: 25%;
+ padding: 0;
+ border: 1px solid white;
+
+}
+
+.report video {
+ width: 450px;
+ border-right: 2px solid rgba(245, 245, 250, 1);
+}
+
.edit-container {
position: fixed;
@@ -1806,6 +1891,154 @@ video {
margin-bottom: 0;
}
+/* Стили для вашего календаря */
+.calendar {
+ width: 250px;
+ height: 300px;
+ margin: 5px;
+ padding: 0 25px;
+ border-radius: 15px;
+ overflow: hidden;
+ position: absolute;
+ bottom: 0;
+ left: 25%;
+ background-color:white;
+ margin: 4px;
+}
+
+.calendar-header {
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ /* background-color: #f0f0f0; */
+ padding: 20px 5px;
+ border-bottom: 1px solid #ccc;
+}
+
+.calendar-header h2 {
+ font-size: 16px;
+ font-weight: 600;
+ margin: 0;
+ color: rgba(0, 0, 0, 0.90);
+}
+
+#prevMonth {
+ background-image: url(../img/left.svg);
+}
+
+#nextMonth {
+ background-image: url(../img/right.svg);
+}
+
+#prevMonth, #nextMonth {
+ background-color: transparent;
+ border: none;
+ height: 18px;
+ cursor: pointer;
+ background-repeat: no-repeat;
+}
+
+.daysOfWeek {
+ display: flex;
+ justify-content: space-around;
+ padding: 15px 0;
+ font-size: 14px;
+ color: #7E818C;
+}
+
+.dates {
+ display: grid;
+ grid-template-columns: repeat(7, 1fr);
+ gap: 10px;
+}
+
+.date {
+ width: 26px;
+ height: 26px;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ cursor: pointer;
+ border-radius: 50%;
+ font-size: 14px;
+ color: rgba(0, 0, 0, 0.75);
+}
+
+.date:hover {
+ background-color: #8086F939;
+}
+
+.date.selected {
+ background-color: #8086F9;
+ color: white;
+}
+
+.video-popup {
+ display: none;
+ position: fixed;
+ top: 0;
+ left: 0;
+ width: 100%;
+ height: 100%;
+ /* background-color: rgba(0, 0, 0, 0.6); */
+ z-index: 999;
+}
+
+.video-popup-content {
+ position: absolute;
+ /* top: 10%; */
+ /* left: 50%; */
+ transform: translate(-50%, -50%);
+ /* background-color: white; */
+ padding: 20px;
+ /* box-shadow: 0 0 10px rgba(0, 0, 0, 0.3); */
+ text-align: center;
+ z-index: 101;
+}
+
+.close-popup {
+ position: absolute;
+ top: 10px;
+ right: 10px;
+ font-size: 24px;
+ cursor: pointer;
+}
+
+.popup-video-container {
+ width: 80%;
+ max-width: 800px;
+ margin: 0 auto;
+}
+
+.video-time {
+ width: calc(75% - 300px - 10px - 5px);
+ height: 50px;
+ position: absolute;
+ right: 7px;
+ bottom: 7px;
+ background-color:white;
+ border-radius: 15px;
+}
+input[type="time"]::-webkit-calendar-picker-indicator {
+ display: none;
+}
+
+.video-time input[type="time"] {
+ padding: 6px;
+ border-radius: 10px;
+ border: 1px solid rgba(0, 0, 0, 0.10);
+ background: #FFF;
+ font-size: 16px;
+ outline: none;
+ cursor: text;
+ margin: 8px !important;
+}
+
+input[type="time"]:hover,
+input[type="time"]:focus {
+ border: 1px solid rgba(0, 0, 0, 0.30);
+}
+
@media (max-width: 1950px) {
/* при разрешении монитора до 1950 пикселей */
diff --git a/static/templates/devices/drivers.html b/static/templates/devices/drivers.html
index 4bafed0..2e30eb1 100644
--- a/static/templates/devices/drivers.html
+++ b/static/templates/devices/drivers.html
@@ -40,7 +40,7 @@