main commit

This commit is contained in:
2025-10-06 09:40:51 +09:00
parent b1de55d253
commit 79256cd9fc
2375 changed files with 370050 additions and 4033 deletions

533
operator-interface.html Normal file
View File

@@ -0,0 +1,533 @@
<!DOCTYPE html>
<html lang="ru">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>GodEye - Интерфейс Оператора</title>
<style>
body {
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
margin: 0;
padding: 20px;
background: #1a1a1a;
color: #fff;
}
.container {
max-width: 1200px;
margin: 0 auto;
}
.header {
text-align: center;
margin-bottom: 30px;
}
.status {
padding: 15px;
border-radius: 8px;
margin: 10px 0;
font-weight: bold;
}
.status.disconnected { background: #d32f2f; }
.status.connecting { background: #f57c00; }
.status.connected { background: #388e3c; }
.video-container {
position: relative;
background: #000;
border-radius: 8px;
overflow: hidden;
margin: 20px 0;
}
#remoteVideo {
width: 100%;
height: 480px;
object-fit: cover;
}
.controls {
display: flex;
gap: 10px;
margin: 20px 0;
flex-wrap: wrap;
}
button {
padding: 12px 24px;
border: none;
border-radius: 6px;
cursor: pointer;
font-size: 14px;
font-weight: bold;
transition: all 0.3s;
}
.btn-primary { background: #2196f3; color: white; }
.btn-success { background: #4caf50; color: white; }
.btn-danger { background: #f44336; color: white; }
.btn-warning { background: #ff9800; color: white; }
button:hover {
transform: translateY(-2px);
box-shadow: 0 4px 8px rgba(0,0,0,0.3);
}
.device-list {
background: #2a2a2a;
border-radius: 8px;
padding: 20px;
margin: 20px 0;
}
.device-item {
background: #3a3a3a;
padding: 15px;
border-radius: 6px;
margin: 10px 0;
cursor: pointer;
transition: background 0.3s;
}
.device-item:hover {
background: #4a4a4a;
}
.logs {
background: #0a0a0a;
border-radius: 8px;
padding: 15px;
height: 200px;
overflow-y: auto;
font-family: 'Courier New', monospace;
font-size: 12px;
margin: 20px 0;
}
.log-entry {
margin: 2px 0;
padding: 2px 0;
}
.log-info { color: #81c784; }
.log-warning { color: #ffb74d; }
.log-error { color: #e57373; }
.stats {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
gap: 15px;
margin: 20px 0;
}
.stat-card {
background: #2a2a2a;
padding: 15px;
border-radius: 8px;
text-align: center;
}
.stat-value {
font-size: 24px;
font-weight: bold;
color: #2196f3;
}
.network-monitor {
background: #2a2a2a;
border-radius: 8px;
padding: 20px;
margin: 20px 0;
}
.traffic-indicator {
display: flex;
align-items: center;
gap: 10px;
margin: 10px 0;
}
.traffic-bar {
flex: 1;
height: 8px;
background: #1a1a1a;
border-radius: 4px;
overflow: hidden;
}
.traffic-fill {
height: 100%;
transition: width 0.3s;
}
.upload { background: #4caf50; }
.download { background: #2196f3; }
</style>
</head>
<body>
<div class="container">
<div class="header">
<h1>🕵️ GodEye - Интерфейс Оператора</h1>
<div id="connectionStatus" class="status disconnected">
🔴 Не подключен к серверу сигналинга
</div>
</div>
<div class="controls">
<button id="connectBtn" class="btn-primary" onclick="connectToSignaling()">
📡 Подключиться к серверу
</button>
<button id="refreshBtn" class="btn-warning" onclick="refreshDevices()">
🔄 Обновить устройства
</button>
<button id="disconnectBtn" class="btn-danger" onclick="disconnect()" disabled>
🔌 Отключиться
</button>
</div>
<div class="device-list">
<h3>📱 Доступные устройства</h3>
<div id="deviceList">
<p>Нет доступных устройств. Убедитесь, что приложение запущено на устройстве.</p>
</div>
</div>
<div class="video-container">
<video id="remoteVideo" autoplay playsinline muted>
<p>Видео поток будет отображен здесь после подключения к устройству</p>
</video>
</div>
<div class="stats">
<div class="stat-card">
<div class="stat-value" id="connectionTime">--:--</div>
<div>Время подключения</div>
</div>
<div class="stat-card">
<div class="stat-value" id="videoQuality">---</div>
<div>Качество видео</div>
</div>
<div class="stat-card">
<div class="stat-value" id="latency">--- ms</div>
<div>Задержка</div>
</div>
<div class="stat-card">
<div class="stat-value" id="bandwidth">--- kbps</div>
<div>Пропускная способность</div>
</div>
</div>
<div class="network-monitor">
<h3>📊 Мониторинг сети</h3>
<div class="traffic-indicator">
<span>📤 Исходящий:</span>
<div class="traffic-bar">
<div id="uploadBar" class="traffic-fill upload" style="width: 0%"></div>
</div>
<span id="uploadSpeed">0 KB/s</span>
</div>
<div class="traffic-indicator">
<span>📥 Входящий:</span>
<div class="traffic-bar">
<div id="downloadBar" class="traffic-fill download" style="width: 0%"></div>
</div>
<span id="downloadSpeed">0 KB/s</span>
</div>
</div>
<div class="logs">
<div id="logContainer"></div>
</div>
</div>
<script>
// Глобальные переменные
let ws = null;
let pc = null;
let currentSession = null;
let connectionStartTime = null;
let statsInterval = null;
// WebRTC конфигурация
const pcConfig = {
iceServers: [
{ urls: 'stun:stun.l.google.com:19302' },
{ urls: 'stun:stun1.l.google.com:19302' }
]
};
// Функции логирования
function log(message, type = 'info') {
const logContainer = document.getElementById('logContainer');
const timestamp = new Date().toLocaleTimeString();
const logEntry = document.createElement('div');
logEntry.className = `log-entry log-${type}`;
logEntry.textContent = `[${timestamp}] ${message}`;
logContainer.appendChild(logEntry);
logContainer.scrollTop = logContainer.scrollHeight;
console.log(message);
}
// Подключение к серверу сигналинга
function connectToSignaling() {
if (ws && ws.readyState === WebSocket.OPEN) {
log('Уже подключен к серверу сигналинга', 'warning');
return;
}
log('Подключение к серверу сигналинга...', 'info');
updateConnectionStatus('connecting', '🟡 Подключение к серверу...');
ws = new WebSocket('ws://localhost:8765');
ws.onopen = function() {
log('✅ Подключен к серверу сигналинга', 'info');
updateConnectionStatus('connected', '🟢 Подключен к серверу сигналинга');
document.getElementById('connectBtn').disabled = true;
document.getElementById('disconnectBtn').disabled = false;
refreshDevices();
};
ws.onmessage = function(event) {
const data = JSON.parse(event.data);
handleSignalingMessage(data);
};
ws.onclose = function() {
log('❌ Соединение с сервером разорвано', 'error');
updateConnectionStatus('disconnected', '🔴 Не подключен к серверу');
document.getElementById('connectBtn').disabled = false;
document.getElementById('disconnectBtn').disabled = true;
};
ws.onerror = function(error) {
log('❌ Ошибка подключения к серверу: ' + error, 'error');
updateConnectionStatus('disconnected', '🔴 Ошибка подключения');
};
}
// Обработка сообщений сигналинга
function handleSignalingMessage(data) {
log(`📨 Получено сообщение: ${data.type}`, 'info');
switch(data.type) {
case 'client_registered':
log(`🆔 Зарегистрирован как клиент: ${data.client_id}`, 'info');
break;
case 'session_joined':
currentSession = data.session_id;
log(`✅ Подключен к сессии: ${data.session_id}`, 'info');
log(`📱 Устройство: ${data.device_info.model || 'Unknown'}`, 'info');
connectionStartTime = Date.now();
startStatsMonitoring();
break;
case 'offer':
handleOffer(data.sdp);
break;
case 'ice_candidate':
handleIceCandidate(data.candidate);
break;
case 'hangup':
handleHangup();
break;
case 'error':
log(`❌ Ошибка: ${data.message}`, 'error');
break;
}
}
// Обработка SDP offer
async function handleOffer(offer) {
try {
log('📞 Получен SDP Offer, создание PeerConnection...', 'info');
pc = new RTCPeerConnection(pcConfig);
pc.onicecandidate = function(event) {
if (event.candidate) {
log('🧊 Отправка ICE candidate', 'info');
sendMessage({
type: 'ice_candidate',
session_id: currentSession,
candidate: event.candidate
});
}
};
pc.ontrack = function(event) {
log('📹 Получен видео поток', 'info');
const remoteVideo = document.getElementById('remoteVideo');
remoteVideo.srcObject = event.streams[0];
};
await pc.setRemoteDescription(offer);
const answer = await pc.createAnswer();
await pc.setLocalDescription(answer);
log('📞 Отправка SDP Answer', 'info');
sendMessage({
type: 'answer',
session_id: currentSession,
sdp: answer
});
} catch (error) {
log(`❌ Ошибка обработки offer: ${error}`, 'error');
}
}
// Обработка ICE candidate
async function handleIceCandidate(candidate) {
try {
if (pc) {
await pc.addIceCandidate(candidate);
log('🧊 ICE candidate добавлен', 'info');
}
} catch (error) {
log(`❌ Ошибка добавления ICE candidate: ${error}`, 'error');
}
}
// Подключение к устройству
function connectToDevice(sessionId) {
if (!ws || ws.readyState !== WebSocket.OPEN) {
log('❌ Нет подключения к серверу сигналинга', 'error');
return;
}
log(`🔗 Подключение к устройству в сессии: ${sessionId}`, 'info');
sendMessage({
type: 'join_session',
session_id: sessionId
});
}
// Отправка сообщения через WebSocket
function sendMessage(message) {
if (ws && ws.readyState === WebSocket.OPEN) {
ws.send(JSON.stringify(message));
}
}
// Обновление статуса подключения
function updateConnectionStatus(status, text) {
const statusElement = document.getElementById('connectionStatus');
statusElement.className = `status ${status}`;
statusElement.textContent = text;
}
// Обновление списка устройств
function refreshDevices() {
const deviceList = document.getElementById('deviceList');
deviceList.innerHTML = '<p>🔍 Поиск доступных устройств...</p>';
// Имитация поиска устройств (в реальности будет запрос к серверу)
setTimeout(() => {
deviceList.innerHTML = `
<div class="device-item" onclick="connectToDevice('demo-session-1')">
<strong>📱 LG G6 (LGMG600S9b4da66b)</strong><br>
<small>Android 8.0 • IP: 192.168.1.100 • Последняя активность: только что</small>
</div>
`;
}, 1000);
}
// Завершение соединения
function disconnect() {
if (pc) {
pc.close();
pc = null;
}
if (currentSession) {
sendMessage({
type: 'hangup',
session_id: currentSession
});
currentSession = null;
}
if (ws) {
ws.close();
ws = null;
}
if (statsInterval) {
clearInterval(statsInterval);
statsInterval = null;
}
connectionStartTime = null;
updateConnectionStatus('disconnected', '🔴 Отключен');
document.getElementById('remoteVideo').srcObject = null;
log('🔌 Соединение разорвано', 'info');
}
// Обработка завершения сессии
function handleHangup() {
log('📴 Сессия завершена', 'info');
disconnect();
}
// Мониторинг статистики
function startStatsMonitoring() {
if (statsInterval) {
clearInterval(statsInterval);
}
statsInterval = setInterval(updateStats, 1000);
}
function updateStats() {
// Обновление времени подключения
if (connectionStartTime) {
const elapsed = Math.floor((Date.now() - connectionStartTime) / 1000);
const minutes = Math.floor(elapsed / 60);
const seconds = elapsed % 60;
document.getElementById('connectionTime').textContent =
`${minutes.toString().padStart(2, '0')}:${seconds.toString().padStart(2, '0')}`;
}
// WebRTC статистика (упрощенная)
if (pc) {
pc.getStats().then(stats => {
stats.forEach(report => {
if (report.type === 'inbound-rtp' && report.mediaType === 'video') {
const fps = report.framesPerSecond || 0;
const bitrate = Math.round((report.bytesReceived || 0) * 8 / 1000);
document.getElementById('videoQuality').textContent = `${fps} FPS`;
document.getElementById('bandwidth').textContent = `${bitrate} kbps`;
}
});
});
}
}
// Автоподключение при загрузке страницы
window.onload = function() {
log('🚀 GodEye Operator Interface загружен', 'info');
// Попытка автоподключения через 1 секунду
setTimeout(() => {
log('🔄 Попытка автоподключения к серверу...', 'info');
connectToSignaling();
}, 1000);
};
// Обработка закрытия окна
window.onbeforeunload = function() {
disconnect();
};
</script>
</body>
</html>