<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Экспорт записей</title> <link rel="stylesheet" href="../styles/main.css" /> <link rel="stylesheet" href="https://unpkg.com/leaflet@1.7.1/dist/leaflet.css" /> <script src="https://unpkg.com/leaflet@1.7.1/dist/leaflet.js"></script> </head> <body> <header> <img src="../img/argus.png"> <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="/logout"><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 href="https://forms.yandex.ru/cloud/6515ecda3e9d08f17262c332/" target="_blank"> <div><img src="../img/bug.svg">Собщить об ошибке</div> </a> {{#if isAdmin}} <a class="admin-panel" href="/admin"> <div><img src="../img/keyboard.svg">Админка</div> </a> {{/if}} <a class="settings" href="/settings"> <div><img src="../img/gear.svg">Настройки</div> </a> </section> <section class="main"> <section style="display: none;" class="dberror" id="exportLoading" > <div class="erorr-container"> <div class="loader-container"> <div id="loader" class="loader"> <div class="square" id="sq1"></div> <div class="square" id="sq2"></div> <div class="square" id="sq3"></div> <div class="square" id="sq4"></div> <div class="square" id="sq5"></div> <div class="square" id="sq6"></div> <div class="square" id="sq7"></div> <div class="square" id="sq8"></div> <div class="square" id="sq9"></div> </div> </div> <h1>Подготовка видео</h1> <br> <span id="status">Пожалуйста, подождите..</span> </div> </section> <section style="display: none;" class="dberror" id="dataLoading" > <div class="loader-container"> <div class="loader"> <div class="square" id="sq11"></div> <div class="square" id="sq12"></div> <div class="square" id="sq13"></div> <div class="square" id="sq14"></div> <div class="square" id="sq15"></div> <div class="square" id="sq16"></div> <div class="square" id="sq17"></div> <div class="square" id="sq18"></div> <div class="square" id="sq19"></div> </div> </div> </section> {{#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}} <nav> <a href="/videos">Воспроизведение</a> <a class="selected" href="/videos/export">Экспорт</a> </nav> <section class="bg"> <section class="content"> <section style="min-height: 800px;" class="for-table"> <section class="organisation"> <h1>Организация</h1> <ul class="area"> {{#each Groups}} <li class="area-name"><img src="../img/ul.svg"><input type="checkbox" id="{{name}}" class="checkbox-input" hidden checked><label for="{{name}}" class="checkbox-label">{{name}}</label> <ul class="area-devices" id="devices-1"> {{#each devices}} <li class="device"> <img> <input type="number" id="channels-{{this.serial}}" value="{{this.channels}}" hidden> <input type="radio" name="camera-serial" id="radio-{{this.serial}}" class="radio-input" value="{{this.serial}}" hidden> <label for="radio-{{this.serial}}" class="radio-label active-{{this.status}}"> {{#if this.number}} {{this.number}} {{else}} {{this.serial}} {{/if}} </label> </li> {{/each}} </ul> </li> {{/each}} </ul> </section> <section class="table" style="position: relative;"> <div class="map"> <div id="map"></div> </div> <div style="background: white;" class="cameras"> <section style="border: 0;" class="whole-width"> <h1>Экспортировать запись с камеры</h1> <h3>Выберите камеру, время и нажмите кнопку "Скачать запись"</h3> <form id="edit-group-form"> <div class="parameters-input"> <label for="group-id">Камера</label> <select name="group-id" id="group-id"> <option value="">Выберите номер камеры</option> <option value="1">Камера 1</option> <option value="2">Камера 2</option> <option value="3">Камера 3</option> <option value="4">Камера 4</option> <option value="5">Камера 5</option> <option value="6">Камера 6</option> <option value="7">Камера 7</option> <option value="8">Камера 8</option> <option value="9">Камера 9</option> <option value="10">Камера 10</option> <option value="11">Камера 11</option> <option value="12">Камера 12</option> <option value="13">Камера 13</option> </select> </div> </form> <div style="margin-left: 44px; margin-right: 72px; width: calc(100% - 44px - 72px);" class="horizontal-line"></div> <button style="margin-top: 15px;" id="group-edit" class="button-purple" type="button" onclick="playVideo();">Скачать запись</button> </section> </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"> <div style="display: none;" id="speed-bg" class="speed-bg"> <h1>Данных для выбранного периода нет.</h1> </div> <h1>Скорость</h1> <span>км/ч</span> <span style="float: right; font-size: 18px;">1 ч</span> <div> <canvas id="speed"></canvas> </div> </div> <div class="video-time"> <span style="margin-left: 15px;">с</span> <input name="videoTime" type="time" id="video-time" step="1"> <span>до</span> <input name="videoEndTime" type="time" id="video-end-time" step="1"> </div> </section> </section> </section> </section> </section> <style> .table { background-color:#f7f7fa !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: 190px; } </style> <script src="../scripts/jquery.min.js"></script> <script src="https://cdn.jsdelivr.net/npm/chart.js"></script> <script> function updateCameraOptions() { var selectedSerial = document.querySelector('input[name="camera-serial"]:checked').value; var channelsInput = document.querySelector('#channels-' + selectedSerial); var numChannels = parseInt(channelsInput.value, 10); var select = document.querySelector('#group-id'); select.innerHTML = ''; var defaultOption = document.createElement('option'); defaultOption.value = ''; defaultOption.textContent = 'Выберите номер камеры'; select.appendChild(defaultOption); for (var i = 1; i <= numChannels; i++) { var option = document.createElement('option'); option.value = i; option.textContent = 'Камера ' + i; select.appendChild(option); } } </script> <script> document.addEventListener('DOMContentLoaded', function () { const areaNames = document.querySelectorAll('.area-name'); areaNames.forEach(function (areaName) { const areaCheckbox = areaName.querySelector('.checkbox-input'); const deviceCheckboxes = areaName.querySelectorAll('.device .checkbox-input'); const deviceList = areaName.querySelector('.area-devices'); // Функция для скрытия/показа дочерних элементов function toggleChildDevices(show) { if (show) { deviceList.style.display = 'block'; } else { deviceList.style.display = 'none'; } } // Инициализация состояния чекбоксов и скрытия/показа дочерних элементов toggleChildDevices(areaCheckbox.checked); deviceCheckboxes.forEach(function (deviceCheckbox) { deviceCheckbox.checked = areaCheckbox.checked; }); areaCheckbox.addEventListener('change', function () { const isChecked = areaCheckbox.checked; deviceCheckboxes.forEach(function (deviceCheckbox) { deviceCheckbox.checked = isChecked; }); toggleChildDevices(isChecked); }); deviceCheckboxes.forEach(function (deviceCheckbox) { deviceCheckbox.addEventListener('change', function () { const allUnchecked = Array.from(deviceCheckboxes).every(function (checkbox) { return !checkbox.checked; }); if (allUnchecked) { areaCheckbox.checked = false; toggleChildDevices(false); } else { areaCheckbox.checked = true; toggleChildDevices(true); } }); }); }); }); </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; } let HasData; let recordID; var selectedChannel = 1; async function sendPostRequest() { updateCameraOptions(); document.getElementById("dataLoading").style.display = 'flex'; // Получение данных из полей ввода const selectedDate = document.getElementById("selectedDate").value; const videoTime = document.getElementById("video-time").value; const videoEndTime = document.getElementById("video-end-time").value; const selectedSerial = document.querySelector('input[name="camera-serial"]:checked').value; // Объединяем дату и время и преобразуем в нужный формат const combinedDateTime = combineDateTime(selectedDate, videoTime); const response = await fetch(`/getData?serial=${selectedSerial}&selectedDate=${formatDate(selectedDate)}&selectedTime=${formatTime(videoTime)}&selectedChannel=${selectedChannel}`); const data = await response.json(); HasData = data.success; if (data.success) { console.log(`Данные доступны. DATAID: ${data.dataId}`) recordID = data.dataId; 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: data.names, 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: { ticks: { autoSkip: false, callback: function(value, index, values) { const data = this.chart.data.labels; if (index === 0 || index === values.length - 1) { return data[index]; } else { return ''; } }, position: 'bottom', maxRotation: 0, }, grid: { display: false, }, }, }, }, }); const geoData = data.geo; // Очищаем все слои на карте map.eachLayer(layer => { if (layer !== map) { map.removeLayer(layer); } }); // Добавляем слой с плитками OpenStreetMap L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png').addTo(map); // Создаем слой для маршрута const routeLayer = L.layerGroup().addTo(map); // Создаем слой для маркеров const markerLayer = L.layerGroup().addTo(map); // Преобразуем координаты точек маршрута const routePoints = geoData.map(point => [point.latitude, point.longitude]); // Создаем линию маршрута const route = L.polyline(routePoints, { color: 'red',weight: 5 }).addTo(routeLayer); }) .catch((error) => { document.getElementById("dataLoading").style.display = 'none'; var h1Element = document.querySelector('.speedometr h1'); h1Element.textContent = 'Ошибка отправки запроса.'; console.error("Ошибка при отправке запроса:", error); }); const endResponse = await fetch(`/getData?serial=${selectedSerial}&selectedDate=${formatDate(selectedDate)}&selectedTime=${formatTime(videoEndTime)}&selectedChannel=${selectedChannel}`); const endData = await endResponse.json(); if (endData.success) { document.getElementById("dataLoading").style.display = 'none'; console.log(`Конечные данные доступны. DATAID: ${endData.dataId}`) const speedBG = document.getElementById("speed-bg"); speedBG.style.display = 'none'; HasData = true; if (data.dataId != endData.dataId) { const existingChart = Chart.getChart("speed"); if (existingChart) { existingChart.destroy(); } const chart = new Chart("speed", { type: "line", data: { labels: "", datasets: [ { label: "Скорость", borderColor: "#8086F9", fill: false, data: "", 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, }, }, }, }, }); HasData = false; var h1Element = document.querySelector('.speedometr h1'); h1Element.textContent = 'Временной диапазон находится в разных видео сессиях. Измените время.'; const speedBG = document.getElementById("speed-bg"); speedBG.style.display = 'flex'; } } else { document.getElementById("dataLoading").style.display = 'none'; const existingChart = Chart.getChart("speed"); if (existingChart) { existingChart.destroy(); } const chart = new Chart("speed", { type: "line", data: { labels: "", datasets: [ { label: "Скорость", borderColor: "#8086F9", fill: false, data: "", 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, }, }, }, }, }); HasData = false; const speedBG = document.getElementById("speed-bg"); speedBG.style.display = 'flex'; var h1Element = document.querySelector('.speedometr h1'); h1Element.textContent = 'Данных для выбранного периода нет.'; } } else { const existingChart = Chart.getChart("speed"); if (existingChart) { existingChart.destroy(); } const chart = new Chart("speed", { type: "line", data: { labels: "", datasets: [ { label: "Скорость", borderColor: "#8086F9", fill: false, data: "", 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, }, }, }, }, }); var h1Element = document.querySelector('.speedometr h1'); h1Element.textContent = 'Данных для выбранного периода нет.'; const speedBG = document.getElementById("speed-bg"); speedBG.style.display = 'flex'; document.getElementById("dataLoading").style.display = 'none'; } } 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("blur", sendPostRequest); const endVideoTimeInput = document.getElementById("video-end-time"); endVideoTimeInput.addEventListener("blur", 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; const startTimeInput = document.getElementById('video-time'); const endTimeInput = document.getElementById('video-end-time'); const startTime = new Date(`1970-01-01T${startTimeInput.value}Z`); startTime.setHours(startTime.getHours() - 3); startTime.setSeconds(startTime.getSeconds() + 10); const hours = startTime.getHours().toString().padStart(2, '0'); const minutes = startTime.getMinutes().toString().padStart(2, '0'); const seconds = startTime.getSeconds().toString().padStart(2, '0'); const endTimeString = `${hours}:${minutes}:${seconds}`; endTimeInput.value = endTimeString; </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(); sendPostRequest(); }); datesContainer.appendChild(dateCell); } } // Перейти на предыдущий месяц 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}`; } async function playVideo() { const channel = document.getElementById("group-id").value; const selectedDevice = document.querySelector('input[name="camera-serial"]:checked'); if (!selectedDevice) { alert('Пожалуйста, выберите устройство из списка.'); return; } if (channel === "") { alert('Пожалуйста, выберите камеру из списка.'); return; } if (!HasData) { alert('Пожалуйста, выберите другой временной период.'); return; } if (videoTimeInput.value > endVideoTimeInput.value) { alert('Конечное время не может быть больше начального.'); return; } document.getElementById("exportLoading").style.display = 'flex'; const startTimeInput = document.getElementById('video-time'); const endTimeInput = document.getElementById('video-end-time'); const selectedDateInput = document.getElementById('selectedDate'); const startTime = formatTime(startTimeInput.value); const endTime = formatTime(endTimeInput.value); const selectedDate = formatDate(selectedDateInput.value); const reqDate = document.getElementById("selectedDate").value; const reqTime = document.getElementById("video-time").value; const reqSerial = document.querySelector('input[name="camera-serial"]:checked').value; const finalResponse = await fetch(`/getData?serial=${reqSerial}&selectedDate=${formatDate(reqDate)}&selectedTime=${formatTime(reqTime)}&selectedChannel=${channel}`); const resData = await finalResponse.json(); const serial = selectedDevice.value; const url = `http://localhost:8081/export?url=http%3A%2F%2F{{VIRTUAL_HOST}}%2Fhttp%2Fdownload.flv%3Fserial%3D${serial}%26channel%3D${channel}%26queryTime%3D${selectedDate}%26startTime%3D${startTime}%26endTime%3D${endTime}%26recordID%3D${resData.dataId}`; document.getElementById("exportLoading").style.display = 'none'; window.open(url, '_blank'); } </script> <script> window.addEventListener('DOMContentLoaded', function() { var mapContainer = document.querySelector('.map'); var mapArea = document.getElementById('map'); mapArea.style.height = (mapContainer.clientHeight) + 'px'; mapArea.style.width = (mapContainer.clientWidth) + 'px'; }); window.addEventListener("resize", function (event) { var mapContainer = document.querySelector('.map'); var mapArea = document.getElementById('map'); mapArea.style.height = (mapContainer.clientHeight) + 'px'; mapArea.style.width = (mapContainer.clientWidth) + 'px'; }); var startPoint = [0, 0]; var endPoint = [0, 0]; let map; // Создаем карту Leaflet map = L.map('map').setView([59.855198, 30.282995], 10); // Добавляем базовый слой OpenStreetMap L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png').addTo(map); // Создаем маркеры var startMarker = L.marker(startPoint, { icon: L.divIcon({ className: 'custom-icon', html: '<div style="background-color: red; width: 14px; height: 14px; border: 2px solid white; border-radius: 50%;"></div>' }) }).addTo(map); var endMarker = L.marker(endPoint, { icon: L.divIcon({ className: 'custom-icon', html: '<div style="background-color: red; width: 14px; height: 14px; border: 2px solid white; border-radius: 50%;"></div>' }) }).addTo(map); // Создаем линию маршрута var route = L.polyline([startPoint, endPoint], { color: 'red', weight: 6 }).addTo(map); // Скрыть кнопки приближения/отдаления map.zoomControl.remove(); // Скрыть информационную панель map.attributionControl.remove(); </script> <script> // Скрытие/Показ дополнительных меню аккаунта const accountMain = document.getElementById('account-main'); const accountAdditional = document.getElementById('account-additional'); const accountUp = document.getElementById('up'); const accountDown = document.getElementById('down'); accountAdditional.style.display = 'none'; accountUp.style.display = 'none'; accountMain.addEventListener('click', () => { if (accountAdditional.style.display === 'none') { accountAdditional.style.display = 'flex'; accountUp.style.display = 'unset'; accountDown.style.display = 'none'; } else { accountAdditional.style.display = 'none'; accountUp.style.display = 'none'; accountDown.style.display = 'unset'; } }); </script> <script> Chart.defaults.color = "rgba(0, 0, 0, 0.4)"; Chart.defaults.font.size = 15; Chart.defaults.font.weight = 400; var speedData = { labels: [ "", "", "", "", "", "", "", "", "", "", "", ], datasets: [ { label: "Скорость", borderColor: "#8086F9", fill: false, data: "", 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>