settings, more live videos, graphs
This commit is contained in:
parent
f6557a5950
commit
ba525600ed
821
package-lock.json
generated
821
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@ -19,6 +19,7 @@
|
||||
"multer": "^1.4.5-lts.1",
|
||||
"path": "^0.12.7",
|
||||
"pg": "^8.11.1",
|
||||
"ssh2": "^1.14.0",
|
||||
"ws": "^8.13.0"
|
||||
}
|
||||
}
|
||||
|
147
server.js
147
server.js
@ -7,6 +7,8 @@ const handlebars = require("handlebars");
|
||||
require("dotenv").config();
|
||||
const multer = require("multer");
|
||||
const http = require("http");
|
||||
const Client = require('ssh2').Client;
|
||||
|
||||
|
||||
const storage = multer.diskStorage({
|
||||
destination: function (req, file, cb) {
|
||||
@ -31,6 +33,69 @@ 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;
|
||||
@ -83,7 +148,7 @@ async function index(req, res) {
|
||||
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 '11 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
|
||||
@ -104,19 +169,25 @@ async function index(req, res) {
|
||||
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
|
||||
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
|
||||
@ -130,13 +201,6 @@ async function index(req, res) {
|
||||
ORDER BY sort_value DESC, day DESC
|
||||
`;
|
||||
const positionsLast11Days = await client.query(positionsLast11DaysQuery);
|
||||
// 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];
|
||||
// }
|
||||
// }
|
||||
|
||||
templateData.Dates.reverse();
|
||||
|
||||
@ -150,7 +214,7 @@ for (let i = 0; i < dates.length; i++) {
|
||||
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];
|
||||
const dateKey = dates11DaysAgo[i];
|
||||
templateData.Alarms11DaysBefore[i] = beforeDaysMap.has(dateKey) ? beforeDaysMap.get(dateKey) : 0;
|
||||
}
|
||||
|
||||
@ -211,13 +275,14 @@ async function live(req, res) {
|
||||
const minuteInMillis = 90 * 1000;
|
||||
|
||||
const query = `
|
||||
SELECT id, serial, lastkeepalive FROM registrars ORDER BY id ASC
|
||||
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,
|
||||
}));
|
||||
|
||||
@ -444,7 +509,7 @@ pool.query(subquery, selectedDevices, (err, result) => {
|
||||
};
|
||||
});
|
||||
|
||||
console.log(devicesData)
|
||||
// console.log(devicesData)
|
||||
|
||||
res.json({ devicesData });
|
||||
});
|
||||
@ -1466,6 +1531,10 @@ 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: "Название организации",
|
||||
@ -1499,7 +1568,7 @@ async function videos(req, res) {
|
||||
|
||||
console.log(templateData);
|
||||
|
||||
const source = fs.readFileSync("static/templates/videos/see.html", "utf8");
|
||||
const source = fs.readFileSync("static/templates/videos/playback.html", "utf8");
|
||||
const template = handlebars.compile(source);
|
||||
const resultHTML = template(templateData);
|
||||
res.send(resultHTML);
|
||||
@ -1509,7 +1578,57 @@ async function videos(req, res) {
|
||||
console.error(error);
|
||||
templateData.ifDBError = true;
|
||||
|
||||
const source = fs.readFileSync("static/templates/videos/see.html", "utf8");
|
||||
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: [],
|
||||
};
|
||||
|
||||
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/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);
|
||||
|
@ -311,6 +311,106 @@ header h2 span {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.whole-width {
|
||||
width: 100%;
|
||||
background-color: white;
|
||||
border: 2px solid rgba(245, 245, 250, 1);
|
||||
border-radius: 30px;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.whole-width h1 {
|
||||
font-weight: 500;
|
||||
font-size: 30px;
|
||||
margin: 35px 0 0 44px;
|
||||
}
|
||||
|
||||
.whole-width h3 {
|
||||
font-weight: 400;
|
||||
opacity: 50%;
|
||||
font-size: 16px;
|
||||
margin: 10px 0 0 44px;
|
||||
}
|
||||
|
||||
.whole-width button {
|
||||
margin: 30px 0 35px 44px;
|
||||
}
|
||||
|
||||
@keyframes loader_5191 {
|
||||
from {
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
to {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
||||
.loader {
|
||||
margin-bottom: 25px;
|
||||
}
|
||||
|
||||
.square {
|
||||
background: #8086F9;
|
||||
width: 10px;
|
||||
height: 10px;
|
||||
position: absolute;
|
||||
/* top: 50%; */
|
||||
/* left: 50%; */
|
||||
margin-top: -5px;
|
||||
margin-left: -5px;
|
||||
margin-bottom: 25px;
|
||||
}
|
||||
|
||||
#sq1 {
|
||||
margin-top: -25px;
|
||||
margin-left: -25px;
|
||||
animation: loader_5191 675ms ease-in-out 0s infinite alternate;
|
||||
}
|
||||
|
||||
#sq2 {
|
||||
margin-top: -25px;
|
||||
animation: loader_5191 675ms ease-in-out 75ms infinite alternate;
|
||||
}
|
||||
|
||||
#sq3 {
|
||||
margin-top: -25px;
|
||||
margin-left: 15px;
|
||||
animation: loader_5191 675ms ease-in-out 150ms infinite;
|
||||
}
|
||||
|
||||
#sq4 {
|
||||
margin-left: -25px;
|
||||
animation: loader_5191 675ms ease-in-out 225ms infinite;
|
||||
}
|
||||
|
||||
#sq5 {
|
||||
animation: loader_5191 675ms ease-in-out 300ms infinite;
|
||||
}
|
||||
|
||||
#sq6 {
|
||||
margin-left: 15px;
|
||||
animation: loader_5191 675ms ease-in-out 375ms infinite;
|
||||
}
|
||||
|
||||
#sq7 {
|
||||
margin-top: 15px;
|
||||
margin-left: -25px;
|
||||
animation: loader_5191 675ms ease-in-out 450ms infinite;
|
||||
}
|
||||
|
||||
#sq8 {
|
||||
margin-top: 15px;
|
||||
animation: loader_5191 675ms ease-in-out 525ms infinite;
|
||||
}
|
||||
|
||||
#sq9 {
|
||||
margin-top: 15px;
|
||||
margin-left: 15px;
|
||||
animation: loader_5191 675ms ease-in-out 600ms infinite;
|
||||
}
|
||||
|
||||
|
||||
.content .organisation {
|
||||
width: 250px;
|
||||
display: inline-block;
|
||||
@ -1013,7 +1113,8 @@ tr:nth-child(even) {
|
||||
}
|
||||
|
||||
.new-parameters button,
|
||||
.update-info button {
|
||||
.update-info button,
|
||||
.button-purple {
|
||||
padding: 10px 16px;
|
||||
font-weight: 500;
|
||||
font-size: 16px;
|
||||
@ -1023,19 +1124,24 @@ tr:nth-child(even) {
|
||||
background: #8086f9;
|
||||
border: 1px solid rgba(0, 0, 0, 0.1);
|
||||
border-radius: 5px;
|
||||
float: right;
|
||||
margin-bottom: 50px;
|
||||
cursor: pointer;
|
||||
transition: 0.2s;
|
||||
}
|
||||
|
||||
.new-parameters button,
|
||||
.update-info button {
|
||||
float: right;
|
||||
margin-bottom: 50px;
|
||||
}
|
||||
|
||||
.update-info button {
|
||||
width: 100%;
|
||||
margin-top: 5px;
|
||||
}
|
||||
|
||||
.new-parameters button:hover,
|
||||
.update-info button:hover {
|
||||
.update-info button:hover,
|
||||
.button-purple:hover {
|
||||
filter: brightness(0.9);
|
||||
}
|
||||
|
||||
@ -1633,7 +1739,7 @@ input[type="datetime-local"] {
|
||||
position: absolute;
|
||||
top: 10px;
|
||||
left: 50%;
|
||||
z-index: 3;
|
||||
z-index: 999;
|
||||
background-color: white;
|
||||
padding: 9px 11px;
|
||||
border-radius: 10px;
|
||||
@ -1694,6 +1800,18 @@ input[type="datetime-local"] {
|
||||
width: calc(100% - 345px - 2px);
|
||||
z-index: 3;
|
||||
}
|
||||
|
||||
.stream-cameras button {
|
||||
font-family: "Nunito";
|
||||
background-color: white;
|
||||
border: 2px solid rgb(201, 201, 206);
|
||||
padding: 5px;
|
||||
border-radius: 5px;
|
||||
cursor: pointer;
|
||||
}
|
||||
.stream-cameras button:hover {
|
||||
filter: brightness(0.9);
|
||||
}
|
||||
.cameras {
|
||||
background: #F5F5FA;
|
||||
position: absolute;
|
||||
@ -1727,6 +1845,16 @@ input[type="datetime-local"] {
|
||||
flex-wrap: wrap;
|
||||
width: 100%;
|
||||
}
|
||||
/* Скрыть все видео кроме первой группы */
|
||||
.video-group {
|
||||
display: none;
|
||||
}
|
||||
|
||||
/* Отобразить активную группу */
|
||||
.video-group.active {
|
||||
display: block;
|
||||
}
|
||||
|
||||
|
||||
.stream-video-container video {
|
||||
height: auto;
|
||||
|
@ -43,7 +43,7 @@
|
||||
<a href="/videos">
|
||||
<div><img src="../img/play.svg">Записи</div>
|
||||
</a>
|
||||
<a class="settings" href="/">
|
||||
<a class="settings" href="/settings">
|
||||
<div><img src="../img/gear.svg">Настройки</div>
|
||||
</a>
|
||||
</section>
|
||||
@ -397,7 +397,6 @@
|
||||
|
||||
|
||||
|
||||
</section>
|
||||
</section>
|
||||
<section style="display: none;" class="dberror" id="deleteConfirmation">
|
||||
<div class="erorr-container">
|
||||
|
@ -43,7 +43,7 @@
|
||||
<a href="/videos">
|
||||
<div><img src="../img/play.svg">Записи</div>
|
||||
</a>
|
||||
<a class="settings" href="/">
|
||||
<a class="settings" href="/settings">
|
||||
<div><img src="../img/gear.svg">Настройки</div>
|
||||
</a>
|
||||
</section>
|
||||
|
@ -43,7 +43,7 @@
|
||||
<a href="/videos">
|
||||
<div><img src="../img/play.svg">Записи</div>
|
||||
</a>
|
||||
<a class="settings" href="/">
|
||||
<a class="settings" href="/settings">
|
||||
<div><img src="../img/gear.svg">Настройки</div>
|
||||
</a>
|
||||
</section>
|
||||
|
@ -45,7 +45,7 @@
|
||||
<a href="/videos">
|
||||
<div><img src="../img/play.svg">Записи</div>
|
||||
</a>
|
||||
<a class="settings" href="/">
|
||||
<a class="settings" href="/settings">
|
||||
<div><img src="../img/gear.svg">Настройки</div>
|
||||
</a>
|
||||
</section>
|
||||
|
@ -47,7 +47,7 @@
|
||||
<a href="/videos">
|
||||
<div><img src="../img/play.svg">Записи</div>
|
||||
</a>
|
||||
<a class="settings" href="/">
|
||||
<a class="settings" href="/settings">
|
||||
<div><img src="../img/gear.svg">Настройки</div>
|
||||
</a>
|
||||
</section>
|
||||
@ -86,6 +86,7 @@
|
||||
<label for="{{this.id}}" class="checkbox-label active-{{this.status}}">
|
||||
<div class="checkmark"></div>
|
||||
</label>
|
||||
<input type="number" id="channels-{{this.serial}}" value="{{this.channels}}" hidden>
|
||||
<input type="radio" name="camera-serial" id="radio-{{this.serial}}" class="radio-input" value="{{this.serial}}" hidden>
|
||||
<label for="radio-{{this.serial}}" class="radio-label active-{{this.status}}">
|
||||
{{this.serial}}
|
||||
@ -177,29 +178,14 @@
|
||||
</div>
|
||||
|
||||
<div class="stream-cameras">
|
||||
<div class="cameras-swipe"><svg xmlns="http://www.w3.org/2000/svg" width="17" height="11" fill="none" viewBox="0 0 17 11">
|
||||
<path fill="#000" fill-opacity=".5" d="M8.486 10.4a.88.88 0 0 0 .655-.283l7.558-7.744a.871.871 0 0 0 .264-.625.876.876 0 0 0-.889-.898.927.927 0 0 0-.635.254L7.96 8.75h1.045l-7.48-7.646A.892.892 0 0 0 .888.85.876.876 0 0 0 0 1.748a.91.91 0 0 0 .264.635l7.558 7.734c.186.186.41.283.664.283Z"/>
|
||||
</svg></div>
|
||||
<div class="stream-video-container">
|
||||
<!-- <video id="camera-1"></video> -->
|
||||
<video id="camera-1"></video>
|
||||
<video id="camera-2"></video>
|
||||
<video id="camera-3"></video>
|
||||
<video id="camera-4"></video>
|
||||
<video id="camera-5"></video>
|
||||
<!-- <video id="camera-6"></video>
|
||||
<video id="camera-7"></video>
|
||||
<video id="camera-8"></video>
|
||||
<video id="camera-9"></video>
|
||||
<video id="camera-10"></video>
|
||||
<video id="camera-11"></video>
|
||||
<video id="camera-12"></video>
|
||||
<video id="camera-13"></video>
|
||||
<video id="camera-14"></video>
|
||||
<video id="camera-15"></video>
|
||||
<video id="camera-16"></video> -->
|
||||
</div>
|
||||
|
||||
|
||||
<button onclick="switchCameras(-1)">Назад</button>
|
||||
<button onclick="switchCameras(1)">Вперед</button>
|
||||
</div>
|
||||
|
||||
|
||||
@ -214,69 +200,6 @@
|
||||
|
||||
<script src="../scripts/jquery.min.js"></script>
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/flv.js/1.5.0/flv.min.js"></script>
|
||||
<script>
|
||||
let flvPlayers = [];
|
||||
function stopAllCameras() {
|
||||
flvPlayers.forEach(player => {
|
||||
player.pause();
|
||||
player.unload();
|
||||
player.detachMediaElement();
|
||||
player.destroy();
|
||||
});
|
||||
flvPlayers = [];
|
||||
}
|
||||
|
||||
function stopAllCameras() {
|
||||
flvPlayers.forEach(player => {
|
||||
player.pause();
|
||||
player.unload();
|
||||
player.detachMediaElement();
|
||||
player.destroy();
|
||||
});
|
||||
flvPlayers = [];
|
||||
}
|
||||
function playflv() {
|
||||
if (!flvjs.isSupported()) {
|
||||
console.log("Browser does not support.");
|
||||
}
|
||||
|
||||
const cameras = 5;
|
||||
const baseURL = "http://45.9.42.252:8080/http/live.flv";
|
||||
let currentCamera = 2;
|
||||
|
||||
function playNextCamera(i) {
|
||||
console.log(flvPlayers)
|
||||
|
||||
const videoElement = document.getElementById(`camera-${i}`);
|
||||
const flvPlayer = flvjs.createPlayer({
|
||||
type: 'flv',
|
||||
isLive: true,
|
||||
cors: true,
|
||||
url: `${baseURL}?serial=${serial}&channel=${i}&quality=0`,
|
||||
}, {
|
||||
enableWorker: true,
|
||||
enableStashBuffer: false,
|
||||
autoCleanupSourceBuffer: true,
|
||||
});
|
||||
|
||||
flvPlayer.attachMediaElement(videoElement);
|
||||
flvPlayer.load();
|
||||
flvPlayer.play();
|
||||
|
||||
flvPlayers.push(flvPlayer);
|
||||
|
||||
setTimeout(() => {
|
||||
playNextCamera(i + 1);
|
||||
}, 100);
|
||||
}
|
||||
|
||||
playNextCamera(currentCamera);
|
||||
}
|
||||
|
||||
|
||||
|
||||
</script>
|
||||
|
||||
<script>
|
||||
var cameraSerials = document.querySelectorAll('.radio-input');
|
||||
|
||||
@ -285,7 +208,10 @@
|
||||
console.log($("input[name=camera-serial]:checked").val())
|
||||
serial = $("input[name=camera-serial]:checked").val()
|
||||
stopAllCameras();
|
||||
playflv();
|
||||
currentCameraGroup = 1
|
||||
totalCameras = $(`#channels-${serial}`).val() - 1;
|
||||
console.log(totalCameras);
|
||||
playNextCamerasInGroup();
|
||||
|
||||
var svgId = "marker-" + serial;
|
||||
var svgElement = document.getElementById(svgId);
|
||||
@ -298,6 +224,84 @@
|
||||
});
|
||||
});
|
||||
</script>
|
||||
<script>
|
||||
let flvPlayers = [];
|
||||
let currentCameraGroup = 1;
|
||||
const camerasPerGroup = 4;
|
||||
var totalCameras = 12 - 1;
|
||||
const baseURL = "http://45.9.42.252:8080/http/live.flv";
|
||||
|
||||
function stopAllCameras() {
|
||||
flvPlayers.forEach(player => {
|
||||
player.pause();
|
||||
player.unload();
|
||||
player.detachMediaElement();
|
||||
player.destroy();
|
||||
});
|
||||
flvPlayers = [];
|
||||
}
|
||||
|
||||
async function playNextCamerasInGroup() {
|
||||
const startCamera = (currentCameraGroup - 1) * camerasPerGroup + 2;
|
||||
const endCamera = startCamera + camerasPerGroup - 1;
|
||||
|
||||
if (startCamera > totalCameras) {
|
||||
// currentCameraGroup = 1;
|
||||
// playNextCamerasInGroup();
|
||||
return;
|
||||
}
|
||||
|
||||
stopAllCameras();
|
||||
|
||||
let videoElementIndex = 1;
|
||||
|
||||
for (let i = startCamera; i <= endCamera && i <= totalCameras; i++) {
|
||||
const videoElement = document.getElementById(`camera-${videoElementIndex}`);
|
||||
const flvPlayer = flvjs.createPlayer({
|
||||
type: 'flv',
|
||||
isLive: true,
|
||||
cors: true,
|
||||
url: `${baseURL}?serial=${serial}&channel=${i}&quality=1`,
|
||||
}, {
|
||||
enableWorker: true,
|
||||
enableStashBuffer: false,
|
||||
autoCleanupSourceBuffer: true,
|
||||
});
|
||||
|
||||
flvPlayer.attachMediaElement(videoElement);
|
||||
flvPlayer.load();
|
||||
flvPlayer.play();
|
||||
|
||||
flvPlayers.push(flvPlayer);
|
||||
|
||||
videoElementIndex++;
|
||||
|
||||
await new Promise(resolve => setTimeout(resolve, 100));
|
||||
}
|
||||
}
|
||||
|
||||
function switchCameras(direction) {
|
||||
if ((currentCameraGroup === 1 && direction === -1) || (currentCameraGroup === Math.ceil(totalCameras / camerasPerGroup) && direction === 1)) {
|
||||
// Если текущая группа - первая и переключаемся назад, или текущая группа - последняя и переключаемся вперед, не переключаемся
|
||||
return;
|
||||
}
|
||||
currentCameraGroup += direction;
|
||||
if (currentCameraGroup < 1) {
|
||||
currentCameraGroup = Math.ceil(totalCameras / camerasPerGroup);
|
||||
} else if (currentCameraGroup > Math.ceil(totalCameras / camerasPerGroup)) {
|
||||
currentCameraGroup = 1;
|
||||
}
|
||||
playNextCamerasInGroup();
|
||||
}
|
||||
|
||||
// Запустить воспроизведение видео при загрузке страницы
|
||||
playNextCamerasInGroup();
|
||||
</script>
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
<script>
|
||||
@ -346,7 +350,7 @@
|
||||
|
||||
<script>
|
||||
|
||||
var map = L.map('map').setView([59.855198, 30.282995], 10);
|
||||
var map = L.map('map').setView([59.855198, 30.282995], 12);
|
||||
|
||||
L.tileLayer('https://server.arcgisonline.com/ArcGIS/rest/services/World_Street_Map/MapServer/tile/{z}/{y}/{x}').addTo(map);
|
||||
|
||||
|
@ -43,7 +43,7 @@
|
||||
<a href="/videos">
|
||||
<div><img src="../img/play.svg">Записи</div>
|
||||
</a>
|
||||
<a class="settings" href="/">
|
||||
<a class="settings" href="/settings">
|
||||
<div><img src="../img/gear.svg">Настройки</div>
|
||||
</a>
|
||||
</section>
|
||||
|
@ -45,7 +45,7 @@
|
||||
<a href="/videos">
|
||||
<div><img src="../img/play.svg">Записи</div>
|
||||
</a>
|
||||
<a class="settings" href="/">
|
||||
<a class="settings" href="/settings">
|
||||
<div><img src="../img/gear.svg">Настройки</div>
|
||||
</a>
|
||||
</section>
|
||||
|
200
static/templates/settings.html
Normal file
200
static/templates/settings.html
Normal file
@ -0,0 +1,200 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Настройки</title>
|
||||
<link rel="stylesheet" href="../styles/main.css" />
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<header>
|
||||
<h1>Аргус</h1>
|
||||
<h2><span>/</span> Название организации</h2>
|
||||
</header>
|
||||
|
||||
<section class="account-info">
|
||||
<div id="account-main">
|
||||
<img id="person" src="../img/person.svg">
|
||||
<span>Тестовое Имя</span>
|
||||
<img id="down" src="../img/down.svg">
|
||||
<img id="up" src="../img/up.svg">
|
||||
</div>
|
||||
<a href="/login"><div id="account-additional" class="additional">Выйти</div></a>
|
||||
</section>
|
||||
|
||||
|
||||
|
||||
|
||||
<section class="navigation">
|
||||
<a href="/">
|
||||
<div><img src="../img/chart.svg">Главная</div>
|
||||
</a>
|
||||
<a href="/devices">
|
||||
<div><img src="../img/cloud.svg">Устройства</div>
|
||||
</a>
|
||||
<a href="/reports">
|
||||
<div><img src="../img/bubble.svg">Отчёты</div>
|
||||
</a>
|
||||
<a href="/live">
|
||||
<div><img src="../img/waves.svg">Трансляция</div>
|
||||
</a>
|
||||
<a href="/videos">
|
||||
<div><img src="../img/play.svg">Записи</div>
|
||||
</a>
|
||||
<a class="settings" href="/settings">
|
||||
<div class="selected"><img src="../img/gear.svg">Настройки</div>
|
||||
</a>
|
||||
</section>
|
||||
|
||||
<section class="main">
|
||||
<!-- {{#if ifDBError}}
|
||||
<section class="dberror">
|
||||
<div class="erorr-container">
|
||||
<img src="../img/warning.svg"> <br>
|
||||
<h1>Ошибка </h1> <br>
|
||||
<span>Не удалось получить данные из БД</span>
|
||||
<button type="button" onclick="location.reload();">Повторить попытку</button>
|
||||
</div>
|
||||
</section>
|
||||
{{/if}} -->
|
||||
<section style="display: none;" class="dberror" id="deleteConfirmation" >
|
||||
<div class="erorr-container">
|
||||
<div id="loader" class="loader">
|
||||
<div class="square" id="sq1"></div>
|
||||
<div class="square" id="sq2"></div>
|
||||
<div class="square" id="sq3"></div>
|
||||
<div class="square" id="sq4"></div>
|
||||
<div class="square" id="sq5"></div>
|
||||
<div class="square" id="sq6"></div>
|
||||
<div class="square" id="sq7"></div>
|
||||
<div class="square" id="sq8"></div>
|
||||
<div class="square" id="sq9"></div>
|
||||
</div>
|
||||
<svg id="success-mark" style="display: none;" xmlns="http://www.w3.org/2000/svg" width="108" height="108" fill="none" viewBox="0 0 108 108">
|
||||
<g clip-path="url(#a)">
|
||||
<path fill="#8086F9" fill-opacity=".85" d="M54 107.947c29.541 0 54-24.5 54-53.973C108 24.447 83.488 0 53.947 0 24.459 0 0 24.447 0 53.974c0 29.474 24.512 53.973 54 53.973Zm0-8.995c-24.988 0-44.947-20.002-44.947-44.978 0-24.976 19.906-44.978 44.894-44.978S99 28.998 99 53.974c0 24.976-20.012 44.978-45 44.978Zm-5.824-19.844c1.747 0 3.23-.846 4.289-2.487l24.194-38.046c.582-1.058 1.27-2.222 1.27-3.386 0-2.382-2.117-3.916-4.341-3.916-1.323 0-2.647.847-3.653 2.381l-21.97 35.241-10.43-13.493c-1.27-1.693-2.435-2.116-3.917-2.116-2.277 0-4.077 1.852-4.077 4.18 0 1.164.477 2.276 1.218 3.28l12.917 15.875c1.324 1.747 2.753 2.487 4.5 2.487Z"/>
|
||||
</g>
|
||||
<defs>
|
||||
<clipPath id="a">
|
||||
<path fill="#fff" d="M0 0h108v108H0z"/>
|
||||
</clipPath>
|
||||
</defs>
|
||||
</svg>
|
||||
|
||||
<h1>Перезагрузка сервера</h1> <br>
|
||||
<span id="status">Пожалуйста, подождите</span>
|
||||
<button id="closeButton" style="display: none;" onclick="hideMessage()">Закрыть</button>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
|
||||
<div class="name">
|
||||
<span>Настройки</span>
|
||||
</div>
|
||||
<nav>
|
||||
<a class="selected" href="/devices">Настройки</a>
|
||||
</nav>
|
||||
<section class="bg">
|
||||
<section class="content">
|
||||
|
||||
<section class="for-table">
|
||||
|
||||
<section class="whole-width">
|
||||
<h1>Перезагрузка видео-сервера</h1>
|
||||
<h3>При перебоях в работе трансляций камер нажмите кнопку "Перезагрузить".</h3>
|
||||
<button class="button-purple" onclick="performRestart()">Перезагрузить</button>
|
||||
</section>
|
||||
|
||||
</section>
|
||||
|
||||
</section>
|
||||
</section>
|
||||
</section>
|
||||
|
||||
|
||||
|
||||
<script src="../scripts/jquery.min.js"></script>
|
||||
|
||||
<script>
|
||||
|
||||
|
||||
function performRestart() {
|
||||
const deleteConfirmation = document.getElementById("deleteConfirmation");
|
||||
const loader = document.getElementById("loader");
|
||||
const status = document.getElementById("status");
|
||||
const closeButton = document.getElementById("closeButton");
|
||||
const mark = document.getElementById("success-mark");
|
||||
|
||||
loader.style.display = "block";
|
||||
closeButton.style.display = "none";
|
||||
deleteConfirmation.style.display = "flex";
|
||||
mark.style.display = "none";
|
||||
|
||||
const xhr = new XMLHttpRequest();
|
||||
xhr.open("POST", "/videos/restart", true);
|
||||
xhr.onreadystatechange = function () {
|
||||
if (xhr.readyState === 4) {
|
||||
if (xhr.status === 200) {
|
||||
showMessage("Перезагрузка успешна", true);
|
||||
} else if (xhr.status === 500) {
|
||||
showMessage("При перезагрузке произошла ошибка", false);
|
||||
} else {
|
||||
showMessage("Не удалось выполнить перезагрузку", false);
|
||||
}
|
||||
}
|
||||
};
|
||||
xhr.send();
|
||||
}
|
||||
|
||||
function hideMessage() {
|
||||
const deleteConfirmation = document.getElementById("deleteConfirmation");
|
||||
deleteConfirmation.style.display = "none";
|
||||
}
|
||||
|
||||
function showMessage(messageText, isSuccess) {
|
||||
const loader = document.getElementById("loader");
|
||||
const status = document.getElementById("status");
|
||||
const closeButton = document.getElementById("closeButton");
|
||||
const mark = document.getElementById("success-mark");
|
||||
|
||||
loader.style.display = "none";
|
||||
status.textContent = messageText;
|
||||
if (isSuccess) {
|
||||
mark.style.display = "block";
|
||||
status.style.color = "green";
|
||||
} else {
|
||||
status.style.color = "red";
|
||||
}
|
||||
closeButton.style.display = "block";
|
||||
}
|
||||
|
||||
|
||||
|
||||
</script>
|
||||
<script>
|
||||
// Скрытие/Показ дополнительных меню аккаунта
|
||||
const accountMain = document.getElementById('account-main');
|
||||
const accountAdditional = document.getElementById('account-additional');
|
||||
const accountUp = document.getElementById('up');
|
||||
const accountDown = document.getElementById('down');
|
||||
accountAdditional.style.display = 'none';
|
||||
accountUp.style.display = 'none';
|
||||
|
||||
accountMain.addEventListener('click', () => {
|
||||
if (accountAdditional.style.display === 'none') {
|
||||
accountAdditional.style.display = 'flex';
|
||||
accountUp.style.display = 'unset';
|
||||
accountDown.style.display = 'none';
|
||||
} else {
|
||||
accountAdditional.style.display = 'none';
|
||||
accountUp.style.display = 'none';
|
||||
accountDown.style.display = 'unset';
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
|
||||
</body>
|
||||
</html>
|
676
static/templates/videos/export.html
Normal file
676
static/templates/videos/export.html
Normal file
@ -0,0 +1,676 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Экспорт записей</title>
|
||||
<link rel="stylesheet" href="../styles/main.css" />
|
||||
<link rel="stylesheet" href="https://unpkg.com/leaflet@1.7.1/dist/leaflet.css" />
|
||||
<script src="https://unpkg.com/leaflet@1.7.1/dist/leaflet.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
|
||||
|
||||
<header>
|
||||
<h1>Аргус</h1>
|
||||
<h2><span>/</span> {{Organisation}}</h2>
|
||||
</header>
|
||||
|
||||
<section class="account-info">
|
||||
<div id="account-main">
|
||||
<img id="person" src="../img/person.svg">
|
||||
<span>{{User}}</span>
|
||||
<img id="down" src="../img/down.svg">
|
||||
<img id="up" src="../img/up.svg">
|
||||
</div>
|
||||
<a href="/login"><div id="account-additional" class="additional">Выйти</div></a>
|
||||
</section>
|
||||
|
||||
|
||||
|
||||
|
||||
<section class="navigation">
|
||||
<a href="/">
|
||||
<div><img src="../img/chart.svg">Главная</div>
|
||||
</a>
|
||||
<a href="/devices">
|
||||
<div><img src="../img/cloud.svg">Устройства</div>
|
||||
</a>
|
||||
<a href="/reports">
|
||||
<div><img src="../img/bubble.svg">Отчёты</div>
|
||||
</a>
|
||||
<a href="/live">
|
||||
<div><img src="../img/waves.svg">Трансляция</div>
|
||||
</a>
|
||||
<a href="/videos">
|
||||
<div class="selected"><img src="../img/play.svg">Записи</div>
|
||||
</a>
|
||||
<a class="settings" href="/settings">
|
||||
<div><img src="../img/gear.svg">Настройки</div>
|
||||
</a>
|
||||
</section>
|
||||
|
||||
<section class="main">
|
||||
{{#if ifDBError}}
|
||||
<section class="dberror">
|
||||
<div class="erorr-container">
|
||||
<img src="../img/warning.svg"> <br>
|
||||
<h1>Ошибка </h1> <br>
|
||||
<span>Не удалось получить данные из БД</span>
|
||||
<button type="button" onclick="location.reload();">Повторить попытку</button>
|
||||
</div>
|
||||
</section>
|
||||
{{/if}}
|
||||
<div class="name">
|
||||
<span>Управление записями</span>
|
||||
</div>
|
||||
<nav>
|
||||
<a href="/videos">Воспроизведение</a>
|
||||
<a class="selected" href="/videos/export">Экспорт</a>
|
||||
</nav>
|
||||
<section class="bg">
|
||||
<section class="content">
|
||||
<section class="for-table">
|
||||
|
||||
<section class="organisation">
|
||||
<h1>Организация</h1>
|
||||
|
||||
<ul class="area">
|
||||
<li class="area-name"><img src="../img/ul.svg"><input type="checkbox" id="name-1" class="checkbox-input" hidden checked><label for="name-1" class="checkbox-label">Группа 1</label>
|
||||
<ul class="area-devices" id="devices-1">
|
||||
{{#each Registrars}}
|
||||
<li class="device">
|
||||
<img>
|
||||
<input type="radio" name="camera-serial" id="radio-{{this.serial}}" class="radio-input" value="{{this.serial}}" hidden>
|
||||
<label for="radio-{{this.serial}}" class="radio-label">
|
||||
{{this.serial}}
|
||||
</label>
|
||||
</li>
|
||||
{{/each}}
|
||||
</ul>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
|
||||
</section>
|
||||
|
||||
|
||||
|
||||
<section class="table" style="position: relative;">
|
||||
|
||||
<div class="map">
|
||||
<div id="properties" class="properties" style="display: none;">
|
||||
<div class="propert"><h1>Группа</h1><br><h2 id="propert-group">Автобусы</h2></div>
|
||||
<div class="propert"><h1>Скорость</h1><br><h2 id="propert-speed"> км/ч</h2></div>
|
||||
<div class="propert"><h1>Номерной знак</h1><br><h2 id="propert-plate"></h2></div>
|
||||
<div class="propert"><h1>Геопозиция</h1><br><h2 id="propert-geo"></h2></div>
|
||||
</div>
|
||||
<div id="map"></div>
|
||||
</div>
|
||||
|
||||
<div class="cameras">
|
||||
<div class="video-container">
|
||||
<div id="camera-1" onclick="playVideo(1);">
|
||||
<img src="../../img/play-circle.svg">
|
||||
<span>Запустить 1 камеру</span>
|
||||
</div>
|
||||
<div id="camera-2" onclick="playVideo(2);">
|
||||
<img src="../../img/play-circle.svg">
|
||||
<span>Запустить 2 камеру</span>
|
||||
</div>
|
||||
<div id="camera-3" onclick="playVideo(3);">
|
||||
<img src="../../img/play-circle.svg">
|
||||
<span>Запустить 3 камеру</span>
|
||||
</div>
|
||||
<div id="camera-4" onclick="playVideo(4);">
|
||||
<img src="../../img/play-circle.svg">
|
||||
<span>Запустить 4 камеру</span>
|
||||
</div>
|
||||
<div id="camera-5" onclick="playVideo(5);">
|
||||
<img src="../../img/play-circle.svg">
|
||||
<span>Запустить 5 камеру</span>
|
||||
</div>
|
||||
<div id="camera-6" onclick="playVideo(6);">
|
||||
<img src="../../img/play-circle.svg">
|
||||
<span>Запустить 6 камеру</span>
|
||||
</div>
|
||||
<div id="camera-7" onclick="playVideo(7);">
|
||||
<img src="../../img/play-circle.svg">
|
||||
<span>Запустить 7 камеру</span>
|
||||
</div>
|
||||
<div id="camera-8" onclick="playVideo(8);">
|
||||
<img src="../../img/play-circle.svg">
|
||||
<span>Запустить 8 камеру</span>
|
||||
</div>
|
||||
<div id="camera-9" onclick="playVideo(9);">
|
||||
<img src="../../img/play-circle.svg">
|
||||
<span>Запустить 9 камеру</span>
|
||||
</div>
|
||||
<div id="camera-10" onclick="playVideo(10);">
|
||||
<img src="../../img/play-circle.svg">
|
||||
<span>Запустить 10 камеру</span>
|
||||
</div>
|
||||
<div id="camera-11" onclick="playVideo(11);">
|
||||
<img src="../../img/play-circle.svg">
|
||||
<span>Запустить 11 камеру</span>
|
||||
</div>
|
||||
<div id="camera-12" onclick="playVideo(12);">
|
||||
<img src="../../img/play-circle.svg">
|
||||
<span>Запустить 12 камеру</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="video-container-right">
|
||||
<div id="camera-13" onclick="playVideo(13);">
|
||||
<img src="../../img/play-circle.svg">
|
||||
<span>Запустить 13 камеру</span>
|
||||
</div>
|
||||
<div id="camera-14" onclick="playVideo(14);">
|
||||
<img src="../../img/play-circle.svg">
|
||||
<span>Запустить 14 камеру</span>
|
||||
</div>
|
||||
<div id="camera-15" onclick="playVideo(15);">
|
||||
<img src="../../img/play-circle.svg">
|
||||
<span>Запустить 15 камеру</span>
|
||||
</div>
|
||||
<div id="camera-16" onclick="playVideo(16);">
|
||||
<img src="../../img/play-circle.svg">
|
||||
<span>Запустить 16 камеру</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="calendar">
|
||||
<div class="calendar-header">
|
||||
<button id="prevMonth"></button>
|
||||
<h2 id="monthYear"></h2>
|
||||
<button id="nextMonth"></button>
|
||||
</div>
|
||||
<div class="daysOfWeek">
|
||||
<div>Пн</div>
|
||||
<div>Вт</div>
|
||||
<div>Ср</div>
|
||||
<div>Чт</div>
|
||||
<div>Пт</div>
|
||||
<div>Сб</div>
|
||||
<div>Вс</div>
|
||||
</div>
|
||||
<div class="dates" id="dates">
|
||||
</div>
|
||||
</div>
|
||||
<input type="hidden" id="selectedDate" name="selectedDate" hidden>
|
||||
|
||||
<div class="speedometr">
|
||||
<h1>Скорость</h1>
|
||||
<span>км/ч</span>
|
||||
<span style="float: right; font-size: 18px;">1 ч</span>
|
||||
<div>
|
||||
<canvas id="speed"></canvas>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="video-time">
|
||||
<input name="videoTime" type="time" id="video-time" step="1">
|
||||
</div>
|
||||
|
||||
|
||||
</section>
|
||||
|
||||
|
||||
</section>
|
||||
|
||||
</section>
|
||||
</section>
|
||||
</section>
|
||||
|
||||
<style>
|
||||
.table {
|
||||
background-color:rgba(0, 0, 0, 0.02) !important;
|
||||
}
|
||||
|
||||
.speedometr {
|
||||
position: absolute;
|
||||
right: 0;
|
||||
bottom: 62px;
|
||||
width: calc(75% - 300px - 10px - 32px);
|
||||
height: 220px;
|
||||
border-radius: 15px;
|
||||
background-color: white;
|
||||
padding-right: 32px;
|
||||
}
|
||||
|
||||
.speedometr div {
|
||||
height: 200px;
|
||||
}
|
||||
</style>
|
||||
|
||||
<script src="../scripts/jquery.min.js"></script>
|
||||
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
|
||||
|
||||
<script>
|
||||
function combineDateTime(dateString, timeString) {
|
||||
const date = new Date(dateString);
|
||||
const year = date.getFullYear();
|
||||
const month = String(date.getMonth() + 1).padStart(2, "0");
|
||||
const day = String(date.getDate()).padStart(2, "0");
|
||||
|
||||
// Разбиение времени на часы, минуты и секунды
|
||||
const timeParts = timeString.split(":");
|
||||
const hours = timeParts[0].padStart(2, "0");
|
||||
const minutes = timeParts[1].padStart(2, "0");
|
||||
const seconds = timeParts[2].padStart(2, "0");
|
||||
|
||||
// Собираем дату и время в нужном формате
|
||||
const combinedDateTime = `${year}-${month}-${day} ${hours}:${minutes}:${seconds}`;
|
||||
|
||||
return combinedDateTime;
|
||||
}
|
||||
|
||||
function sendPostRequest() {
|
||||
// Получение данных из полей ввода
|
||||
const selectedDate = document.getElementById("selectedDate").value;
|
||||
const videoTime = document.getElementById("video-time").value;
|
||||
const selectedSerial = document.querySelector('input[name="camera-serial"]:checked').value;
|
||||
|
||||
// Объединяем дату и время и преобразуем в нужный формат
|
||||
const combinedDateTime = combineDateTime(selectedDate, videoTime);
|
||||
|
||||
const requestData = {
|
||||
serial: selectedSerial,
|
||||
datetime: combinedDateTime,
|
||||
};
|
||||
|
||||
fetch("/getspeedarchive", {
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
body: JSON.stringify(requestData),
|
||||
})
|
||||
.then((response) => response.json())
|
||||
.then((data) => {
|
||||
|
||||
const existingChart = Chart.getChart("speed");
|
||||
|
||||
if (existingChart) {
|
||||
existingChart.destroy();
|
||||
}
|
||||
|
||||
const numberOfLabels = data.speeds.length;
|
||||
const labels = Array.from({ length: numberOfLabels }, () => "");
|
||||
|
||||
// Обновление данных графика
|
||||
const chart = new Chart("speed", {
|
||||
type: "line",
|
||||
data: {
|
||||
labels: labels,
|
||||
datasets: [
|
||||
{
|
||||
label: "Скорость",
|
||||
borderColor: "#8086F9",
|
||||
fill: false,
|
||||
data: data.speeds,
|
||||
pointStyle: false,
|
||||
pointRadius: 25,
|
||||
pointHoverRadius: 25,
|
||||
tension: 0.1,
|
||||
},
|
||||
],
|
||||
},
|
||||
options: {
|
||||
plugins: {
|
||||
legend: {
|
||||
display: false,
|
||||
},
|
||||
},
|
||||
labelStep: "3",
|
||||
responsive: true,
|
||||
maintainAspectRatio: false,
|
||||
scales: {
|
||||
y: {
|
||||
stacked: true,
|
||||
grid: {
|
||||
display: true,
|
||||
color: "#D9D9D9",
|
||||
},
|
||||
ticks: {
|
||||
stepSize: 10,
|
||||
},
|
||||
},
|
||||
x: {
|
||||
grid: {
|
||||
display: false,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
|
||||
const geoData = data.geo;
|
||||
|
||||
// Очищаем все слои на карте
|
||||
map.eachLayer(layer => {
|
||||
if (layer !== map) {
|
||||
map.removeLayer(layer);
|
||||
}
|
||||
});
|
||||
|
||||
// Добавляем слой с плитками OpenStreetMap
|
||||
L.tileLayer('https://server.arcgisonline.com/ArcGIS/rest/services/World_Street_Map/MapServer/tile/{z}/{y}/{x}').addTo(map);
|
||||
|
||||
// Создаем слой для маршрута
|
||||
const routeLayer = L.layerGroup().addTo(map);
|
||||
|
||||
// Создаем слой для маркеров
|
||||
const markerLayer = L.layerGroup().addTo(map);
|
||||
|
||||
// Преобразуем координаты точек маршрута
|
||||
const routePoints = geoData.map(point => [point.latitude, point.longitude]);
|
||||
|
||||
// Создаем линию маршрута
|
||||
const route = L.polyline(routePoints, { color: 'red',weight: 5 }).addTo(routeLayer);
|
||||
|
||||
|
||||
|
||||
})
|
||||
.catch((error) => {
|
||||
console.error("Ошибка при отправке запроса:", error);
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
const radioInputs = document.querySelectorAll(".radio-input");
|
||||
radioInputs.forEach((input) => {
|
||||
input.addEventListener("change", sendPostRequest);
|
||||
});
|
||||
|
||||
const selectedDateInput = document.getElementById("selectedDate");
|
||||
selectedDateInput.addEventListener("change", sendPostRequest);
|
||||
|
||||
const videoTimeInput = document.getElementById("video-time");
|
||||
videoTimeInput.addEventListener("change", sendPostRequest);
|
||||
|
||||
</script>
|
||||
|
||||
|
||||
<script>
|
||||
var now = new Date();
|
||||
now.setHours(now.getHours() - 1);
|
||||
var formattedTime = now.toISOString().substr(11, 8);
|
||||
document.getElementById("video-time").value = formattedTime;
|
||||
</script>
|
||||
|
||||
|
||||
<script>
|
||||
document.addEventListener("DOMContentLoaded", function () {
|
||||
const prevMonthBtn = document.getElementById("prevMonth");
|
||||
const nextMonthBtn = document.getElementById("nextMonth");
|
||||
const monthYear = document.getElementById("monthYear");
|
||||
const datesContainer = document.getElementById("dates");
|
||||
const dateForm = document.getElementById("dateForm");
|
||||
const selectedDateInput = document.getElementById("selectedDate");
|
||||
|
||||
let currentDate = new Date();
|
||||
let selectedDate = currentDate;
|
||||
selectedDateInput.value = selectedDate.toISOString();
|
||||
function renderCalendar() {
|
||||
// Очистить предыдущий календарь
|
||||
datesContainer.innerHTML = "";
|
||||
|
||||
// Установить заголовок с месяцем и годом
|
||||
const options = { year: "numeric", month: "long" };
|
||||
const formattedDate = currentDate.toLocaleDateString("ru-RU", options);
|
||||
const formattedMonthYear = formattedDate.charAt(0).toUpperCase() + formattedDate.slice(1);
|
||||
monthYear.textContent = formattedMonthYear.replace('г.', '');
|
||||
|
||||
// Найти первый день текущего месяца
|
||||
const firstDayOfMonth = new Date(currentDate.getFullYear(), currentDate.getMonth(), 1);
|
||||
|
||||
// Определить день недели, с которого начнется месяц
|
||||
const startingDay = firstDayOfMonth.getDay() - 1;
|
||||
|
||||
// Определить количество дней в текущем месяце
|
||||
const daysInMonth = new Date(currentDate.getFullYear(), currentDate.getMonth() + 1, 0).getDate();
|
||||
|
||||
// Создать дни месяца
|
||||
for (let i = 0; i < startingDay; i++) {
|
||||
const emptyDay = document.createElement("div");
|
||||
emptyDay.classList.add("empty-day");
|
||||
datesContainer.appendChild(emptyDay);
|
||||
}
|
||||
|
||||
for (let day = 1; day <= daysInMonth; day++) {
|
||||
const dateCell = document.createElement("div");
|
||||
dateCell.textContent = day;
|
||||
dateCell.classList.add("date");
|
||||
if (selectedDate.getDate() === day && selectedDate.getMonth() === currentDate.getMonth() && selectedDate.getFullYear() === currentDate.getFullYear()) {
|
||||
dateCell.classList.add("selected");
|
||||
}
|
||||
dateCell.addEventListener("click", () => {
|
||||
selectedDate = new Date(currentDate.getFullYear(), currentDate.getMonth(), day);
|
||||
renderCalendar();
|
||||
selectedDateInput.value = selectedDate.toISOString();
|
||||
});
|
||||
datesContainer.appendChild(dateCell);
|
||||
}
|
||||
sendPostRequest();
|
||||
}
|
||||
|
||||
// Перейти на предыдущий месяц
|
||||
prevMonthBtn.addEventListener("click", () => {
|
||||
currentDate = new Date(currentDate.getFullYear(), currentDate.getMonth() - 1, 1);
|
||||
renderCalendar();
|
||||
});
|
||||
|
||||
// Перейти на следующий месяц
|
||||
nextMonthBtn.addEventListener("click", () => {
|
||||
currentDate = new Date(currentDate.getFullYear(), currentDate.getMonth() + 1, 1);
|
||||
renderCalendar();
|
||||
});
|
||||
|
||||
// Инициализировать календарь
|
||||
renderCalendar();
|
||||
});
|
||||
|
||||
</script>
|
||||
|
||||
<script>
|
||||
function formatDate(selectedDate) {
|
||||
const date = new Date(selectedDate);
|
||||
const year = date.getFullYear();
|
||||
const month = (date.getMonth() + 1).toString().padStart(2, '0');
|
||||
const day = date.getDate().toString().padStart(2, '0');
|
||||
return `${year}${month}${day}`;
|
||||
}
|
||||
|
||||
function formatTime(videoTime) {
|
||||
const parts = videoTime.split(':');
|
||||
const hours = parts[0].toString().padStart(2, '0');
|
||||
const minutes = parts[1].toString().padStart(2, '0');
|
||||
const seconds = parts[2].toString().padStart(2, '0');
|
||||
return `${hours}${minutes}${seconds}`;
|
||||
}
|
||||
|
||||
function addOneHourToTime(videoTime) {
|
||||
const parts = videoTime.split(':');
|
||||
let hours = parseInt(parts[0], 10);
|
||||
hours += 1;
|
||||
const minutes = parts[1].toString().padStart(2, '0');
|
||||
const seconds = parts[2].toString().padStart(2, '0');
|
||||
return formatTime(`${hours}:${minutes}:${seconds}`);
|
||||
}
|
||||
|
||||
function playVideo(channel) {
|
||||
const selectedDevice = document.querySelector('input[name="camera-serial"]:checked');
|
||||
if (!selectedDevice) {
|
||||
alert('Пожалуйста, выберите устройство из списка.');
|
||||
return;
|
||||
}
|
||||
|
||||
const startTimeInput = document.getElementById('video-time');
|
||||
const selectedDateInput = document.getElementById('selectedDate');
|
||||
const startTime = formatTime(startTimeInput.value);
|
||||
const selectedDate = formatDate(selectedDateInput.value);
|
||||
|
||||
const endTime = addOneHourToTime(startTimeInput.value);
|
||||
|
||||
const serial = selectedDevice.value;
|
||||
|
||||
const url = `http://localhost:7081/playback?url=http%3A%2F%2Fkrbl.ru%3A8080%2Fhttp%2Fplayback.flv%3Fserial%3D${serial}%26channel%3D${channel}%26quality%3D1%26queryTime%3D${selectedDate}%26startTime%3D${startTime}%26endTime%3D${endTime}`;
|
||||
window.open(url, '_blank');
|
||||
}
|
||||
</script>
|
||||
|
||||
|
||||
|
||||
|
||||
<script>
|
||||
window.addEventListener('DOMContentLoaded', function() {
|
||||
var mapContainer = document.querySelector('.map');
|
||||
var mapArea = document.getElementById('map');
|
||||
|
||||
mapArea.style.height = (mapContainer.clientHeight) + 'px';
|
||||
mapArea.style.width = (mapContainer.clientWidth) + 'px';
|
||||
});
|
||||
|
||||
window.addEventListener("resize", function (event) {
|
||||
var mapContainer = document.querySelector('.map');
|
||||
var mapArea = document.getElementById('map');
|
||||
|
||||
mapArea.style.height = (mapContainer.clientHeight) + 'px';
|
||||
mapArea.style.width = (mapContainer.clientWidth) + 'px';
|
||||
});
|
||||
|
||||
var startPoint = [0, 0];
|
||||
var endPoint = [0, 0];
|
||||
|
||||
let map;
|
||||
|
||||
// Создаем карту Leaflet
|
||||
map = L.map('map').setView([59.855198, 30.282995], 10);
|
||||
|
||||
// Добавляем базовый слой OpenStreetMap
|
||||
L.tileLayer('https://server.arcgisonline.com/ArcGIS/rest/services/World_Street_Map/MapServer/tile/{z}/{y}/{x}').addTo(map);
|
||||
|
||||
// Создаем маркеры
|
||||
var startMarker = L.marker(startPoint, {
|
||||
icon: L.divIcon({
|
||||
className: 'custom-icon',
|
||||
html: '<div style="background-color: red; width: 14px; height: 14px; border: 2px solid white; border-radius: 50%;"></div>'
|
||||
})
|
||||
}).addTo(map);
|
||||
|
||||
var endMarker = L.marker(endPoint, {
|
||||
icon: L.divIcon({
|
||||
className: 'custom-icon',
|
||||
html: '<div style="background-color: red; width: 14px; height: 14px; border: 2px solid white; border-radius: 50%;"></div>'
|
||||
})
|
||||
}).addTo(map);
|
||||
|
||||
// Создаем линию маршрута
|
||||
var route = L.polyline([startPoint, endPoint], { color: 'red', weight: 6 }).addTo(map);
|
||||
|
||||
// Скрыть кнопки приближения/отдаления
|
||||
map.zoomControl.remove();
|
||||
|
||||
// Скрыть информационную панель
|
||||
map.attributionControl.remove();
|
||||
</script>
|
||||
|
||||
<script>
|
||||
// Скрытие/Показ дополнительных меню аккаунта
|
||||
const accountMain = document.getElementById('account-main');
|
||||
const accountAdditional = document.getElementById('account-additional');
|
||||
const accountUp = document.getElementById('up');
|
||||
const accountDown = document.getElementById('down');
|
||||
accountAdditional.style.display = 'none';
|
||||
accountUp.style.display = 'none';
|
||||
|
||||
accountMain.addEventListener('click', () => {
|
||||
if (accountAdditional.style.display === 'none') {
|
||||
accountAdditional.style.display = 'flex';
|
||||
accountUp.style.display = 'unset';
|
||||
accountDown.style.display = 'none';
|
||||
} else {
|
||||
accountAdditional.style.display = 'none';
|
||||
accountUp.style.display = 'none';
|
||||
accountDown.style.display = 'unset';
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
<script>
|
||||
Chart.defaults.color = "rgba(0, 0, 0, 0.4)";
|
||||
Chart.defaults.font.size = 15;
|
||||
Chart.defaults.font.weight = 400;
|
||||
var speedData = {
|
||||
labels: [
|
||||
"",
|
||||
"",
|
||||
"",
|
||||
"",
|
||||
"",
|
||||
"",
|
||||
"",
|
||||
"",
|
||||
"",
|
||||
"",
|
||||
"",
|
||||
],
|
||||
datasets: [
|
||||
{
|
||||
label: "Скорость",
|
||||
borderColor: "#8086F9",
|
||||
fill: false,
|
||||
data: [
|
||||
{{Speeds}}
|
||||
],
|
||||
pointStyle: false,
|
||||
pointRadius: 25,
|
||||
pointHoverRadius: 25,
|
||||
tension: 0.1,
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
var speedOptions = {
|
||||
plugins: {
|
||||
legend: {
|
||||
display: false,
|
||||
},
|
||||
},
|
||||
labelStep: "3",
|
||||
responsive: true,
|
||||
maintainAspectRatio: false,
|
||||
scales: {
|
||||
y: {
|
||||
stacked: true,
|
||||
grid: {
|
||||
display: true,
|
||||
color: "#D9D9D9",
|
||||
},
|
||||
ticks: {
|
||||
stepSize: 10,
|
||||
},
|
||||
},
|
||||
x: {
|
||||
grid: {
|
||||
display: false,
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
new Chart("speed", {
|
||||
type: "line",
|
||||
data: speedData,
|
||||
options: speedOptions,
|
||||
});
|
||||
</script>
|
||||
|
||||
|
||||
|
||||
</body>
|
||||
</html>
|
@ -47,7 +47,7 @@
|
||||
<a href="/videos">
|
||||
<div class="selected"><img src="../img/play.svg">Записи</div>
|
||||
</a>
|
||||
<a class="settings" href="/">
|
||||
<a class="settings" href="/settings">
|
||||
<div><img src="../img/gear.svg">Настройки</div>
|
||||
</a>
|
||||
</section>
|
||||
@ -493,6 +493,15 @@ function formatTime(videoTime) {
|
||||
return `${hours}${minutes}${seconds}`;
|
||||
}
|
||||
|
||||
function addOneHourToTime(videoTime) {
|
||||
const parts = videoTime.split(':');
|
||||
let hours = parseInt(parts[0], 10);
|
||||
hours += 1;
|
||||
const minutes = parts[1].toString().padStart(2, '0');
|
||||
const seconds = parts[2].toString().padStart(2, '0');
|
||||
return formatTime(`${hours}:${minutes}:${seconds}`);
|
||||
}
|
||||
|
||||
function playVideo(channel) {
|
||||
const selectedDevice = document.querySelector('input[name="camera-serial"]:checked');
|
||||
if (!selectedDevice) {
|
||||
@ -504,19 +513,19 @@ function playVideo(channel) {
|
||||
const selectedDateInput = document.getElementById('selectedDate');
|
||||
const startTime = formatTime(startTimeInput.value);
|
||||
const selectedDate = formatDate(selectedDateInput.value);
|
||||
|
||||
const endTime = addOneHourToTime(startTimeInput.value);
|
||||
|
||||
const serial = selectedDevice.value;
|
||||
|
||||
const url = `http://localhost:8081/?url=http%3A%2F%2Fkrbl.ru%3A8080%2Fhttp%2Fplayback.flv%3Fserial%3D${serial}%26channel%3D${channel}%26quality%3D1%26queryTime%3D${selectedDate}%26startTime%3D${startTime}%26endTime%3D130000`;
|
||||
const url = `http://localhost:7081/playback?url=http%3A%2F%2Fkrbl.ru%3A8080%2Fhttp%2Fplayback.flv%3Fserial%3D${serial}%26channel%3D${channel}%26quality%3D1%26queryTime%3D${selectedDate}%26startTime%3D${startTime}%26endTime%3D${endTime}`;
|
||||
window.open(url, '_blank');
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
</script>
|
||||
|
||||
|
||||
|
||||
|
||||
<script>
|
||||
window.addEventListener('DOMContentLoaded', function() {
|
||||
var mapContainer = document.querySelector('.map');
|
Loading…
Reference in New Issue
Block a user