archive, bug fixes

This commit is contained in:
Ivan 2023-08-21 06:28:58 +03:00
parent 5e3ba53c95
commit 20f2053a86
Signed by untrusted user who does not match committer: ppechenkoo
GPG Key ID: 0C191B86D9582583
13 changed files with 1351 additions and 81 deletions

206
server.js
View File

@ -30,6 +30,7 @@ app.get("/reports", reports);
app.get("/devices", devices); app.get("/devices", devices);
app.get("/devices/drivers", drivers); app.get("/devices/drivers", drivers);
app.get("/devices/update", update); app.get("/devices/update", update);
app.get("/videos", videos);
// const DB_User = process.env.DB_USER; // const DB_User = process.env.DB_USER;
@ -64,7 +65,7 @@ async function index(req, res) {
port: DB_Port, port: DB_Port,
}); });
const client = await pool.connect(); const client = await pool.connect();
// Выполняем запрос и получаем результат
const query = ` const query = `
SELECT COUNT(*) AS count SELECT COUNT(*) AS count
FROM registrars FROM registrars
@ -74,40 +75,40 @@ async function index(req, res) {
templateData.Count = registrars.rows[0].count; templateData.Count = registrars.rows[0].count;
const last11DaysQuery = ` const last11DaysQuery = `
SELECT COUNT(DISTINCT evtuuid) AS count WITH date_sequence AS (
FROM alarms SELECT DATE_TRUNC('day', NOW() - INTERVAL '10 days') + (generate_series(0, 10) || ' days')::interval AS day
WHERE time >= NOW() - INTERVAL '10 days' + INTERVAL '3 hours' )
AND time <= NOW() + INTERVAL '1 day' + INTERVAL '3 hours' SELECT
AND st IS NOT NULL date_sequence.day AS day,
GROUP BY DATE_TRUNC('day', time) COALESCE(COUNT(DISTINCT evtuuid), 0) AS count
ORDER BY DATE_TRUNC('day', time) 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 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 = ` const daysBeforeQuery = `
SELECT COUNT(DISTINCT evtuuid) AS count WITH date_sequence AS (
FROM alarms SELECT DATE_TRUNC('day', NOW() - INTERVAL '21 days') + (generate_series(0, 10) || ' days')::interval AS day
WHERE time >= NOW() - INTERVAL '21 days' + INTERVAL '3 hours' )
AND time <= NOW() - INTERVAL '10 days' + INTERVAL '3 hours' SELECT
AND st IS NOT NULL date_sequence.day AS day,
GROUP BY DATE_TRUNC('day', time) COALESCE(COUNT(DISTINCT evtuuid), 0) AS count
ORDER BY DATE_TRUNC('day', time) FROM date_sequence
`; LEFT JOIN alarms ON DATE_TRUNC('day', alarms.time) = date_sequence.day
const daysBeforeAlarms = await client.query(daysBeforeQuery); AND alarms.time >= NOW() - INTERVAL '21 days' + INTERVAL '3 hours'
const daysBeforeCounts = daysBeforeAlarms.rows.map(row => parseInt(row.count, 10)); AND alarms.time <= NOW() - INTERVAL '10 days' + INTERVAL '3 hours'
for (let i = 0; i < daysBeforeCounts.length; i++) { AND alarms.st IS NOT NULL
if (daysBeforeCounts[i] > 0) { GROUP BY date_sequence.day
templateData.Alarms11DaysBefore[i] = daysBeforeCounts[i]; ORDER BY date_sequence.day DESC
} `;
}
const daysBeforeAlarms = await client.query(daysBeforeQuery);
// Создание массива дат в формате "dd.mm" за последние 11 дней
const currentDate = new Date(); const currentDate = new Date();
const dates = []; const dates = [];
for (let i = 10; i >= 0; i--) { for (let i = 10; i >= 0; i--) {
@ -118,22 +119,50 @@ async function index(req, res) {
templateData.Dates = dates; templateData.Dates = dates;
const positionsLast11DaysQuery = ` const positionsLast11DaysQuery = `
SELECT COUNT(*) AS count SELECT
COUNT(*) AS count,
DATE_TRUNC('day', time) AS day,
CASE WHEN COUNT(*) = 0 THEN 0 ELSE 1 END AS sort_value
FROM geo FROM geo
WHERE time >= NOW() - INTERVAL '10 days' + INTERVAL '3 hours' WHERE time >= NOW() - INTERVAL '10 days' + INTERVAL '3 hours'
AND time <= NOW() + INTERVAL '1 day' + INTERVAL '3 hours' AND time <= NOW() + INTERVAL '1 day' + INTERVAL '3 hours'
GROUP BY DATE_TRUNC('day', time) GROUP BY DATE_TRUNC('day', time)
ORDER BY DATE_TRUNC('day', time) ORDER BY sort_value DESC, day DESC
`; `;
const positionsLast11Days = await client.query(positionsLast11DaysQuery); const positionsLast11Days = await client.query(positionsLast11DaysQuery);
const positionsLast11DaysCounts = positionsLast11Days.rows.map(row => parseInt(row.count, 10)); // console.log(positionsLast11Days.rows)
for (let i = 0; i < positionsLast11DaysCounts.length; i++) { // const positionsLast11DaysCounts = positionsLast11Days.rows.map(row => parseInt(row.count, 10));
if (positionsLast11DaysCounts[i] > 0) { // for (let i = 0; i < positionsLast11DaysCounts.length; i++) {
templateData.PositionsLast11Days[i] = positionsLast11DaysCounts[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"); 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")); 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; const port = 8081;
app.listen(port, () => { app.listen(port, () => {
console.log(`Server is running on port ${port}`); console.log(`Server is running on port ${port}`);

11
static/img/left.svg Normal file
View File

@ -0,0 +1,11 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--Generator: Apple Native CoreSVG 175.5-->
<!DOCTYPE svg
PUBLIC "-//W3C//DTD SVG 1.1//EN"
"http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="9.55078" height="16.9629">
<g>
<rect height="16.9629" opacity="0" width="9.55078" x="0" y="0"/>
<path d="M0 8.47656C0 8.7207 0.0878906 8.93555 0.273438 9.12109L8.01758 16.6895C8.18359 16.8652 8.39844 16.9531 8.65234 16.9531C9.16016 16.9531 9.55078 16.5723 9.55078 16.0645C9.55078 15.8105 9.44336 15.5957 9.28711 15.4297L2.17773 8.47656L9.28711 1.52344C9.44336 1.35742 9.55078 1.13281 9.55078 0.888672C9.55078 0.380859 9.16016 0 8.65234 0C8.39844 0 8.18359 0.0878906 8.01758 0.253906L0.273438 7.83203C0.0878906 8.00781 0 8.23242 0 8.47656Z" fill="#000000" fill-opacity="0.85"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 889 B

View File

@ -0,0 +1,11 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--Generator: Apple Native CoreSVG 175.5-->
<!DOCTYPE svg
PUBLIC "-//W3C//DTD SVG 1.1//EN"
"http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="19.9219" height="19.9316">
<g>
<rect height="19.9316" opacity="0" width="19.9219" x="0" y="0"/>
<path d="M9.96094 19.9219C15.4102 19.9219 19.9219 15.4004 19.9219 9.96094C19.9219 4.51172 15.4004 0 9.95117 0C4.51172 0 0 4.51172 0 9.96094C0 15.4004 4.52148 19.9219 9.96094 19.9219ZM9.96094 18.2617C5.35156 18.2617 1.66992 14.5703 1.66992 9.96094C1.66992 5.35156 5.3418 1.66016 9.95117 1.66016C14.5605 1.66016 18.2617 5.35156 18.2617 9.96094C18.2617 14.5703 14.5703 18.2617 9.96094 18.2617ZM8.13477 13.7793L13.6914 10.4883C14.1016 10.2539 14.0918 9.6875 13.6914 9.44336L8.13477 6.15234C7.71484 5.9082 7.1582 6.09375 7.1582 6.57227L7.1582 13.3594C7.1582 13.8281 7.67578 14.0527 8.13477 13.7793Z" fill="#000000" fill-opacity="0.85"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.0 KiB

11
static/img/right.svg Normal file
View File

@ -0,0 +1,11 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--Generator: Apple Native CoreSVG 175.5-->
<!DOCTYPE svg
PUBLIC "-//W3C//DTD SVG 1.1//EN"
"http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="9.55078" height="16.9629">
<g>
<rect height="16.9629" opacity="0" width="9.55078" x="0" y="0"/>
<path d="M9.55078 8.47656C9.55078 8.23242 9.45312 8.00781 9.26758 7.83203L1.5332 0.253906C1.35742 0.0878906 1.14258 0 0.888672 0C0.390625 0 0 0.380859 0 0.888672C0 1.13281 0.0976562 1.35742 0.253906 1.52344L7.36328 8.47656L0.253906 15.4297C0.0976562 15.5957 0 15.8105 0 16.0645C0 16.5723 0.390625 16.9531 0.888672 16.9531C1.14258 16.9531 1.35742 16.8652 1.5332 16.6895L9.26758 9.12109C9.45312 8.93555 9.55078 8.7207 9.55078 8.47656Z" fill="#000000" fill-opacity="0.85"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 879 B

View File

@ -478,6 +478,7 @@ header h2 span {
border: 2px solid rgba(245, 245, 250, 1); border: 2px solid rgba(245, 245, 250, 1);
border-radius: 30px; border-radius: 30px;
overflow: hidden; overflow: hidden;
position: relative;
} }
.table h1 { .table h1 {
@ -1401,17 +1402,6 @@ input[type="datetime-local"] {
padding: 12px 33px 26px 33px; 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 { .controls {
width: 100%; width: 100%;
display: flex; display: flex;
@ -1476,7 +1466,7 @@ video {
transition: 1s; transition: 1s;
position: relative; position: relative;
float: left; float: left;
z-index: 10; z-index: 3;
} }
.signals-list.hide { .signals-list.hide {
@ -1612,12 +1602,24 @@ video {
background: rgba(0, 0, 0, 0.10); background: rgba(0, 0, 0, 0.10);
} }
.map { .stream-map {
/* background-color: #ff443a2f; */ /* background-color: #ff443a2f; */
width: 100%; width: 100%;
height: 100%; height: 100%;
display: block; display: block;
/* float: right; */ position: absolute;
bottom: 0;
right: 0;
}
.map {
height: 310px;
width: 25%;
display: block;
position: absolute;
bottom: 0;
left: 0;
} }
#map { #map {
@ -1631,7 +1633,7 @@ video {
position: absolute; position: absolute;
top: 10px; top: 10px;
left: 50%; left: 50%;
z-index: 10; z-index: 3;
background-color: white; background-color: white;
padding: 9px 11px; padding: 9px 11px;
border-radius: 10px; border-radius: 10px;
@ -1674,7 +1676,7 @@ video {
font-weight: bold; font-weight: bold;
} }
.cameras { .stream-cameras {
background: #F5F5FA; background: #F5F5FA;
position: absolute; position: absolute;
bottom: 0; bottom: 0;
@ -1682,6 +1684,16 @@ video {
width: calc(100% - 345px - 2px); width: calc(100% - 345px - 2px);
z-index: 3; 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 { .cameras-swipe {
display: flex; display: flex;
@ -1700,13 +1712,13 @@ video {
background: #dcdce2; background: #dcdce2;
} }
.video-container { .stream-video-container {
display: flex; display: flex;
flex-wrap: wrap; flex-wrap: wrap;
width: 100%; width: 100%;
} }
.video-container video { .stream-video-container video {
height: auto; height: auto;
box-sizing: border-box; box-sizing: border-box;
width: 25%; width: 25%;
@ -1714,6 +1726,79 @@ video {
border: 1px solid white; 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 { .edit-container {
position: fixed; position: fixed;
@ -1806,6 +1891,154 @@ video {
margin-bottom: 0; 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) { @media (max-width: 1950px) {
/* при разрешении монитора до 1950 пикселей */ /* при разрешении монитора до 1950 пикселей */

View File

@ -40,7 +40,7 @@
<a href="/live"> <a href="/live">
<div><img src="../img/waves.svg">Трансляция</div> <div><img src="../img/waves.svg">Трансляция</div>
</a> </a>
<a href="/"> <a href="/videos">
<div><img src="../img/play.svg">Записи</div> <div><img src="../img/play.svg">Записи</div>
</a> </a>
<a class="settings" href="/"> <a class="settings" href="/">

View File

@ -40,7 +40,7 @@
<a href="/live"> <a href="/live">
<div><img src="../img/waves.svg">Трансляция</div> <div><img src="../img/waves.svg">Трансляция</div>
</a> </a>
<a href="/"> <a href="/videos">
<div><img src="../img/play.svg">Записи</div> <div><img src="../img/play.svg">Записи</div>
</a> </a>
<a class="settings" href="/"> <a class="settings" href="/">

View File

@ -40,7 +40,7 @@
<a href="/live"> <a href="/live">
<div><img src="../img/waves.svg">Трансляция</div> <div><img src="../img/waves.svg">Трансляция</div>
</a> </a>
<a href="/"> <a href="/videos">
<div><img src="../img/play.svg">Записи</div> <div><img src="../img/play.svg">Записи</div>
</a> </a>
<a class="settings" href="/"> <a class="settings" href="/">

View File

@ -42,7 +42,7 @@
<a href="/live"> <a href="/live">
<div><img src="../img/waves.svg">Трансляция</div> <div><img src="../img/waves.svg">Трансляция</div>
</a> </a>
<a href="/"> <a href="/videos">
<div><img src="../img/play.svg">Записи</div> <div><img src="../img/play.svg">Записи</div>
</a> </a>
<a class="settings" href="/"> <a class="settings" href="/">

View File

@ -45,7 +45,7 @@
<a href="/live"> <a href="/live">
<div class="selected"><img src="../img/waves.svg">Трансляция</div> <div class="selected"><img src="../img/waves.svg">Трансляция</div>
</a> </a>
<a href="/"> <a href="/videos">
<div><img src="../img/play.svg">Записи</div> <div><img src="../img/play.svg">Записи</div>
</a> </a>
<a class="settings" href="/"> <a class="settings" href="/">
@ -115,6 +115,15 @@
</section> </section>
<div id="video-popup" class="video-popup">
<div id="video-popup-content" class="video-popup-content">
<span class="close-popup" id="close-popup">&times;</span>
<div id="popup-video-container" class="popup-video-container">
</div>
</div>
</div>
<section class="table" style="position: relative;"> <section class="table" style="position: relative;">
@ -128,7 +137,8 @@
<ul id="list"> <ul id="list">
{{#each Alarms}} {{#each Alarms}}
<li> <li>
<input type="radio" name="signal" id="signal-{{this.id}}" hidden> <!-- <input type="radio" name="signal" id="signal-{{this.id}}" hidden> -->
<a style="color:rgba(0, 0, 0, 0.7);" href="/reports/{{this.id}}" target="_blank">
<label for="signal-{{this.id}}"> <label for="signal-{{this.id}}">
<h2>{{this.type}}</h2> <h2>{{this.type}}</h2>
<span>ID {{this.id}}</span> <span>ID {{this.id}}</span>
@ -149,6 +159,7 @@
<path fill="#000" fill-opacity=".75" d="M10.38 14c1.25 0 2.07-.336 2.792-1.15.057-.056.114-.12.164-.178.428-.478.628-.95.628-1.4 0-.513-.3-.992-.935-1.434l-2.077-1.442c-.643-.443-1.393-.493-1.992.1l-.55.55c-.164.164-.307.17-.464.07-.386-.242-1.164-.92-1.692-1.448-.557-.55-1.092-1.164-1.378-1.614-.093-.164-.086-.3.079-.464l.542-.55c.6-.6.55-1.356.107-1.992L4.162.971C3.72.336 3.242.043 2.727.036c-.45-.007-.92.2-1.4.628-.063.057-.12.107-.178.157C.336 1.549 0 2.371 0 3.605c0 2.042 1.256 4.527 3.562 6.832C5.854 12.73 8.346 14 10.38 14Zm.008-1.1c-1.82.036-4.155-1.363-6.005-3.205-1.863-1.856-3.326-4.27-3.29-6.09.014-.785.292-1.463.849-1.949.05-.043.086-.078.136-.114.214-.186.442-.285.65-.285.206 0 .392.078.527.292L4.64 3.627c.15.221.165.47-.057.692l-.628.628c-.492.493-.457 1.093-.1 1.571.407.55 1.114 1.35 1.664 1.892.542.55 1.406 1.32 1.963 1.735.478.357 1.078.393 1.57-.1l.629-.628c.221-.222.464-.207.692-.065l2.078 1.385a.6.6 0 0 1 .292.536c0 .207-.1.435-.285.65l-.114.135c-.486.557-1.164.828-1.956.843Z"/> <path fill="#000" fill-opacity=".75" d="M10.38 14c1.25 0 2.07-.336 2.792-1.15.057-.056.114-.12.164-.178.428-.478.628-.95.628-1.4 0-.513-.3-.992-.935-1.434l-2.077-1.442c-.643-.443-1.393-.493-1.992.1l-.55.55c-.164.164-.307.17-.464.07-.386-.242-1.164-.92-1.692-1.448-.557-.55-1.092-1.164-1.378-1.614-.093-.164-.086-.3.079-.464l.542-.55c.6-.6.55-1.356.107-1.992L4.162.971C3.72.336 3.242.043 2.727.036c-.45-.007-.92.2-1.4.628-.063.057-.12.107-.178.157C.336 1.549 0 2.371 0 3.605c0 2.042 1.256 4.527 3.562 6.832C5.854 12.73 8.346 14 10.38 14Zm.008-1.1c-1.82.036-4.155-1.363-6.005-3.205-1.863-1.856-3.326-4.27-3.29-6.09.014-.785.292-1.463.849-1.949.05-.043.086-.078.136-.114.214-.186.442-.285.65-.285.206 0 .392.078.527.292L4.64 3.627c.15.221.165.47-.057.692l-.628.628c-.492.493-.457 1.093-.1 1.571.407.55 1.114 1.35 1.664 1.892.542.55 1.406 1.32 1.963 1.735.478.357 1.078.393 1.57-.1l.629-.628c.221-.222.464-.207.692-.065l2.078 1.385a.6.6 0 0 1 .292.536c0 .207-.1.435-.285.65l-.114.135c-.486.557-1.164.828-1.956.843Z"/>
</svg></p> --> </svg></p> -->
</label> </label>
</a>
</li> </li>
{{/each}} {{/each}}
@ -156,7 +167,7 @@
</div> </div>
<div class="map"> <div class="stream-map">
<div id="properties" class="properties" style="display: none;"> <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-group">Автобусы</h2></div>
<div class="propert"><h1>Скорость</h1><br><h2 id="propert-speed"> км/ч</h2></div> <div class="propert"><h1>Скорость</h1><br><h2 id="propert-speed"> км/ч</h2></div>
@ -166,11 +177,11 @@
<div id="map"></div> <div id="map"></div>
</div> </div>
<div class="cameras"> <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"> <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"/> <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> </svg></div>
<div class="video-container"> <div class="stream-video-container">
<!-- <video id="camera-1"></video> --> <!-- <video id="camera-1"></video> -->
<video id="camera-2"></video> <video id="camera-2"></video>
<video id="camera-3"></video> <video id="camera-3"></video>
@ -178,8 +189,8 @@
<video id="camera-5"></video> <video id="camera-5"></video>
<!-- <video id="camera-6"></video> <!-- <video id="camera-6"></video>
<video id="camera-7"></video> <video id="camera-7"></video>
<video id="camera-8"></video> --> <video id="camera-8"></video>
<!-- <video id="camera-9"></video> <video id="camera-9"></video>
<video id="camera-10"></video> <video id="camera-10"></video>
<video id="camera-11"></video> <video id="camera-11"></video>
<video id="camera-12"></video> <video id="camera-12"></video>
@ -348,6 +359,19 @@
zoom: 10 zoom: 10
}) })
}); });
// Скрыть кнопки приближения/отдаления
map.getControls().forEach(function(control) {
if (control instanceof ol.control.Zoom) {
map.removeControl(control);
}
});
// Скрыть информационную панель
map.getControls().forEach(function(control) {
if (control instanceof ol.control.Attribution) {
map.removeControl(control);
}
});
var xhr = new XMLHttpRequest(); var xhr = new XMLHttpRequest();
xhr.open("GET", "../scripts/style.json", true); xhr.open("GET", "../scripts/style.json", true);
@ -366,10 +390,22 @@
{{#if ifDBError}} {{#if ifDBError}}
{{else}} {{else}}
const selectedDevices = Array.from(checkboxes) const selectedDevices = Array.from(checkboxes)
.filter(checkbox => checkbox.checked && checkbox.value !== 'on') .filter(checkbox => checkbox.checked && checkbox.value !== 'on')
.map(checkbox => checkbox.value); .map(checkbox => checkbox.value);
checkboxes.forEach(checkbox => {
checkbox.addEventListener('change', function() {
selectedDevices.length = 0; // Очищаем массив
selectedDevices.push(...Array.from(checkboxes)
.filter(checkbox => checkbox.checked && checkbox.value !== 'on')
.map(checkbox => checkbox.value));
console.log(selectedDevices); // Вывести выбранные устройства в консоль
});
});
function addMarker(device) { function addMarker(device) {
const { serial, status, longitude, latitude, direction, speed } = device; const { serial, status, longitude, latitude, direction, speed } = device;
@ -410,6 +446,7 @@
} }
function fetchAndShowMarkers() { function fetchAndShowMarkers() {
console.log(selectedDevices);
fetch('/devices-geo', { fetch('/devices-geo', {
method: 'POST', method: 'POST',
headers: { headers: {
@ -436,8 +473,9 @@
const propertiesDiv = document.getElementById('properties'); const propertiesDiv = document.getElementById('properties');
console.log(selectedDevice);
if (selectedDevice) { if (selectedDevice) {
// Вывод нужных свойств устройства в консоль
// groupElement.textContent = selectedDevice.group; // groupElement.textContent = selectedDevice.group;
speedElement.textContent = selectedDevice.speed + ' км/ч'; speedElement.textContent = selectedDevice.speed + ' км/ч';
plateElement.textContent = selectedDevice.plate; plateElement.textContent = selectedDevice.plate;
@ -574,6 +612,80 @@ for (var i = 0; i < tableCheckboxes.length; i++) {
</script> </script>
<script>
// Получаем ссылки на элементы
const videoContainers = document.querySelectorAll('.stream-video-container video');
const popup = document.getElementById('video-popup');
const popupVideo = document.getElementById('popup-video');
const closePopup = document.getElementById('close-popup');
const popupVideoContainer = document.getElementById('popup-video-container');
const popupContainer = document.getElementById('video-popup-content');
let originalVideo = null; // Сохраняем оригинальное видео элемент
// Функция для открытия попапа с видео
function openVideoPopup(video) {
// Сохраняем оригинальное видео
originalVideo = video;
// Меняем стили видео
video.style.position = 'fixed';
video.style.top = '10%';
video.style.left = '0';
video.style.width = '100%';
video.style.height = '80%';
video.style.zIndex = '1000';
popup.style.display = 'block';
popupContainer.style.width = '100%';
popupContainer.style.height = '80%';
}
// Функция для закрытия попапа с видео
function closeVideoPopup() {
// Восстанавливаем оригинальные стили видео
if (originalVideo) {
originalVideo.style.position = '';
originalVideo.style.top = '';
originalVideo.style.left = '';
originalVideo.style.width = '';
originalVideo.style.height = '';
originalVideo.style.zIndex = '';
// Очищаем контейнер попапа
popup.style.display = 'none';
// Сбрасываем оригинальное видео
originalVideo = null;
}
}
// Добавляем обработчики событий для клика на видео и кнопку закрытия
videoContainers.forEach((video) => {
video.addEventListener('click', () => {
openVideoPopup(video);
});
});
closePopup.addEventListener('click', () => {
closeVideoPopup();
});
// Закрыть попап при клике вне его области
window.addEventListener('click', (event) => {
if (event.target === popup) {
closeVideoPopup();
}
});
// Закрыть попап при нажатии на клавишу Esc
window.addEventListener('keydown', (event) => {
if (event.key === 'Escape') {
closeVideoPopup();
}
});
</script>
</body> </body>

View File

@ -40,7 +40,7 @@
<a href="/live"> <a href="/live">
<div><img src="../img/waves.svg">Трансляция</div> <div><img src="../img/waves.svg">Трансляция</div>
</a> </a>
<a href="/"> <a href="/videos">
<div><img src="../img/play.svg">Записи</div> <div><img src="../img/play.svg">Записи</div>
</a> </a>
<a class="settings" href="/"> <a class="settings" href="/">

View File

@ -43,7 +43,7 @@
<a href="/live"> <a href="/live">
<div><img src="../img/waves.svg">Трансляция</div> <div><img src="../img/waves.svg">Трансляция</div>
</a> </a>
<a href="/"> <a href="/videos">
<div><img src="../img/play.svg">Записи</div> <div><img src="../img/play.svg">Записи</div>
</a> </a>
<a class="settings" href="/"> <a class="settings" href="/">
@ -168,14 +168,18 @@
<video id="my-video" src="../test_video.MP4"></video> <video id="my-video" src="../test_video.MP4"></video>
<div class="map"> <div class="stream-map">
<div id="map"></div> <div id="map"></div>
</div> </div>
</section> </section>
</section>
</section>
</section>
<style> <style>
.map { .stream-map {
width: calc(100% - 450px - 514px); width: calc(100% - 450px - 514px);
height: calc(100% - 62px);
float: right; float: right;
} }
</style> </style>
@ -253,7 +257,7 @@ var map = new ol.Map({
], ],
view: new ol.View({ view: new ol.View({
center: ol.proj.fromLonLat([prevLongitude, prevLatitude]), center: ol.proj.fromLonLat([prevLongitude, prevLatitude]),
zoom: 17 zoom: 14
}) })
}); });

View File

@ -0,0 +1,760 @@
<!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://cdnjs.cloudflare.com/ajax/libs/ol3/4.6.5/ol.css" type="text/css">
<script src="https://cdnjs.cloudflare.com/ajax/libs/ol3/4.6.5/ol.js"></script>
<script src="https://unpkg.com/ol-mapbox-style@9.4.0/dist/olms.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="/">
<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 class="selected" href="/videos">Воспроизведение</a>
<a href="/videos">Экспорт</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,
},
},
},
},
});
routeLayer.getSource().clear();
markerLayer.getSource().clear();
const geoData = data.geo;
const routePoints = geoData.map((point) => {
return ol.proj.fromLonLat([point.longitude, point.latitude]);
});
const routeFeature = new ol.Feature({
geometry: new ol.geom.LineString(routePoints),
});
routeFeature.setStyle(routeStyle);
routeLayer.getSource().addFeature(routeFeature);
const startMarker = new ol.Feature({
geometry: new ol.geom.Point(routePoints[0]),
});
startMarker.setStyle(markerStyle);
const endMarker = new ol.Feature({
geometry: new ol.geom.Point(routePoints[routePoints.length - 1]),
});
endMarker.setStyle(markerStyle);
markerLayer.getSource().addFeatures([startMarker, endMarker]);
const mapElement = document.getElementById("map");
const map = new ol.Map({
target: null,
layers: [
new ol.layer.Tile({
source: new ol.source.OSM(),
}),
markerLayer,
routeLayer,
],
view: new ol.View({
center: ol.proj.fromLonLat([30.282995, 59.855198]),
zoom: 10
}),
});
map.getLayers().clear();
map.setTarget(mapElement);
// Скрыть кнопки приближения/отдаления
map.getControls().forEach(function (control) {
if (control instanceof ol.control.Zoom) {
map.removeControl(control);
}
});
// Скрыть информационную панель
map.getControls().forEach(function (control) {
if (control instanceof ol.control.Attribution) {
map.removeControl(control);
}
});
})
.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 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 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`;
fetch(url)
.then(response => {
if (response.status === 200) {
console.log('Запрос выполнен успешно.');
} else {
console.error('Произошла ошибка при выполнении запроса.');
}
})
.catch(error => {
console.error('Произошла ошибка при выполнении запроса:', error);
});
}
</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 = ol.proj.fromLonLat([0, 0]);
var endPoint = ol.proj.fromLonLat([0, 0]);
var routeStyle = new ol.style.Style({
stroke: new ol.style.Stroke({
color: 'red',
width: 6
})
});
var markerStyle = new ol.style.Style({
image: new ol.style.Circle({
radius: 7,
fill: new ol.style.Fill({
color: 'red'
}),
stroke: new ol.style.Stroke({
color: 'white',
width: 2
})
})
});
var routeSource = new ol.source.Vector({
features: [
new ol.Feature({
geometry: new ol.geom.LineString([startPoint, endPoint])
})
]
});
var routeLayer = new ol.layer.Vector({
source: routeSource,
style: routeStyle
});
var startMarker = new ol.Feature({
geometry: new ol.geom.Point(startPoint)
});
startMarker.setStyle(markerStyle);
var endMarker = new ol.Feature({
geometry: new ol.geom.Point(endPoint)
});
endMarker.setStyle(markerStyle);
var markerSource = new ol.source.Vector({
features: [startMarker, endMarker]
});
var markerLayer = new ol.layer.Vector({
source: markerSource
});
var map = new ol.Map({
target: 'map',
layers: [
new ol.layer.Tile({
source: new ol.source.OSM()
}),
markerLayer,
routeLayer,
],
view: new ol.View({
center: ol.proj.fromLonLat([30.282995, 59.855198]),
zoom: 10
})
});
// Скрыть кнопки приближения/отдаления
map.getControls().forEach(function(control) {
if (control instanceof ol.control.Zoom) {
map.removeControl(control);
}
});
// Скрыть информационную панель
map.getControls().forEach(function(control) {
if (control instanceof ol.control.Attribution) {
map.removeControl(control);
}
});
</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>