← back to Dear Bubbe Admin
backend/routes/monitoring.js
516 lines
const express = require('express')
const Session = require('../models/Session')
const Conversation = require('../models/Conversation')
const Alert = require('../models/Alert')
const User = require('../models/User')
const os = require('os')
const fs = require('fs')
const path = require('path')
const axios = require('axios')
const router = express.Router()
// Get real-time dashboard stats
router.get('/dashboard', async (req, res) => {
try {
const now = new Date()
const last24h = new Date(now - 24 * 60 * 60 * 1000)
const lastHour = new Date(now - 60 * 60 * 1000)
const last5min = new Date(now - 5 * 60 * 1000)
// Active users and sessions
const activeUsers = await Session.countDocuments({
lastActivity: { $gte: last5min },
ended: false
})
const activeSessions = await Session.countDocuments({
lastActivity: { $gte: new Date(now - 30 * 60 * 1000) },
ended: false
})
// Conversations stats
const conversationsToday = await Conversation.countDocuments({
timestamp: { $gte: last24h }
})
const conversationsLastHour = await Conversation.countDocuments({
timestamp: { $gte: lastHour }
})
// Alert stats
const alertsToday = await Alert.countDocuments({
timestamp: { $gte: last24h }
})
const criticalAlerts = await Alert.countDocuments({
timestamp: { $gte: last24h },
severity: { $in: ['high', 'critical'] }
})
// System metrics
const totalMem = os.totalmem()
const freeMem = os.freemem()
const memUsage = ((totalMem - freeMem) / totalMem) * 100
const loadAvg = os.loadavg()
const cpuUsage = (loadAvg[0] / os.cpus().length) * 100
// Process metrics
const processMemory = process.memoryUsage()
// User stats
const totalUsers = await User.countDocuments()
const newUsersToday = await User.countDocuments({
createdAt: { $gte: last24h }
})
const blockedUsers = await User.countDocuments({
'security.blocked': true
})
res.json({
success: true,
timestamp: now,
userActivity: {
activeUsers,
activeSessions,
totalUsers,
newUsersToday,
blockedUsers
},
conversations: {
today: conversationsToday,
lastHour: conversationsLastHour
},
alerts: {
today: alertsToday,
critical: criticalAlerts
},
system: {
memoryUsage: Math.round(memUsage),
cpuUsage: Math.min(Math.round(cpuUsage), 100),
uptime: os.uptime(),
loadAverage: loadAvg[0],
processMemoryMB: Math.round(processMemory.rss / 1024 / 1024)
}
})
} catch (error) {
console.error('Error fetching dashboard stats:', error)
res.status(500).json({
success: false,
error: 'Failed to fetch dashboard statistics'
})
}
})
// Get hourly activity for the last 24 hours
router.get('/hourly-activity', async (req, res) => {
try {
const now = new Date()
const last24h = new Date(now - 24 * 60 * 60 * 1000)
// Get hourly conversation stats
const hourlyConversations = await Conversation.aggregate([
{
$match: {
timestamp: { $gte: last24h }
}
},
{
$group: {
_id: {
year: { $year: '$timestamp' },
month: { $month: '$timestamp' },
day: { $dayOfMonth: '$timestamp' },
hour: { $hour: '$timestamp' }
},
conversations: { $sum: 1 },
uniqueUsers: { $addToSet: '$userId' }
}
},
{
$addFields: {
userCount: { $size: '$uniqueUsers' },
hour: '$_id.hour'
}
},
{
$sort: { '_id.year': 1, '_id.month': 1, '_id.day': 1, '_id.hour': 1 }
}
])
// Get hourly alert stats
const hourlyAlerts = await Alert.aggregate([
{
$match: {
timestamp: { $gte: last24h }
}
},
{
$group: {
_id: {
year: { $year: '$timestamp' },
month: { $month: '$timestamp' },
day: { $dayOfMonth: '$timestamp' },
hour: { $hour: '$timestamp' }
},
alerts: { $sum: 1 },
critical: { $sum: { $cond: [{ $in: ['$severity', ['high', 'critical']] }, 1, 0] } }
}
},
{
$sort: { '_id.year': 1, '_id.month': 1, '_id.day': 1, '_id.hour': 1 }
}
])
res.json({
success: true,
timeframe: 'last_24_hours',
conversations: hourlyConversations,
alerts: hourlyAlerts
})
} catch (error) {
console.error('Error fetching hourly activity:', error)
res.status(500).json({
success: false,
error: 'Failed to fetch hourly activity'
})
}
})
// Get geographic distribution
router.get('/geographic', async (req, res) => {
try {
const { timeframe = '24h' } = req.query
const now = new Date()
let startTime
switch (timeframe) {
case '1h':
startTime = new Date(now - 60 * 60 * 1000)
break
case '24h':
startTime = new Date(now - 24 * 60 * 60 * 1000)
break
case '7d':
startTime = new Date(now - 7 * 24 * 60 * 60 * 1000)
break
default:
startTime = new Date(now - 24 * 60 * 60 * 1000)
}
// Get session geographic data
const geoData = await Session.aggregate([
{
$match: {
startTime: { $gte: startTime }
}
},
{
$group: {
_id: {
country: '$location.country',
state: '$location.state'
},
sessions: { $sum: 1 },
uniqueUsers: { $addToSet: '$userId' },
messages: { $sum: '$activity.messageCount' },
alerts: { $sum: '$flags.alertsTriggered' }
}
},
{
$addFields: {
userCount: { $size: '$uniqueUsers' }
}
},
{
$sort: { sessions: -1 }
},
{
$limit: 50
}
])
// Get country-level stats
const countryStats = await Session.aggregate([
{
$match: {
startTime: { $gte: startTime }
}
},
{
$group: {
_id: '$location.country',
sessions: { $sum: 1 },
uniqueUsers: { $addToSet: '$userId' },
messages: { $sum: '$activity.messageCount' }
}
},
{
$addFields: {
userCount: { $size: '$uniqueUsers' }
}
},
{
$sort: { sessions: -1 }
},
{
$limit: 20
}
])
res.json({
success: true,
timeframe,
detailed: geoData,
byCountry: countryStats
})
} catch (error) {
console.error('Error fetching geographic data:', error)
res.status(500).json({
success: false,
error: 'Failed to fetch geographic data'
})
}
})
// Get topic analysis
router.get('/topics', async (req, res) => {
try {
const { timeframe = '24h', limit = 20 } = req.query
const topTopics = await Conversation.getTopTopics(parseInt(limit), timeframe)
// Get sentiment analysis for top topics
const topicSentiment = await Conversation.aggregate([
{
$match: {
timestamp: { $gte: new Date(Date.now() - 24 * 60 * 60 * 1000) }
}
},
{
$unwind: '$topics'
},
{
$group: {
_id: {
topic: '$topics',
sentiment: '$message.sentiment'
},
count: { $sum: 1 }
}
},
{
$group: {
_id: '$_id.topic',
sentiments: {
$push: {
sentiment: '$_id.sentiment',
count: '$count'
}
},
total: { $sum: '$count' }
}
},
{
$sort: { total: -1 }
},
{
$limit: 10
}
])
res.json({
success: true,
timeframe,
topTopics,
sentimentAnalysis: topicSentiment
})
} catch (error) {
console.error('Error fetching topic analysis:', error)
res.status(500).json({
success: false,
error: 'Failed to fetch topic analysis'
})
}
})
// Check Dear Bubbe API health
router.get('/bubbe-health', async (req, res) => {
try {
const startTime = Date.now()
// Check main API
const apiResponse = await axios.get('http://localhost:3011/api/health', {
timeout: 10000
})
const responseTime = Date.now() - startTime
// Check if PM2 process is running
let pm2Status = 'unknown'
try {
const { execSync } = require('child_process')
const pm2List = execSync('pm2 jlist', { encoding: 'utf-8' })
const processes = JSON.parse(pm2List)
const bubbeProcess = processes.find(p => p.name === 'bubbe')
if (bubbeProcess) {
pm2Status = bubbeProcess.pm2_env.status
}
} catch (error) {
console.warn('Could not check PM2 status:', error.message)
}
// Check disk space
const stats = fs.statSync('/')
const diskUsage = {
// This is a simplified check - in production you'd use proper disk space checking
available: true
}
res.json({
success: true,
timestamp: new Date(),
api: {
status: apiResponse.status === 200 ? 'healthy' : 'unhealthy',
responseTime,
data: apiResponse.data
},
pm2: {
status: pm2Status
},
disk: diskUsage,
overall: apiResponse.status === 200 && pm2Status === 'online' ? 'healthy' : 'degraded'
})
} catch (error) {
console.error('Bubbe health check failed:', error.message)
res.json({
success: false,
timestamp: new Date(),
api: {
status: 'unhealthy',
error: error.message
},
overall: 'unhealthy'
})
}
})
// Get live session data
router.get('/live-sessions', async (req, res) => {
try {
const activeSessions = await Session.getActiveSessions(30) // Last 30 minutes
// Enrich with user data
const enrichedSessions = await Promise.all(
activeSessions.map(async (session) => {
const user = await User.findOne({ userId: session.userId })
.select('profile.name profile.preferredName security.riskLevel security.blocked')
.lean()
return {
...session,
user: user || { profile: { name: 'Unknown User' } }
}
})
)
res.json({
success: true,
timestamp: new Date(),
activeSessions: enrichedSessions,
count: enrichedSessions.length
})
} catch (error) {
console.error('Error fetching live sessions:', error)
res.status(500).json({
success: false,
error: 'Failed to fetch live sessions'
})
}
})
// Get performance metrics
router.get('/performance', async (req, res) => {
try {
const { timeframe = '24h' } = req.query
const now = new Date()
let startTime
switch (timeframe) {
case '1h':
startTime = new Date(now - 60 * 60 * 1000)
break
case '24h':
startTime = new Date(now - 24 * 60 * 60 * 1000)
break
case '7d':
startTime = new Date(now - 7 * 24 * 60 * 60 * 1000)
break
default:
startTime = new Date(now - 24 * 60 * 60 * 1000)
}
// Response time stats
const responseTimeStats = await Conversation.aggregate([
{
$match: {
timestamp: { $gte: startTime },
'analysis.responseTime': { $exists: true }
}
},
{
$group: {
_id: null,
avgResponseTime: { $avg: '$analysis.responseTime' },
minResponseTime: { $min: '$analysis.responseTime' },
maxResponseTime: { $max: '$analysis.responseTime' },
count: { $sum: 1 }
}
}
])
// Error rate
const errorStats = await Session.aggregate([
{
$match: {
startTime: { $gte: startTime }
}
},
{
$group: {
_id: null,
totalSessions: { $sum: 1 },
totalErrors: { $sum: '$activity.errorsEncountered' },
avgErrors: { $avg: '$activity.errorsEncountered' }
}
}
])
res.json({
success: true,
timeframe,
responseTime: responseTimeStats[0] || {},
errors: errorStats[0] || {},
errorRate: errorStats[0] ? (errorStats[0].totalErrors / errorStats[0].totalSessions) * 100 : 0
})
} catch (error) {
console.error('Error fetching performance metrics:', error)
res.status(500).json({
success: false,
error: 'Failed to fetch performance metrics'
})
}
})
module.exports = router