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
@@ -0,0 +1,152 @@
|
||||
const db = require('./database');
|
||||
|
||||
class AuditLogger {
|
||||
// Log an audit event
|
||||
log(action, entityType, entityId, changes = {}, userId = 'system', ipAddress = null) {
|
||||
try {
|
||||
db.prepare(`
|
||||
INSERT INTO audit_logs
|
||||
(action, entity_type, entity_id, user_id, changes, ip_address, created_at)
|
||||
VALUES (?, ?, ?, ?, ?, ?, datetime('now'))
|
||||
`).run(
|
||||
action,
|
||||
entityType,
|
||||
entityId,
|
||||
userId,
|
||||
JSON.stringify(changes),
|
||||
ipAddress
|
||||
);
|
||||
} catch (error) {
|
||||
// Silent failure - don't crash on audit logging errors
|
||||
}
|
||||
}
|
||||
|
||||
// Log resource changes
|
||||
logResourceChange(action, resourceId, changes, userId = 'system', ipAddress = null) {
|
||||
this.log(action, 'resource', resourceId, changes, userId, ipAddress);
|
||||
}
|
||||
|
||||
// Log settings changes
|
||||
logSettingsChange(settingKey, oldValue, newValue, userId = 'system', ipAddress = null) {
|
||||
this.log('update', 'settings', settingKey, { old: oldValue, new: newValue }, userId, ipAddress);
|
||||
}
|
||||
|
||||
// Log group changes
|
||||
logGroupChange(action, groupId, changes, userId = 'system', ipAddress = null) {
|
||||
this.log(action, 'group', groupId, changes, userId, ipAddress);
|
||||
}
|
||||
|
||||
// Log maintenance window changes
|
||||
logMaintenanceChange(action, windowId, changes, userId = 'system', ipAddress = null) {
|
||||
this.log(action, 'maintenance_window', windowId, changes, userId, ipAddress);
|
||||
}
|
||||
|
||||
// Get audit logs with filters
|
||||
getAuditLogs(filters = {}) {
|
||||
const {
|
||||
entityType = null,
|
||||
entityId = null,
|
||||
userId = null,
|
||||
action = null,
|
||||
startDate = null,
|
||||
endDate = null,
|
||||
limit = 100,
|
||||
offset = 0
|
||||
} = filters;
|
||||
|
||||
let query = `
|
||||
SELECT
|
||||
id,
|
||||
action,
|
||||
entity_type,
|
||||
entity_id,
|
||||
user_id,
|
||||
changes,
|
||||
ip_address,
|
||||
REPLACE(created_at, ' ', 'T') || 'Z' as created_at
|
||||
FROM audit_logs
|
||||
WHERE 1=1
|
||||
`;
|
||||
const params = [];
|
||||
|
||||
if (entityType) {
|
||||
query += ' AND entity_type = ?';
|
||||
params.push(entityType);
|
||||
}
|
||||
|
||||
if (entityId) {
|
||||
query += ' AND entity_id = ?';
|
||||
params.push(entityId);
|
||||
}
|
||||
|
||||
if (userId) {
|
||||
query += ' AND user_id = ?';
|
||||
params.push(userId);
|
||||
}
|
||||
|
||||
if (action) {
|
||||
query += ' AND action = ?';
|
||||
params.push(action);
|
||||
}
|
||||
|
||||
if (startDate) {
|
||||
query += ' AND created_at >= ?';
|
||||
params.push(startDate);
|
||||
}
|
||||
|
||||
if (endDate) {
|
||||
query += ' AND created_at <= ?';
|
||||
params.push(endDate);
|
||||
}
|
||||
|
||||
query += ' ORDER BY created_at DESC LIMIT ? OFFSET ?';
|
||||
params.push(limit, offset);
|
||||
|
||||
try {
|
||||
const logs = db.prepare(query).all(...params);
|
||||
|
||||
// Parse JSON changes
|
||||
return logs.map(log => ({
|
||||
...log,
|
||||
changes: log.changes ? JSON.parse(log.changes) : {}
|
||||
}));
|
||||
} catch (error) {
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
// Get audit summary stats
|
||||
getSummary(days = 7) {
|
||||
try {
|
||||
const stats = db.prepare(`
|
||||
SELECT
|
||||
COUNT(*) as total_events,
|
||||
COUNT(DISTINCT user_id) as unique_users,
|
||||
COUNT(DISTINCT entity_type) as entity_types,
|
||||
action,
|
||||
COUNT(*) as action_count
|
||||
FROM audit_logs
|
||||
WHERE created_at > datetime('now', '-' || ? || ' days')
|
||||
GROUP BY action
|
||||
`).all(days);
|
||||
|
||||
const totalEvents = db.prepare(`
|
||||
SELECT COUNT(*) as count
|
||||
FROM audit_logs
|
||||
WHERE created_at > datetime('now', '-' || ? || ' days')
|
||||
`).get(days);
|
||||
|
||||
return {
|
||||
totalEvents: totalEvents?.count || 0,
|
||||
actionBreakdown: stats.map(s => ({
|
||||
action: s.action,
|
||||
count: s.action_count
|
||||
}))
|
||||
};
|
||||
} catch (error) {
|
||||
return { totalEvents: 0, actionBreakdown: [] };
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = new AuditLogger();
|
||||
Reference in new issue
Block a user