Files
god_eye/backend/public/demo.js
2025-09-28 22:00:44 +09:00

803 lines
30 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

// Глобальные переменные
let socket = null;
let androidSocket = null;
let operatorSocket = null;
let localStream = null;
let peerConnection = null;
let currentSessionId = null;
// Конфигурация WebRTC
const rtcConfig = {
iceServers: [
{ urls: 'stun:stun.l.google.com:19302' },
{ urls: 'stun:stun1.l.google.com:19302' }
]
};
// Инициализация при загрузке страницы
document.addEventListener('DOMContentLoaded', function() {
initializeSocket();
loadSystemStats();
// Обновление статистики каждые 5 секунд
setInterval(loadSystemStats, 5000);
});
// Управление вкладками
function showTab(tabName) {
// Скрываем все вкладки
document.querySelectorAll('.tab-content').forEach(tab => {
tab.classList.remove('active');
});
// Убираем активный класс с кнопок
document.querySelectorAll('.tab').forEach(btn => {
btn.classList.remove('active');
});
// Показываем выбранную вкладку
document.getElementById(tabName).classList.add('active');
event.target.classList.add('active');
}
// Инициализация основного сокета для мониторинга
function initializeSocket() {
socket = io();
socket.on('connect', () => {
logMessage('info', 'Подключение к серверу установлено');
updateConnectionStatus(true);
});
socket.on('disconnect', () => {
logMessage('warn', 'Подключение к серверу потеряно');
updateConnectionStatus(false);
});
socket.on('device:connected', (data) => {
logMessage('info', `Устройство подключено: ${data.deviceId}`);
});
socket.on('device:disconnected', (data) => {
logMessage('warn', `Устройство отключено: ${data.deviceId}`);
});
}
// Обновление статуса подключения
function updateConnectionStatus(connected) {
const statusCard = document.getElementById('connection-status');
const statusText = document.getElementById('connection-text');
if (connected) {
statusCard.className = 'status-card status-connected';
statusText.textContent = '✅ Подключено к серверу';
} else {
statusCard.className = 'status-card status-disconnected';
statusText.textContent = '❌ Нет подключения к серверу';
}
}
// Загрузка системной статистики
async function loadSystemStats() {
try {
const response = await fetch('/api/status');
const data = await response.json();
if (data.devices && data.sessions) {
document.getElementById('stat-devices').textContent = data.devices.connectedDevices || 0;
document.getElementById('stat-operators').textContent = data.devices.connectedOperators || 0;
document.getElementById('stat-sessions').textContent = data.sessions.activeSessions || 0;
document.getElementById('stat-uptime').textContent = Math.round(data.uptime / 60) || 0;
}
} catch (error) {
console.error('Ошибка загрузки статистики:', error);
}
}
// Система логирования
function logMessage(level, message) {
const logs = document.getElementById('system-logs');
const timestamp = new Date().toLocaleTimeString();
const logEntry = document.createElement('div');
logEntry.className = `log-entry log-${level}`;
logEntry.textContent = `[${timestamp}] ${message}`;
logs.appendChild(logEntry);
logs.scrollTop = logs.scrollHeight;
// Ограничиваем количество логов
const maxLogs = 100;
while (logs.children.length > maxLogs) {
logs.removeChild(logs.firstChild);
}
}
function clearLogs() {
document.getElementById('system-logs').innerHTML = '';
}
// === ANDROID DEVICE SIMULATION ===
function connectAndroid() {
const deviceId = document.getElementById('android-device-id').value;
if (!deviceId) {
alert('Введите Device ID');
return;
}
androidSocket = io();
androidSocket.on('connect', () => {
const deviceInfo = {
model: document.getElementById('android-model').value,
manufacturer: document.getElementById('android-manufacturer').value,
androidVersion: document.getElementById('android-version').value,
availableCameras: Array.from(document.getElementById('android-cameras').selectedOptions)
.map(option => option.value).join(',')
};
androidSocket.emit('register:android', { deviceId, deviceInfo });
logMessage('info', `Android устройство подключается: ${deviceId}`);
});
androidSocket.on('register:success', (data) => {
logMessage('info', `Android устройство зарегистрировано: ${data.deviceId}`);
document.getElementById('android-connect').disabled = true;
document.getElementById('android-disconnect').disabled = false;
});
androidSocket.on('camera:request', (data) => {
showCameraRequest(data);
});
androidSocket.on('camera:switch', (data) => {
logMessage('info', `Запрос переключения камеры: ${data.cameraType}`);
showAlert('info', `Переключение камеры на: ${data.cameraType}`);
});
androidSocket.on('camera:disconnect', (data) => {
logMessage('warn', `Сессия завершена: ${data.sessionId}`);
removeCameraSession(data.sessionId);
});
setupAndroidWebRTC();
}
function disconnectAndroid() {
if (androidSocket) {
androidSocket.disconnect();
androidSocket = null;
logMessage('warn', 'Android устройство отключено');
document.getElementById('android-connect').disabled = false;
document.getElementById('android-disconnect').disabled = true;
// Очищаем запросы и сессии
document.getElementById('android-requests').innerHTML = '';
document.getElementById('android-sessions').innerHTML = '';
}
}
function showCameraRequest(data) {
const container = document.getElementById('android-requests');
const requestDiv = document.createElement('div');
requestDiv.className = 'session-card session-pending';
requestDiv.innerHTML = `
<h4>Запрос камеры</h4>
<p><strong>Сессия:</strong> ${data.sessionId}</p>
<p><strong>Оператор:</strong> ${data.operatorId}</p>
<p><strong>Камера:</strong> ${data.cameraType}</p>
<button class="btn btn-success" onclick="acceptCameraRequest('${data.sessionId}')">Принять</button>
<button class="btn btn-danger" onclick="declineCameraRequest('${data.sessionId}')">Отклонить</button>
`;
container.appendChild(requestDiv);
logMessage('info', `Получен запрос камеры от ${data.operatorId}`);
}
function acceptCameraRequest(sessionId) {
if (androidSocket) {
androidSocket.emit('camera:response', {
sessionId,
accepted: true,
streamUrl: 'webrtc'
});
// Перемещаем в активные сессии
moveToActiveSessions(sessionId, 'active');
logMessage('info', `Запрос камеры принят: ${sessionId}`);
}
}
function declineCameraRequest(sessionId) {
if (androidSocket) {
androidSocket.emit('camera:response', {
sessionId,
accepted: false,
error: 'Пользователь отклонил запрос'
});
// Удаляем запрос
removeCameraRequest(sessionId);
logMessage('warn', `Запрос камеры отклонен: ${sessionId}`);
}
}
function moveToActiveSessions(sessionId, status) {
removeCameraRequest(sessionId);
const container = document.getElementById('android-sessions');
const sessionDiv = document.createElement('div');
sessionDiv.className = `session-card session-${status}`;
sessionDiv.id = `android-session-${sessionId}`;
sessionDiv.innerHTML = `
<h4>Активная сессия</h4>
<p><strong>ID:</strong> ${sessionId}</p>
<p><strong>Статус:</strong> ${status}</p>
<button class="btn btn-danger" onclick="endAndroidSession('${sessionId}')">Завершить</button>
`;
container.appendChild(sessionDiv);
currentSessionId = sessionId;
document.getElementById('current-session-id').value = sessionId;
}
function removeCameraRequest(sessionId) {
const container = document.getElementById('android-requests');
const requests = container.children;
for (let i = 0; i < requests.length; i++) {
if (requests[i].innerHTML.includes(sessionId)) {
container.removeChild(requests[i]);
break;
}
}
}
function removeCameraSession(sessionId) {
const sessionElement = document.getElementById(`android-session-${sessionId}`);
if (sessionElement) {
sessionElement.remove();
}
if (currentSessionId === sessionId) {
currentSessionId = null;
document.getElementById('current-session-id').value = '';
}
}
function endAndroidSession(sessionId) {
if (androidSocket) {
androidSocket.emit('camera:disconnect', { sessionId });
removeCameraSession(sessionId);
}
}
// === OPERATOR SIMULATION ===
function connectOperator() {
const operatorId = document.getElementById('operator-id').value;
if (!operatorId) {
alert('Введите Operator ID');
return;
}
operatorSocket = io();
operatorSocket.on('connect', () => {
operatorSocket.emit('register:operator', {
operatorId,
operatorInfo: {
name: 'Demo Operator',
permissions: ['view_cameras', 'request_camera']
}
});
logMessage('info', `Оператор подключается: ${operatorId}`);
});
operatorSocket.on('register:success', (data) => {
logMessage('info', `Оператор зарегистрирован: ${data.operatorId}`);
document.getElementById('operator-connect').disabled = true;
document.getElementById('operator-disconnect').disabled = false;
showAvailableDevices(data.availableDevices || []);
});
operatorSocket.on('device:connected', (data) => {
logMessage('info', `Новое устройство доступно: ${data.deviceId}`);
refreshDevices();
});
operatorSocket.on('camera:stream-ready', (data) => {
logMessage('info', `Камера готова: ${data.sessionId}`);
showOperatorSession(data.sessionId, 'active');
});
operatorSocket.on('camera:denied', (data) => {
logMessage('warn', `Запрос отклонен: ${data.error}`);
showAlert('warning', `Запрос камеры отклонен: ${data.error}`);
});
setupOperatorWebRTC();
}
function disconnectOperator() {
if (operatorSocket) {
operatorSocket.disconnect();
operatorSocket = null;
logMessage('warn', 'Оператор отключен');
document.getElementById('operator-connect').disabled = false;
document.getElementById('operator-disconnect').disabled = true;
// Очищаем списки
document.getElementById('available-devices').innerHTML = '';
document.getElementById('operator-sessions').innerHTML = '';
}
}
async function refreshDevices() {
const operatorId = document.getElementById('operator-id').value;
if (!operatorId) return;
try {
const response = await fetch('/api/operators/devices', {
headers: { 'X-Operator-Id': operatorId }
});
if (response.ok) {
const data = await response.json();
showAvailableDevices(data.devices || []);
}
} catch (error) {
logMessage('error', 'Ошибка загрузки устройств: ' + error.message);
}
}
function showAvailableDevices(devices) {
const container = document.getElementById('available-devices');
container.innerHTML = '';
devices.forEach(device => {
const deviceDiv = document.createElement('div');
deviceDiv.className = `device-card ${device.isConnected ? 'device-online' : 'device-offline'}`;
deviceDiv.innerHTML = `
<h4>${device.deviceInfo.model || 'Unknown Device'}</h4>
<p><strong>ID:</strong> ${device.deviceId}</p>
<p><strong>Статус:</strong> ${device.status}</p>
<p><strong>Камеры:</strong> ${device.capabilities?.cameras?.join(', ') || 'Unknown'}</p>
${device.canAcceptSession ?
`<select id="camera-${device.deviceId}">
${device.capabilities?.cameras?.map(camera =>
`<option value="${camera}">${getCameraName(camera)}</option>`
).join('') || '<option value="back">Основная</option>'}
</select>
<button class="btn" onclick="requestCamera('${device.deviceId}')">Запросить камеру</button>`
: '<p style="color: #666;">Недоступен для новых сессий</p>'
}
`;
container.appendChild(deviceDiv);
});
}
function getCameraName(cameraType) {
const names = {
'back': 'Основная',
'front': 'Фронтальная',
'ultra_wide': 'Широкоугольная',
'telephoto': 'Телеобъектив'
};
return names[cameraType] || cameraType;
}
async function requestCamera(deviceId) {
const operatorId = document.getElementById('operator-id').value;
const cameraSelect = document.getElementById(`camera-${deviceId}`);
const cameraType = cameraSelect ? cameraSelect.value : 'back';
try {
const response = await fetch('/api/operators/camera/request', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-Operator-Id': operatorId
},
body: JSON.stringify({ deviceId, cameraType })
});
if (response.ok) {
const data = await response.json();
logMessage('info', `Запрос камеры отправлен: ${data.sessionId}`);
showOperatorSession(data.sessionId, 'pending');
} else {
const error = await response.json();
showAlert('danger', 'Ошибка запроса камеры: ' + error.error);
}
} catch (error) {
logMessage('error', 'Ошибка запроса камеры: ' + error.message);
}
}
function showOperatorSession(sessionId, status) {
const container = document.getElementById('operator-sessions');
const existingSession = document.getElementById(`operator-session-${sessionId}`);
if (existingSession) {
// Обновляем существующую сессию
existingSession.className = `session-card session-${status}`;
const statusElement = existingSession.querySelector('.session-status');
if (statusElement) statusElement.textContent = status;
return;
}
const sessionDiv = document.createElement('div');
sessionDiv.className = `session-card session-${status}`;
sessionDiv.id = `operator-session-${sessionId}`;
sessionDiv.innerHTML = `
<h4>Сессия камеры</h4>
<p><strong>ID:</strong> ${sessionId}</p>
<p><strong>Статус:</strong> <span class="session-status">${status}</span></p>
<button class="btn" onclick="switchCamera('${sessionId}', 'front')">Фронтальная</button>
<button class="btn" onclick="switchCamera('${sessionId}', 'back')">Основная</button>
<button class="btn btn-danger" onclick="endOperatorSession('${sessionId}')">Завершить</button>
`;
container.appendChild(sessionDiv);
currentSessionId = sessionId;
document.getElementById('current-session-id').value = sessionId;
}
async function switchCamera(sessionId, cameraType) {
const operatorId = document.getElementById('operator-id').value;
try {
const response = await fetch(`/api/operators/camera/${sessionId}/switch`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-Operator-Id': operatorId
},
body: JSON.stringify({ cameraType })
});
if (response.ok) {
logMessage('info', `Переключение камеры: ${cameraType}`);
showAlert('info', `Запрос переключения камеры на: ${getCameraName(cameraType)}`);
} else {
const error = await response.json();
showAlert('danger', 'Ошибка переключения камеры: ' + error.error);
}
} catch (error) {
logMessage('error', 'Ошибка переключения камеры: ' + error.message);
}
}
async function endOperatorSession(sessionId) {
const operatorId = document.getElementById('operator-id').value;
try {
const response = await fetch(`/api/operators/camera/${sessionId}`, {
method: 'DELETE',
headers: { 'X-Operator-Id': operatorId }
});
if (response.ok) {
const sessionElement = document.getElementById(`operator-session-${sessionId}`);
if (sessionElement) sessionElement.remove();
if (currentSessionId === sessionId) {
currentSessionId = null;
document.getElementById('current-session-id').value = '';
}
logMessage('info', `Сессия завершена: ${sessionId}`);
}
} catch (error) {
logMessage('error', 'Ошибка завершения сессии: ' + error.message);
}
}
async function refreshSessions() {
const operatorId = document.getElementById('operator-id').value;
if (!operatorId) return;
try {
const response = await fetch('/api/operators/sessions', {
headers: { 'X-Operator-Id': operatorId }
});
if (response.ok) {
const data = await response.json();
const container = document.getElementById('operator-sessions');
container.innerHTML = '';
data.sessions.forEach(session => {
showOperatorSession(session.sessionId, session.status);
});
}
} catch (error) {
logMessage('error', 'Ошибка загрузки сессий: ' + error.message);
}
}
// === ADMIN FUNCTIONS ===
async function getSystemHealth() {
try {
const response = await fetch('/api/admin/health');
const data = await response.json();
const container = document.getElementById('admin-info');
container.innerHTML = `
<div class="alert alert-${data.health.status === 'healthy' ? 'success' : 'warning'}">
<h4>Состояние системы: ${data.health.status}</h4>
<p><strong>Память:</strong> ${data.health.memory.used}MB / ${data.health.memory.total}MB</p>
<p><strong>Время работы:</strong> ${data.health.uptime} сек</p>
<p><strong>Подключения:</strong> ${data.health.connections.devices} устройств, ${data.health.connections.operators} операторов</p>
${data.health.warnings ? `<p><strong>Предупреждения:</strong> ${data.health.warnings.join(', ')}</p>` : ''}
</div>
`;
} catch (error) {
showAlert('danger', 'Ошибка проверки здоровья: ' + error.message);
}
}
async function getSystemStats() {
try {
const response = await fetch('/api/admin/stats');
const data = await response.json();
const container = document.getElementById('admin-info');
container.innerHTML = `
<div class="alert alert-info">
<h4>Детальная статистика</h4>
<pre>${JSON.stringify(data.stats, null, 2)}</pre>
</div>
`;
} catch (error) {
showAlert('danger', 'Ошибка загрузки статистики: ' + error.message);
}
}
async function cleanupSystem() {
try {
const response = await fetch('/api/admin/cleanup', { method: 'POST' });
const data = await response.json();
showAlert('success', `Очистка завершена. Удалено сессий: ${data.removedSessions}`);
logMessage('info', `Системная очистка: удалено ${data.removedSessions} сессий`);
} catch (error) {
showAlert('danger', 'Ошибка очистки: ' + error.message);
}
}
async function getAllDevices() {
try {
const response = await fetch('/api/admin/devices');
const data = await response.json();
const container = document.getElementById('all-devices');
container.innerHTML = '';
data.devices.forEach(device => {
const deviceDiv = document.createElement('div');
deviceDiv.className = `device-card ${device.isConnected ? 'device-online' : 'device-offline'}`;
deviceDiv.innerHTML = `
<h5>${device.deviceInfo.model || 'Unknown Device'}</h5>
<p><strong>ID:</strong> ${device.deviceId}</p>
<p><strong>Статус:</strong> ${device.status}</p>
<p><strong>Активных сессий:</strong> ${device.activeSessions}</p>
<p><strong>Время работы:</strong> ${device.uptime} сек</p>
`;
container.appendChild(deviceDiv);
});
} catch (error) {
showAlert('danger', 'Ошибка загрузки устройств: ' + error.message);
}
}
async function getAllSessions() {
try {
const response = await fetch('/api/admin/sessions');
const data = await response.json();
const container = document.getElementById('all-sessions');
container.innerHTML = '';
data.sessions.forEach(session => {
const sessionDiv = document.createElement('div');
sessionDiv.className = `session-card session-${session.status}`;
sessionDiv.innerHTML = `
<h5>Сессия ${session.sessionId}</h5>
<p><strong>Устройство:</strong> ${session.deviceId}</p>
<p><strong>Оператор:</strong> ${session.operatorId}</p>
<p><strong>Статус:</strong> ${session.status}</p>
<p><strong>Продолжительность:</strong> ${session.duration}</p>
<p><strong>Камера:</strong> ${getCameraName(session.cameraType)}</p>
`;
container.appendChild(sessionDiv);
});
} catch (error) {
showAlert('danger', 'Ошибка загрузки сессий: ' + error.message);
}
}
// === WebRTC TEST ===
function setupAndroidWebRTC() {
if (!androidSocket) return;
androidSocket.on('webrtc:offer', async (data) => {
try {
const pc = getOrCreatePeerConnection();
await pc.setRemoteDescription(new RTCSessionDescription({
type: 'offer',
sdp: data.offer
}));
const answer = await pc.createAnswer();
await pc.setLocalDescription(answer);
androidSocket.emit('webrtc:answer', {
sessionId: data.sessionId,
answer: answer.sdp
});
logMessage('info', 'WebRTC answer отправлен');
} catch (error) {
logMessage('error', 'WebRTC ошибка: ' + error.message);
}
});
androidSocket.on('webrtc:ice-candidate', async (data) => {
try {
const pc = getOrCreatePeerConnection();
const candidate = JSON.parse(data.candidate);
await pc.addIceCandidate(new RTCIceCandidate(candidate));
} catch (error) {
logMessage('error', 'ICE candidate ошибка: ' + error.message);
}
});
}
function setupOperatorWebRTC() {
if (!operatorSocket) return;
operatorSocket.on('webrtc:answer', async (data) => {
try {
const pc = getOrCreatePeerConnection();
await pc.setRemoteDescription(new RTCSessionDescription({
type: 'answer',
sdp: data.answer
}));
logMessage('info', 'WebRTC answer получен');
} catch (error) {
logMessage('error', 'WebRTC ошибка: ' + error.message);
}
});
operatorSocket.on('webrtc:ice-candidate', async (data) => {
try {
const pc = getOrCreatePeerConnection();
const candidate = JSON.parse(data.candidate);
await pc.addIceCandidate(new RTCIceCandidate(candidate));
} catch (error) {
logMessage('error', 'ICE candidate ошибка: ' + error.message);
}
});
}
function getOrCreatePeerConnection() {
if (!peerConnection) {
peerConnection = new RTCPeerConnection(rtcConfig);
peerConnection.onicecandidate = (event) => {
if (event.candidate) {
const candidateData = JSON.stringify({
candidate: event.candidate.candidate,
sdpMid: event.candidate.sdpMid,
sdpMLineIndex: event.candidate.sdpMLineIndex
});
if (androidSocket) {
androidSocket.emit('webrtc:ice-candidate', {
sessionId: currentSessionId,
candidate: candidateData
});
} else if (operatorSocket) {
operatorSocket.emit('webrtc:ice-candidate', {
sessionId: currentSessionId,
candidate: candidateData
});
}
}
};
peerConnection.ontrack = (event) => {
const remoteVideo = document.getElementById('remoteVideo');
remoteVideo.srcObject = event.streams[0];
remoteVideo.style.display = 'block';
logMessage('info', 'Получен удаленный видеопоток');
};
peerConnection.onconnectionstatechange = () => {
const state = peerConnection.connectionState;
logMessage('info', `WebRTC состояние: ${state}`);
const statusDiv = document.getElementById('webrtc-status');
statusDiv.innerHTML = `
<div class="alert alert-info">
<strong>WebRTC состояние:</strong> ${state}
</div>
`;
};
}
return peerConnection;
}
async function startLocalVideo() {
try {
localStream = await navigator.mediaDevices.getUserMedia({
video: { width: 640, height: 480 },
audio: true
});
const localVideo = document.getElementById('localVideo');
localVideo.srcObject = localStream;
const pc = getOrCreatePeerConnection();
localStream.getTracks().forEach(track => {
pc.addTrack(track, localStream);
});
logMessage('info', 'Локальное видео запущено');
} catch (error) {
logMessage('error', 'Ошибка доступа к камере: ' + error.message);
showAlert('danger', 'Ошибка доступа к камере: ' + error.message);
}
}
function stopLocalVideo() {
if (localStream) {
localStream.getTracks().forEach(track => track.stop());
localStream = null;
const localVideo = document.getElementById('localVideo');
localVideo.srcObject = null;
logMessage('info', 'Локальное видео остановлено');
}
if (peerConnection) {
peerConnection.close();
peerConnection = null;
const remoteVideo = document.getElementById('remoteVideo');
remoteVideo.srcObject = null;
remoteVideo.style.display = 'none';
logMessage('info', 'WebRTC соединение закрыто');
}
}
// Вспомогательная функция для показа уведомлений
function showAlert(type, message) {
// Создаем временное уведомление
const alert = document.createElement('div');
alert.className = `alert alert-${type}`;
alert.textContent = message;
alert.style.position = 'fixed';
alert.style.top = '20px';
alert.style.right = '20px';
alert.style.zIndex = '9999';
alert.style.minWidth = '300px';
document.body.appendChild(alert);
// Удаляем через 5 секунд
setTimeout(() => {
if (document.body.contains(alert)) {
document.body.removeChild(alert);
}
}, 5000);
}