const express = require('express'); const router = express.Router(); const { DroneDetection, Device, Heartbeat, Tenant } = require('../models'); const { Op } = require('sequelize'); const { sequelize } = require('../models'); const { authenticateToken } = require('../middleware/auth'); const { MultiTenantAuth } = require('../middleware/multi-tenant-auth'); // GET /api/dashboard/overview - Get dashboard overview statistics router.get('/overview', authenticateToken, async (req, res) => { try { const { hours = 24 } = req.query; const timeWindow = new Date(Date.now() - hours * 60 * 60 * 1000); // Initialize multi-tenant auth to determine tenant const multiTenantAuth = new MultiTenantAuth(); const tenantId = await multiTenantAuth.determineTenant(req); if (!tenantId) { return res.status(403).json({ success: false, message: 'Access denied: No tenant context' }); } // Create base filter for tenant devices const tenantDeviceFilter = { tenant_id: tenantId }; // Get basic statistics - filtered by tenant const [ totalDevices, activeDevices, totalDetections, recentDetections, uniqueDronesDetected ] = await Promise.all([ Device.count({ where: tenantDeviceFilter }), Device.count({ where: { ...tenantDeviceFilter, is_active: true } }), DroneDetection.count({ include: [{ model: Device, where: tenantDeviceFilter, attributes: [] }], where: { drone_type: { [Op.ne]: 0 } } }), DroneDetection.count({ include: [{ model: Device, where: tenantDeviceFilter, attributes: [] }], where: { server_timestamp: { [Op.gte]: timeWindow }, drone_type: { [Op.ne]: 0 } } }), DroneDetection.count({ include: [{ model: Device, where: tenantDeviceFilter, attributes: [] }], where: { server_timestamp: { [Op.gte]: timeWindow }, drone_type: { [Op.ne]: 0 } }, distinct: true, col: 'drone_id' }) ]); // Get device status breakdown - filtered by tenant const devices = await Device.findAll({ where: tenantDeviceFilter, attributes: ['id', 'last_heartbeat', 'heartbeat_interval', 'is_active'] }); const now = new Date(); let onlineDevices = 0; let offlineDevices = 0; devices.forEach(device => { if (!device.is_active) return; const timeSinceLastHeartbeat = device.last_heartbeat ? (now - new Date(device.last_heartbeat)) / 1000 : null; const expectedInterval = device.heartbeat_interval || 300; const isOnline = timeSinceLastHeartbeat && timeSinceLastHeartbeat < (expectedInterval * 2); if (isOnline) { onlineDevices++; } else { offlineDevices++; } }); // Get recent alerts count // This would require AlertLog model which we haven't imported yet // const recentAlerts = await AlertLog.count({ // where: { created_at: { [Op.gte]: timeWindow } } // }); res.json({ success: true, data: { summary: { total_devices: totalDevices, active_devices: activeDevices, online_devices: onlineDevices, offline_devices: offlineDevices, total_detections: totalDetections, recent_detections: recentDetections, unique_drones_detected: uniqueDronesDetected, // recent_alerts: recentAlerts || 0, time_window_hours: hours }, device_status: { total: totalDevices, active: activeDevices, online: onlineDevices, offline: offlineDevices, inactive: totalDevices - activeDevices } } }); } catch (error) { console.error('Error fetching dashboard overview:', error); res.status(500).json({ success: false, message: 'Failed to fetch dashboard overview', error: process.env.NODE_ENV === 'development' ? error.message : 'Internal server error' }); } }); // GET /api/dashboard/activity - Get recent activity feed router.get('/activity', authenticateToken, async (req, res) => { try { const { limit = 50, hours = 24 } = req.query; const timeWindow = new Date(Date.now() - hours * 60 * 60 * 1000); // Initialize multi-tenant auth to determine tenant const multiTenantAuth = new MultiTenantAuth(); const tenantId = await multiTenantAuth.determineTenant(req); if (!tenantId) { return res.status(403).json({ success: false, message: 'Access denied: No tenant context' }); } // Get recent detections with device info - filtered by tenant const recentDetections = await DroneDetection.findAll({ where: { server_timestamp: { [Op.gte]: timeWindow }, drone_type: { [Op.ne]: 0 } }, include: [{ model: Device, as: 'device', where: { tenant_id: tenantId }, attributes: ['id', 'name', 'geo_lat', 'geo_lon', 'location_description'] }], limit: Math.min(parseInt(limit), 200), order: [['server_timestamp', 'DESC']] }); // Get recent heartbeats - filtered by tenant const recentHeartbeats = await Heartbeat.findAll({ where: { received_at: { [Op.gte]: timeWindow } }, include: [{ model: Device, as: 'device', where: { tenant_id: tenantId }, attributes: ['id', 'name', 'geo_lat', 'geo_lon'] }], limit: Math.min(parseInt(limit), 50), order: [['received_at', 'DESC']] }); // Combine and sort activities const activities = [ ...recentDetections.map(detection => ({ type: 'detection', timestamp: detection.server_timestamp, data: { detection_id: detection.id, device_id: detection.device_id, device_name: detection.device.name || `Device ${detection.device_id}`, drone_id: detection.drone_id, drone_type: detection.drone_type, rssi: detection.rssi, freq: detection.freq, location: detection.device.location_description || `${detection.device.geo_lat}, ${detection.device.geo_lon}` } })), ...recentHeartbeats.map(heartbeat => ({ type: 'heartbeat', timestamp: heartbeat.received_at, data: { device_id: heartbeat.device_id, device_name: heartbeat.device.name || `Device ${heartbeat.device_id}`, battery_level: heartbeat.battery_level, signal_strength: heartbeat.signal_strength, temperature: heartbeat.temperature } })) ]; // Sort by timestamp descending activities.sort((a, b) => new Date(b.timestamp) - new Date(a.timestamp)); res.json({ success: true, data: activities.slice(0, parseInt(limit)) }); } catch (error) { console.error('Error fetching dashboard activity:', error); res.status(500).json({ success: false, message: 'Failed to fetch dashboard activity', error: process.env.NODE_ENV === 'development' ? error.message : 'Internal server error' }); } }); // GET /api/dashboard/charts/detections - Get detection chart data router.get('/charts/detections', authenticateToken, async (req, res) => { try { const { hours = 24, interval = 'hour' } = req.query; const timeWindow = new Date(Date.now() - hours * 60 * 60 * 1000); // Initialize multi-tenant auth to determine tenant const multiTenantAuth = new MultiTenantAuth(); const tenantId = await multiTenantAuth.determineTenant(req); if (!tenantId) { return res.status(403).json({ success: false, message: 'Access denied: No tenant context' }); } let groupBy; switch (interval) { case 'minute': groupBy = "DATE_TRUNC('minute', server_timestamp)"; break; case 'hour': groupBy = "DATE_TRUNC('hour', server_timestamp)"; break; case 'day': groupBy = "DATE_TRUNC('day', server_timestamp)"; break; default: groupBy = "DATE_TRUNC('hour', server_timestamp)"; } const detectionCounts = await DroneDetection.findAll({ include: [{ model: Device, where: { tenant_id: tenantId }, attributes: [] }], where: { server_timestamp: { [Op.gte]: timeWindow }, drone_type: { [Op.ne]: 0 } }, attributes: [ [sequelize.fn('DATE_TRUNC', interval, sequelize.col('server_timestamp')), 'time_bucket'], [sequelize.fn('COUNT', '*'), 'count'] ], group: ['time_bucket'], order: [['time_bucket', 'ASC']], raw: true }); res.json({ success: true, data: detectionCounts.map(item => ({ timestamp: item.time_bucket, count: parseInt(item.count) })) }); } catch (error) { console.error('Error fetching detection chart data:', error); res.status(500).json({ success: false, message: 'Failed to fetch detection chart data', error: process.env.NODE_ENV === 'development' ? error.message : 'Internal server error' }); } }); // GET /api/dashboard/charts/devices - Get device activity chart data router.get('/charts/devices', authenticateToken, async (req, res) => { try { const { hours = 24 } = req.query; const timeWindow = new Date(Date.now() - hours * 60 * 60 * 1000); // Initialize multi-tenant auth to determine tenant const multiTenantAuth = new MultiTenantAuth(); const tenantId = await multiTenantAuth.determineTenant(req); if (!tenantId) { return res.status(403).json({ success: false, message: 'Access denied: No tenant context' }); } const deviceActivity = await DroneDetection.findAll({ where: { server_timestamp: { [Op.gte]: timeWindow }, drone_type: { [Op.ne]: 0 } }, attributes: [ 'device_id', [sequelize.fn('COUNT', '*'), 'detection_count'] ], include: [{ model: Device, as: 'device', where: { tenant_id: tenantId }, attributes: ['name', 'location_description'] }], group: ['device_id', 'device.id', 'device.name', 'device.location_description'], order: [[sequelize.fn('COUNT', '*'), 'DESC']], raw: false }); res.json({ success: true, data: deviceActivity.map(item => ({ device_id: item.device_id, device_name: item.device.name || `Device ${item.device_id}`, location: item.device.location_description, detection_count: parseInt(item.dataValues.detection_count) })) }); } catch (error) { console.error('Error fetching device chart data:', error); res.status(500).json({ success: false, message: 'Failed to fetch device chart data', error: process.env.NODE_ENV === 'development' ? error.message : 'Internal server error' }); } }); module.exports = router;