anon-identity
Version:
Decentralized identity framework with DIDs, Verifiable Credentials, and privacy-preserving selective disclosure
430 lines (427 loc) • 17.1 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
exports.RevocationMonitoringDashboard = void 0;
/**
* Monitoring dashboard for revocation and delegation audit trail
* Provides real-time metrics, analytics, and alerting capabilities
*/
class RevocationMonitoringDashboard {
constructor(auditTrail, revocationManager, agentManager, activityLogger, config = {
refreshInterval: 30000, // 30 seconds
alertRetentionDays: 30,
maxDisplayItems: 100,
enableRealTimeUpdates: true,
alertNotifications: {}
}) {
this.auditTrail = auditTrail;
this.revocationManager = revocationManager;
this.agentManager = agentManager;
this.activityLogger = activityLogger;
this.config = config;
this.metrics = null;
this.lastUpdate = new Date(0);
this.subscribers = new Map();
this.refreshTimer = null;
this.startAutoRefresh();
this.subscribeToAlerts();
}
/**
* Gets current dashboard metrics
*/
async getMetrics(filter = { timeRange: '24h' }) {
const shouldRefresh = this.shouldRefreshMetrics();
if (!this.metrics || shouldRefresh) {
await this.refreshMetrics(filter);
}
return this.metrics;
}
/**
* Subscribes to real-time dashboard updates
*/
subscribe(subscriberId, callback) {
this.subscribers.set(subscriberId, callback);
}
/**
* Unsubscribes from dashboard updates
*/
unsubscribe(subscriberId) {
this.subscribers.delete(subscriberId);
}
/**
* Forces a refresh of dashboard metrics
*/
async refreshMetrics(filter = { timeRange: '24h' }) {
const timeRange = this.calculateTimeRange(filter);
const analytics = this.auditTrail.generateAnalytics(true);
const activeAlerts = this.auditTrail.getActiveAlerts();
// Get recent alerts
const recentAlerts = this.getRecentAlerts(filter);
// Calculate real-time metrics
const realTimeMetrics = await this.calculateRealTimeMetrics();
// Calculate historical metrics
const historicalMetrics = this.calculateHistoricalMetrics(analytics, timeRange);
// Calculate trends
const trends = this.calculateTrends(analytics, timeRange);
// Alert summary
const alertSummary = this.calculateAlertSummary(activeAlerts);
this.metrics = {
realTime: realTimeMetrics,
historical: historicalMetrics,
trends,
alerts: {
active: activeAlerts,
recent: recentAlerts,
summary: alertSummary
}
};
this.lastUpdate = new Date();
// Notify subscribers
this.notifySubscribers(this.metrics);
return this.metrics;
}
/**
* Gets revocation statistics for a specific time period
*/
getRevocationStats(filter) {
const timeRange = this.calculateTimeRange(filter);
const query = { dateRange: timeRange };
const entries = this.auditTrail.queryAuditEntries(query);
const successful = entries.filter(e => e.status === 'completed').length;
const failed = entries.filter(e => e.status === 'failed').length;
// Count reasons
const reasonCounts = new Map();
const serviceCounts = new Map();
entries.forEach(entry => {
reasonCounts.set(entry.reason, (reasonCounts.get(entry.reason) || 0) + 1);
const service = entry.serviceDID || 'global';
serviceCounts.set(service, (serviceCounts.get(service) || 0) + 1);
});
const topReasons = Array.from(reasonCounts.entries())
.sort(([, a], [, b]) => b - a)
.slice(0, 10)
.map(([reason, count]) => ({ reason, count }));
const topServices = Array.from(serviceCounts.entries())
.sort(([, a], [, b]) => b - a)
.slice(0, 10)
.map(([service, count]) => ({ service, count }));
return {
totalRevocations: entries.length,
successfulRevocations: successful,
failedRevocations: failed,
averageProcessingTime: entries.length > 0 ? entries.length * 0.5 : 0, // Estimate
topReasons,
topServices
};
}
/**
* Gets agent health overview
*/
getAgentHealthOverview() {
const allAgents = this.agentManager.getAllAgents();
const now = new Date();
const last24h = new Date(now.getTime() - 24 * 60 * 60 * 1000);
const depthDistribution = new Map();
let revokedCount = 0;
let issuesCount = 0;
let recentlyCreated = 0;
let recentlyRevoked = 0;
allAgents.forEach((agent) => {
// Depth distribution
const depth = agent.delegationDepth || 0;
depthDistribution.set(depth, (depthDistribution.get(depth) || 0) + 1);
// Check if revoked
if (this.revocationManager.isAgentRevoked(agent.did)) {
revokedCount++;
// Check if recently revoked
const auditEntries = this.revocationManager.getRevocationAudit(agent.did);
if (auditEntries.some(entry => entry.timestamp >= last24h)) {
recentlyRevoked++;
}
}
// Check for issues (example: approaching max depth)
if (agent.delegationDepth >= (agent.maxDelegationDepth || 3) - 1) {
issuesCount++;
}
});
return {
totalAgents: allAgents.length,
activeAgents: allAgents.length - revokedCount,
revokedAgents: revokedCount,
agentsWithIssues: issuesCount,
delegationDepthDistribution: depthDistribution,
recentlyCreated: 0, // Would need creation timestamps
recentlyRevoked
};
}
/**
* Generates dashboard report
*/
generateReport(filter, format = 'json') {
const metrics = this.metrics;
if (!metrics) {
throw new Error('No metrics available. Call refreshMetrics() first.');
}
const stats = this.getRevocationStats(filter);
const healthOverview = this.getAgentHealthOverview();
const report = {
generatedAt: new Date().toISOString(),
filter,
summary: {
systemHealth: metrics.realTime.systemHealth,
totalRevocations: stats.totalRevocations,
successRate: stats.totalRevocations > 0 ? (stats.successfulRevocations / stats.totalRevocations) * 100 : 100,
activeAlerts: metrics.alerts.active.length,
agentHealth: healthOverview
},
metrics,
statistics: stats,
recommendations: this.generateRecommendations(metrics, stats, healthOverview)
};
switch (format) {
case 'json':
return JSON.stringify(report, null, 2);
case 'html':
return this.generateHtmlReport(report);
case 'pdf':
throw new Error('PDF format not implemented yet');
default:
return JSON.stringify(report, null, 2);
}
}
/**
* Stops the dashboard and cleans up resources
*/
stop() {
if (this.refreshTimer) {
clearInterval(this.refreshTimer);
this.refreshTimer = null;
}
this.subscribers.clear();
}
// Private helper methods
startAutoRefresh() {
if (!this.config.enableRealTimeUpdates) {
return;
}
this.refreshTimer = setInterval(async () => {
try {
await this.refreshMetrics();
}
catch (error) {
console.error('Dashboard auto-refresh error:', error);
}
}, this.config.refreshInterval);
}
subscribeToAlerts() {
this.auditTrail.subscribeToAlerts('dashboard', (alert) => {
// Handle new alerts for notifications
this.handleNewAlert(alert);
});
}
async handleNewAlert(alert) {
// Send notifications if configured
if (this.config.alertNotifications.email && alert.severity === 'critical') {
// Email notification logic would go here
console.log(`Critical alert: ${alert.message}`);
}
if (this.config.alertNotifications.webhook) {
// Webhook notification logic would go here
console.log(`Webhook alert: ${alert.message}`);
}
// Refresh metrics to include new alert
await this.refreshMetrics();
}
shouldRefreshMetrics() {
const refreshAge = Date.now() - this.lastUpdate.getTime();
return refreshAge > this.config.refreshInterval;
}
calculateTimeRange(filter) {
const now = new Date();
let start;
switch (filter.timeRange) {
case '1h':
start = new Date(now.getTime() - 60 * 60 * 1000);
break;
case '24h':
start = new Date(now.getTime() - 24 * 60 * 60 * 1000);
break;
case '7d':
start = new Date(now.getTime() - 7 * 24 * 60 * 60 * 1000);
break;
case '30d':
start = new Date(now.getTime() - 30 * 24 * 60 * 60 * 1000);
break;
case 'custom':
if (!filter.customRange) {
throw new Error('Custom range requires start and end dates');
}
return filter.customRange;
default:
start = new Date(now.getTime() - 24 * 60 * 60 * 1000);
}
return { start, end: now };
}
async calculateRealTimeMetrics() {
const allAgents = this.agentManager.getAllAgents();
const activeAgents = allAgents.filter((agent) => !this.revocationManager.isAgentRevoked(agent.did));
const now = new Date();
const last24h = new Date(now.getTime() - 24 * 60 * 60 * 1000);
const recentAlerts = this.auditTrail.getActiveAlerts()
.filter(alert => alert.timestamp >= last24h);
// Determine system health
let systemHealth = 'healthy';
const criticalAlerts = recentAlerts.filter(a => a.severity === 'critical').length;
const highAlerts = recentAlerts.filter(a => a.severity === 'high').length;
if (criticalAlerts > 0) {
systemHealth = 'critical';
}
else if (highAlerts > 2 || recentAlerts.length > 10) {
systemHealth = 'warning';
}
return {
activeAgents: activeAgents.length,
ongoingRevocations: 0, // Would track active revocation processes
alertsInLast24h: recentAlerts.length,
systemHealth
};
}
calculateHistoricalMetrics(analytics, timeRange) {
const peakHour = analytics.peakRevocationHours[0] || 12;
const peakTime = `${peakHour.toString().padStart(2, '0')}:00`;
return {
totalRevocations: analytics.totalRevocations,
successRate: analytics.complianceMetrics.auditCompleteness,
avgProcessingTime: analytics.complianceMetrics.avgProcessingTime,
peakRevocationTime: peakTime
};
}
calculateTrends(analytics, timeRange) {
// Daily revocations trend
const dailyRevocations = Array.from(analytics.revocationTrends.daily.entries())
.map(([date, count]) => ({ date, count }))
.sort((a, b) => a.date.localeCompare(b.date))
.slice(-30); // Last 30 days
// Revocation reasons
const totalRevocations = analytics.totalRevocations;
const revocationReasons = Array.from(analytics.revocationsByReason.entries())
.map(([reason, count]) => ({
reason,
count,
percentage: totalRevocations > 0 ? (count / totalRevocations) * 100 : 0
}))
.sort((a, b) => b.count - a.count)
.slice(0, 10);
// Service impact
const serviceImpact = Array.from(analytics.revocationsByService.entries())
.map(([service, revocations]) => ({
service,
revocations,
agents: revocations // Simplified - would need actual agent count per service
}))
.sort((a, b) => b.revocations - a.revocations)
.slice(0, 10);
return {
dailyRevocations,
revocationReasons,
serviceImpact
};
}
calculateAlertSummary(alerts) {
return {
critical: alerts.filter(a => a.severity === 'critical').length,
high: alerts.filter(a => a.severity === 'high').length,
medium: alerts.filter(a => a.severity === 'medium').length,
low: alerts.filter(a => a.severity === 'low').length
};
}
getRecentAlerts(filter) {
const timeRange = this.calculateTimeRange(filter);
return this.auditTrail.getActiveAlerts()
.filter(alert => alert.timestamp >= timeRange.start && alert.timestamp <= timeRange.end)
.slice(0, this.config.maxDisplayItems);
}
notifySubscribers(metrics) {
this.subscribers.forEach(callback => {
try {
callback(metrics);
}
catch (error) {
console.error('Error notifying dashboard subscriber:', error);
}
});
}
generateRecommendations(metrics, stats, healthOverview) {
const recommendations = [];
if (metrics.realTime.systemHealth === 'critical') {
recommendations.push('URGENT: System health is critical - investigate active alerts immediately');
}
if (metrics.alerts.summary.critical > 0) {
recommendations.push(`Address ${metrics.alerts.summary.critical} critical alerts`);
}
if (stats.totalRevocations > 0 && stats.successfulRevocations / stats.totalRevocations < 0.9) {
recommendations.push('Investigate high revocation failure rate');
}
if (healthOverview.agentsWithIssues > healthOverview.totalAgents * 0.1) {
recommendations.push('Review agent delegation depth policies');
}
if (metrics.trends.dailyRevocations.length > 7) {
const recent = metrics.trends.dailyRevocations.slice(-7);
const totalRecent = recent.reduce((sum, day) => sum + day.count, 0);
if (totalRecent > recent.length * 10) {
recommendations.push('High revocation volume detected - review security policies');
}
}
return recommendations;
}
generateHtmlReport(report) {
return `
<!DOCTYPE html>
<html>
<head>
<title>Revocation Monitoring Dashboard Report</title>
<style>
body { font-family: Arial, sans-serif; margin: 20px; }
.header { background: #f5f5f5; padding: 20px; margin-bottom: 20px; }
.section { margin-bottom: 30px; }
.metric { display: inline-block; margin: 10px; padding: 15px; background: #e9ecef; border-radius: 5px; }
.alert { padding: 10px; margin: 5px 0; border-radius: 3px; }
.alert.critical { background: #f8d7da; border: 1px solid #f5c6cb; }
.alert.high { background: #fff3cd; border: 1px solid #ffeaa7; }
.alert.medium { background: #d1ecf1; border: 1px solid #bee5eb; }
.alert.low { background: #d4edda; border: 1px solid #c3e6cb; }
</style>
</head>
<body>
<div class="header">
<h1>Revocation Monitoring Dashboard Report</h1>
<p>Generated: ${report.generatedAt}</p>
<p>System Health: <strong>${report.summary.systemHealth}</strong></p>
</div>
<div class="section">
<h2>Summary</h2>
<div class="metric">Total Revocations: ${report.summary.totalRevocations}</div>
<div class="metric">Success Rate: ${report.summary.successRate.toFixed(1)}%</div>
<div class="metric">Active Alerts: ${report.summary.activeAlerts}</div>
<div class="metric">Active Agents: ${report.summary.agentHealth.activeAgents}</div>
</div>
<div class="section">
<h2>Active Alerts</h2>
${report.metrics.alerts.active.map((alert) => `
<div class="alert ${alert.severity}">
<strong>${alert.severity.toUpperCase()}</strong>: ${alert.message}
<br><small>${alert.timestamp}</small>
</div>
`).join('')}
</div>
<div class="section">
<h2>Recommendations</h2>
<ul>
${report.recommendations.map((rec) => `<li>${rec}</li>`).join('')}
</ul>
</div>
</body>
</html>`;
}
}
exports.RevocationMonitoringDashboard = RevocationMonitoringDashboard;
//# sourceMappingURL=monitoring-dashboard.js.map