archive, bug fixes
This commit is contained in:
parent
5e3ba53c95
commit
20f2053a86
214
server.js
214
server.js
@ -30,6 +30,7 @@ app.get("/reports", reports);
|
||||
app.get("/devices", devices);
|
||||
app.get("/devices/drivers", drivers);
|
||||
app.get("/devices/update", update);
|
||||
app.get("/videos", videos);
|
||||
|
||||
|
||||
// const DB_User = process.env.DB_USER;
|
||||
@ -64,7 +65,7 @@ async function index(req, res) {
|
||||
port: DB_Port,
|
||||
});
|
||||
const client = await pool.connect();
|
||||
// Выполняем запрос и получаем результат
|
||||
|
||||
const query = `
|
||||
SELECT COUNT(*) AS count
|
||||
FROM registrars
|
||||
@ -74,40 +75,40 @@ async function index(req, res) {
|
||||
templateData.Count = registrars.rows[0].count;
|
||||
|
||||
const last11DaysQuery = `
|
||||
SELECT COUNT(DISTINCT evtuuid) AS count
|
||||
FROM alarms
|
||||
WHERE time >= NOW() - INTERVAL '10 days' + INTERVAL '3 hours'
|
||||
AND time <= NOW() + INTERVAL '1 day' + INTERVAL '3 hours'
|
||||
AND st IS NOT NULL
|
||||
GROUP BY DATE_TRUNC('day', time)
|
||||
ORDER BY DATE_TRUNC('day', time)
|
||||
WITH date_sequence AS (
|
||||
SELECT DATE_TRUNC('day', NOW() - INTERVAL '10 days') + (generate_series(0, 10) || ' days')::interval AS day
|
||||
)
|
||||
SELECT
|
||||
date_sequence.day AS day,
|
||||
COALESCE(COUNT(DISTINCT evtuuid), 0) AS count
|
||||
FROM date_sequence
|
||||
LEFT JOIN alarms ON DATE_TRUNC('day', alarms.time) = date_sequence.day
|
||||
AND alarms.time >= NOW() - INTERVAL '10 days' + INTERVAL '3 hours'
|
||||
AND alarms.time <= NOW() + INTERVAL '1 day' + INTERVAL '3 hours'
|
||||
AND alarms.st IS NOT NULL
|
||||
GROUP BY date_sequence.day
|
||||
ORDER BY date_sequence.day DESC
|
||||
`;
|
||||
const last11DaysAlarms = await client.query(last11DaysQuery);
|
||||
const last11DaysCounts = last11DaysAlarms.rows.map(row => parseInt(row.count, 10));
|
||||
for (let i = 0; i < last11DaysCounts.length; i++) {
|
||||
if (last11DaysCounts[i] > 0) {
|
||||
templateData.AlarmsLast11Days[i] = last11DaysCounts[i];
|
||||
}
|
||||
}
|
||||
|
||||
const daysBeforeQuery = `
|
||||
SELECT COUNT(DISTINCT evtuuid) AS count
|
||||
FROM alarms
|
||||
WHERE time >= NOW() - INTERVAL '21 days' + INTERVAL '3 hours'
|
||||
AND time <= NOW() - INTERVAL '10 days' + INTERVAL '3 hours'
|
||||
AND st IS NOT NULL
|
||||
GROUP BY DATE_TRUNC('day', time)
|
||||
ORDER BY DATE_TRUNC('day', time)
|
||||
`;
|
||||
const daysBeforeAlarms = await client.query(daysBeforeQuery);
|
||||
const daysBeforeCounts = daysBeforeAlarms.rows.map(row => parseInt(row.count, 10));
|
||||
for (let i = 0; i < daysBeforeCounts.length; i++) {
|
||||
if (daysBeforeCounts[i] > 0) {
|
||||
templateData.Alarms11DaysBefore[i] = daysBeforeCounts[i];
|
||||
}
|
||||
}
|
||||
WITH date_sequence AS (
|
||||
SELECT DATE_TRUNC('day', NOW() - INTERVAL '21 days') + (generate_series(0, 10) || ' days')::interval AS day
|
||||
)
|
||||
SELECT
|
||||
date_sequence.day AS day,
|
||||
COALESCE(COUNT(DISTINCT evtuuid), 0) AS count
|
||||
FROM date_sequence
|
||||
LEFT JOIN alarms ON DATE_TRUNC('day', alarms.time) = date_sequence.day
|
||||
AND alarms.time >= NOW() - INTERVAL '21 days' + INTERVAL '3 hours'
|
||||
AND alarms.time <= NOW() - INTERVAL '10 days' + INTERVAL '3 hours'
|
||||
AND alarms.st IS NOT NULL
|
||||
GROUP BY date_sequence.day
|
||||
ORDER BY date_sequence.day DESC
|
||||
`;
|
||||
|
||||
const daysBeforeAlarms = await client.query(daysBeforeQuery);
|
||||
|
||||
// Создание массива дат в формате "dd.mm" за последние 11 дней
|
||||
const currentDate = new Date();
|
||||
const dates = [];
|
||||
for (let i = 10; i >= 0; i--) {
|
||||
@ -118,22 +119,50 @@ async function index(req, res) {
|
||||
templateData.Dates = dates;
|
||||
|
||||
const positionsLast11DaysQuery = `
|
||||
SELECT COUNT(*) AS count
|
||||
FROM geo
|
||||
WHERE time >= NOW() - INTERVAL '10 days' + INTERVAL '3 hours'
|
||||
AND time <= NOW() + INTERVAL '1 day' + INTERVAL '3 hours'
|
||||
GROUP BY DATE_TRUNC('day', time)
|
||||
ORDER BY DATE_TRUNC('day', time)
|
||||
SELECT
|
||||
COUNT(*) AS count,
|
||||
DATE_TRUNC('day', time) AS day,
|
||||
CASE WHEN COUNT(*) = 0 THEN 0 ELSE 1 END AS sort_value
|
||||
FROM geo
|
||||
WHERE time >= NOW() - INTERVAL '10 days' + INTERVAL '3 hours'
|
||||
AND time <= NOW() + INTERVAL '1 day' + INTERVAL '3 hours'
|
||||
GROUP BY DATE_TRUNC('day', time)
|
||||
ORDER BY sort_value DESC, day DESC
|
||||
`;
|
||||
const positionsLast11Days = await client.query(positionsLast11DaysQuery);
|
||||
const positionsLast11DaysCounts = positionsLast11Days.rows.map(row => parseInt(row.count, 10));
|
||||
for (let i = 0; i < positionsLast11DaysCounts.length; i++) {
|
||||
if (positionsLast11DaysCounts[i] > 0) {
|
||||
templateData.PositionsLast11Days[i] = positionsLast11DaysCounts[i];
|
||||
}
|
||||
}
|
||||
// console.log(positionsLast11Days.rows)
|
||||
// const positionsLast11DaysCounts = positionsLast11Days.rows.map(row => parseInt(row.count, 10));
|
||||
// for (let i = 0; i < positionsLast11DaysCounts.length; i++) {
|
||||
// if (positionsLast11DaysCounts[i] > 0) {
|
||||
// templateData.PositionsLast11Days[i] = positionsLast11DaysCounts[i];
|
||||
// }
|
||||
// }
|
||||
|
||||
// console.log(templateData);
|
||||
templateData.Dates.reverse();
|
||||
|
||||
const last11DaysMap = new Map(last11DaysAlarms.rows.map(row => [row.day.toLocaleDateString('ru-RU', { day: '2-digit', month: '2-digit' }), parseInt(row.count, 10)]));
|
||||
|
||||
for (let i = 0; i < dates.length; i++) {
|
||||
const dateKey = dates[i];
|
||||
templateData.AlarmsLast11Days[i] = last11DaysMap.has(dateKey) ? last11DaysMap.get(dateKey) : 0;
|
||||
}
|
||||
|
||||
const beforeDaysMap = new Map(daysBeforeAlarms.rows.map(row => [row.day.toLocaleDateString('ru-RU', { day: '2-digit', month: '2-digit' }), parseInt(row.count, 10)]));
|
||||
|
||||
for (let i = 0; i < dates.length; i++) {
|
||||
const dateKey = dates[i];
|
||||
templateData.Alarms11DaysBefore[i] = beforeDaysMap.has(dateKey) ? beforeDaysMap.get(dateKey) : 0;
|
||||
}
|
||||
|
||||
const positionsMap = new Map(positionsLast11Days.rows.map(row => [row.day.toLocaleDateString('ru-RU', { day: '2-digit', month: '2-digit' }), parseInt(row.count, 10)]));
|
||||
|
||||
for (let i = 0; i < dates.length; i++) {
|
||||
const dateKey = dates[i];
|
||||
templateData.PositionsLast11Days[i] = positionsMap.has(dateKey) ? positionsMap.get(dateKey) : 0;
|
||||
}
|
||||
|
||||
|
||||
console.log(templateData);
|
||||
|
||||
const source = fs.readFileSync("static/templates/index.html", "utf8");
|
||||
|
||||
@ -1437,6 +1466,105 @@ function update(req, res) {
|
||||
res.sendFile(path.join(__dirname, "static/templates/devices/update.html"));
|
||||
}
|
||||
|
||||
async function videos(req, res) {
|
||||
let templateData = {
|
||||
Organisation: "Название организации",
|
||||
User: "Тестовое Имя",
|
||||
ifDBError: false,
|
||||
Registrars: [],
|
||||
};
|
||||
|
||||
try {
|
||||
const pool = new Pool({
|
||||
user: DB_User,
|
||||
host: DB_Host,
|
||||
database: DB_Name,
|
||||
password: DB_Password,
|
||||
port: DB_Port,
|
||||
});
|
||||
const client = await pool.connect();
|
||||
|
||||
const minuteInMillis = 60 * 1000;
|
||||
|
||||
const query = `
|
||||
SELECT id, serial, lastkeepalive FROM registrars ORDER BY id ASC
|
||||
`;
|
||||
const registrars = await client.query(query);
|
||||
|
||||
templateData.Registrars = registrars.rows.map((row) => ({
|
||||
id: row.id,
|
||||
serial: row.serial,
|
||||
status: Date.now() - Date.parse(row.lastkeepalive) <= minuteInMillis,
|
||||
}));
|
||||
|
||||
console.log(templateData);
|
||||
|
||||
const source = fs.readFileSync("static/templates/videos/see.html", "utf8");
|
||||
const template = handlebars.compile(source);
|
||||
const resultHTML = template(templateData);
|
||||
res.send(resultHTML);
|
||||
|
||||
client.release();
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
templateData.ifDBError = true;
|
||||
|
||||
const source = fs.readFileSync("static/templates/videos/see.html", "utf8");
|
||||
const template = handlebars.compile(source);
|
||||
const resultT = template(templateData);
|
||||
res.send(resultT);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
app.post("/getspeedarchive", async (req, res) => {
|
||||
const { serial, datetime } = req.body;
|
||||
|
||||
const formattedDateTime = new Date(datetime).toISOString();
|
||||
const pool = new Pool({
|
||||
user: DB_User,
|
||||
host: DB_Host,
|
||||
database: DB_Name,
|
||||
password: DB_Password,
|
||||
port: DB_Port,
|
||||
});
|
||||
const client = await pool.connect();
|
||||
const sqlQuery = `
|
||||
SELECT speed, latitude, longitude
|
||||
FROM geo
|
||||
WHERE serial = $1
|
||||
AND time >= $2
|
||||
AND time < $3;
|
||||
`;
|
||||
|
||||
const startTime = new Date(formattedDateTime);
|
||||
startTime.setMinutes(0, 0, 0); // Округление до начала часа
|
||||
const endTime = new Date(startTime);
|
||||
endTime.setHours(endTime.getHours() + 1);
|
||||
|
||||
pool.query(sqlQuery, [serial, startTime, endTime], (error, results) => {
|
||||
if (error) {
|
||||
console.error("Ошибка при выполнении SQL-запроса:", error);
|
||||
res.status(500).json({ error: "Ошибка на сервере" });
|
||||
} else {
|
||||
const speeds = results.rows.map((row) => row.speed);
|
||||
const transformedSpeeds = speeds.map((speed) => {
|
||||
if (speed > 150) {
|
||||
return speed / 100;
|
||||
} else {
|
||||
return speed;
|
||||
}
|
||||
});
|
||||
|
||||
const geoData = results.rows.map((row) => ({
|
||||
latitude: row.latitude,
|
||||
longitude: row.longitude,
|
||||
}));
|
||||
res.json({ speeds: transformedSpeeds, geo: geoData });
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
const port = 8081;
|
||||
app.listen(port, () => {
|
||||
console.log(`Server is running on port ${port}`);
|
||||
|
11
static/img/left.svg
Normal file
11
static/img/left.svg
Normal 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 |
11
static/img/play-circle.svg
Normal file
11
static/img/play-circle.svg
Normal 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
11
static/img/right.svg
Normal 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 |
@ -478,6 +478,7 @@ header h2 span {
|
||||
border: 2px solid rgba(245, 245, 250, 1);
|
||||
border-radius: 30px;
|
||||
overflow: hidden;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.table h1 {
|
||||
@ -1401,17 +1402,6 @@ input[type="datetime-local"] {
|
||||
padding: 12px 33px 26px 33px;
|
||||
}
|
||||
|
||||
.video-container {
|
||||
position: relative;
|
||||
width: 500px;
|
||||
}
|
||||
|
||||
video {
|
||||
width: 450px;
|
||||
display: inline-block;
|
||||
border-right: 2px solid rgba(245, 245, 250, 1);
|
||||
}
|
||||
|
||||
.controls {
|
||||
width: 100%;
|
||||
display: flex;
|
||||
@ -1476,7 +1466,7 @@ video {
|
||||
transition: 1s;
|
||||
position: relative;
|
||||
float: left;
|
||||
z-index: 10;
|
||||
z-index: 3;
|
||||
}
|
||||
|
||||
.signals-list.hide {
|
||||
@ -1612,12 +1602,24 @@ video {
|
||||
background: rgba(0, 0, 0, 0.10);
|
||||
}
|
||||
|
||||
.map {
|
||||
.stream-map {
|
||||
/* background-color: #ff443a2f; */
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
display: block;
|
||||
/* float: right; */
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
right: 0;
|
||||
|
||||
}
|
||||
|
||||
.map {
|
||||
height: 310px;
|
||||
width: 25%;
|
||||
display: block;
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
}
|
||||
|
||||
#map {
|
||||
@ -1631,7 +1633,7 @@ video {
|
||||
position: absolute;
|
||||
top: 10px;
|
||||
left: 50%;
|
||||
z-index: 10;
|
||||
z-index: 3;
|
||||
background-color: white;
|
||||
padding: 9px 11px;
|
||||
border-radius: 10px;
|
||||
@ -1674,7 +1676,7 @@ video {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.cameras {
|
||||
.stream-cameras {
|
||||
background: #F5F5FA;
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
@ -1682,6 +1684,16 @@ video {
|
||||
width: calc(100% - 345px - 2px);
|
||||
z-index: 3;
|
||||
}
|
||||
.cameras {
|
||||
background: #F5F5FA;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
right: 0;
|
||||
width: 100%;
|
||||
height: calc(100% - 310px);
|
||||
z-index: 3;
|
||||
display: inline-flex;
|
||||
}
|
||||
|
||||
.cameras-swipe {
|
||||
display: flex;
|
||||
@ -1700,13 +1712,13 @@ video {
|
||||
background: #dcdce2;
|
||||
}
|
||||
|
||||
.video-container {
|
||||
.stream-video-container {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.video-container video {
|
||||
.stream-video-container video {
|
||||
height: auto;
|
||||
box-sizing: border-box;
|
||||
width: 25%;
|
||||
@ -1714,6 +1726,79 @@ video {
|
||||
border: 1px solid white;
|
||||
}
|
||||
|
||||
.video-container {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
width: 80%;
|
||||
float: left;
|
||||
}
|
||||
|
||||
.video-container div {
|
||||
height: auto;
|
||||
box-sizing: border-box;
|
||||
width: 25%;
|
||||
padding: 0;
|
||||
height: 33.4%;
|
||||
border: 1px solid white;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
text-align: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.video-container div,
|
||||
.video-container-right div {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
text-align: center;
|
||||
justify-content: center;
|
||||
cursor: pointer;
|
||||
transition: 0.1s;
|
||||
}
|
||||
|
||||
.video-container div:hover,
|
||||
.video-container-right div:hover {
|
||||
background-color: rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
.video-container div img,
|
||||
.video-container-right div img {
|
||||
height: 32px;
|
||||
width: 32px;
|
||||
}
|
||||
|
||||
.video-container div span,
|
||||
.video-container-right div span {
|
||||
font-size: 16px;
|
||||
font-weight: 500;
|
||||
margin-top: 15px;
|
||||
}
|
||||
|
||||
.video-container-right {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
float: right;
|
||||
width: 20%;
|
||||
float: right;
|
||||
}
|
||||
|
||||
.video-container-right div {
|
||||
height: auto;
|
||||
box-sizing: border-box;
|
||||
width: 100%;
|
||||
height: 25%;
|
||||
padding: 0;
|
||||
border: 1px solid white;
|
||||
|
||||
}
|
||||
|
||||
.report video {
|
||||
width: 450px;
|
||||
border-right: 2px solid rgba(245, 245, 250, 1);
|
||||
}
|
||||
|
||||
|
||||
.edit-container {
|
||||
position: fixed;
|
||||
@ -1806,6 +1891,154 @@ video {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
/* Стили для вашего календаря */
|
||||
.calendar {
|
||||
width: 250px;
|
||||
height: 300px;
|
||||
margin: 5px;
|
||||
padding: 0 25px;
|
||||
border-radius: 15px;
|
||||
overflow: hidden;
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
left: 25%;
|
||||
background-color:white;
|
||||
margin: 4px;
|
||||
}
|
||||
|
||||
.calendar-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
/* background-color: #f0f0f0; */
|
||||
padding: 20px 5px;
|
||||
border-bottom: 1px solid #ccc;
|
||||
}
|
||||
|
||||
.calendar-header h2 {
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
margin: 0;
|
||||
color: rgba(0, 0, 0, 0.90);
|
||||
}
|
||||
|
||||
#prevMonth {
|
||||
background-image: url(../img/left.svg);
|
||||
}
|
||||
|
||||
#nextMonth {
|
||||
background-image: url(../img/right.svg);
|
||||
}
|
||||
|
||||
#prevMonth, #nextMonth {
|
||||
background-color: transparent;
|
||||
border: none;
|
||||
height: 18px;
|
||||
cursor: pointer;
|
||||
background-repeat: no-repeat;
|
||||
}
|
||||
|
||||
.daysOfWeek {
|
||||
display: flex;
|
||||
justify-content: space-around;
|
||||
padding: 15px 0;
|
||||
font-size: 14px;
|
||||
color: #7E818C;
|
||||
}
|
||||
|
||||
.dates {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(7, 1fr);
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.date {
|
||||
width: 26px;
|
||||
height: 26px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
cursor: pointer;
|
||||
border-radius: 50%;
|
||||
font-size: 14px;
|
||||
color: rgba(0, 0, 0, 0.75);
|
||||
}
|
||||
|
||||
.date:hover {
|
||||
background-color: #8086F939;
|
||||
}
|
||||
|
||||
.date.selected {
|
||||
background-color: #8086F9;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.video-popup {
|
||||
display: none;
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
/* background-color: rgba(0, 0, 0, 0.6); */
|
||||
z-index: 999;
|
||||
}
|
||||
|
||||
.video-popup-content {
|
||||
position: absolute;
|
||||
/* top: 10%; */
|
||||
/* left: 50%; */
|
||||
transform: translate(-50%, -50%);
|
||||
/* background-color: white; */
|
||||
padding: 20px;
|
||||
/* box-shadow: 0 0 10px rgba(0, 0, 0, 0.3); */
|
||||
text-align: center;
|
||||
z-index: 101;
|
||||
}
|
||||
|
||||
.close-popup {
|
||||
position: absolute;
|
||||
top: 10px;
|
||||
right: 10px;
|
||||
font-size: 24px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.popup-video-container {
|
||||
width: 80%;
|
||||
max-width: 800px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
.video-time {
|
||||
width: calc(75% - 300px - 10px - 5px);
|
||||
height: 50px;
|
||||
position: absolute;
|
||||
right: 7px;
|
||||
bottom: 7px;
|
||||
background-color:white;
|
||||
border-radius: 15px;
|
||||
}
|
||||
input[type="time"]::-webkit-calendar-picker-indicator {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.video-time input[type="time"] {
|
||||
padding: 6px;
|
||||
border-radius: 10px;
|
||||
border: 1px solid rgba(0, 0, 0, 0.10);
|
||||
background: #FFF;
|
||||
font-size: 16px;
|
||||
outline: none;
|
||||
cursor: text;
|
||||
margin: 8px !important;
|
||||
}
|
||||
|
||||
input[type="time"]:hover,
|
||||
input[type="time"]:focus {
|
||||
border: 1px solid rgba(0, 0, 0, 0.30);
|
||||
}
|
||||
|
||||
@media (max-width: 1950px) {
|
||||
/* при разрешении монитора до 1950 пикселей */
|
||||
|
||||
|
@ -40,7 +40,7 @@
|
||||
<a href="/live">
|
||||
<div><img src="../img/waves.svg">Трансляция</div>
|
||||
</a>
|
||||
<a href="/">
|
||||
<a href="/videos">
|
||||
<div><img src="../img/play.svg">Записи</div>
|
||||
</a>
|
||||
<a class="settings" href="/">
|
||||
|
@ -40,7 +40,7 @@
|
||||
<a href="/live">
|
||||
<div><img src="../img/waves.svg">Трансляция</div>
|
||||
</a>
|
||||
<a href="/">
|
||||
<a href="/videos">
|
||||
<div><img src="../img/play.svg">Записи</div>
|
||||
</a>
|
||||
<a class="settings" href="/">
|
||||
|
@ -40,7 +40,7 @@
|
||||
<a href="/live">
|
||||
<div><img src="../img/waves.svg">Трансляция</div>
|
||||
</a>
|
||||
<a href="/">
|
||||
<a href="/videos">
|
||||
<div><img src="../img/play.svg">Записи</div>
|
||||
</a>
|
||||
<a class="settings" href="/">
|
||||
|
@ -42,7 +42,7 @@
|
||||
<a href="/live">
|
||||
<div><img src="../img/waves.svg">Трансляция</div>
|
||||
</a>
|
||||
<a href="/">
|
||||
<a href="/videos">
|
||||
<div><img src="../img/play.svg">Записи</div>
|
||||
</a>
|
||||
<a class="settings" href="/">
|
||||
|
@ -45,7 +45,7 @@
|
||||
<a href="/live">
|
||||
<div class="selected"><img src="../img/waves.svg">Трансляция</div>
|
||||
</a>
|
||||
<a href="/">
|
||||
<a href="/videos">
|
||||
<div><img src="../img/play.svg">Записи</div>
|
||||
</a>
|
||||
<a class="settings" href="/">
|
||||
@ -115,6 +115,15 @@
|
||||
|
||||
</section>
|
||||
|
||||
<div id="video-popup" class="video-popup">
|
||||
<div id="video-popup-content" class="video-popup-content">
|
||||
<span class="close-popup" id="close-popup">×</span>
|
||||
<div id="popup-video-container" class="popup-video-container">
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
<section class="table" style="position: relative;">
|
||||
@ -128,7 +137,8 @@
|
||||
<ul id="list">
|
||||
{{#each Alarms}}
|
||||
<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}}">
|
||||
<h2>{{this.type}}</h2>
|
||||
<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"/>
|
||||
</svg></p> -->
|
||||
</label>
|
||||
</a>
|
||||
</li>
|
||||
{{/each}}
|
||||
|
||||
@ -156,7 +167,7 @@
|
||||
|
||||
</div>
|
||||
|
||||
<div class="map">
|
||||
<div class="stream-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>
|
||||
@ -166,11 +177,11 @@
|
||||
<div id="map"></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">
|
||||
<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="video-container">
|
||||
<div class="stream-video-container">
|
||||
<!-- <video id="camera-1"></video> -->
|
||||
<video id="camera-2"></video>
|
||||
<video id="camera-3"></video>
|
||||
@ -178,8 +189,8 @@
|
||||
<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-8"></video>
|
||||
<video id="camera-9"></video>
|
||||
<video id="camera-10"></video>
|
||||
<video id="camera-11"></video>
|
||||
<video id="camera-12"></video>
|
||||
@ -348,6 +359,19 @@
|
||||
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();
|
||||
xhr.open("GET", "../scripts/style.json", true);
|
||||
@ -366,9 +390,21 @@
|
||||
{{#if ifDBError}}
|
||||
{{else}}
|
||||
|
||||
const selectedDevices = Array.from(checkboxes)
|
||||
.filter(checkbox => checkbox.checked && checkbox.value !== 'on')
|
||||
.map(checkbox => checkbox.value);
|
||||
const selectedDevices = Array.from(checkboxes)
|
||||
.filter(checkbox => checkbox.checked && checkbox.value !== 'on')
|
||||
.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) {
|
||||
const { serial, status, longitude, latitude, direction, speed } = device;
|
||||
@ -410,6 +446,7 @@
|
||||
}
|
||||
|
||||
function fetchAndShowMarkers() {
|
||||
console.log(selectedDevices);
|
||||
fetch('/devices-geo', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
@ -436,8 +473,9 @@
|
||||
|
||||
const propertiesDiv = document.getElementById('properties');
|
||||
|
||||
console.log(selectedDevice);
|
||||
|
||||
if (selectedDevice) {
|
||||
// Вывод нужных свойств устройства в консоль
|
||||
// groupElement.textContent = selectedDevice.group;
|
||||
speedElement.textContent = selectedDevice.speed + ' км/ч';
|
||||
plateElement.textContent = selectedDevice.plate;
|
||||
@ -574,6 +612,80 @@ for (var i = 0; i < tableCheckboxes.length; i++) {
|
||||
|
||||
</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>
|
||||
|
@ -40,7 +40,7 @@
|
||||
<a href="/live">
|
||||
<div><img src="../img/waves.svg">Трансляция</div>
|
||||
</a>
|
||||
<a href="/">
|
||||
<a href="/videos">
|
||||
<div><img src="../img/play.svg">Записи</div>
|
||||
</a>
|
||||
<a class="settings" href="/">
|
||||
|
@ -43,7 +43,7 @@
|
||||
<a href="/live">
|
||||
<div><img src="../img/waves.svg">Трансляция</div>
|
||||
</a>
|
||||
<a href="/">
|
||||
<a href="/videos">
|
||||
<div><img src="../img/play.svg">Записи</div>
|
||||
</a>
|
||||
<a class="settings" href="/">
|
||||
@ -168,14 +168,18 @@
|
||||
|
||||
<video id="my-video" src="../test_video.MP4"></video>
|
||||
|
||||
<div class="map">
|
||||
<div class="stream-map">
|
||||
<div id="map"></div>
|
||||
</div>
|
||||
</section>
|
||||
</section>
|
||||
</section>
|
||||
</section>
|
||||
|
||||
<style>
|
||||
.map {
|
||||
.stream-map {
|
||||
width: calc(100% - 450px - 514px);
|
||||
height: calc(100% - 62px);
|
||||
float: right;
|
||||
}
|
||||
</style>
|
||||
@ -253,7 +257,7 @@ var map = new ol.Map({
|
||||
],
|
||||
view: new ol.View({
|
||||
center: ol.proj.fromLonLat([prevLongitude, prevLatitude]),
|
||||
zoom: 17
|
||||
zoom: 14
|
||||
})
|
||||
});
|
||||
|
||||
|
760
static/templates/videos/see.html
Normal file
760
static/templates/videos/see.html
Normal 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>
|
Loading…
Reference in New Issue
Block a user