guardrail-compliance 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (149) hide show
  1. package/dist/audit/emitter.d.ts +97 -0
  2. package/dist/audit/emitter.d.ts.map +1 -0
  3. package/dist/audit/emitter.js +197 -0
  4. package/dist/audit/events.d.ts +304 -0
  5. package/dist/audit/events.d.ts.map +1 -0
  6. package/dist/audit/events.js +267 -0
  7. package/dist/audit/index.d.ts +11 -0
  8. package/dist/audit/index.d.ts.map +1 -0
  9. package/dist/audit/index.js +51 -0
  10. package/dist/audit/storage.d.ts +93 -0
  11. package/dist/audit/storage.d.ts.map +1 -0
  12. package/dist/audit/storage.js +337 -0
  13. package/dist/automation/__tests__/compliance-scheduler.test.d.ts +2 -0
  14. package/dist/automation/__tests__/compliance-scheduler.test.d.ts.map +1 -0
  15. package/dist/automation/__tests__/compliance-scheduler.test.js +140 -0
  16. package/dist/automation/audit-logger.d.ts +129 -0
  17. package/dist/automation/audit-logger.d.ts.map +1 -0
  18. package/dist/automation/audit-logger.js +473 -0
  19. package/dist/automation/compliance-scheduler-fixed.d.ts +1 -0
  20. package/dist/automation/compliance-scheduler-fixed.d.ts.map +1 -0
  21. package/dist/automation/compliance-scheduler-fixed.js +1 -0
  22. package/dist/automation/compliance-scheduler.d.ts +83 -0
  23. package/dist/automation/compliance-scheduler.d.ts.map +1 -0
  24. package/dist/automation/compliance-scheduler.js +414 -0
  25. package/dist/automation/dashboard.d.ts +194 -0
  26. package/dist/automation/dashboard.d.ts.map +1 -0
  27. package/dist/automation/dashboard.js +768 -0
  28. package/dist/automation/email-service.d.ts +69 -0
  29. package/dist/automation/email-service.d.ts.map +1 -0
  30. package/dist/automation/email-service.js +218 -0
  31. package/dist/automation/evidence-collector.d.ts +140 -0
  32. package/dist/automation/evidence-collector.d.ts.map +1 -0
  33. package/dist/automation/evidence-collector.js +682 -0
  34. package/dist/automation/index.d.ts +8 -0
  35. package/dist/automation/index.d.ts.map +1 -0
  36. package/dist/automation/index.js +24 -0
  37. package/dist/automation/pdf-exporter.d.ts +90 -0
  38. package/dist/automation/pdf-exporter.d.ts.map +1 -0
  39. package/dist/automation/pdf-exporter.js +381 -0
  40. package/dist/automation/reporting-engine.d.ts +116 -0
  41. package/dist/automation/reporting-engine.d.ts.map +1 -0
  42. package/dist/automation/reporting-engine.js +329 -0
  43. package/dist/container/index.d.ts +4 -0
  44. package/dist/container/index.d.ts.map +1 -0
  45. package/dist/container/index.js +19 -0
  46. package/dist/container/kubernetes.d.ts +94 -0
  47. package/dist/container/kubernetes.d.ts.map +1 -0
  48. package/dist/container/kubernetes.js +268 -0
  49. package/dist/container/rules.d.ts +27 -0
  50. package/dist/container/rules.d.ts.map +1 -0
  51. package/dist/container/rules.js +216 -0
  52. package/dist/container/scanner.d.ts +50 -0
  53. package/dist/container/scanner.d.ts.map +1 -0
  54. package/dist/container/scanner.js +143 -0
  55. package/dist/frameworks/engine.d.ts +108 -0
  56. package/dist/frameworks/engine.d.ts.map +1 -0
  57. package/dist/frameworks/engine.js +206 -0
  58. package/dist/frameworks/gdpr.d.ts +6 -0
  59. package/dist/frameworks/gdpr.d.ts.map +1 -0
  60. package/dist/frameworks/gdpr.js +198 -0
  61. package/dist/frameworks/hipaa.d.ts +6 -0
  62. package/dist/frameworks/hipaa.d.ts.map +1 -0
  63. package/dist/frameworks/hipaa.js +183 -0
  64. package/dist/frameworks/index.d.ts +8 -0
  65. package/dist/frameworks/index.d.ts.map +1 -0
  66. package/dist/frameworks/index.js +30 -0
  67. package/dist/frameworks/iso27001.d.ts +63 -0
  68. package/dist/frameworks/iso27001.d.ts.map +1 -0
  69. package/dist/frameworks/iso27001.js +331 -0
  70. package/dist/frameworks/nist.d.ts +62 -0
  71. package/dist/frameworks/nist.d.ts.map +1 -0
  72. package/dist/frameworks/nist.js +424 -0
  73. package/dist/frameworks/pci.d.ts +6 -0
  74. package/dist/frameworks/pci.d.ts.map +1 -0
  75. package/dist/frameworks/pci.js +201 -0
  76. package/dist/frameworks/soc2.d.ts +7 -0
  77. package/dist/frameworks/soc2.d.ts.map +1 -0
  78. package/dist/frameworks/soc2.js +248 -0
  79. package/dist/iac/drift-detector.d.ts +64 -0
  80. package/dist/iac/drift-detector.d.ts.map +1 -0
  81. package/dist/iac/drift-detector.js +134 -0
  82. package/dist/iac/index.d.ts +4 -0
  83. package/dist/iac/index.d.ts.map +1 -0
  84. package/dist/iac/index.js +19 -0
  85. package/dist/iac/rules.d.ts +17 -0
  86. package/dist/iac/rules.d.ts.map +1 -0
  87. package/dist/iac/rules.js +385 -0
  88. package/dist/iac/scanner.d.ts +104 -0
  89. package/dist/iac/scanner.d.ts.map +1 -0
  90. package/dist/iac/scanner.js +343 -0
  91. package/dist/index.d.ts +7 -0
  92. package/dist/index.d.ts.map +1 -0
  93. package/dist/index.js +28 -0
  94. package/dist/pii/data-flow.d.ts +58 -0
  95. package/dist/pii/data-flow.d.ts.map +1 -0
  96. package/dist/pii/data-flow.js +154 -0
  97. package/dist/pii/detector.d.ts +60 -0
  98. package/dist/pii/detector.d.ts.map +1 -0
  99. package/dist/pii/detector.js +267 -0
  100. package/dist/pii/index.d.ts +4 -0
  101. package/dist/pii/index.d.ts.map +1 -0
  102. package/dist/pii/index.js +19 -0
  103. package/dist/pii/patterns.d.ts +36 -0
  104. package/dist/pii/patterns.d.ts.map +1 -0
  105. package/dist/pii/patterns.js +108 -0
  106. package/dist/policy/index.d.ts +5 -0
  107. package/dist/policy/index.d.ts.map +1 -0
  108. package/dist/policy/index.js +20 -0
  109. package/dist/policy/opa-engine.d.ts +121 -0
  110. package/dist/policy/opa-engine.d.ts.map +1 -0
  111. package/dist/policy/opa-engine.js +423 -0
  112. package/package.json +31 -0
  113. package/src/audit/emitter.ts +383 -0
  114. package/src/audit/events.ts +351 -0
  115. package/src/audit/index.ts +35 -0
  116. package/src/audit/storage.ts +394 -0
  117. package/src/automation/__tests__/compliance-scheduler.test.ts +183 -0
  118. package/src/automation/audit-logger.ts +629 -0
  119. package/src/automation/compliance-scheduler-fixed.ts +0 -0
  120. package/src/automation/compliance-scheduler.ts +516 -0
  121. package/src/automation/dashboard.ts +947 -0
  122. package/src/automation/email-service.ts +230 -0
  123. package/src/automation/evidence-collector.ts +866 -0
  124. package/src/automation/index.ts +8 -0
  125. package/src/automation/pdf-exporter.ts +434 -0
  126. package/src/automation/reporting-engine.ts +462 -0
  127. package/src/container/index.ts +3 -0
  128. package/src/container/kubernetes.ts +379 -0
  129. package/src/container/rules.ts +244 -0
  130. package/src/container/scanner.ts +202 -0
  131. package/src/frameworks/engine.ts +298 -0
  132. package/src/frameworks/gdpr.ts +204 -0
  133. package/src/frameworks/hipaa.ts +209 -0
  134. package/src/frameworks/index.ts +23 -0
  135. package/src/frameworks/iso27001.ts +398 -0
  136. package/src/frameworks/nist.ts +518 -0
  137. package/src/frameworks/pci.ts +226 -0
  138. package/src/frameworks/soc2.ts +281 -0
  139. package/src/iac/drift-detector.ts +197 -0
  140. package/src/iac/index.ts +3 -0
  141. package/src/iac/rules.ts +420 -0
  142. package/src/iac/scanner.ts +445 -0
  143. package/src/index.ts +17 -0
  144. package/src/pii/data-flow.ts +216 -0
  145. package/src/pii/detector.ts +327 -0
  146. package/src/pii/index.ts +3 -0
  147. package/src/pii/patterns.ts +128 -0
  148. package/src/policy/index.ts +5 -0
  149. package/src/policy/opa-engine.ts +504 -0
