feat: Add real-time notification center with WebSocket integration
- Add in-app notification system with database storage - Implement bell icon component with unread count badge in dashboard header - Add full Notifications page tab with pagination and filtering (All/Unread/Read) - Implement real-time WebSocket push notifications to all connected clients - Add notification API endpoints: GET, PUT /read, DELETE, POST /clear - Fix critical bug in notificationService where 'status' variable was undefined - Add Notifications route to App.js routing - Enhanced UI with notification popup in header and dedicated full-page view - Auto-reconnect WebSocket with 5s timeout on disconnect - Support rich notification content with resource details and timestamps
This commit is contained in:
1 parent
7968c9231e
commit
db85573986
37 files changed
+3521
-207
No files matched your search
+44
-27
@@ -6,12 +6,20 @@ const notificationService = require('./notificationService');
|
||||
class Scheduler {
|
||||
constructor() {
|
||||
this.jobs = new Map();
|
||||
this.isRunning = false;
|
||||
}
|
||||
|
||||
start() {
|
||||
// Run checks every minute
|
||||
cron.schedule('* * * * *', async () => {
|
||||
await this.runChecks();
|
||||
if (!this.isRunning) {
|
||||
this.isRunning = true;
|
||||
try {
|
||||
await this.runChecks();
|
||||
} finally {
|
||||
this.isRunning = false;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// Archive old checks daily at 2 AM
|
||||
@@ -23,37 +31,44 @@ class Scheduler {
|
||||
setTimeout(() => {
|
||||
this.archiveOldChecks();
|
||||
}, 5000);
|
||||
|
||||
console.log('Scheduler started');
|
||||
}
|
||||
|
||||
archiveOldChecks() {
|
||||
try {
|
||||
// Get global retention setting
|
||||
const retentionSetting = db.prepare(`
|
||||
SELECT value FROM settings WHERE key = 'retention_days'
|
||||
`).get();
|
||||
|
||||
const retentionDays = retentionSetting ? parseInt(retentionSetting.value) : 30;
|
||||
const cutoffDate = new Date();
|
||||
cutoffDate.setDate(cutoffDate.getDate() - retentionDays);
|
||||
const cutoffString = cutoffDate.toISOString().replace('T', ' ').split('.')[0];
|
||||
const globalRetentionDays = retentionSetting ? parseInt(retentionSetting.value) : 30;
|
||||
|
||||
// Move old checks to archived_checks
|
||||
const oldChecks = db.prepare(`
|
||||
SELECT * FROM checks WHERE checked_at < ?
|
||||
`).all(cutoffString);
|
||||
// Get all resources with their retention settings
|
||||
const resources = db.prepare(`
|
||||
SELECT id, retention_days FROM resources
|
||||
`).all();
|
||||
|
||||
if (oldChecks.length > 0) {
|
||||
const insertArchived = db.prepare(`
|
||||
INSERT INTO archived_checks (resource_id, status, response_time, status_code, error_message, details, checked_at)
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?)
|
||||
`);
|
||||
const insertArchived = db.prepare(`
|
||||
INSERT INTO archived_checks (resource_id, status, response_time, status_code, error_message, details, checked_at)
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?)
|
||||
`);
|
||||
|
||||
const deleteOld = db.prepare(`
|
||||
DELETE FROM checks WHERE id = ?
|
||||
`);
|
||||
const deleteOld = db.prepare(`
|
||||
DELETE FROM checks WHERE id = ?
|
||||
`);
|
||||
|
||||
const transaction = db.transaction(() => {
|
||||
resources.forEach(resource => {
|
||||
// Use per-resource retention if set, otherwise use global
|
||||
const retentionDays = resource.retention_days || globalRetentionDays;
|
||||
const cutoffDate = new Date();
|
||||
cutoffDate.setDate(cutoffDate.getDate() - retentionDays);
|
||||
const cutoffString = cutoffDate.toISOString().replace('T', ' ').split('.')[0];
|
||||
|
||||
// Get old checks for this resource
|
||||
const oldChecks = db.prepare(`
|
||||
SELECT * FROM checks WHERE resource_id = ? AND checked_at < ?
|
||||
`).all(resource.id, cutoffString);
|
||||
|
||||
const transaction = db.transaction(() => {
|
||||
oldChecks.forEach(check => {
|
||||
insertArchived.run(
|
||||
check.resource_id,
|
||||
@@ -67,12 +82,11 @@ class Scheduler {
|
||||
deleteOld.run(check.id);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
transaction();
|
||||
console.log(`[Archival] Archived ${oldChecks.length} checks older than ${retentionDays} days`);
|
||||
}
|
||||
transaction();
|
||||
} catch (error) {
|
||||
console.error('[Archival] Error archiving checks:', error.message);
|
||||
// Archive error handled silently
|
||||
}
|
||||
}
|
||||
|
||||
@@ -133,10 +147,8 @@ class Scheduler {
|
||||
await notificationService.sendAlert(resource, incident, stats);
|
||||
}
|
||||
}
|
||||
|
||||
console.log(`Checked ${resource.name}: ${result.status} (${result.response_time}ms)`);
|
||||
} catch (error) {
|
||||
console.error(`Error checking ${resource.name}:`, error.message);
|
||||
// Check error handled silently
|
||||
}
|
||||
})());
|
||||
|
||||
@@ -147,6 +159,11 @@ class Scheduler {
|
||||
global.broadcastDashboardUpdate();
|
||||
}
|
||||
}
|
||||
|
||||
stop() {
|
||||
// Stop the scheduler (cron tasks will continue running but we flag isRunning)
|
||||
this.isRunning = false;
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = new Scheduler();
|
||||
Reference in new issue
Block a user