init commit
This commit is contained in:
411
backend/src/routes/admin.js
Normal file
411
backend/src/routes/admin.js
Normal file
@@ -0,0 +1,411 @@
|
||||
const express = require('express');
|
||||
const router = express.Router();
|
||||
|
||||
/**
|
||||
* GET /api/admin/stats
|
||||
* Получение общей статистики системы
|
||||
*/
|
||||
router.get('/stats', (req, res) => {
|
||||
try {
|
||||
const { deviceManager, sessionManager } = req.app.locals;
|
||||
|
||||
const deviceStats = deviceManager.getStats();
|
||||
const sessionStats = sessionManager.getStats();
|
||||
|
||||
const systemStats = {
|
||||
timestamp: new Date().toISOString(),
|
||||
uptime: Math.round(process.uptime()),
|
||||
memory: process.memoryUsage(),
|
||||
devices: deviceStats,
|
||||
sessions: sessionStats,
|
||||
system: {
|
||||
nodeVersion: process.version,
|
||||
platform: process.platform,
|
||||
arch: process.arch
|
||||
}
|
||||
};
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
stats: systemStats
|
||||
});
|
||||
} catch (error) {
|
||||
res.status(500).json({ error: error.message });
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* GET /api/admin/devices
|
||||
* Получение списка всех устройств (включая отключенные)
|
||||
*/
|
||||
router.get('/devices', (req, res) => {
|
||||
try {
|
||||
const { deviceManager } = req.app.locals;
|
||||
const { status, limit = 100, offset = 0 } = req.query;
|
||||
|
||||
let devices = Array.from(deviceManager.devices.values());
|
||||
|
||||
// Фильтрация по статусу
|
||||
if (status) {
|
||||
devices = devices.filter(device => device.status === status);
|
||||
}
|
||||
|
||||
// Пагинация
|
||||
const total = devices.length;
|
||||
devices = devices.slice(offset, offset + parseInt(limit));
|
||||
|
||||
const devicesData = devices.map(device => device.getSummary());
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
devices: devicesData,
|
||||
pagination: {
|
||||
total,
|
||||
limit: parseInt(limit),
|
||||
offset: parseInt(offset),
|
||||
hasMore: (offset + parseInt(limit)) < total
|
||||
}
|
||||
});
|
||||
} catch (error) {
|
||||
res.status(500).json({ error: error.message });
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* GET /api/admin/operators
|
||||
* Получение списка всех операторов
|
||||
*/
|
||||
router.get('/operators', (req, res) => {
|
||||
try {
|
||||
const { deviceManager } = req.app.locals;
|
||||
const { status, limit = 100, offset = 0 } = req.query;
|
||||
|
||||
let operators = Array.from(deviceManager.operators.values());
|
||||
|
||||
// Фильтрация по статусу
|
||||
if (status) {
|
||||
operators = operators.filter(operator => operator.status === status);
|
||||
}
|
||||
|
||||
// Пагинация
|
||||
const total = operators.length;
|
||||
operators = operators.slice(offset, offset + parseInt(limit));
|
||||
|
||||
const operatorsData = operators.map(operator => operator.getSummary());
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
operators: operatorsData,
|
||||
pagination: {
|
||||
total,
|
||||
limit: parseInt(limit),
|
||||
offset: parseInt(offset),
|
||||
hasMore: (offset + parseInt(limit)) < total
|
||||
}
|
||||
});
|
||||
} catch (error) {
|
||||
res.status(500).json({ error: error.message });
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* GET /api/admin/sessions
|
||||
* Получение списка всех сессий
|
||||
*/
|
||||
router.get('/sessions', (req, res) => {
|
||||
try {
|
||||
const { sessionManager } = req.app.locals;
|
||||
const { status, deviceId, operatorId, limit = 100, offset = 0 } = req.query;
|
||||
|
||||
let sessions = Array.from(sessionManager.sessions.values());
|
||||
|
||||
// Фильтрация
|
||||
if (status) {
|
||||
sessions = sessions.filter(session => session.status === status);
|
||||
}
|
||||
if (deviceId) {
|
||||
sessions = sessions.filter(session => session.deviceId === deviceId);
|
||||
}
|
||||
if (operatorId) {
|
||||
sessions = sessions.filter(session => session.operatorId === operatorId);
|
||||
}
|
||||
|
||||
// Сортировка по времени создания (новые первыми)
|
||||
sessions.sort((a, b) => b.startTime - a.startTime);
|
||||
|
||||
// Пагинация
|
||||
const total = sessions.length;
|
||||
sessions = sessions.slice(offset, offset + parseInt(limit));
|
||||
|
||||
const sessionsData = sessions.map(session => session.getSummary());
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
sessions: sessionsData,
|
||||
pagination: {
|
||||
total,
|
||||
limit: parseInt(limit),
|
||||
offset: parseInt(offset),
|
||||
hasMore: (offset + parseInt(limit)) < total
|
||||
}
|
||||
});
|
||||
} catch (error) {
|
||||
res.status(500).json({ error: error.message });
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* GET /api/admin/sessions/:sessionId
|
||||
* Получение детальной информации о сессии (включая события)
|
||||
*/
|
||||
router.get('/sessions/:sessionId', (req, res) => {
|
||||
try {
|
||||
const { sessionId } = req.params;
|
||||
const { sessionManager } = req.app.locals;
|
||||
|
||||
const session = sessionManager.getSession(sessionId);
|
||||
if (!session) {
|
||||
return res.status(404).json({ error: 'Session not found' });
|
||||
}
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
session: session.getFullInfo()
|
||||
});
|
||||
} catch (error) {
|
||||
res.status(500).json({ error: error.message });
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* POST /api/admin/sessions/:sessionId/terminate
|
||||
* Принудительное завершение сессии
|
||||
*/
|
||||
router.post('/sessions/:sessionId/terminate', (req, res) => {
|
||||
try {
|
||||
const { sessionId } = req.params;
|
||||
const { reason = 'Terminated by admin' } = req.body;
|
||||
|
||||
const { sessionManager, deviceManager } = req.app.locals;
|
||||
const session = sessionManager.getSession(sessionId);
|
||||
|
||||
if (!session) {
|
||||
return res.status(404).json({ error: 'Session not found' });
|
||||
}
|
||||
|
||||
const device = deviceManager.getDevice(session.deviceId);
|
||||
const operator = deviceManager.getOperator(session.operatorId);
|
||||
|
||||
// Уведомляем участников
|
||||
if (device && device.isConnected()) {
|
||||
device.socket.emit('camera:disconnect', { sessionId, reason });
|
||||
device.removeSession(sessionId);
|
||||
}
|
||||
|
||||
if (operator && operator.isConnected()) {
|
||||
operator.socket.emit('camera:disconnected', { sessionId, reason });
|
||||
operator.removeSession(sessionId);
|
||||
}
|
||||
|
||||
// Закрываем сессию
|
||||
session.updateStatus('terminated', { reason, terminatedBy: 'admin' });
|
||||
sessionManager.closeSession(sessionId);
|
||||
|
||||
req.app.locals.logger.warn(`Session ${sessionId} terminated by admin: ${reason}`);
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
message: 'Session terminated',
|
||||
sessionId
|
||||
});
|
||||
|
||||
} catch (error) {
|
||||
req.app.locals.logger.error('Error terminating session', error);
|
||||
res.status(500).json({ error: error.message });
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* POST /api/admin/devices/:deviceId/disconnect
|
||||
* Принудительное отключение устройства
|
||||
*/
|
||||
router.post('/devices/:deviceId/disconnect', (req, res) => {
|
||||
try {
|
||||
const { deviceId } = req.params;
|
||||
const { reason = 'Disconnected by admin' } = req.body;
|
||||
|
||||
const { deviceManager, sessionManager } = req.app.locals;
|
||||
const device = deviceManager.getDevice(deviceId);
|
||||
|
||||
if (!device) {
|
||||
return res.status(404).json({ error: 'Device not found' });
|
||||
}
|
||||
|
||||
// Завершаем все активные сессии устройства
|
||||
const deviceSessions = sessionManager.getDeviceSessions(deviceId);
|
||||
deviceSessions.forEach(session => {
|
||||
if (session.status === 'active' || session.status === 'pending') {
|
||||
const operator = deviceManager.getOperator(session.operatorId);
|
||||
if (operator && operator.isConnected()) {
|
||||
operator.socket.emit('camera:disconnected', {
|
||||
sessionId: session.sessionId,
|
||||
reason: `Device disconnected: ${reason}`
|
||||
});
|
||||
operator.removeSession(session.sessionId);
|
||||
}
|
||||
|
||||
session.updateStatus('terminated', { reason, terminatedBy: 'admin' });
|
||||
sessionManager.closeSession(session.sessionId);
|
||||
}
|
||||
});
|
||||
|
||||
// Отключаем устройство
|
||||
if (device.isConnected()) {
|
||||
device.socket.emit('force:disconnect', { reason });
|
||||
device.socket.disconnect();
|
||||
}
|
||||
|
||||
deviceManager.disconnectDevice(deviceId);
|
||||
|
||||
req.app.locals.logger.warn(`Device ${deviceId} disconnected by admin: ${reason}`);
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
message: 'Device disconnected',
|
||||
deviceId,
|
||||
terminatedSessions: deviceSessions.length
|
||||
});
|
||||
|
||||
} catch (error) {
|
||||
req.app.locals.logger.error('Error disconnecting device', error);
|
||||
res.status(500).json({ error: error.message });
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* POST /api/admin/cleanup
|
||||
* Очистка старых сессий и отключенных устройств
|
||||
*/
|
||||
router.post('/cleanup', (req, res) => {
|
||||
try {
|
||||
const { maxAgeMinutes = 60 } = req.body;
|
||||
|
||||
const { deviceManager, sessionManager } = req.app.locals;
|
||||
|
||||
// Очистка старых сессий
|
||||
const cleanedSessions = sessionManager.cleanupOldSessions(maxAgeMinutes);
|
||||
|
||||
// Очистка отключенных устройств
|
||||
deviceManager.cleanup();
|
||||
|
||||
req.app.locals.logger.info(`Cleanup completed: ${cleanedSessions} sessions removed`);
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
message: 'Cleanup completed',
|
||||
removedSessions: cleanedSessions
|
||||
});
|
||||
|
||||
} catch (error) {
|
||||
req.app.locals.logger.error('Error during cleanup', error);
|
||||
res.status(500).json({ error: error.message });
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* GET /api/admin/health
|
||||
* Проверка состояния системы
|
||||
*/
|
||||
router.get('/health', (req, res) => {
|
||||
try {
|
||||
const { deviceManager, sessionManager } = req.app.locals;
|
||||
|
||||
const health = {
|
||||
status: 'healthy',
|
||||
timestamp: new Date().toISOString(),
|
||||
uptime: Math.round(process.uptime()),
|
||||
memory: {
|
||||
used: Math.round(process.memoryUsage().heapUsed / 1024 / 1024),
|
||||
total: Math.round(process.memoryUsage().heapTotal / 1024 / 1024),
|
||||
external: Math.round(process.memoryUsage().external / 1024 / 1024)
|
||||
},
|
||||
connections: {
|
||||
devices: deviceManager.getConnectedDevices().length,
|
||||
operators: deviceManager.getConnectedOperators().length,
|
||||
activeSessions: sessionManager.getActiveSessions().length
|
||||
}
|
||||
};
|
||||
|
||||
// Проверка на критические состояния
|
||||
if (health.memory.used / health.memory.total > 0.9) {
|
||||
health.status = 'warning';
|
||||
health.warnings = health.warnings || [];
|
||||
health.warnings.push('High memory usage');
|
||||
}
|
||||
|
||||
if (health.uptime < 60) {
|
||||
health.status = 'warning';
|
||||
health.warnings = health.warnings || [];
|
||||
health.warnings.push('Recently restarted');
|
||||
}
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
health
|
||||
});
|
||||
|
||||
} catch (error) {
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
health: {
|
||||
status: 'unhealthy',
|
||||
error: error.message,
|
||||
timestamp: new Date().toISOString()
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* GET /api/admin/logs
|
||||
* Получение логов системы (последние записи)
|
||||
*/
|
||||
router.get('/logs', (req, res) => {
|
||||
try {
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
const { lines = 100 } = req.query;
|
||||
|
||||
const logFile = path.join(process.cwd(), 'god-eye.log');
|
||||
|
||||
if (!fs.existsSync(logFile)) {
|
||||
return res.json({
|
||||
success: true,
|
||||
logs: [],
|
||||
message: 'Log file not found'
|
||||
});
|
||||
}
|
||||
|
||||
const logData = fs.readFileSync(logFile, 'utf8');
|
||||
const logLines = logData.trim().split('\n').slice(-parseInt(lines));
|
||||
|
||||
const logs = logLines.map(line => {
|
||||
try {
|
||||
return JSON.parse(line);
|
||||
} catch (e) {
|
||||
return { message: line, timestamp: new Date().toISOString(), level: 'info' };
|
||||
}
|
||||
}).reverse(); // Новые записи сверху
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
logs,
|
||||
total: logs.length
|
||||
});
|
||||
|
||||
} catch (error) {
|
||||
res.status(500).json({ error: error.message });
|
||||
}
|
||||
});
|
||||
|
||||
module.exports = router;
|
||||
337
backend/src/routes/operators.js
Normal file
337
backend/src/routes/operators.js
Normal file
@@ -0,0 +1,337 @@
|
||||
const express = require('express');
|
||||
const router = express.Router();
|
||||
|
||||
/**
|
||||
* Middleware для проверки аутентификации оператора
|
||||
*/
|
||||
const authenticateOperator = (req, res, next) => {
|
||||
const operatorId = req.headers['x-operator-id'];
|
||||
|
||||
if (!operatorId) {
|
||||
return res.status(401).json({ error: 'Operator ID required' });
|
||||
}
|
||||
|
||||
const operator = req.app.locals.deviceManager.getOperator(operatorId);
|
||||
if (!operator || !operator.isConnected()) {
|
||||
return res.status(401).json({ error: 'Invalid or disconnected operator' });
|
||||
}
|
||||
|
||||
req.operator = operator;
|
||||
next();
|
||||
};
|
||||
|
||||
/**
|
||||
* Middleware для проверки разрешений
|
||||
*/
|
||||
const requirePermission = (permission) => {
|
||||
return (req, res, next) => {
|
||||
if (!req.operator.hasPermission(permission)) {
|
||||
return res.status(403).json({ error: `Permission '${permission}' required` });
|
||||
}
|
||||
next();
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* GET /api/operators/devices
|
||||
* Получение списка доступных устройств
|
||||
*/
|
||||
router.get('/devices', authenticateOperator, requirePermission('view_cameras'), (req, res) => {
|
||||
try {
|
||||
const { deviceManager } = req.app.locals;
|
||||
const devices = deviceManager.getAvailableDevicesForOperator(req.operator.operatorId);
|
||||
|
||||
const devicesData = devices.map(device => device.getSummary());
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
devices: devicesData,
|
||||
total: devicesData.length
|
||||
});
|
||||
} catch (error) {
|
||||
res.status(500).json({ error: error.message });
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* GET /api/operators/devices/:deviceId
|
||||
* Получение информации о конкретном устройстве
|
||||
*/
|
||||
router.get('/devices/:deviceId', authenticateOperator, requirePermission('view_cameras'), (req, res) => {
|
||||
try {
|
||||
const { deviceManager } = req.app.locals;
|
||||
const device = deviceManager.getDevice(req.params.deviceId);
|
||||
|
||||
if (!device) {
|
||||
return res.status(404).json({ error: 'Device not found' });
|
||||
}
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
device: device.getSummary()
|
||||
});
|
||||
} catch (error) {
|
||||
res.status(500).json({ error: error.message });
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* POST /api/operators/camera/request
|
||||
* Создание запроса на доступ к камере
|
||||
*/
|
||||
router.post('/camera/request', authenticateOperator, requirePermission('request_camera'), (req, res) => {
|
||||
try {
|
||||
const { deviceId, cameraType = 'back' } = req.body;
|
||||
|
||||
if (!deviceId) {
|
||||
return res.status(400).json({ error: 'Device ID required' });
|
||||
}
|
||||
|
||||
const { deviceManager, sessionManager, io } = req.app.locals;
|
||||
const device = deviceManager.getDevice(deviceId);
|
||||
|
||||
if (!device) {
|
||||
return res.status(404).json({ error: 'Device not found' });
|
||||
}
|
||||
|
||||
if (!device.canAcceptNewSession()) {
|
||||
return res.status(409).json({ error: 'Device is busy or unavailable' });
|
||||
}
|
||||
|
||||
// Создаем сессию
|
||||
const session = sessionManager.createSession(deviceId, req.operator.operatorId, cameraType);
|
||||
|
||||
// Отправляем запрос на Android устройство
|
||||
device.socket.emit('camera:request', {
|
||||
sessionId: session.sessionId,
|
||||
operatorId: req.operator.operatorId,
|
||||
cameraType
|
||||
});
|
||||
|
||||
// Добавляем сессию к устройству и оператору
|
||||
device.addSession(session.sessionId);
|
||||
req.operator.addSession(session.sessionId);
|
||||
|
||||
req.app.locals.logger.info(`Camera request created: ${session.sessionId}`, {
|
||||
deviceId,
|
||||
operatorId: req.operator.operatorId,
|
||||
cameraType
|
||||
});
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
sessionId: session.sessionId,
|
||||
message: 'Camera request sent to device'
|
||||
});
|
||||
|
||||
} catch (error) {
|
||||
req.app.locals.logger.error('Error creating camera request', error);
|
||||
res.status(500).json({ error: error.message });
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* POST /api/operators/camera/:sessionId/switch
|
||||
* Переключение типа камеры в активной сессии
|
||||
*/
|
||||
router.post('/camera/:sessionId/switch', authenticateOperator, requirePermission('request_camera'), (req, res) => {
|
||||
try {
|
||||
const { sessionId } = req.params;
|
||||
const { cameraType } = req.body;
|
||||
|
||||
if (!cameraType) {
|
||||
return res.status(400).json({ error: 'Camera type required' });
|
||||
}
|
||||
|
||||
const { sessionManager, deviceManager, io } = req.app.locals;
|
||||
const session = sessionManager.getSession(sessionId);
|
||||
|
||||
if (!session) {
|
||||
return res.status(404).json({ error: 'Session not found' });
|
||||
}
|
||||
|
||||
if (session.operatorId !== req.operator.operatorId) {
|
||||
return res.status(403).json({ error: 'Access denied to this session' });
|
||||
}
|
||||
|
||||
if (session.status !== 'active') {
|
||||
return res.status(409).json({ error: 'Session is not active' });
|
||||
}
|
||||
|
||||
const device = deviceManager.getDevice(session.deviceId);
|
||||
if (!device || !device.isConnected()) {
|
||||
return res.status(409).json({ error: 'Device not available' });
|
||||
}
|
||||
|
||||
// Отправляем команду переключения камеры
|
||||
device.socket.emit('camera:switch', {
|
||||
sessionId,
|
||||
cameraType
|
||||
});
|
||||
|
||||
// Обновляем сессию
|
||||
session.switchCamera(cameraType);
|
||||
|
||||
req.app.locals.logger.info(`Camera switch requested: ${sessionId} -> ${cameraType}`);
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
message: `Camera switch to ${cameraType} requested`
|
||||
});
|
||||
|
||||
} catch (error) {
|
||||
req.app.locals.logger.error('Error switching camera', error);
|
||||
res.status(500).json({ error: error.message });
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* DELETE /api/operators/camera/:sessionId
|
||||
* Завершение сессии камеры
|
||||
*/
|
||||
router.delete('/camera/:sessionId', authenticateOperator, requirePermission('request_camera'), (req, res) => {
|
||||
try {
|
||||
const { sessionId } = req.params;
|
||||
|
||||
const { sessionManager, deviceManager } = req.app.locals;
|
||||
const session = sessionManager.getSession(sessionId);
|
||||
|
||||
if (!session) {
|
||||
return res.status(404).json({ error: 'Session not found' });
|
||||
}
|
||||
|
||||
if (session.operatorId !== req.operator.operatorId) {
|
||||
return res.status(403).json({ error: 'Access denied to this session' });
|
||||
}
|
||||
|
||||
const device = deviceManager.getDevice(session.deviceId);
|
||||
|
||||
// Отправляем команду отключения (если устройство подключено)
|
||||
if (device && device.isConnected()) {
|
||||
device.socket.emit('camera:disconnect', { sessionId });
|
||||
device.removeSession(sessionId);
|
||||
}
|
||||
|
||||
// Закрываем сессию
|
||||
sessionManager.closeSession(sessionId);
|
||||
req.operator.removeSession(sessionId);
|
||||
|
||||
req.app.locals.logger.info(`Session terminated: ${sessionId}`);
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
message: 'Session terminated'
|
||||
});
|
||||
|
||||
} catch (error) {
|
||||
req.app.locals.logger.error('Error terminating session', error);
|
||||
res.status(500).json({ error: error.message });
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* GET /api/operators/sessions
|
||||
* Получение активных сессий оператора
|
||||
*/
|
||||
router.get('/sessions', authenticateOperator, (req, res) => {
|
||||
try {
|
||||
const { sessionManager } = req.app.locals;
|
||||
const sessions = sessionManager.getOperatorSessions(req.operator.operatorId);
|
||||
|
||||
const sessionsData = sessions.map(session => session.getSummary());
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
sessions: sessionsData,
|
||||
total: sessionsData.length
|
||||
});
|
||||
} catch (error) {
|
||||
res.status(500).json({ error: error.message });
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* GET /api/operators/sessions/:sessionId
|
||||
* Получение детальной информации о сессии
|
||||
*/
|
||||
router.get('/sessions/:sessionId', authenticateOperator, (req, res) => {
|
||||
try {
|
||||
const { sessionId } = req.params;
|
||||
const { sessionManager } = req.app.locals;
|
||||
|
||||
const session = sessionManager.getSession(sessionId);
|
||||
if (!session) {
|
||||
return res.status(404).json({ error: 'Session not found' });
|
||||
}
|
||||
|
||||
if (session.operatorId !== req.operator.operatorId && !req.operator.hasPermission('admin')) {
|
||||
return res.status(403).json({ error: 'Access denied to this session' });
|
||||
}
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
session: session.getFullInfo()
|
||||
});
|
||||
} catch (error) {
|
||||
res.status(500).json({ error: error.message });
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* GET /api/operators/profile
|
||||
* Получение профиля текущего оператора
|
||||
*/
|
||||
router.get('/profile', authenticateOperator, (req, res) => {
|
||||
try {
|
||||
res.json({
|
||||
success: true,
|
||||
operator: req.operator.getSummary()
|
||||
});
|
||||
} catch (error) {
|
||||
res.status(500).json({ error: error.message });
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* POST /api/operators/ping/:deviceId
|
||||
* Проверка доступности устройства
|
||||
*/
|
||||
router.post('/ping/:deviceId', authenticateOperator, requirePermission('view_cameras'), (req, res) => {
|
||||
try {
|
||||
const { deviceId } = req.params;
|
||||
const { deviceManager } = req.app.locals;
|
||||
|
||||
const device = deviceManager.getDevice(deviceId);
|
||||
if (!device || !device.isConnected()) {
|
||||
return res.status(404).json({ error: 'Device not found or disconnected' });
|
||||
}
|
||||
|
||||
const pingStart = Date.now();
|
||||
|
||||
// Отправляем ping и ждем pong
|
||||
device.socket.emit('ping', { timestamp: pingStart }, (response) => {
|
||||
const responseTime = Date.now() - pingStart;
|
||||
device.updateHealthCheck(responseTime);
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
deviceId,
|
||||
responseTime,
|
||||
timestamp: new Date().toISOString()
|
||||
});
|
||||
});
|
||||
|
||||
// Таймаут для ping
|
||||
setTimeout(() => {
|
||||
if (!res.headersSent) {
|
||||
device.recordError();
|
||||
res.status(408).json({ error: 'Device ping timeout' });
|
||||
}
|
||||
}, 5000);
|
||||
|
||||
} catch (error) {
|
||||
res.status(500).json({ error: error.message });
|
||||
}
|
||||
});
|
||||
|
||||
module.exports = router;
|
||||
Reference in New Issue
Block a user