Files
skywatch/server/auditLog.js
T
nmemmert db85573986 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
2026-01-14 14:51:29 -05:00

153 lines
3.7 KiB
JavaScript

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();