<!DOCTYPE html> <html lang="ru"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Система мониторинга утечек данных</title> <link rel="stylesheet" href="https://unpkg.com/leaflet@1.7.1/dist/leaflet.css" /> <style> body { background-color: #1e1e1e; color: #ffffff; font-family: 'Arial', sans-serif; margin: 0; padding: 0; display: flex; transition: margin-left 0.3s ease; min-height: 100vh; overflow: hidden; } .sidebar { width: 250px; background-color: #2e2e2e; padding: 20px; box-shadow: 2px 0 5px rgba(0, 0, 0, 0.3); transition: width 0.3s ease; overflow-y: auto; position: fixed; top: 0; left: 0; bottom: 0; z-index: 1; } .sidebar.collapsed { width: 60px; } .sidebar.collapsed h2, .sidebar.collapsed ul li a span { display: none; } .sidebar h2 { color: #3399FF; text-align: center; margin-bottom: 20px; font-size: 24px; transition: opacity 0.3s ease; } .sidebar ul { list-style: none; padding: 0; } .sidebar ul li { margin: 15px 0; } .sidebar ul li a { color: #ffffff; text-decoration: none; font-size: 16px; transition: color 0.3s ease; display: flex; align-items: center; } .sidebar ul li a:hover { color: #3399FF; } .sidebar ul li a i { margin-right: 10px; font-size: 20px; } .sidebar ul li a span { transition: opacity 0.3s ease; } .toggle-btn { position: fixed; left: 10px; top: 10px; background-color: #3399FF; border: none; color: #fff; padding: 10px; border-radius: 5px; cursor: pointer; z-index: 1000; } .container { flex: 1; padding: 20px; margin-left: 250px; transition: margin-left 0.3s ease; overflow-y: auto; height: 100vh; position: relative; z-index: 0; } .container.collapsed { margin-left: 60px; } h1 { color: #3399FF; text-align: center; margin-bottom: 20px; } .grid { display: grid; grid-template-columns: repeat(2, 1fr); gap: 20px; margin-top: 20px; } .card { background-color: #2e2e2e; padding: 20px; border-radius: 8px; box-shadow: 0 0 5px rgba(0, 0, 0, 0.2); } #map { height: 400px; border-radius: 8px; grid-column: span 2; } .screenshots { display: flex; overflow-x: auto; gap: 10px; padding: 10px; } .screenshots img { max-height: 200px; border-radius: 5px; border: 2px solid #3399FF; } .logs { background-color: #2e2e2e; padding: 15px; border-radius: 8px; height: 200px; overflow-y: auto; font-family: 'Courier New', monospace; font-size: 14px; color: #3399FF; } .logs p { margin: 5px 0; } .leaflet-marker-icon { background-color: #3399FF; border: 2px solid #ffffff; border-radius: 50%; width: 20px !important; height: 20px !important; box-shadow: 0 0 10px rgba(51, 153, 255, 0.5); } .leaflet-popup-content { color: #1e1e1e; font-size: 14px; } .leaflet-popup-content-wrapper { border-radius: 8px; box-shadow: 0 0 10px rgba(0, 0, 0, 0.3); } </style> <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0-beta3/css/all.min.css"> </head> <body> <!-- Боковое меню --> <div class="sidebar" id="sidebar"> <h2>Меню</h2> <ul> <li><a href="#"><i class="fas fa-home"></i><span>Главная</span></a></li> <li><a href="#"><i class="fas fa-cog"></i><span>Настройки</span></a></li> <li><a href="#"><i class="fas fa-chart-line"></i><span>Отчеты</span></a></li> <li><a href="#"><i class="fas fa-code"></i><span>Парсеры</span></a></li> <li><a href="logs.html"><i class="fas fa-file-alt"></i><span>Логи</span></a></li> </ul> </div> <!-- Кнопка для сворачивания/разворачивания меню --> <button class="toggle-btn" id="toggle-btn"> <i class="fas fa-bars"></i> </button> <!-- Основной контент --> <div class="container" id="main-content"> <h1>Система мониторинга утечек данных</h1> <!-- Карта --> <div class="card"> <h2>Карта утечек</h2> <div id="map"></div> </div> <!-- Графики, скриншоты и логи --> <div class="grid"> <div class="card"> <h2>Утечки за месяц</h2> <canvas id="monthlyLeaksChart"></canvas> </div> <div class="card"> <h2>Состояние парсеров</h2> <div class="parsers-status"> <p>TG Node 0: <span class="status-{% if parser_status['tg-node-0'] %}active{% else %}error{% endif %}"> {% if parser_status['tg-node-0'] %}Активен{% else %}Ошибка{% endif %} </span> </p> <p>TG Node 1: <span class="status-{% if parser_status['tg-node-1'] %}active{% else %}error{% endif %}"> {% if parser_status['tg-node-1'] %}Активен{% else %}Ошибка{% endif %} </span> </p> <canvas id="parsersStatusChart"></canvas> </div> </div> <div class="card"> <h2>Источники утечек</h2> <canvas id="sourcesChart"></canvas> </div> <div class="card"> <h2>Скриншоты с форумов</h2> <div class="screenshots"> <img src="https://via.placeholder.com/300x200.png?text=Скриншот+1" alt="Скриншот 1"> <img src="https://via.placeholder.com/300x200.png?text=Скриншот+2" alt="Скриншот 2"> <img src="https://via.placeholder.com/300x200.png?text=Скриншот+3" alt="Скриншот 3"> <img src="https://via.placeholder.com/300x200.png?text=Скриншот+4" alt="Скриншот 4"> <img src="https://via.placeholder.com/300x200.png?text=Скриншот+5" alt="Скриншот 5"> </div> </div> <div class="card" style="grid-column: span 2;"> <h2>Логи системы</h2> <div class="logs"> <p>[2023-10-01 12:34] Парсер форума запущен.</p> <p>[2023-10-01 12:35] Найдена новая утечка на форуме X.</p> <p>[2023-10-01 12:36] Ошибка парсера Telegram: timeout.</p> <p>[2023-10-01 12:37] Утечка данных в Москве зафиксирована.</p> <p>[2023-10-01 12:38] Парсер даркнета завершил работу.</p> <p>[2023-10-01 12:39] Новый скриншот добавлен в базу.</p> </div> </div> </div> </div> <script src="https://cdn.jsdelivr.net/npm/chart.js"></script> <script src="https://unpkg.com/leaflet@1.7.1/dist/leaflet.js"></script> <script> const monthlyLeaksCtx = document.getElementById('monthlyLeaksChart').getContext('2d'); new Chart(monthlyLeaksCtx, { type: 'line', data: { labels: [ {% for day in leaks_stats['leaks_by_day'] %} "{{ day[0].strftime('%d.%m') }}"{% if not loop.last %}, {% endif %} {% endfor %} ], datasets: [{ label: 'Утечки', data: [ {% for day in leaks_stats['leaks_by_day'] %} {{ day[1] }}{% if not loop.last %}, {% endif %} {% endfor %} ], borderColor: '#3399FF', tension: 0.1, fill: true, backgroundColor: 'rgba(51, 153, 255, 0.1)' }] }, options: { responsive: true, plugins: { legend: { labels: { color: '#fff' } } }, scales: { y: { beginAtZero: true, grid: { color: '#444' }, ticks: { color: '#fff' } }, x: { grid: { color: '#444' }, ticks: { color: '#fff' } } } } }); // График состояния парсеров const parsersStatusCtx = document.getElementById('parsersStatusChart').getContext('2d'); new Chart(parsersStatusCtx, { type: 'doughnut', data: { labels: ['Активны', 'Ошибки', 'Неактивны'], datasets: [{ label: 'Состояние', data: [ {{ parser_status['active'] }}, {{ parser_status['errors'] }}, 0 // Неактивных нет в текущей логике ], backgroundColor: ['#3399FF', '#ff4444', '#666666'], }] }, options: { responsive: true, plugins: { legend: { labels: { color: '#fff', } } } } }); // График источников утечек const sourcesCtx = document.getElementById('sourcesChart').getContext('2d'); new Chart(sourcesCtx, { type: 'bar', data: { labels: [ {% for source in leaks_stats['leaks_by_source'] %} '{{ source[0] }}'{% if not loop.last %},{% endif %} {% endfor %} ], datasets: [{ label: 'Количество утечек', data: [ {% for source in leaks_stats['leaks_by_source'] %} {{ source[1] }}{% if not loop.last %},{% endif %} {% endfor %} ], backgroundColor: ['#3399FF', '#00cc99', '#0099cc'], }] }, options: { responsive: true, plugins: { legend: { labels: { color: '#fff', } } }, scales: { y: { beginAtZero: true, grid: { color: '#444', }, ticks: { color: '#fff', } }, x: { grid: { color: '#444', }, ticks: { color: '#fff', } } } } }); const map = L.map('map').setView([55.7558, 37.6176], 5); L.tileLayer('https://{s}.basemaps.cartocdn.com/dark_all/{z}/{x}/{y}{r}.png', { attribution: '© OpenStreetMap contributors, © CARTO' }).addTo(map); const marker1 = L.marker([55.7558, 37.6176], { icon: L.divIcon({ className: 'leaflet-marker-icon' }) }).addTo(map).bindPopup('Утечка данных в Москве'); const marker2 = L.marker([59.9343, 30.3351], { icon: L.divIcon({ className: 'leaflet-marker-icon' }) }).addTo(map).bindPopup('Утечка данных в Санкт-Петербурге'); const sidebar = document.getElementById('sidebar'); const mainContent = document.getElementById('main-content'); const toggleBtn = document.getElementById('toggle-btn'); toggleBtn.addEventListener('click', () => { sidebar.classList.toggle('collapsed'); mainContent.classList.toggle('collapsed'); }); </script> </body> </html>