664 lines
		
	
	
		
			28 KiB
		
	
	
	
		
			HTML
		
	
	
	
	
	
			
		
		
	
	
			664 lines
		
	
	
		
			28 KiB
		
	
	
	
		
			HTML
		
	
	
	
	
	
| <!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 class="selected"><img src="../img/waves.svg">Трансляция</div>
 | ||
|         </a>
 | ||
|         <a href="/videos">
 | ||
|             <div><img src="../img/play.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">
 | ||
|       {{#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 class="selected" href="/live">Отслеживание</a>
 | ||
|         </nav>
 | ||
|         <section class="bg">
 | ||
|             <section class="content">
 | ||
|                 <section 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="checkbox" name="devices" id="{{this.id}}" class="checkbox-input device-filter" value="{{this.id}}" hidden checked>
 | ||
|                                 <label for="{{this.id}}" class="checkbox-label active-{{this.status}}">
 | ||
|                                   <div class="checkmark"></div>
 | ||
|                                 </label>
 | ||
|                                 <input type="number" id="channels-{{this.serial}}" value="{{this.channels}}" hidden>
 | ||
|                                 <input type="radio" name="camera-serial" id="radio-{{this.serial}}" class="radio-input" value="{{this.serial}}" hidden>
 | ||
|                                 <label for="radio-{{this.serial}}" class="radio-label active-{{this.status}}">
 | ||
|                                   {{#if this.number}}
 | ||
|                                       {{this.number}}
 | ||
|                                     {{else}}
 | ||
|                                       {{this.serial}}
 | ||
|                                   {{/if}}
 | ||
|                                   
 | ||
|                                 </label>
 | ||
|                               </li>
 | ||
|                                   {{/each}}
 | ||
|                             </ul>
 | ||
|                           </li>
 | ||
|                           {{/each}}
 | ||
|                           </ul>
 | ||
| 
 | ||
|                         
 | ||
| 
 | ||
|                           
 | ||
|                     </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;">
 | ||
|                       <div id="signals-list" class="signals-list active">
 | ||
|                         <h1 id="signalsListName">Сигналы ТС</h1>
 | ||
|                         <!-- <input class="search" type="text" placeholder="Поиск"> -->
 | ||
|                         <svg id="left-slider" class="left-slider" xmlns="http://www.w3.org/2000/svg" width="19" height="19" fill="none" viewBox="0 0 11 19">
 | ||
|                           <path fill="#000" fill-opacity=".75" d="M0 9.495c0 .273.101.514.315.722l8.92 8.477a.981.981 0 0 0 .73.295c.585 0 1.035-.427 1.035-.995 0-.285-.124-.525-.304-.711L2.508 9.495l8.188-7.789c.18-.186.304-.437.304-.71C11 .425 10.55 0 9.965 0c-.292 0-.54.098-.73.284L.314 8.773A.955.955 0 0 0 0 9.495Z"/>
 | ||
|                         </svg>
 | ||
| 
 | ||
|                         <ul id="list">
 | ||
|                           {{#each Alarms}}
 | ||
|                           <li>
 | ||
|                             <a style="color:rgba(0, 0, 0, 0.7);" href="/reports/{{this.id}}" target="_blank">
 | ||
|                             <label for="signal-{{this.id}}">
 | ||
|                               <p class="name"><svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" fill="none" viewBox="0 0 24 19">
 | ||
|                                 <path fill="#000" fill-opacity=".75" d="M4.58 6.143c-.078.341.068.478.44.459 1.562-.108 3.418-.196 6.533-.196 3.125 0 4.98.088 6.543.196.361.02.508-.118.43-.46-.235-1.035-.684-2.382-.997-2.92-.254-.439-.537-.634-1.045-.702-.703-.098-2.304-.166-4.931-.166-2.617 0-4.219.068-4.932.166-.508.068-.79.263-1.045.703-.312.537-.752 1.884-.996 2.92Zm-.068 6.65c.888 0 1.572-.684 1.572-1.572 0-.899-.684-1.573-1.572-1.573-.889 0-1.573.674-1.573 1.573 0 .888.684 1.572 1.573 1.572Zm4.463-.39h5.166c.664 0 1.123-.46 1.123-1.133 0-.665-.46-1.123-1.123-1.123H8.975c-.674 0-1.133.458-1.133 1.123 0 .673.459 1.132 1.133 1.132Zm9.619.39c.898 0 1.572-.684 1.572-1.572 0-.899-.674-1.573-1.572-1.573-.889 0-1.563.674-1.563 1.573 0 .888.674 1.572 1.563 1.572Zm-7.041 2.637c3.281 0 7.646-.166 9.492-.381 1.328-.147 2.06-.88 2.06-2.13v-1.718c0-1.65-.332-2.568-1.23-3.74l-.83-1.065c-.352-1.757-1.006-3.603-1.338-4.326C19.18.967 18.174.313 16.875.137 16.221.049 14.082 0 11.553 0 9.033 0 6.895.059 6.24.137 4.941.293 3.926.967 3.408 2.07c-.342.723-.986 2.569-1.347 4.326l-.82 1.065C.331 8.633 0 9.55 0 11.2v1.719c0 1.25.742 1.982 2.06 2.129 1.856.215 6.211.38 9.493.38Zm0-1.28c-3.32 0-7.578-.156-9.15-.351-.83-.098-1.124-.527-1.124-1.27v-1.328c0-1.338.215-1.963.977-2.959l.996-1.308c.264-1.416.898-3.39 1.318-4.288.313-.673.928-1.093 1.817-1.2.625-.079 2.607-.167 5.166-.167 2.568 0 4.58.088 5.146.166.918.117 1.524.537 1.846 1.201.42.899 1.055 2.872 1.308 4.288l1.006 1.308c.752.996.967 1.621.967 2.96v1.327c0 .742-.293 1.172-1.123 1.27-1.562.195-5.83.351-9.15.351Zm-9.756 3.965h1.142c.733 0 1.3-.566 1.3-1.289v-2.324l-3.741-.537v2.861c0 .723.566 1.29 1.299 1.29Zm18.369 0h1.152a1.27 1.27 0 0 0 1.29-1.289v-2.861l-3.731.537v2.324a1.27 1.27 0 0 0 1.289 1.29Z"/>
 | ||
|                               </svg>{{this.serial}}</p>
 | ||
|                               <p class="name"><svg xmlns="http://www.w3.org/2000/svg" width="13" height="14" fill="none" viewBox="0 0 13 14">
 | ||
|                                 <path fill="rgba(255, 106, 75, 0.8)" fill-opacity=".85" d="M0 10.722c0 .494.386.82 1.04.82h2.908c.055 1.307 1.095 2.451 2.549 2.451 1.46 0 2.5-1.138 2.555-2.452h2.908c.647 0 1.04-.325 1.04-.82 0-.677-.703-1.286-1.295-1.889-.455-.467-.579-1.429-.634-2.208-.048-2.668-.75-4.389-2.583-5.04C8.253.699 7.516 0 6.497 0 5.484 0 4.74.698 4.512 1.585c-1.832.65-2.535 2.37-2.583 5.04-.055.778-.18 1.74-.634 2.207C.695 9.435 0 10.044 0 10.722Zm1.337-.203v-.082c.124-.196.537-.596.895-.989.496-.541.73-1.415.792-2.736.055-2.96.951-3.901 2.13-4.22.171-.04.268-.121.275-.298.02-.704.434-1.198 1.068-1.198.64 0 1.047.494 1.074 1.198.007.177.097.258.269.299 1.185.318 2.08 1.26 2.136 4.22.062 1.32.296 2.194.785 2.735.365.393.772.793.896.99v.08H1.337Zm3.685 1.022h2.956c-.055.922-.648 1.497-1.481 1.497-.827 0-1.427-.575-1.475-1.497Z"/>
 | ||
|                               </svg>{{this.type}}</p>
 | ||
|                             </label>
 | ||
|                           </a>
 | ||
|                           </li>
 | ||
|                               {{/each}}
 | ||
|                           
 | ||
|                         </ul>
 | ||
| 
 | ||
|                       </div>
 | ||
| 
 | ||
|                       <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>
 | ||
|                           <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="stream-cameras">
 | ||
|                         <div class="stream-video-container">
 | ||
|                           <video id="camera-1"></video>
 | ||
|                           <video id="camera-2"></video>
 | ||
|                           <video id="camera-3"></video>
 | ||
|                           <video id="camera-4"></video>
 | ||
|                           <video style="display: none;" id="camera-5"></video>
 | ||
|                         </div>
 | ||
|                         <button onclick="switchCameras(-1)">Назад</button>
 | ||
|                         <button onclick="switchCameras(1)">Вперед</button>
 | ||
|                       </div>
 | ||
| 
 | ||
| 
 | ||
|                     </section>
 | ||
|                       
 | ||
| 
 | ||
|                 </section>
 | ||
| 
 | ||
|             </section>
 | ||
|         </section>
 | ||
|     </section>
 | ||
| 
 | ||
|     <script src="../scripts/jquery.min.js"></script>
 | ||
|     <script src="https://cdnjs.cloudflare.com/ajax/libs/flv.js/1.5.0/flv.min.js"></script>
 | ||
| 
 | ||
|     <script>
 | ||
|       document.addEventListener("DOMContentLoaded", function () {
 | ||
|       const leftSlider = document.getElementById("left-slider");
 | ||
|       const signalsList = document.getElementById("signals-list");
 | ||
| 
 | ||
|       leftSlider.addEventListener("click", function () {
 | ||
|         signalsList.classList.toggle("active");
 | ||
| 
 | ||
|         const listItems = signalsList.querySelectorAll("li");
 | ||
| 
 | ||
|         if (signalsList.classList.contains("active")) {
 | ||
|           listItems.forEach(function (item, index) {
 | ||
|             setTimeout(function () {
 | ||
|               item.classList.add("active");
 | ||
|             }, index * 50); 
 | ||
|           });
 | ||
|           $(".stream-cameras").css("width", "calc(100% - 345px - 2px)");
 | ||
|           setTimeout(() => $("#signalsListName").show(), 200);
 | ||
|         } else {
 | ||
|           listItems.forEach(function (item) {
 | ||
|             item.classList.remove("active");
 | ||
|           });
 | ||
|           $(".stream-cameras").css("width", "calc(100% - 40px - 2px)");
 | ||
|             $("#signalsListName").hide();
 | ||
|         }
 | ||
|       });
 | ||
|     });
 | ||
| 
 | ||
|     </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>
 | ||
|       var cameraSerials = document.querySelectorAll('.radio-input');
 | ||
| 
 | ||
|       cameraSerials.forEach((checkbox) => {
 | ||
|         checkbox.addEventListener('change', function() {
 | ||
|           serial = $("input[name=camera-serial]:checked").val()
 | ||
|           stopAllCameras();
 | ||
|           currentCameraGroup = 1
 | ||
|           totalCameras = $(`#channels-${serial}`).val() - 1;
 | ||
|           playNextCamerasInGroup();
 | ||
| 
 | ||
|           const markers = document.querySelectorAll('.area-devices .device');
 | ||
|           markers.forEach(marker => {
 | ||
|             const serial = marker.querySelector('.radio-input').value;
 | ||
| 
 | ||
|             // Получаем элемент с соответствующим id
 | ||
|             const svgId = `marker-${serial}`;
 | ||
|             const svgElement = document.getElementById(svgId);
 | ||
| 
 | ||
|             if (svgElement) {
 | ||
|               // Проверяем serial и устанавливаем цвет
 | ||
|               if (serial === $("input[name=camera-serial]:checked").val()) {
 | ||
|                 svgElement.innerHTML = `
 | ||
|                   <path fill="#FF6A4B" d="M12.669 24.673c6.565 0 12-5.435 12-12 0-6.553-5.447-12-12.012-12C6.104.673.67 6.12.67 12.673c0 6.565 5.447 12 12 12Z"/>
 | ||
|                   <path fill="#fff" fill-opacity=".85" d="m7.104 17.92 4.683-11.93c.33-.823 1.423-.846 1.753-.023l4.682 11.953c.318.788-.553 1.47-1.294.73l-3.953-3.953c-.188-.2-.424-.2-.612 0L8.41 18.65c-.753.74-1.623.059-1.306-.730Z"/>
 | ||
|                 `;
 | ||
|               } else {
 | ||
|                 svgElement.innerHTML = `
 | ||
|                   <path d="M12.9815 25.0195C19.5462 25.0336 24.9931 19.6101 25.0073 13.0454C25.0214 6.49253 19.5861 1.03375 13.0214 1.01961C6.46848 1.00549 1.02147 6.44081 1.00735 12.9937C0.993201 19.5584 6.42852 25.0054 12.9815 25.0195Z" fill="#8086F9"/>
 | ||
|                   <path d="M7.43175 18.2547L12.1398 6.33536C12.471 5.51254 13.5652 5.49137 13.8928 6.31561L18.5493 18.2786C18.8653 19.0675 17.9932 19.748 17.2537 19.0052L13.3092 15.0438C13.1215 14.8434 12.8862 14.8429 12.6975 15.0425L8.73605 18.9868C7.98152 19.7264 7.1124 19.0422 7.43175 18.2547Z" fill="white" fill-opacity="0.85"/>
 | ||
|                 `;
 | ||
|               }
 | ||
|             }
 | ||
|           });
 | ||
| 
 | ||
|         });
 | ||
|       });
 | ||
|     </script>
 | ||
|     <script>
 | ||
|       let flvPlayers = [];
 | ||
|       let currentCameraGroup = 1;
 | ||
|       const camerasPerGroup = 5;
 | ||
|       var totalCameras = 12 - 1; 
 | ||
|       const baseURL = "http://45.9.42.252:8080/http/live.flv";
 | ||
|     
 | ||
|       function stopAllCameras() {
 | ||
|         flvPlayers.forEach(player => {
 | ||
|           player.pause();
 | ||
|           player.unload();
 | ||
|           player.detachMediaElement();
 | ||
|           player.destroy();
 | ||
|         });
 | ||
|         flvPlayers = [];
 | ||
|       }
 | ||
|     
 | ||
|       async function playNextCamerasInGroup() {
 | ||
|   const startCamera = currentCameraGroup === 1 ? 2 : (currentCameraGroup - 1) * camerasPerGroup;
 | ||
|   const endCamera = startCamera + camerasPerGroup - 1;
 | ||
| 
 | ||
|   if (startCamera > totalCameras) {
 | ||
|     // currentCameraGroup = 1;
 | ||
|     // playNextCamerasInGroup();
 | ||
|     return;
 | ||
|   }
 | ||
| 
 | ||
|   stopAllCameras();
 | ||
| 
 | ||
|   let videoElementIndex = 1;
 | ||
| 
 | ||
|   for (let i = startCamera; i <= endCamera && i <= totalCameras; i++) {
 | ||
|     const videoElement = document.getElementById(`camera-${videoElementIndex}`);
 | ||
|     let flvPlayer = flvjs.createPlayer({
 | ||
|       type: 'flv',
 | ||
|       isLive: true,
 | ||
|       cors: true,
 | ||
|       url: `${baseURL}?serial=${serial}&channel=${i}&quality=0`,
 | ||
|     }, {
 | ||
|       enableWorker: true,
 | ||
|       enableStashBuffer: false,
 | ||
|       autoCleanupSourceBuffer: true,
 | ||
|     });
 | ||
| 
 | ||
|     flvPlayer.attachMediaElement(videoElement);
 | ||
|     flvPlayer.load();
 | ||
|     flvPlayer.play();
 | ||
| 
 | ||
|     flvPlayers.push(flvPlayer);
 | ||
| 
 | ||
|     let hasStarted = false;
 | ||
| 
 | ||
|     const checkStarted = () => {
 | ||
|       if (!hasStarted && videoElement.readyState >= 2) {
 | ||
|         hasStarted = true;
 | ||
|         console.log(`Трансляция началась для камеры ${i}`);
 | ||
|       }
 | ||
|     };
 | ||
| 
 | ||
|     const checkInterval = setInterval(checkStarted, 1000);
 | ||
| 
 | ||
|     setTimeout(() => {
 | ||
|       clearInterval(checkInterval);
 | ||
|       if (!hasStarted) {
 | ||
|         console.log(`Трансляция для камеры ${i} не началась, запрашиваем повторно...`);
 | ||
|         flvPlayer.unload();
 | ||
|         flvPlayer.load();
 | ||
|         flvPlayer.play();
 | ||
|         // if (i > 0) {
 | ||
|         //   i--; 
 | ||
|         // }
 | ||
|       } 
 | ||
|     }, 3000);
 | ||
| 
 | ||
|     videoElementIndex++;
 | ||
| 
 | ||
|     await new Promise(resolve => setTimeout(resolve, 2000));
 | ||
|   }
 | ||
| }
 | ||
| 
 | ||
| 
 | ||
| 
 | ||
|     
 | ||
|       function switchCameras(direction) {
 | ||
|         if ((currentCameraGroup === 1 && direction === -1) || (currentCameraGroup === Math.ceil(totalCameras / camerasPerGroup) && direction === 1)) {
 | ||
|           return;
 | ||
|         }
 | ||
|         currentCameraGroup += direction;
 | ||
|         if (currentCameraGroup < 1) {
 | ||
|           currentCameraGroup = Math.ceil(totalCameras / camerasPerGroup);
 | ||
|         } else if (currentCameraGroup > Math.ceil(totalCameras / camerasPerGroup)) {
 | ||
|           currentCameraGroup = 1;
 | ||
|         }
 | ||
|         playNextCamerasInGroup();
 | ||
|       }
 | ||
|     
 | ||
|       // Запустить воспроизведение видео при загрузке страницы
 | ||
|       playNextCamerasInGroup();
 | ||
|     </script>
 | ||
|     
 | ||
|     
 | ||
|     
 | ||
| 
 | ||
|               
 | ||
| 
 | ||
| 
 | ||
|     <script>
 | ||
|       window.addEventListener('DOMContentLoaded', function() {
 | ||
|         var signalsList = document.querySelector('.signals-list');
 | ||
|         var list = document.getElementById('list');
 | ||
|     
 | ||
|         list.style.height = (signalsList.clientHeight - 50 - 75) + 'px';
 | ||
|       });
 | ||
|       window.addEventListener('resize', function() {
 | ||
|         var signalsList = document.querySelector('.signals-list');
 | ||
|         var list = document.getElementById('list');
 | ||
|     
 | ||
|         list.style.height = (signalsList.clientHeight - 50 - 75) + 'px';
 | ||
|       });
 | ||
|     </script>
 | ||
| 
 | ||
|     <script>
 | ||
| 
 | ||
|       var checkboxes = document.querySelectorAll('.checkbox-input');
 | ||
|       const form = document.getElementById('devices-1');
 | ||
| 
 | ||
|       form.addEventListener('change', () => {
 | ||
|         fetchAndShowMarkers();
 | ||
|       });
 | ||
| 
 | ||
|     </script>
 | ||
| 
 | ||
| <script>
 | ||
|   
 | ||
|   var map = L.map('map').setView([59.855198, 30.282995], 12);
 | ||
| 
 | ||
|         L.tileLayer('https://server.arcgisonline.com/ArcGIS/rest/services/World_Street_Map/MapServer/tile/{z}/{y}/{x}').addTo(map);
 | ||
| 
 | ||
|         map.zoomControl.remove();
 | ||
| 
 | ||
|         map.attributionControl.remove();
 | ||
| 
 | ||
|   let markers = [];
 | ||
|   {{#if ifDBError}}
 | ||
| {{else}}
 | ||
| 
 | ||
| const selectedDevices = Array.from(checkboxes)
 | ||
|             .filter(checkbox => checkbox.checked && checkbox.value !== 'on')
 | ||
|             .map(checkbox => checkbox.value);
 | ||
| 
 | ||
|             checkboxes.forEach(checkbox => {
 | ||
|     checkbox.addEventListener('change', async function() {
 | ||
|       await new Promise(resolve => setTimeout(resolve, 100));
 | ||
|         selectedDevices.length = 0; // Очищаем массив
 | ||
|         selectedDevices.push(...Array.from(checkboxes)
 | ||
|             .filter(checkbox => checkbox.checked && checkbox.value !== 'on')
 | ||
|             .map(checkbox => checkbox.value));
 | ||
|         
 | ||
|     });
 | ||
| });
 | ||
| 
 | ||
| 
 | ||
|   function addMarker(device) {
 | ||
|     const { serial, status, longitude, latitude, direction, speed, number, groupName } = device;
 | ||
| 
 | ||
|             
 | ||
|     if (serial === $("input[name=camera-serial]:checked").val()) {
 | ||
|       var marker = L.divIcon({
 | ||
|                 className: 'marker',
 | ||
|                 html: `<div class="marker-name active-${status}">${number !== null && number !== "" ? number : serial}</div>
 | ||
|     <svg class="active-${status}" id="marker-${serial}" style="transform: rotate(${direction}deg)" xmlns="http://www.w3.org/2000/svg" width="26" height="26" viewBox="0 0 26 26" fill="none">
 | ||
|       <path fill="#FF6A4B" d="M12.669 24.673c6.565 0 12-5.435 12-12 0-6.553-5.447-12-12.012-12C6.104.673.67 6.12.67 12.673c0 6.565 5.447 12 12 12Z"/>
 | ||
|       <path fill="#fff" fill-opacity=".85" d="m7.104 17.92 4.683-11.93c.33-.823 1.423-.846 1.753-.023l4.682 11.953c.318.788-.553 1.47-1.294.73l-3.953-3.953c-.188-.2-.424-.2-.612 0L8.41 18.65c-.753.74-1.623.059-1.306-.73Z"/>
 | ||
| </svg>`
 | ||
|             });
 | ||
|     } else {
 | ||
|       var marker = L.divIcon({
 | ||
|                 className: 'marker',
 | ||
|                 html: `<div class="marker-name active-${status}">${number !== null && number !== "" ? number : serial}</div>
 | ||
|     <svg class="active-${status}" id="marker-${serial}" style="transform: rotate(${direction}deg)" xmlns="http://www.w3.org/2000/svg" width="26" height="26" viewBox="0 0 26 26" fill="none">
 | ||
|   <path d="M12.9815 25.0195C19.5462 25.0336 24.9931 19.6101 25.0073 13.0454C25.0214 6.49253 19.5861 1.03375 13.0214 1.01961C6.46848 1.00549 1.02147 6.44081 1.00735 12.9937C0.993201 19.5584 6.42852 25.0054 12.9815 25.0195Z" fill="#8086F9"/>
 | ||
|   <path d="M7.43175 18.2547L12.1398 6.33536C12.471 5.51254 13.5652 5.49137 13.8928 6.31561L18.5493 18.2786C18.8653 19.0675 17.9932 19.748 17.2537 19.0052L13.3092 15.0438C13.1215 14.8434 12.8862 14.8429 12.6975 15.0425L8.73605 18.9868C7.98152 19.7264 7.1124 19.0422 7.43175 18.2547Z" fill="white" fill-opacity="0.85"/>
 | ||
| </svg>`
 | ||
|             });
 | ||
| }
 | ||
| 
 | ||
| var markerObj = L.marker([latitude, longitude], { icon: marker });
 | ||
|             markerObj.addTo(map);
 | ||
|             markers.push(markerObj);
 | ||
|   }
 | ||
| 
 | ||
|   function clearMarkers() {
 | ||
|             markers.forEach(marker => map.removeLayer(marker));
 | ||
|             markers = [];
 | ||
|         }
 | ||
| 
 | ||
|   function fetchAndShowMarkers() {
 | ||
|     fetch('/devices-geo', {
 | ||
|           method: 'POST',
 | ||
|           headers: {
 | ||
|             'Content-Type': 'application/json',
 | ||
|           },
 | ||
|           body: JSON.stringify({ devices: selectedDevices }),
 | ||
|         })
 | ||
|       .then(response => response.json())
 | ||
|       .then(data => {
 | ||
|         clearMarkers();
 | ||
|         data.devicesData.forEach(device => {
 | ||
|           addMarker(device);
 | ||
|         });
 | ||
| 
 | ||
|         // Поиск устройства с выбранным серийным номером
 | ||
|         const selectedDevice = data.devicesData.find(device => device.serial === $("input[name=camera-serial]:checked").val());
 | ||
| 
 | ||
|         // Получение ссылок на элементы для заполнения данных
 | ||
|         const groupElement = document.getElementById('propert-group');
 | ||
|         const speedElement = document.getElementById('propert-speed');
 | ||
|         const plateElement = document.getElementById('propert-plate');
 | ||
|         const geoElement = document.getElementById('propert-geo');
 | ||
| 
 | ||
|         const propertiesDiv = document.getElementById('properties');
 | ||
| 
 | ||
| 
 | ||
|         if (selectedDevice) {
 | ||
|           groupElement.textContent = selectedDevice.groupName;
 | ||
|           speedElement.textContent = selectedDevice.speed + ' км/ч';
 | ||
|           plateElement.textContent = selectedDevice.plate;
 | ||
|           geoElement.textContent = `${selectedDevice.latitude.toFixed(6)}, ${selectedDevice.longitude.toFixed(6)}`;
 | ||
| 
 | ||
|           propertiesDiv.style.display = 'inline-flex';
 | ||
|         } else {
 | ||
|           propertiesDiv.style.display = 'none';
 | ||
|           console.log('Устройство с выбранным серийным номером не найдено.');
 | ||
|         }
 | ||
|       })
 | ||
|       .catch(error => {
 | ||
|         console.error('Ошибка при получении данных:', error);
 | ||
| 
 | ||
|         const propertiesDiv = document.getElementById('properties');
 | ||
|         propertiesDiv.style.display = 'none';
 | ||
|       });
 | ||
|       
 | ||
|   }
 | ||
| 
 | ||
|   // Инициализация карты
 | ||
|   fetchAndShowMarkers();
 | ||
|   // Обновление маркеров каждые 12 секунд (12000 миллисекунд)
 | ||
|   setInterval(fetchAndShowMarkers, 12000);
 | ||
| 
 | ||
|   {{/if}}
 | ||
| 
 | ||
| </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>
 | ||
|   // Получаем ссылки на элементы
 | ||
| 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>
 | ||
| </html> |