Skip to main content
Glama
by Coder-RL
disaster-recovery.ts37.6 kB
import { EventEmitter } from 'events'; import * as crypto from 'crypto'; export interface DisasterRecoveryPlan { id: string; name: string; description: string; version: string; criticality: 'low' | 'medium' | 'high' | 'critical'; rto: number; // Recovery Time Objective in minutes rpo: number; // Recovery Point Objective in minutes components: RecoveryComponent[]; dependencies: PlanDependency[]; procedures: RecoveryProcedure[]; contacts: EmergencyContact[]; triggers: DisasterTrigger[]; testSchedule: TestSchedule; compliance: ComplianceRequirement[]; documentation: PlanDocumentation; status: 'draft' | 'approved' | 'active' | 'inactive' | 'archived'; lastTested: Date; lastActivated?: Date; created: Date; modified: Date; } export interface RecoveryComponent { id: string; name: string; type: 'application' | 'database' | 'infrastructure' | 'network' | 'storage' | 'service'; priority: number; // 1 = highest priority dependencies: string[]; // component IDs primaryLocation: Location; recoveryLocation: Location; backupSources: BackupSource[]; recoveryStrategies: RecoveryStrategy[]; healthChecks: HealthCheck[]; metrics: ComponentMetrics; status: 'operational' | 'degraded' | 'failed' | 'recovering' | 'recovered'; } export interface Location { id: string; name: string; type: 'datacenter' | 'cloud-region' | 'edge' | 'hybrid'; region: string; zone: string; provider: string; connectivity: ConnectivityInfo; resources: ResourceInfo; coordinates?: { latitude: number; longitude: number; }; } export interface ConnectivityInfo { bandwidth: number; // Mbps latency: number; // ms redundancy: string[]; networkProviders: string[]; } export interface ResourceInfo { compute: { cpu: number; memory: number; // GB storage: number; // GB }; network: { internalBandwidth: number; externalBandwidth: number; }; availability: number; // 0-1 (e.g., 0.9999 for 99.99%) } export interface BackupSource { id: string; type: 'full' | 'incremental' | 'differential' | 'snapshot' | 'replica'; location: string; frequency: string; // cron expression retention: number; // days encryption: boolean; compression: boolean; lastBackup: Date; nextBackup: Date; size: number; // bytes status: 'healthy' | 'warning' | 'error'; } export interface RecoveryStrategy { id: string; name: string; type: 'failover' | 'failback' | 'restore' | 'rebuild' | 'manual'; automationLevel: 'manual' | 'semi-automatic' | 'automatic'; estimatedTime: number; // minutes procedures: ProcedureStep[]; prerequisites: string[]; rollbackProcedure?: ProcedureStep[]; successCriteria: SuccessCriteria[]; } export interface ProcedureStep { id: string; name: string; description: string; type: 'manual' | 'script' | 'api-call' | 'command' | 'approval'; order: number; timeout: number; // minutes retries: number; command?: string; script?: string; apiEndpoint?: string; expectedResult?: string; rollbackCommand?: string; approvers?: string[]; dependencies: string[]; // step IDs } export interface SuccessCriteria { id: string; name: string; metric: string; operator: 'equals' | 'greater-than' | 'less-than' | 'contains'; expectedValue: any; timeout: number; // minutes critical: boolean; } export interface HealthCheck { id: string; name: string; type: 'http' | 'tcp' | 'database' | 'custom'; endpoint: string; method?: string; expectedStatus?: number; expectedContent?: string; timeout: number; // seconds interval: number; // seconds retries: number; lastCheck?: Date; status: 'healthy' | 'unhealthy' | 'unknown'; } export interface ComponentMetrics { availability: number; // 0-1 responseTime: number; // ms errorRate: number; // 0-1 throughput: number; // requests/second lastUpdated: Date; historicalData: MetricDataPoint[]; } export interface MetricDataPoint { timestamp: Date; availability: number; responseTime: number; errorRate: number; throughput: number; } export interface PlanDependency { id: string; dependentPlan: string; dependency: string; type: 'hard' | 'soft'; description: string; } export interface RecoveryProcedure { id: string; name: string; description: string; phase: 'assessment' | 'notification' | 'recovery' | 'validation' | 'communication'; steps: ProcedureStep[]; estimatedDuration: number; // minutes assignedTo: string[]; approvalRequired: boolean; approvers: string[]; } export interface EmergencyContact { id: string; name: string; role: string; primary: boolean; phone: string; email: string; alternatePhone?: string; alternateEmail?: string; escalationLevel: number; availability: ContactAvailability; } export interface ContactAvailability { timezone: string; workingHours: { start: string; // HH:MM end: string; // HH:MM }; weekdays: number[]; // 0-6, Sunday = 0 emergency24x7: boolean; } export interface DisasterTrigger { id: string; name: string; type: 'manual' | 'automatic' | 'external'; conditions: TriggerCondition[]; actions: TriggerAction[]; enabled: boolean; lastTriggered?: Date; } export interface TriggerCondition { id: string; type: 'metric' | 'event' | 'health-check' | 'external-signal'; source: string; operator: 'equals' | 'greater-than' | 'less-than' | 'contains' | 'matches'; threshold: any; duration: number; // seconds severity: 'low' | 'medium' | 'high' | 'critical'; } export interface TriggerAction { id: string; type: 'notify' | 'execute-plan' | 'escalate' | 'log'; configuration: Record<string, any>; delay: number; // seconds } export interface TestSchedule { enabled: boolean; frequency: string; // cron expression type: 'full' | 'partial' | 'tabletop' | 'walkthrough'; scope: string[]; duration: number; // hours participants: string[]; lastTest?: Date; nextTest?: Date; results: TestResult[]; } export interface TestResult { id: string; date: Date; type: string; duration: number; // minutes success: boolean; findings: TestFinding[]; recommendations: string[]; participants: string[]; artifacts: string[]; } export interface TestFinding { id: string; severity: 'low' | 'medium' | 'high' | 'critical'; category: 'procedure' | 'technology' | 'communication' | 'documentation'; description: string; impact: string; recommendation: string; assignedTo?: string; dueDate?: Date; status: 'open' | 'in-progress' | 'resolved' | 'accepted-risk'; } export interface ComplianceRequirement { id: string; framework: string; // SOX, HIPAA, PCI-DSS, etc. requirement: string; description: string; evidence: string[]; lastAudit?: Date; nextAudit?: Date; status: 'compliant' | 'non-compliant' | 'under-review'; } export interface PlanDocumentation { overview: string; scope: string; assumptions: string[]; limitations: string[]; roles: RoleDefinition[]; communicationPlan: CommunicationPlan; escalationMatrix: EscalationLevel[]; attachments: DocumentAttachment[]; } export interface RoleDefinition { role: string; responsibilities: string[]; authority: string[]; skillsRequired: string[]; backupPersonnel: string[]; } export interface CommunicationPlan { channels: CommunicationChannel[]; templates: CommunicationTemplate[]; schedules: CommunicationSchedule[]; } export interface CommunicationChannel { id: string; name: string; type: 'email' | 'sms' | 'phone' | 'slack' | 'teams' | 'webhook'; priority: number; configuration: Record<string, any>; enabled: boolean; } export interface CommunicationTemplate { id: string; name: string; type: 'initial-notification' | 'status-update' | 'resolution' | 'post-mortem'; subject: string; body: string; channels: string[]; recipients: string[]; } export interface CommunicationSchedule { phase: string; frequency: number; // minutes recipients: string[]; channels: string[]; template: string; } export interface EscalationLevel { level: number; trigger: string; timeframe: number; // minutes contacts: string[]; actions: string[]; } export interface DocumentAttachment { id: string; name: string; type: string; size: number; url: string; lastModified: Date; } export interface DisasterEvent { id: string; planId: string; type: string; severity: 'low' | 'medium' | 'high' | 'critical'; startTime: Date; endTime?: Date; duration?: number; // minutes status: 'active' | 'resolved' | 'closed'; impactedComponents: string[]; recoveryProgress: RecoveryProgress; timeline: EventTimeline[]; notifications: NotificationLog[]; decisions: DecisionLog[]; costs: CostEstimate; } export interface RecoveryProgress { overallProgress: number; // 0-100 phases: PhaseProgress[]; components: ComponentProgress[]; estimatedCompletion?: Date; actualRTO?: number; // minutes actualRPO?: number; // minutes } export interface PhaseProgress { phase: string; status: 'pending' | 'in-progress' | 'completed' | 'failed'; progress: number; // 0-100 startTime?: Date; endTime?: Date; assignedTo: string[]; } export interface ComponentProgress { componentId: string; status: 'pending' | 'recovering' | 'recovered' | 'failed'; progress: number; // 0-100 currentStrategy?: string; startTime?: Date; endTime?: Date; issues: string[]; } export interface EventTimeline { id: string; timestamp: Date; type: 'detection' | 'notification' | 'decision' | 'action' | 'milestone' | 'resolution'; description: string; source: string; severity?: string; metadata?: Record<string, any>; } export interface NotificationLog { id: string; timestamp: Date; type: string; recipients: string[]; channels: string[]; template: string; success: boolean; error?: string; } export interface DecisionLog { id: string; timestamp: Date; decision: string; rationale: string; alternatives: string[]; decisionMaker: string; approvers: string[]; impact: string; reversible: boolean; } export interface CostEstimate { directCosts: { recovery: number; resources: number; personnel: number; external: number; }; indirectCosts: { businessImpact: number; reputation: number; opportunity: number; }; totalEstimated: number; actualCosts?: number; currency: string; } export class DisasterRecoveryManager extends EventEmitter { private plans = new Map<string, DisasterRecoveryPlan>(); private events = new Map<string, DisasterEvent>(); private monitoringInterval: NodeJS.Timeout | null = null; private testScheduler: NodeJS.Timeout | null = null; private activeRecoveries = new Set<string>(); constructor() { super(); this.startMonitoring(); this.startTestScheduler(); } createPlan(config: Omit<DisasterRecoveryPlan, 'id' | 'status' | 'created' | 'modified'>): string { const plan: DisasterRecoveryPlan = { id: crypto.randomUUID(), status: 'draft', created: new Date(), modified: new Date(), ...config }; this.plans.set(plan.id, plan); this.emit('plan-created', plan); return plan.id; } updatePlan(planId: string, updates: Partial<DisasterRecoveryPlan>): boolean { const plan = this.plans.get(planId); if (!plan) { return false; } Object.assign(plan, updates); plan.modified = new Date(); this.emit('plan-updated', plan); return true; } activatePlan(planId: string): boolean { const plan = this.plans.get(planId); if (!plan) { return false; } if (plan.status !== 'approved') { throw new Error('Plan must be approved before activation'); } plan.status = 'active'; plan.lastActivated = new Date(); plan.modified = new Date(); // Start monitoring triggers this.startPlanMonitoring(plan); this.emit('plan-activated', plan); return true; } async executeRecovery(planId: string, triggerId?: string, manual: boolean = false): Promise<string> { const plan = this.plans.get(planId); if (!plan) { throw new Error(`Disaster recovery plan not found: ${planId}`); } if (plan.status !== 'active' && !manual) { throw new Error(`Plan is not active: ${planId}`); } const event: DisasterEvent = { id: crypto.randomUUID(), planId, type: triggerId ? 'automatic' : 'manual', severity: plan.criticality, startTime: new Date(), status: 'active', impactedComponents: plan.components.map(c => c.id), recoveryProgress: { overallProgress: 0, phases: plan.procedures.map(p => ({ phase: p.phase, status: 'pending', progress: 0, assignedTo: p.assignedTo })), components: plan.components.map(c => ({ componentId: c.id, status: 'pending', progress: 0, issues: [] })) }, timeline: [{ id: crypto.randomUUID(), timestamp: new Date(), type: 'detection', description: 'Disaster recovery initiated', source: manual ? 'manual' : triggerId || 'unknown' }], notifications: [], decisions: [], costs: { directCosts: { recovery: 0, resources: 0, personnel: 0, external: 0 }, indirectCosts: { businessImpact: 0, reputation: 0, opportunity: 0 }, totalEstimated: 0, currency: 'USD' } }; this.events.set(event.id, event); this.activeRecoveries.add(event.id); this.emit('recovery-started', event); try { // Send initial notifications await this.sendNotifications(plan, event, 'initial-notification'); // Execute recovery procedures await this.executeRecoveryProcedures(plan, event); event.status = 'resolved'; event.endTime = new Date(); event.duration = event.endTime.getTime() - event.startTime.getTime(); this.addTimelineEntry(event, 'resolution', 'Recovery completed successfully'); this.emit('recovery-completed', event); } catch (error) { event.status = 'active'; // Keep active for manual intervention this.addTimelineEntry(event, 'action', `Recovery failed: ${(error as Error).message}`); this.emit('recovery-failed', { event, error: (error as Error).message }); throw error; } finally { this.activeRecoveries.delete(event.id); } return event.id; } private async executeRecoveryProcedures(plan: DisasterRecoveryPlan, event: DisasterEvent): Promise<void> { // Sort procedures by phase const proceduresByPhase = new Map<string, RecoveryProcedure[]>(); for (const procedure of plan.procedures) { if (!proceduresByPhase.has(procedure.phase)) { proceduresByPhase.set(procedure.phase, []); } proceduresByPhase.get(procedure.phase)!.push(procedure); } const phases = ['assessment', 'notification', 'recovery', 'validation', 'communication']; for (const phase of phases) { const procedures = proceduresByPhase.get(phase) || []; if (procedures.length === 0) continue; const phaseProgress = event.recoveryProgress.phases.find(p => p.phase === phase)!; phaseProgress.status = 'in-progress'; phaseProgress.startTime = new Date(); this.addTimelineEntry(event, 'milestone', `Starting ${phase} phase`); try { for (const procedure of procedures) { await this.executeProcedure(procedure, plan, event); } phaseProgress.status = 'completed'; phaseProgress.progress = 100; phaseProgress.endTime = new Date(); this.addTimelineEntry(event, 'milestone', `Completed ${phase} phase`); } catch (error) { phaseProgress.status = 'failed'; this.addTimelineEntry(event, 'action', `Failed ${phase} phase: ${(error as Error).message}`); throw error; } } // Update overall progress const completedPhases = event.recoveryProgress.phases.filter(p => p.status === 'completed').length; event.recoveryProgress.overallProgress = (completedPhases / event.recoveryProgress.phases.length) * 100; } private async executeProcedure(procedure: RecoveryProcedure, plan: DisasterRecoveryPlan, event: DisasterEvent): Promise<void> { this.addTimelineEntry(event, 'action', `Executing procedure: ${procedure.name}`); // Check if approval is required if (procedure.approvalRequired) { await this.requestApproval(procedure, plan, event); } // Execute steps in order for (const step of procedure.steps.sort((a, b) => a.order - b.order)) { await this.executeStep(step, procedure, plan, event); } } private async requestApproval(procedure: RecoveryProcedure, plan: DisasterRecoveryPlan, event: DisasterEvent): Promise<void> { this.addTimelineEntry(event, 'decision', `Requesting approval for procedure: ${procedure.name}`); // In a real implementation, this would integrate with approval systems // For now, we'll simulate approval const decision: DecisionLog = { id: crypto.randomUUID(), timestamp: new Date(), decision: `Approve execution of ${procedure.name}`, rationale: 'Automated approval for disaster recovery', alternatives: ['Manual intervention', 'Skip procedure'], decisionMaker: 'system', approvers: procedure.approvers, impact: 'Enables recovery procedure execution', reversible: false }; event.decisions.push(decision); } private async executeStep(step: ProcedureStep, procedure: RecoveryProcedure, plan: DisasterRecoveryPlan, event: DisasterEvent): Promise<void> { this.addTimelineEntry(event, 'action', `Executing step: ${step.name}`); const startTime = Date.now(); let attempt = 0; while (attempt <= step.retries) { try { switch (step.type) { case 'script': await this.executeScript(step); break; case 'command': await this.executeCommand(step); break; case 'api-call': await this.executeApiCall(step); break; case 'manual': await this.executeManualStep(step, event); break; case 'approval': await this.executeApprovalStep(step, event); break; } // Step completed successfully this.addTimelineEntry(event, 'action', `Step completed: ${step.name}`); return; } catch (error) { attempt++; if (attempt > step.retries) { this.addTimelineEntry(event, 'action', `Step failed after ${step.retries} retries: ${step.name} - ${(error as Error).message}`); throw error; } this.addTimelineEntry(event, 'action', `Step failed (attempt ${attempt}/${step.retries + 1}): ${step.name} - ${(error as Error).message}`); // Wait before retry await new Promise(resolve => setTimeout(resolve, 5000)); } } // Check timeout const elapsed = Date.now() - startTime; if (elapsed > step.timeout * 60 * 1000) { throw new Error(`Step timeout exceeded: ${step.name}`); } } private async executeScript(step: ProcedureStep): Promise<void> { if (!step.script) { throw new Error(`No script provided for step: ${step.name}`); } // In production, this would execute the actual script this.emit('step-executed', { stepId: step.id, type: 'script' }); } private async executeCommand(step: ProcedureStep): Promise<void> { if (!step.command) { throw new Error(`No command provided for step: ${step.name}`); } // In production, this would execute the actual command this.emit('step-executed', { stepId: step.id, type: 'command' }); } private async executeApiCall(step: ProcedureStep): Promise<void> { if (!step.apiEndpoint) { throw new Error(`No API endpoint provided for step: ${step.name}`); } try { const response = await fetch(step.apiEndpoint, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ step: step.name }) }); if (!response.ok) { throw new Error(`API call failed: ${response.statusText}`); } this.emit('step-executed', { stepId: step.id, type: 'api-call' }); } catch (error) { throw new Error(`API call failed for step ${step.name}: ${(error as Error).message}`); } } private async executeManualStep(step: ProcedureStep, event: DisasterEvent): Promise<void> { // Manual step requires human intervention this.addTimelineEntry(event, 'action', `Manual intervention required: ${step.description}`); // In production, this would notify operators and wait for confirmation this.emit('manual-step-required', { step, event }); // Simulate manual completion await new Promise(resolve => setTimeout(resolve, 2000)); } private async executeApprovalStep(step: ProcedureStep, event: DisasterEvent): Promise<void> { if (!step.approvers || step.approvers.length === 0) { throw new Error(`No approvers specified for approval step: ${step.name}`); } this.addTimelineEntry(event, 'decision', `Approval required from: ${step.approvers.join(', ')}`); // In production, this would integrate with approval workflow systems this.emit('approval-required', { step, event }); // Simulate approval await new Promise(resolve => setTimeout(resolve, 1000)); } private async sendNotifications(plan: DisasterRecoveryPlan, event: DisasterEvent, templateType: string): Promise<void> { const template = plan.documentation.communicationPlan.templates.find(t => t.type === templateType); if (!template) { return; } for (const channelId of template.channels) { const channel = plan.documentation.communicationPlan.channels.find(c => c.id === channelId); if (!channel || !channel.enabled) { continue; } try { await this.sendNotification(channel, template, plan, event); event.notifications.push({ id: crypto.randomUUID(), timestamp: new Date(), type: templateType, recipients: template.recipients, channels: [channel.name], template: template.name, success: true }); } catch (error) { event.notifications.push({ id: crypto.randomUUID(), timestamp: new Date(), type: templateType, recipients: template.recipients, channels: [channel.name], template: template.name, success: false, error: (error as Error).message }); } } } private async sendNotification( channel: CommunicationChannel, template: CommunicationTemplate, plan: DisasterRecoveryPlan, event: DisasterEvent ): Promise<void> { // Replace template variables const subject = this.replaceTemplateVariables(template.subject, plan, event); const body = this.replaceTemplateVariables(template.body, plan, event); switch (channel.type) { case 'email': await this.sendEmail(channel, template.recipients, subject, body); break; case 'sms': await this.sendSMS(channel, template.recipients, body); break; case 'slack': await this.sendSlackMessage(channel, body); break; case 'webhook': await this.sendWebhook(channel, { subject, body, event }); break; } } private replaceTemplateVariables(text: string, plan: DisasterRecoveryPlan, event: DisasterEvent): string { return text .replace(/\{plan\.name\}/g, plan.name) .replace(/\{event\.id\}/g, event.id) .replace(/\{event\.severity\}/g, event.severity) .replace(/\{event\.startTime\}/g, event.startTime.toISOString()) .replace(/\{event\.status\}/g, event.status); } private async sendEmail(channel: CommunicationChannel, recipients: string[], subject: string, body: string): Promise<void> { // Email sending implementation this.emit('notification-sent', { type: 'email', recipients, subject }); } private async sendSMS(channel: CommunicationChannel, recipients: string[], message: string): Promise<void> { // SMS sending implementation this.emit('notification-sent', { type: 'sms', recipients, message }); } private async sendSlackMessage(channel: CommunicationChannel, message: string): Promise<void> { // Slack message sending implementation this.emit('notification-sent', { type: 'slack', message }); } private async sendWebhook(channel: CommunicationChannel, payload: any): Promise<void> { // Webhook sending implementation this.emit('notification-sent', { type: 'webhook', payload }); } private addTimelineEntry(event: DisasterEvent, type: EventTimeline['type'], description: string): void { event.timeline.push({ id: crypto.randomUUID(), timestamp: new Date(), type, description, source: 'disaster-recovery-manager' }); } private startMonitoring(): void { this.monitoringInterval = setInterval(async () => { await this.monitorTriggers(); await this.updateComponentMetrics(); }, 30000); // Every 30 seconds } private async monitorTriggers(): Promise<void> { for (const plan of this.plans.values()) { if (plan.status !== 'active') { continue; } for (const trigger of plan.triggers) { if (!trigger.enabled) { continue; } try { const shouldTrigger = await this.evaluateTrigger(trigger); if (shouldTrigger) { this.emit('trigger-activated', { planId: plan.id, triggerId: trigger.id }); // Execute trigger actions for (const action of trigger.actions) { await this.executeTriggerAction(action, plan, trigger); } trigger.lastTriggered = new Date(); } } catch (error) { this.emit('trigger-error', { planId: plan.id, triggerId: trigger.id, error: (error as Error).message }); } } } } private async evaluateTrigger(trigger: DisasterTrigger): Promise<boolean> { let conditionsMet = 0; for (const condition of trigger.conditions) { if (await this.evaluateTriggerCondition(condition)) { conditionsMet++; } } // All conditions must be met return conditionsMet === trigger.conditions.length; } private async evaluateTriggerCondition(condition: TriggerCondition): Promise<boolean> { switch (condition.type) { case 'metric': return this.evaluateMetricCondition(condition); case 'health-check': return this.evaluateHealthCheckCondition(condition); case 'event': return this.evaluateEventCondition(condition); case 'external-signal': return this.evaluateExternalSignalCondition(condition); default: return false; } } private evaluateMetricCondition(condition: TriggerCondition): boolean { // Metric evaluation implementation return false; } private evaluateHealthCheckCondition(condition: TriggerCondition): boolean { // Health check evaluation implementation return false; } private evaluateEventCondition(condition: TriggerCondition): boolean { // Event evaluation implementation return false; } private evaluateExternalSignalCondition(condition: TriggerCondition): boolean { // External signal evaluation implementation return false; } private async executeTriggerAction(action: TriggerAction, plan: DisasterRecoveryPlan, trigger: DisasterTrigger): Promise<void> { setTimeout(async () => { switch (action.type) { case 'execute-plan': await this.executeRecovery(plan.id, trigger.id); break; case 'notify': await this.sendTriggerNotification(action, plan, trigger); break; case 'escalate': await this.escalateTrigger(action, plan, trigger); break; case 'log': this.logTriggerEvent(action, plan, trigger); break; } }, action.delay * 1000); } private async sendTriggerNotification(action: TriggerAction, plan: DisasterRecoveryPlan, trigger: DisasterTrigger): Promise<void> { // Send trigger notification this.emit('trigger-notification', { planId: plan.id, triggerId: trigger.id, action }); } private async escalateTrigger(action: TriggerAction, plan: DisasterRecoveryPlan, trigger: DisasterTrigger): Promise<void> { // Escalate trigger this.emit('trigger-escalated', { planId: plan.id, triggerId: trigger.id, action }); } private logTriggerEvent(action: TriggerAction, plan: DisasterRecoveryPlan, trigger: DisasterTrigger): void { // Log trigger event this.emit('trigger-logged', { planId: plan.id, triggerId: trigger.id, action }); } private async updateComponentMetrics(): Promise<void> { for (const plan of this.plans.values()) { for (const component of plan.components) { try { await this.updateComponentHealth(component); } catch (error) { // Handle component health update errors } } } } private async updateComponentHealth(component: RecoveryComponent): Promise<void> { for (const healthCheck of component.healthChecks) { try { const isHealthy = await this.performHealthCheck(healthCheck); healthCheck.status = isHealthy ? 'healthy' : 'unhealthy'; healthCheck.lastCheck = new Date(); // Update component status based on health checks const allHealthy = component.healthChecks.every(hc => hc.status === 'healthy'); if (component.status === 'operational' && !allHealthy) { component.status = 'degraded'; } else if (component.status === 'degraded' && allHealthy) { component.status = 'operational'; } } catch (error) { healthCheck.status = 'unknown'; } } } private async performHealthCheck(healthCheck: HealthCheck): Promise<boolean> { switch (healthCheck.type) { case 'http': return this.performHttpHealthCheck(healthCheck); case 'tcp': return this.performTcpHealthCheck(healthCheck); case 'database': return this.performDatabaseHealthCheck(healthCheck); case 'custom': return this.performCustomHealthCheck(healthCheck); default: return false; } } private async performHttpHealthCheck(healthCheck: HealthCheck): Promise<boolean> { try { const controller = new AbortController(); const timeoutId = setTimeout(() => controller.abort(), healthCheck.timeout * 1000); const response = await fetch(healthCheck.endpoint, { method: healthCheck.method || 'GET', signal: controller.signal }); clearTimeout(timeoutId); if (healthCheck.expectedStatus && response.status !== healthCheck.expectedStatus) { return false; } if (healthCheck.expectedContent) { const content = await response.text(); return content.includes(healthCheck.expectedContent); } return response.ok; } catch (error) { return false; } } private async performTcpHealthCheck(healthCheck: HealthCheck): Promise<boolean> { // TCP health check implementation return true; // Simplified } private async performDatabaseHealthCheck(healthCheck: HealthCheck): Promise<boolean> { // Database health check implementation return true; // Simplified } private async performCustomHealthCheck(healthCheck: HealthCheck): Promise<boolean> { // Custom health check implementation return true; // Simplified } private startPlanMonitoring(plan: DisasterRecoveryPlan): void { // Start monitoring for the specific plan this.emit('plan-monitoring-started', { planId: plan.id }); } private startTestScheduler(): void { this.testScheduler = setInterval(async () => { await this.checkScheduledTests(); }, 60000); // Check every minute } private async checkScheduledTests(): Promise<void> { const now = new Date(); for (const plan of this.plans.values()) { if (!plan.testSchedule.enabled || !plan.testSchedule.nextTest) { continue; } if (plan.testSchedule.nextTest <= now) { try { await this.executeTest(plan); } catch (error) { this.emit('test-failed', { planId: plan.id, error: (error as Error).message }); } } } } private async executeTest(plan: DisasterRecoveryPlan): Promise<void> { const testResult: TestResult = { id: crypto.randomUUID(), date: new Date(), type: plan.testSchedule.type, duration: 0, success: false, findings: [], recommendations: [], participants: plan.testSchedule.participants, artifacts: [] }; const startTime = Date.now(); try { // Execute test based on type switch (plan.testSchedule.type) { case 'full': await this.executeFullTest(plan, testResult); break; case 'partial': await this.executePartialTest(plan, testResult); break; case 'tabletop': await this.executeTabletopTest(plan, testResult); break; case 'walkthrough': await this.executeWalkthroughTest(plan, testResult); break; } testResult.success = true; } catch (error) { testResult.findings.push({ id: crypto.randomUUID(), severity: 'high', category: 'procedure', description: `Test execution failed: ${(error as Error).message}`, impact: 'Test could not be completed', recommendation: 'Review test procedures and fix identified issues', status: 'open' }); } testResult.duration = Math.floor((Date.now() - startTime) / 1000 / 60); // minutes plan.testSchedule.results.push(testResult); plan.testSchedule.lastTest = testResult.date; // Schedule next test this.scheduleNextTest(plan); this.emit('test-completed', { planId: plan.id, testResult }); } private async executeFullTest(plan: DisasterRecoveryPlan, testResult: TestResult): Promise<void> { // Full disaster recovery test this.emit('test-started', { planId: plan.id, type: 'full' }); } private async executePartialTest(plan: DisasterRecoveryPlan, testResult: TestResult): Promise<void> { // Partial disaster recovery test this.emit('test-started', { planId: plan.id, type: 'partial' }); } private async executeTabletopTest(plan: DisasterRecoveryPlan, testResult: TestResult): Promise<void> { // Tabletop exercise this.emit('test-started', { planId: plan.id, type: 'tabletop' }); } private async executeWalkthroughTest(plan: DisasterRecoveryPlan, testResult: TestResult): Promise<void> { // Walkthrough test this.emit('test-started', { planId: plan.id, type: 'walkthrough' }); } private scheduleNextTest(plan: DisasterRecoveryPlan): void { // Calculate next test date based on cron expression // Simplified - would use proper cron library const now = new Date(); plan.testSchedule.nextTest = new Date(now.getTime() + 30 * 24 * 60 * 60 * 1000); // 30 days } // Public API methods getPlans(): DisasterRecoveryPlan[] { return Array.from(this.plans.values()); } getPlan(planId: string): DisasterRecoveryPlan | null { return this.plans.get(planId) || null; } getEvents(): DisasterEvent[] { return Array.from(this.events.values()); } getEvent(eventId: string): DisasterEvent | null { return this.events.get(eventId) || null; } getActiveRecoveries(): DisasterEvent[] { return Array.from(this.activeRecoveries) .map(id => this.events.get(id)!) .filter(Boolean); } getStats(): any { const plans = Array.from(this.plans.values()); const events = Array.from(this.events.values()); return { plans: { total: plans.length, active: plans.filter(p => p.status === 'active').length, approved: plans.filter(p => p.status === 'approved').length, draft: plans.filter(p => p.status === 'draft').length }, events: { total: events.length, active: events.filter(e => e.status === 'active').length, resolved: events.filter(e => e.status === 'resolved').length }, activeRecoveries: this.activeRecoveries.size, components: plans.reduce((sum, p) => sum + p.components.length, 0), tests: plans.reduce((sum, p) => sum + p.testSchedule.results.length, 0) }; } destroy(): void { if (this.monitoringInterval) { clearInterval(this.monitoringInterval); } if (this.testScheduler) { clearInterval(this.testScheduler); } this.plans.clear(); this.events.clear(); this.activeRecoveries.clear(); this.removeAllListeners(); } }

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/Coder-RL/Claude_MCPServer_Dev1'

If you have feedback or need assistance with the MCP directory API, please join our Discord server