@@ -0,0 +1,947 @@
1
+ import { prisma } from "@guardrail/database";
2
+ import { auditLogger } from "./audit-logger";
3
+
4
+ interface DashboardData {
5
+ projectId: string;
6
+ overview: {
7
+ overallScore: number;
8
+ status: "compliant" | "partial" | "non-compliant";
9
+ lastAssessment: Date;
10
+ nextAssessment?: Date;
11
+ totalControls: number;
12
+ activeFrameworks: string[];
13
+ };
14
+ trends: {
15
+ scores: Array<{
16
+ date: Date;
17
+ score: number;
18
+ framework: string;
19
+ }>;
20
+ violations: Array<{
21
+ date: Date;
22
+ count: number;
23
+ severity: string;
24
+ }>;
25
+ remediation: Array<{
26
+ date: Date;
27
+ completed: number;
28
+ pending: number;
29
+ }>;
30
+ };
31
+ alerts: Array<{
32
+ id: string;
33
+ type: "violation" | "deadline" | "score_drop" | "system";
34
+ severity: "low" | "medium" | "high" | "critical";
35
+ message: string;
36
+ timestamp: Date;
37
+ acknowledged: boolean;
38
+ }>;
39
+ frameworkStatus: Array<{
40
+ frameworkId: string;
41
+ score: number;
42
+ status: string;
43
+ lastRun: Date;
44
+ nextRun?: Date;
45
+ gaps: number;
46
+ }>;
47
+ recentActivity: Array<{
48
+ type: string;
49
+ description: string;
50
+ timestamp: Date;
51
+ user?: string;
52
+ }>;
53
+ upcomingTasks: Array<{
54
+ id: string;
55
+ type: "assessment" | "remediation" | "review";
56
+ title: string;
57
+ dueDate: Date;
58
+ priority: "low" | "medium" | "high" | "critical";
59
+ assignedTo?: string;
60
+ }>;
61
+ }
62
+
63
+ interface AlertConfig {
64
+ id: string;
65
+ projectId: string;
66
+ type:
67
+ | "score_threshold"
68
+ | "violation_detected"
69
+ | "deadline_approaching"
70
+ | "system_error";
71
+ enabled: boolean;
72
+ threshold?: number;
73
+ recipients: {
74
+ email?: string[];
75
+ slack?: string;
76
+ webhook?: string;
77
+ };
78
+ conditions: any;
79
+ }
80
+
81
+ /**
82
+ * Compliance Dashboard and Alerting System
83
+ *
84
+ * Provides real-time compliance monitoring, dashboards,
85
+ * and intelligent alerting for compliance issues
86
+ */
87
+ export class ComplianceDashboard {
88
+ private alertConfigs: Map<string, AlertConfig> = new Map();
89
+ private alertIntervals: Map<string, NodeJS.Timeout> = new Map();
90
+
91
+ /**
92
+ * Get dashboard data for a project
93
+ */
94
+ async getDashboardData(projectId: string): Promise<DashboardData> {
95
+ // Get project details
96
+ const project = await prisma.project.findUnique({
97
+ where: { id: projectId },
98
+ });
99
+
100
+ if (!project) {
101
+ throw new Error(`Project ${projectId} not found`);
102
+ }
103
+
104
+ // Get assessments
105
+ let assessments: any[] = [];
106
+ try {
107
+ assessments = await prisma.complianceAssessment.findMany({
108
+ where: { projectId },
109
+ orderBy: { createdAt: "desc" },
110
+ take: 10,
111
+ });
112
+ } catch (error) {
113
+ console.warn("Could not fetch assessments:", error);
114
+ }
115
+
116
+ // Get schedules
117
+ let schedules: any[] = [];
118
+ try {
119
+ schedules = await prisma.complianceSchedule.findMany({
120
+ where: { projectId, enabled: true },
121
+ orderBy: { nextRun: "asc" },
122
+ });
123
+ } catch (error) {
124
+ console.warn("Could not fetch schedules:", error);
125
+ }
126
+
127
+ // Build dashboard data
128
+ const overview = this.buildOverview(assessments, schedules);
129
+ const trends = await this.getTrends(projectId);
130
+ const alerts = await this.getActiveAlerts(projectId);
131
+ const frameworkStatus = this.buildFrameworkStatus(assessments, schedules);
132
+ const recentActivity = await this.getRecentActivity(projectId);
133
+ const upcomingTasks = await this.getUpcomingTasks(projectId, schedules);
134
+
135
+ return {
136
+ projectId,
137
+ overview,
138
+ trends,
139
+ alerts,
140
+ frameworkStatus,
141
+ recentActivity,
142
+ upcomingTasks,
143
+ };
144
+ }
145
+
146
+ /**
147
+ * Configure alerts for a project
148
+ */
149
+ async configureAlerts(config: AlertConfig): Promise<void> {
150
+ // Save configuration
151
+ try {
152
+ await prisma.alertConfig.upsert({
153
+ where: { id: config.id },
154
+ update: {
155
+ type: config.type,
156
+ enabled: config.enabled,
157
+ threshold: config.threshold,
158
+ // conditions not in schema
159
+ // conditions: config.conditions
160
+ },
161
+ create: {
162
+ name: `${config.type} Alert` as any,
163
+ projectId: config.projectId,
164
+ type: config.type,
165
+ enabled: config.enabled,
166
+ threshold: config.threshold,
167
+ config: {
168
+ recipients: config.recipients,
169
+ conditions: config.conditions,
170
+ } as any,
171
+ } as any,
172
+ });
173
+ } catch (error) {
174
+ console.warn("Could not save alert config to database:", error);
175
+ }
176
+
177
+ this.alertConfigs.set(config.id, config);
178
+
179
+ // Start monitoring if enabled
180
+ if (config.enabled) {
181
+ this.startAlertMonitoring(config);
182
+ } else {
183
+ this.stopAlertMonitoring(config.id);
184
+ }
185
+ }
186
+
187
+ /**
188
+ * Trigger manual compliance check
189
+ */
190
+ async triggerCheck(
191
+ projectId: string,
192
+ frameworkId: string,
193
+ userId?: string,
194
+ ): Promise<string> {
195
+ // Log the trigger
196
+ await auditLogger.logEvent({
197
+ type: "compliance_check_triggered",
198
+ category: "compliance",
199
+ projectId,
200
+ userId,
201
+ timestamp: new Date(),
202
+ severity: "low",
203
+ source: "dashboard",
204
+ details: {
205
+ action: "Manual compliance check triggered",
206
+ framework: frameworkId,
207
+ triggeredBy: userId || "anonymous",
208
+ },
209
+ });
210
+
211
+ // In production, would actually trigger the compliance check
212
+ const executionId = `manual_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
213
+
214
+ return executionId;
215
+ }
216
+
217
+ /**
218
+ * Acknowledge an alert
219
+ */
220
+ async acknowledgeAlert(alertId: string, userId: string): Promise<void> {
221
+ try {
222
+ await prisma.alert.update({
223
+ where: { id: alertId },
224
+ data: {
225
+ acknowledged: true,
226
+ acknowledgedBy: userId,
227
+ acknowledgedAt: new Date(),
228
+ },
229
+ });
230
+ } catch (error) {
231
+ console.warn("Could not acknowledge alert in database:", error);
232
+ }
233
+
234
+ // Log acknowledgment
235
+ await auditLogger.logEvent({
236
+ type: "alert_acknowledged",
237
+ category: "system",
238
+ timestamp: new Date(),
239
+ severity: "low",
240
+ source: "dashboard",
241
+ metadata: {
242
+ alertId,
243
+ acknowledgedBy: userId,
244
+ },
245
+ details: {
246
+ action: "Alert acknowledged",
247
+ alertId,
248
+ user: userId,
249
+ },
250
+ });
251
+ }
252
+
253
+ /**
254
+ * Get compliance metrics for widgets
255
+ */
256
+ async getWidgetData(
257
+ projectId: string,
258
+ widgetType: "score" | "trends" | "violations" | "remediation",
259
+ ): Promise<any> {
260
+ switch (widgetType) {
261
+ case "score":
262
+ return this.getScoreWidget(projectId);
263
+ case "trends":
264
+ return this.getTrendsWidget(projectId);
265
+ case "violations":
266
+ return this.getViolationsWidget(projectId);
267
+ case "remediation":
268
+ return this.getRemediationWidget(projectId);
269
+ default:
270
+ throw new Error(`Unknown widget type: ${widgetType}`);
271
+ }
272
+ }
273
+
274
+ /**
275
+ * Export dashboard data
276
+ */
277
+ async exportData(
278
+ projectId: string,
279
+ format: "json" | "csv",
280
+ _dateRange?: { start: Date; end: Date },
281
+ ): Promise<string> {
282
+ const data = await this.getDashboardData(projectId);
283
+
284
+ if (format === "json") {
285
+ return JSON.stringify(data, null, 2);
286
+ }
287
+
288
+ if (format === "csv") {
289
+ return this.convertToCSV(data);
290
+ }
291
+
292
+ throw new Error(`Unsupported export format: ${format}`);
293
+ }
294
+
295
+ /**
296
+ * Build overview section
297
+ */
298
+ private buildOverview(
299
+ assessments: any[],
300
+ schedules: any[],
301
+ ): DashboardData["overview"] {
302
+ if (assessments.length === 0) {
303
+ return {
304
+ overallScore: 0,
305
+ status: "non-compliant",
306
+ lastAssessment: new Date(),
307
+ totalControls: 0,
308
+ activeFrameworks: [],
309
+ };
310
+ }
311
+
312
+ const latest = assessments[0];
313
+ const frameworks = [...new Set(assessments.map((a: any) => a.frameworkId))];
314
+
315
+ // Handle JSON value type for summary
316
+ const summary = latest.summary as any;
317
+ const score = typeof summary?.score === "number" ? summary.score : 0;
318
+ const totalControls =
319
+ typeof summary?.totalControls === "number" ? summary.totalControls : 0;
320
+
321
+ return {
322
+ overallScore: score,
323
+ status:
324
+ score >= 90 ? "compliant" : score >= 70 ? "partial" : "non-compliant",
325
+ lastAssessment: latest.createdAt,
326
+ nextAssessment: schedules[0]?.nextRun,
327
+ totalControls,
328
+ activeFrameworks: frameworks,
329
+ };
330
+ }
331
+
332
+ /**
333
+ * Get compliance trends
334
+ */
335
+ private async getTrends(projectId: string): Promise<DashboardData["trends"]> {
336
+ // Get score history
337
+ let scoreHistory: any[] = [];
338
+ try {
339
+ scoreHistory = await prisma.complianceAssessment.findMany({
340
+ where: { projectId },
341
+ orderBy: { createdAt: "asc" },
342
+ take: 30,
343
+ });
344
+ } catch (error) {
345
+ console.warn("Could not fetch score history:", error);
346
+ }
347
+
348
+ const scores = scoreHistory.map((h: any) => {
349
+ const summary = h.summary as any;
350
+ return {
351
+ date: h.createdAt,
352
+ score: typeof summary?.score === "number" ? summary.score : 0,
353
+ framework: h.frameworkId,
354
+ };
355
+ });
356
+
357
+ // Get violation trends
358
+ const violations = await this.getViolationTrends(projectId);
359
+
360
+ // Get remediation trends
361
+ const remediation = await this.getRemediationTrends(projectId);
362
+
363
+ return {
364
+ scores,
365
+ violations,
366
+ remediation,
367
+ };
368
+ }
369
+
370
+ /**
371
+ * Get active alerts
372
+ */
373
+ private async getActiveAlerts(
374
+ projectId: string,
375
+ ): Promise<DashboardData["alerts"]> {
376
+ let alerts: any[] = [];
377
+ try {
378
+ alerts = await prisma.alert.findMany({
379
+ where: {
380
+ projectId,
381
+ // resolved not in schema
382
+ // resolved: false,
383
+ createdAt: {
384
+ gte: new Date(Date.now() - 7 * 24 * 60 * 60 * 1000), // Last 7 days
385
+ },
386
+ },
387
+ orderBy: { createdAt: "desc" },
388
+ take: 50,
389
+ });
390
+ } catch (error) {
391
+ console.warn("Could not fetch alerts from database:", error);
392
+ }
393
+
394
+ return alerts.map((a: any) => ({
395
+ id: a.id,
396
+ type: a.type as any,
397
+ severity: a.severity.toLowerCase() as any,
398
+ message: a.message,
399
+ timestamp: a.createdAt,
400
+ acknowledged: a.acknowledged,
401
+ }));
402
+ }
403
+
404
+ /**
405
+ * Build framework status
406
+ */
407
+ private buildFrameworkStatus(
408
+ assessments: any[],
409
+ schedules: any[],
410
+ ): DashboardData["frameworkStatus"] {
411
+ const statusMap = new Map<string, any>();
412
+
413
+ // Group assessments by framework
414
+ for (const assessment of assessments) {
415
+ if (!statusMap.has(assessment.frameworkId)) {
416
+ statusMap.set(assessment.frameworkId, assessment);
417
+ }
418
+ }
419
+
420
+ // Build status array
421
+ const frameworkStatus: DashboardData["frameworkStatus"] = [];
422
+
423
+ for (const [frameworkId, assessment] of statusMap) {
424
+ const schedule = schedules.find((s) => s.frameworkId === frameworkId);
425
+
426
+ // Handle JSON value type for summary
427
+ const summary = assessment.summary as any;
428
+ const score = typeof summary?.score === "number" ? summary.score : 0;
429
+
430
+ frameworkStatus.push({
431
+ frameworkId,
432
+ score,
433
+ status:
434
+ score >= 90 ? "compliant" : score >= 70 ? "partial" : "non-compliant",
435
+ lastRun: assessment.createdAt,
436
+ nextRun: schedule?.nextRun,
437
+ gaps: assessment.gaps?.length || 0,
438
+ });
439
+ }
440
+
441
+ return frameworkStatus;
442
+ }
443
+
444
+ /**
445
+ * Get recent activity
446
+ */
447
+ private async getRecentActivity(
448
+ projectId: string,
449
+ ): Promise<DashboardData["recentActivity"]> {
450
+ let activity: any[] = [];
451
+ try {
452
+ // Try to get from audit events table
453
+ activity = await prisma.auditEvent.findMany({
454
+ where: {
455
+ projectId,
456
+ timestamp: {
457
+ gte: new Date(Date.now() - 7 * 24 * 60 * 60 * 1000), // Last 7 days
458
+ },
459
+ },
460
+ orderBy: { timestamp: "desc" },
461
+ take: 20,
462
+ });
463
+ } catch (error) {
464
+ console.warn("Could not fetch recent activity from audit events:", error);
465
+ // Return empty array if table doesn't exist
466
+ return [];
467
+ }
468
+
469
+ return activity.map((a: any) => ({
470
+ type: a.type,
471
+ description: `${a.type} - ${a.category}`,
472
+ timestamp: a.timestamp,
473
+ user: a.userId,
474
+ }));
475
+ }
476
+
477
+ /**
478
+ * Get upcoming tasks
479
+ */
480
+ private async getUpcomingTasks(
481
+ projectId: string,
482
+ schedules: any[],
483
+ ): Promise<DashboardData["upcomingTasks"]> {
484
+ const tasks: DashboardData["upcomingTasks"] = [];
485
+
486
+ // Add scheduled assessments
487
+ for (const schedule of schedules) {
488
+ if (schedule.nextRun) {
489
+ tasks.push({
490
+ id: `sched_${schedule.id}`,
491
+ type: "assessment" as const,
492
+ title: `Scheduled ${schedule.frameworkId} assessment`,
493
+ dueDate: schedule.nextRun,
494
+ priority: "medium" as const,
495
+ });
496
+ }
497
+ }
498
+
499
+ // Add remediation tasks
500
+ let remediations: any[] = [];
501
+ try {
502
+ // @ts-ignore - remediationTask may not exist in schema
503
+ remediations = await prisma.remediationTask.findMany({
504
+ where: {
505
+ projectId,
506
+ status: "pending",
507
+ dueDate: {
508
+ gte: new Date(),
509
+ },
510
+ },
511
+ orderBy: { dueDate: "asc" },
512
+ take: 10,
513
+ });
514
+ } catch (error) {
515
+ console.warn("Could not fetch remediation tasks:", error);
516
+ }
517
+
518
+ for (const task of remediations) {
519
+ tasks.push({
520
+ id: task.id,
521
+ type: "remediation" as const,
522
+ title: task.title,
523
+ dueDate: task.dueDate,
524
+ priority: task.priority?.toLowerCase() as any,
525
+ assignedTo: task.assignedTo,
526
+ });
527
+ }
528
+
529
+ return tasks.sort((a, b) => a.dueDate.getTime() - b.dueDate.getTime());
530
+ }
531
+
532
+ /**
533
+ * Start alert monitoring
534
+ */
535
+ private startAlertMonitoring(config: AlertConfig): void {
536
+ // Clear existing interval
537
+ this.stopAlertMonitoring(config.id);
538
+
539
+ // Check every 5 minutes
540
+ const interval = setInterval(
541
+ async () => {
542
+ await this.checkAlertConditions(config);
543
+ },
544
+ 5 * 60 * 1000,
545
+ );
546
+
547
+ this.alertIntervals.set(config.id, interval);
548
+ }
549
+
550
+ /**
551
+ * Stop alert monitoring
552
+ */
553
+ private stopAlertMonitoring(configId: string): void {
554
+ if (this.alertIntervals.has(configId)) {
555
+ clearInterval(this.alertIntervals.get(configId)!);
556
+ this.alertIntervals.delete(configId);
557
+ }
558
+ }
559
+
560
+ /**
561
+ * Check alert conditions
562
+ */
563
+ private async checkAlertConditions(config: AlertConfig): Promise<void> {
564
+ switch (config.type) {
565
+ case "score_threshold":
566
+ await this.checkScoreThreshold(config);
567
+ break;
568
+ case "violation_detected":
569
+ await this.checkViolations(config);
570
+ break;
571
+ case "deadline_approaching":
572
+ await this.checkDeadlines(config);
573
+ break;
574
+ case "system_error":
575
+ await this.checkSystemErrors(config);
576
+ break;
577
+ }
578
+ }
579
+
580
+ /**
581
+ * Check score threshold
582
+ */
583
+ private async checkScoreThreshold(config: AlertConfig): Promise<void> {
584
+ const latest = await prisma.complianceAssessment.findFirst({
585
+ where: { projectId: config.projectId },
586
+ orderBy: { createdAt: "desc" },
587
+ });
588
+
589
+ if (latest) {
590
+ const summary = latest.summary as any;
591
+ const score = typeof summary?.score === "number" ? summary.score : 0;
592
+
593
+ if (score < (config.threshold || 70)) {
594
+ await this.createAlert({
595
+ projectId: config.projectId,
596
+ type: "score_drop",
597
+ severity: "high",
598
+ message: `Compliance score dropped to ${score}%`,
599
+ });
600
+ }
601
+ }
602
+ }
603
+
604
+ /**
605
+ * Check for violations
606
+ */
607
+ private async checkViolations(config: AlertConfig): Promise<void> {
608
+ const recentViolations = await prisma.auditEvent.count({
609
+ where: {
610
+ projectId: config.projectId,
611
+ type: "compliance_violation",
612
+ timestamp: {
613
+ gte: new Date(Date.now() - 24 * 60 * 60 * 1000), // Last 24 hours
614
+ },
615
+ },
616
+ });
617
+
618
+ if (recentViolations > 0) {
619
+ await this.createAlert({
620
+ projectId: config.projectId,
621
+ type: "violation",
622
+ severity: "high",
623
+ message: `${recentViolations} compliance violations detected in the last 24 hours`,
624
+ });
625
+ }
626
+ }
627
+
628
+ /**
629
+ * Check approaching deadlines
630
+ */
631
+ private async checkDeadlines(config: AlertConfig): Promise<void> {
632
+ const upcoming = await prisma.complianceSchedule.findMany({
633
+ where: {
634
+ projectId: config.projectId,
635
+ nextRun: {
636
+ lte: new Date(Date.now() + 24 * 60 * 60 * 1000), // Next 24 hours
637
+ gte: new Date(),
638
+ },
639
+ },
640
+ });
641
+
642
+ for (const schedule of upcoming) {
643
+ await this.createAlert({
644
+ projectId: config.projectId,
645
+ type: "deadline",
646
+ severity: "medium",
647
+ message: `${schedule.frameworkId} assessment scheduled for ${schedule.nextRun}`,
648
+ });
649
+ }
650
+ }
651
+
652
+ /**
653
+ * Check for system errors
654
+ */
655
+ private async checkSystemErrors(config: AlertConfig): Promise<void> {
656
+ const errors = await prisma.auditEvent.count({
657
+ where: {
658
+ projectId: config.projectId,
659
+ type: "compliance_check_failed",
660
+ timestamp: {
661
+ gte: new Date(Date.now() - 60 * 60 * 1000), // Last hour
662
+ },
663
+ },
664
+ });
665
+
666
+ if (errors > 0) {
667
+ await this.createAlert({
668
+ projectId: config.projectId,
669
+ type: "system",
670
+ severity: "critical",
671
+ message: `${errors} compliance check failures in the last hour`,
672
+ });
673
+ }
674
+ }
675
+
676
+ /**
677
+ * Create an alert
678
+ */
679
+ private async createAlert(alert: {
680
+ projectId: string;
681
+ type: string;
682
+ severity: string;
683
+ message: string;
684
+ }): Promise<void> {
685
+ // Check if similar alert already exists
686
+ try {
687
+ // @ts-ignore - resolved field may not exist in schema
688
+ const existing = await prisma.alert.findFirst({
689
+ where: {
690
+ projectId: alert.projectId,
691
+ type: alert.type,
692
+ message: alert.message,
693
+ // resolved not in schema
694
+ // resolved: false,
695
+ createdAt: {
696
+ gte: new Date(Date.now() - 60 * 60 * 1000), // Last hour
697
+ },
698
+ },
699
+ });
700
+
701
+ if (existing) return;
702
+ } catch (error) {
703
+ // resolved field may not exist - skip duplicate check
704
+ }
705
+
706
+ // Create new alert
707
+ try {
708
+ // @ts-ignore - resolved field may not exist in schema
709
+ await prisma.alert.create({
710
+ data: {
711
+ id: `alert_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`,
712
+ projectId: alert.projectId,
713
+ type: alert.type,
714
+ severity: alert.severity.toUpperCase(),
715
+ title: alert.message as any,
716
+ message: alert.message,
717
+ acknowledged: false,
718
+ // resolved not in schema
719
+ // resolved: false
720
+ } as any,
721
+ });
722
+ } catch (error) {
723
+ // resolved field may not exist - create without it
724
+ await prisma.alert.create({
725
+ data: {
726
+ id: `alert_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`,
727
+ projectId: alert.projectId,
728
+ type: alert.type,
729
+ severity: alert.severity.toUpperCase(),
730
+ title: alert.message as any,
731
+ message: alert.message,
732
+ acknowledged: false,
733
+ } as any,
734
+ });
735
+ }
736
+ }
737
+
738
+ /**
739
+ * Get violation trends
740
+ */
741
+ private async getViolationTrends(projectId: string): Promise<any[]> {
742
+ let violations: any[] = [];
743
+ try {
744
+ violations = await prisma.auditEvent.findMany({
745
+ where: {
746
+ projectId,
747
+ type: "compliance_violation",
748
+ timestamp: {
749
+ gte: new Date(Date.now() - 30 * 24 * 60 * 60 * 1000), // Last 30 days
750
+ },
751
+ },
752
+ orderBy: { timestamp: "asc" },
753
+ });
754
+ } catch (error) {
755
+ console.warn("Could not fetch violation trends:", error);
756
+ }
757
+
758
+ // Group by day
759
+ const daily = violations.reduce(
760
+ (acc: any, v: any) => {
761
+ const day = v.timestamp.toISOString().split("T")[0];
762
+ if (!acc[day]) acc[day] = 0;
763
+ acc[day]++;
764
+ return acc;
765
+ },
766
+ {} as Record<string, number>,
767
+ );
768
+
769
+ return Object.entries(daily).map(([date, count]) => ({
770
+ date: new Date(date),
771
+ count,
772
+ severity: "high", // Simplified
773
+ }));
774
+ }
775
+ /**
776
+ * Get remediation trends
777
+ */
778
+ private async getRemediationTrends(projectId: string): Promise<any[]> {
779
+ let remediations: any[] = [];
780
+ try {
781
+ remediations = await prisma.auditEvent.findMany({
782
+ where: {
783
+ projectId,
784
+ type: "remediation_performed",
785
+ timestamp: {
786
+ gte: new Date(Date.now() - 30 * 24 * 60 * 60 * 1000),
787
+ },
788
+ },
789
+ orderBy: { timestamp: "asc" },
790
+ });
791
+ } catch (error) {
792
+ console.warn("Could not fetch remediation trends:", error);
793
+ }
794
+
795
+ // Group by day
796
+ const daily = remediations.reduce(
797
+ (acc: any, r: any) => {
798
+ const day = r.timestamp.toISOString().split("T")[0];
799
+ if (!acc[day]) acc[day] = { completed: 0, pending: 0 };
800
+ acc[day].completed++;
801
+ return acc;
802
+ },
803
+ {} as Record<string, any>,
804
+ );
805
+
806
+ return Object.entries(daily).map(([date, data]: [string, any]) => ({
807
+ date: new Date(date),
808
+ completed: data.completed,
809
+ pending: data.pending || 0,
810
+ }));
811
+ }
812
+
813
+ /**
814
+ * Widget data getters
815
+ */
816
+ private async getScoreWidget(projectId: string): Promise<any> {
817
+ try {
818
+ const latest = await prisma.complianceAssessment.findFirst({
819
+ where: { projectId },
820
+ orderBy: { createdAt: "desc" },
821
+ });
822
+
823
+ const summary = latest?.summary as any;
824
+ const score = typeof summary?.score === "number" ? summary.score : 0;
825
+
826
+ return {
827
+ score,
828
+ status:
829
+ score >= 90 ? "compliant" : score >= 70 ? "partial" : "non-compliant",
830
+ lastUpdated: latest?.createdAt,
831
+ };
832
+ } catch (error) {
833
+ console.warn("Could not get score widget:", error);
834
+ return {
835
+ score: 0,
836
+ status: "non-compliant",
837
+ lastUpdated: new Date(),
838
+ };
839
+ }
840
+ }
841
+
842
+ private async getTrendsWidget(projectId: string): Promise<any> {
843
+ const trends = await this.getTrends(projectId);
844
+ return {
845
+ scores: trends.scores.slice(-7), // Last 7 days
846
+ trend: this.calculateTrend(trends.scores.map((s: any) => s.score)),
847
+ };
848
+ }
849
+
850
+ private async getViolationsWidget(projectId: string): Promise<any> {
851
+ const violations = await this.getViolationTrends(projectId);
852
+ return {
853
+ total: violations.reduce((sum: number, v: any) => sum + v.count, 0),
854
+ recent: violations.slice(-7), // Last 7 days
855
+ };
856
+ }
857
+
858
+ private async getRemediationWidget(projectId: string): Promise<any> {
859
+ try {
860
+ let pending = 0;
861
+ let completed = 0;
862
+
863
+ // Try to get from database, but handle missing table
864
+ try {
865
+ // @ts-ignore - remediationTask may not exist in schema
866
+ pending = await prisma.remediationTask.count({
867
+ where: { projectId, status: "pending" },
868
+ });
869
+
870
+ // @ts-ignore - remediationTask may not exist in schema
871
+ completed = await prisma.remediationTask.count({
872
+ where: {
873
+ projectId,
874
+ status: "completed",
875
+ completedAt: {
876
+ gte: new Date(Date.now() - 30 * 24 * 60 * 60 * 1000),
877
+ },
878
+ },
879
+ });
880
+ } catch (error) {
881
+ console.warn("Remediation tasks table not available:", error);
882
+ }
883
+
884
+ return {
885
+ pending,
886
+ completedThisMonth: completed,
887
+ };
888
+ } catch (error) {
889
+ console.warn("Could not get remediation widget:", error);
890
+ return {
891
+ pending: 0,
892
+ completedThisMonth: 0,
893
+ };
894
+ }
895
+ }
896
+
897
+ /**
898
+ * Calculate trend direction
899
+ */
900
+ private calculateTrend(scores: number[]): "up" | "down" | "stable" {
901
+ if (scores.length < 2) return "stable";
902
+
903
+ const recent = scores.slice(-3);
904
+ const older = scores.slice(-6, -3);
905
+
906
+ const recentAvg =
907
+ recent.reduce((a: number, b: number) => a + b, 0) / recent.length;
908
+ const olderAvg =
909
+ older.reduce((a: number, b: number) => a + b, 0) / older.length;
910
+
911
+ if (recentAvg > olderAvg + 5) return "up";
912
+ if (recentAvg < olderAvg - 5) return "down";
913
+ return "stable";
914
+ }
915
+
916
+ /**
917
+ * Convert dashboard data to CSV
918
+ */
919
+ private convertToCSV(data: DashboardData): string {
920
+ const rows = [
921
+ ["Metric", "Value"],
922
+ ["Project ID", data.projectId],
923
+ ["Overall Score", data.overview.overallScore.toString()],
924
+ ["Status", data.overview.status],
925
+ ["Last Assessment", data.overview.lastAssessment.toISOString()],
926
+ ["Total Controls", data.overview.totalControls.toString()],
927
+ ["Active Frameworks", data.overview.activeFrameworks.join(", ")],
928
+ ["Total Alerts", data.alerts.length.toString()],
929
+ ["Upcoming Tasks", data.upcomingTasks.length.toString()],
930
+ ];
931
+
932
+ return rows.map((row) => row.join(",")).join("\n");
933
+ }
934
+
935
+ /**
936
+ * Shutdown dashboard monitoring
937
+ */
938
+ async shutdown(): Promise<void> {
939
+ for (const interval of this.alertIntervals.values()) {
940
+ clearInterval(interval);
941
+ }
942
+ this.alertIntervals.clear();
943
+ }
944
+ }
945
+
946
+ // Export singleton instance
947
+ export const complianceDashboard = new ComplianceDashboard();