@devilsdev/rag-pipeline-utils 2.1.12 → 2.2.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 (110) hide show
  1. package/.ragrc.schema.json +218 -0
  2. package/README.md +292 -32
  3. package/bin/cli.js +8 -14
  4. package/dist/index.d.ts +237 -0
  5. package/dist/index.mjs +22 -7
  6. package/package.json +17 -13
  7. package/src/ai/adaptive-retrieval.js +191 -0
  8. package/src/ai/federated-learning.js +214 -0
  9. package/src/ai/federation/federated-learning-coordinator.js +345 -0
  10. package/src/ai/federation.js +76 -0
  11. package/src/ai/index-fixed.js +509 -0
  12. package/src/ai/index.js +33 -0
  13. package/src/ai/model-trainer.js +217 -0
  14. package/src/ai/model-training.js +205 -0
  15. package/src/ai/module-template.js +37 -0
  16. package/src/ai/multimodal/multi-modal-processor.js +345 -0
  17. package/src/ai/multimodal-processing.js +268 -0
  18. package/src/ai/multimodal.js +71 -0
  19. package/src/ai/orchestrator.js +70 -0
  20. package/src/ai/retrieval/adaptive-retrieval-engine.js +398 -0
  21. package/src/ai/retrieval-engine.js +69 -0
  22. package/src/ai/training/model-training-orchestrator.js +228 -0
  23. package/src/ci/workflow-analyzer.js +71 -0
  24. package/src/cli/commands/ai-ml.js +967 -0
  25. package/src/cli/commands/docs.js +228 -0
  26. package/src/cli/commands/dx.js +538 -0
  27. package/src/cli/commands/plugin-hub.js +875 -0
  28. package/src/cli/doctor-command.js +639 -0
  29. package/src/cli/enhanced-cli-commands.js +945 -0
  30. package/src/cli/interactive-wizard.js +997 -0
  31. package/src/cli/plugin-marketplace-commands.js +869 -0
  32. package/src/cli.js +17 -0
  33. package/src/config/enhanced-ragrc-schema.js +632 -0
  34. package/src/config/load-config.js +54 -0
  35. package/src/config/load-plugin-config.js +69 -0
  36. package/src/config/normalize-config.js +105 -0
  37. package/src/config/schema-validator.js +315 -0
  38. package/src/config/validate-plugin-schema.js +35 -0
  39. package/src/config/validate-schema.js +113 -0
  40. package/src/contracts/plugin-contracts.js +33 -0
  41. package/src/core/create-pipeline.js +99 -0
  42. package/src/core/dependency-injection.js +242 -0
  43. package/src/core/observability/event-logger.js +241 -0
  44. package/src/core/observability/instrumented-pipeline.js +609 -0
  45. package/src/core/observability/metrics.js +497 -0
  46. package/src/core/observability/tracing.js +781 -0
  47. package/src/core/performance/benchmark.js +516 -0
  48. package/src/core/performance/parallel-processor.js +240 -0
  49. package/src/core/performance/streaming-safeguards.js +611 -0
  50. package/src/core/pipeline-factory.js +60 -0
  51. package/src/core/plugin-contracts.js +89 -0
  52. package/src/core/plugin-marketplace/plugin-metadata.js +485 -0
  53. package/src/core/plugin-marketplace/plugin-publisher.js +574 -0
  54. package/src/core/plugin-marketplace/plugin-registry-format.js +419 -0
  55. package/src/core/plugin-marketplace/version-resolver.js +61 -0
  56. package/src/core/plugin-registry.js +265 -0
  57. package/src/dag/dag-engine.js +954 -0
  58. package/src/dag/dag-node.js +76 -0
  59. package/src/dag/enhanced-dag-engine.js +822 -0
  60. package/src/dx/index.js +40 -0
  61. package/src/dx/integration-templates.js +665 -0
  62. package/src/dx/performance-profiler.js +968 -0
  63. package/src/dx/realtime-debugger.js +755 -0
  64. package/src/dx/visual-pipeline-builder.js +578 -0
  65. package/src/ecosystem/plugin-analytics-dashboard.js +778 -0
  66. package/src/ecosystem/plugin-certification.js +580 -0
  67. package/src/ecosystem/plugin-hub.js +821 -0
  68. package/src/enterprise/audit-logging.js +967 -0
  69. package/src/enterprise/data-governance.js +408 -0
  70. package/src/enterprise/multi-tenancy.js +496 -0
  71. package/src/enterprise/sso-integration.js +903 -0
  72. package/src/evaluate/evaluator.js +52 -0
  73. package/src/evaluate/scoring.js +91 -0
  74. package/src/index.js +67 -0
  75. package/src/ingest.js +59 -0
  76. package/src/loader/csv-loader.js +38 -0
  77. package/src/loader/directory-loader.js +48 -0
  78. package/src/loader/html-loader.js +62 -0
  79. package/src/loader/markdown-loader.js +67 -0
  80. package/src/mocks/openai-embedder.js +119 -0
  81. package/src/mocks/openai-llm.js +77 -0
  82. package/src/mocks/pdf-loader.js +21 -0
  83. package/src/mocks/pinecone-retriever.js +106 -0
  84. package/src/observability/audit-logger.js +758 -0
  85. package/src/observability/dashboard-config.json +675 -0
  86. package/src/observability/event-logger.js +310 -0
  87. package/src/observability/metrics.js +691 -0
  88. package/src/observability/security-monitor.js +830 -0
  89. package/src/observability/slo-monitor.js +319 -0
  90. package/src/observability/telemetry.js +686 -0
  91. package/src/pipeline/pipeline.js +18 -0
  92. package/src/plugins/sample-embedder.js +53 -0
  93. package/src/plugins/sample-llm.js +30 -0
  94. package/src/plugins/sample-loader.js +24 -0
  95. package/src/plugins/sample-retriever.js +84 -0
  96. package/src/query.js +117 -0
  97. package/src/reranker/llm-reranker.js +85 -0
  98. package/src/scripts/script-utilities.js +36 -0
  99. package/src/security/plugin-sandbox.js +586 -0
  100. package/src/security/plugin-signature-verifier.js +414 -0
  101. package/src/security/validator.js +36 -0
  102. package/src/utils/ci/diagnostic-reporter.js +57 -0
  103. package/src/utils/logger.js +211 -0
  104. package/src/utils/performance-monitor.js +561 -0
  105. package/src/utils/plugin-scaffolder.js +447 -0
  106. package/src/utils/retry-policy.js +443 -0
  107. package/src/utils/retry.js +107 -0
  108. package/src/utils/safe-expression-evaluator.js +579 -0
  109. package/src/utils/structured-logger.js +361 -0
  110. package/src/utils/validate-plugin-contract.js +42 -0
@@ -0,0 +1,778 @@
1
+ /**
2
+ * Plugin Analytics Dashboard
3
+ * Real-time analytics and metrics visualization for plugin ecosystem
4
+ */
5
+
6
+ const fs = require('fs').promises;
7
+ // eslint-disable-line global-require
8
+ const path = require('path');
9
+ // eslint-disable-line global-require
10
+ const { EventEmitter } = require('events');
11
+ // eslint-disable-line global-require
12
+
13
+ class PluginAnalyticsDashboard extends EventEmitter {
14
+ constructor(_options = {}) {
15
+ super();
16
+
17
+ this._config = {
18
+ port: _options.port || 3333,
19
+ dataDir: _options.dataDir || path.join(process.cwd(), '.rag-analytics'),
20
+ refreshInterval: _options.refreshInterval || 30000, // 30 seconds
21
+ retentionDays: _options.retentionDays || 90,
22
+ ..._options,
23
+ };
24
+
25
+ this.metrics = new Map();
26
+ this.realTimeData = new Map();
27
+ this.subscribers = new Set();
28
+
29
+ this.initializeMetrics();
30
+ this.startDataCollection();
31
+ }
32
+
33
+ /**
34
+ * Initialize metric collectors
35
+ */
36
+ initializeMetrics() {
37
+ // Plugin usage metrics
38
+ this.metrics.set('plugin_installs', {
39
+ type: 'counter',
40
+ description: 'Total plugin installations',
41
+ value: 0,
42
+ history: [],
43
+ });
44
+
45
+ this.metrics.set('plugin_searches', {
46
+ type: 'counter',
47
+ description: 'Total plugin searches',
48
+ value: 0,
49
+ history: [],
50
+ });
51
+
52
+ this.metrics.set('plugin_ratings', {
53
+ type: 'histogram',
54
+ description: 'Plugin rating distribution',
55
+ buckets: { 1: 0, 2: 0, 3: 0, 4: 0, 5: 0 },
56
+ history: [],
57
+ });
58
+
59
+ this.metrics.set('active_plugins', {
60
+ type: 'gauge',
61
+ description: 'Currently active plugins',
62
+ value: 0,
63
+ history: [],
64
+ });
65
+
66
+ // Performance metrics
67
+ this.metrics.set('installation_time', {
68
+ type: 'histogram',
69
+ description: 'Plugin installation time (ms)',
70
+ buckets: {},
71
+ history: [],
72
+ });
73
+
74
+ this.metrics.set('search_latency', {
75
+ type: 'histogram',
76
+ description: 'Search response time (ms)',
77
+ buckets: {},
78
+ history: [],
79
+ });
80
+
81
+ // Security metrics
82
+ this.metrics.set('security_scans', {
83
+ type: 'counter',
84
+ description: 'Security scans performed',
85
+ value: 0,
86
+ history: [],
87
+ });
88
+
89
+ this.metrics.set('security_issues', {
90
+ type: 'counter',
91
+ description: 'Security issues detected',
92
+ value: 0,
93
+ history: [],
94
+ });
95
+
96
+ // Certification metrics
97
+ this.metrics.set('certification_requests', {
98
+ type: 'counter',
99
+ description: 'Certification requests submitted',
100
+ value: 0,
101
+ history: [],
102
+ });
103
+
104
+ this.metrics.set('certified_plugins', {
105
+ type: 'gauge',
106
+ description: 'Total certified plugins',
107
+ value: 0,
108
+ history: [],
109
+ });
110
+ }
111
+
112
+ /**
113
+ * Start collecting analytics data
114
+ */
115
+ startDataCollection() {
116
+ // Periodic data collection
117
+ setInterval(() => {
118
+ this.collectMetrics();
119
+ this.updateRealTimeData();
120
+ this.notifySubscribers();
121
+ }, this._config.refreshInterval);
122
+
123
+ // Daily cleanup
124
+ setInterval(
125
+ () => {
126
+ this.cleanupOldData();
127
+ },
128
+ 24 * 60 * 60 * 1000,
129
+ ); // 24 hours
130
+ }
131
+
132
+ /**
133
+ * Record plugin installation
134
+ */
135
+ recordInstallation(pluginId, version, metadata = {}) {
136
+ const timestamp = Date.now();
137
+
138
+ this.incrementMetric('plugin_installs');
139
+ this.recordHistogram('installation_time', metadata.installTime || 0);
140
+
141
+ this.realTimeData.set(`install_${timestamp}`, {
142
+ type: 'installation',
143
+ pluginId,
144
+ version,
145
+ timestamp,
146
+ metadata,
147
+ });
148
+
149
+ this.emit('installation_recorded', { pluginId, version, metadata });
150
+ }
151
+
152
+ /**
153
+ * Record plugin search
154
+ */
155
+ recordSearch(query, results, latency) {
156
+ const timestamp = Date.now();
157
+
158
+ this.incrementMetric('plugin_searches');
159
+ this.recordHistogram('search_latency', latency);
160
+
161
+ this.realTimeData.set(`search_${timestamp}`, {
162
+ type: 'search',
163
+ query,
164
+ resultCount: results.length,
165
+ latency,
166
+ timestamp,
167
+ });
168
+
169
+ this.emit('search_recorded', { query, results, latency });
170
+ }
171
+
172
+ /**
173
+ * Record plugin rating
174
+ */
175
+ recordRating(pluginId, rating, review = null) {
176
+ const timestamp = Date.now();
177
+
178
+ const ratingMetric = this.metrics.get('plugin_ratings');
179
+ if (ratingMetric && ratingMetric.buckets[rating] !== undefined) {
180
+ ratingMetric.buckets[rating]++;
181
+ }
182
+
183
+ this.realTimeData.set(`rating_${timestamp}`, {
184
+ type: 'rating',
185
+ pluginId,
186
+ rating,
187
+ review,
188
+ timestamp,
189
+ });
190
+
191
+ this.emit('rating_recorded', { pluginId, rating, review });
192
+ }
193
+
194
+ /**
195
+ * Record security scan
196
+ */
197
+ recordSecurityScan(pluginId, result) {
198
+ const timestamp = Date.now();
199
+
200
+ this.incrementMetric('security_scans');
201
+
202
+ if (result.issues && result.issues.length > 0) {
203
+ this.incrementMetric('security_issues', result.issues.length);
204
+ }
205
+
206
+ this.realTimeData.set(`security_${timestamp}`, {
207
+ type: 'security_scan',
208
+ pluginId,
209
+ risk: result.risk,
210
+ issueCount: result.issues?.length || 0,
211
+ timestamp,
212
+ });
213
+
214
+ this.emit('security_scan_recorded', { pluginId, result });
215
+ }
216
+
217
+ /**
218
+ * Record certification request
219
+ */
220
+ recordCertification(pluginId, level, result) {
221
+ const timestamp = Date.now();
222
+
223
+ this.incrementMetric('certification_requests');
224
+
225
+ if (result.approved) {
226
+ this.incrementMetric('certified_plugins');
227
+ }
228
+
229
+ this.realTimeData.set(`cert_${timestamp}`, {
230
+ type: 'certification',
231
+ pluginId,
232
+ level,
233
+ approved: result.approved,
234
+ score: result.score,
235
+ timestamp,
236
+ });
237
+
238
+ this.emit('certification_recorded', { pluginId, level, result });
239
+ }
240
+
241
+ /**
242
+ * Get dashboard data
243
+ */
244
+ getDashboardData() {
245
+ const now = Date.now();
246
+ const oneHourAgo = now - 60 * 60 * 1000;
247
+
248
+ // Recent activity
249
+ const recentActivity = Array.from(this.realTimeData.values())
250
+ .filter((item) => item.timestamp > oneHourAgo)
251
+ .sort((a, b) => b.timestamp - a.timestamp)
252
+ .slice(0, 50);
253
+
254
+ // Time series data
255
+ const timeSeriesData = this.generateTimeSeriesData();
256
+
257
+ // Top plugins
258
+ const topPlugins = this.getTopPlugins();
259
+
260
+ // Performance summary
261
+ const performanceSummary = this.getPerformanceSummary();
262
+
263
+ // Security overview
264
+ const securityOverview = this.getSecurityOverview();
265
+
266
+ return {
267
+ timestamp: now,
268
+ metrics: Object.fromEntries(this.metrics),
269
+ recentActivity,
270
+ timeSeriesData,
271
+ topPlugins,
272
+ performanceSummary,
273
+ securityOverview,
274
+ summary: {
275
+ totalInstalls: this.metrics.get('plugin_installs')?.value || 0,
276
+ totalSearches: this.metrics.get('plugin_searches')?.value || 0,
277
+ activePlugins: this.metrics.get('active_plugins')?.value || 0,
278
+ certifiedPlugins: this.metrics.get('certified_plugins')?.value || 0,
279
+ avgRating: this.calculateAverageRating(),
280
+ securityScore: this.calculateSecurityScore(),
281
+ },
282
+ };
283
+ }
284
+
285
+ /**
286
+ * Generate time series data for charts
287
+ */
288
+ generateTimeSeriesData() {
289
+ const now = Date.now();
290
+ const oneWeekAgo = now - 7 * 24 * 60 * 60 * 1000;
291
+ const interval = 60 * 60 * 1000; // 1 hour intervals
292
+
293
+ const series = {
294
+ installs: [],
295
+ searches: [],
296
+ ratings: [],
297
+ securityScans: [],
298
+ };
299
+
300
+ for (let time = oneWeekAgo; time <= now; time += interval) {
301
+ const hourData = Array.from(this.realTimeData.values()).filter(
302
+ (item) => item.timestamp >= time && item.timestamp < time + interval,
303
+ );
304
+
305
+ series.installs.push({
306
+ timestamp: time,
307
+ value: hourData.filter((item) => item.type === 'installation').length,
308
+ });
309
+
310
+ series.searches.push({
311
+ timestamp: time,
312
+ value: hourData.filter((item) => item.type === 'search').length,
313
+ });
314
+
315
+ series.ratings.push({
316
+ timestamp: time,
317
+ value: hourData.filter((item) => item.type === 'rating').length,
318
+ });
319
+
320
+ series.securityScans.push({
321
+ timestamp: time,
322
+ value: hourData.filter((item) => item.type === 'security_scan').length,
323
+ });
324
+ }
325
+
326
+ return series;
327
+ }
328
+
329
+ /**
330
+ * Get top performing plugins
331
+ */
332
+ getTopPlugins() {
333
+ const pluginStats = new Map();
334
+
335
+ Array.from(this.realTimeData.values()).forEach((item) => {
336
+ if (item.pluginId) {
337
+ if (!pluginStats.has(item.pluginId)) {
338
+ pluginStats.set(item.pluginId, {
339
+ pluginId: item.pluginId,
340
+ installs: 0,
341
+ ratings: [],
342
+ securityScans: 0,
343
+ certifications: 0,
344
+ });
345
+ }
346
+
347
+ const stats = pluginStats.get(item.pluginId);
348
+
349
+ switch (item.type) {
350
+ case 'installation':
351
+ stats.installs++;
352
+ break;
353
+ case 'rating':
354
+ stats.ratings.push(item.rating);
355
+ break;
356
+ case 'security_scan':
357
+ stats.securityScans++;
358
+ break;
359
+ case 'certification':
360
+ if (item.approved) stats.certifications++;
361
+ break;
362
+ }
363
+ }
364
+ });
365
+
366
+ return Array.from(pluginStats.values())
367
+ .map((stats) => ({
368
+ ...stats,
369
+ avgRating:
370
+ stats.ratings.length > 0
371
+ ? stats.ratings.reduce((a, b) => a + b, 0) / stats.ratings.length
372
+ : 0,
373
+ }))
374
+ .sort((a, b) => b.installs - a.installs)
375
+ .slice(0, 10);
376
+ }
377
+
378
+ /**
379
+ * Get performance summary
380
+ */
381
+ getPerformanceSummary() {
382
+ const installTimes = Array.from(this.realTimeData.values())
383
+ .filter(
384
+ (item) => item.type === 'installation' && item.metadata?.installTime,
385
+ )
386
+ .map((item) => item.metadata.installTime);
387
+
388
+ const searchLatencies = Array.from(this.realTimeData.values())
389
+ .filter((item) => item.type === 'search' && item.latency)
390
+ .map((item) => item.latency);
391
+
392
+ return {
393
+ avgInstallTime:
394
+ installTimes.length > 0
395
+ ? installTimes.reduce((a, b) => a + b, 0) / installTimes.length
396
+ : 0,
397
+ p95InstallTime: this.calculatePercentile(installTimes, 95),
398
+ avgSearchLatency:
399
+ searchLatencies.length > 0
400
+ ? searchLatencies.reduce((a, b) => a + b, 0) / searchLatencies.length
401
+ : 0,
402
+ p95SearchLatency: this.calculatePercentile(searchLatencies, 95),
403
+ };
404
+ }
405
+
406
+ /**
407
+ * Get security overview
408
+ */
409
+ getSecurityOverview() {
410
+ const securityScans = Array.from(this.realTimeData.values()).filter(
411
+ (item) => item.type === 'security_scan',
412
+ );
413
+
414
+ const riskDistribution = { low: 0, medium: 0, high: 0 };
415
+ let totalIssues = 0;
416
+
417
+ securityScans.forEach((scan) => {
418
+ if (scan.risk) {
419
+ riskDistribution[scan.risk]++;
420
+ }
421
+ totalIssues += scan.issueCount || 0;
422
+ });
423
+
424
+ return {
425
+ totalScans: securityScans.length,
426
+ riskDistribution,
427
+ totalIssues,
428
+ avgIssuesPerScan:
429
+ securityScans.length > 0 ? totalIssues / securityScans.length : 0,
430
+ };
431
+ }
432
+
433
+ /**
434
+ * Generate HTML dashboard
435
+ */
436
+ async generateHTMLDashboard() {
437
+ const data = this.getDashboardData();
438
+
439
+ const html = `
440
+ <!DOCTYPE html>
441
+ <html lang="en">
442
+ <head>
443
+ ${this._generateHTMLHead()}
444
+ </head>
445
+ <body>
446
+ ${this._generateHTMLBody(data)}
447
+ ${this._generateHTMLScripts(data)}
448
+ </body>
449
+ </html>`;
450
+
451
+ return html;
452
+ }
453
+
454
+ _generateHTMLHead() {
455
+ return `
456
+ <meta charset="UTF-8">
457
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
458
+ <title>RAG Pipeline Plugin Analytics</title>
459
+ <script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
460
+ ${this._generateHTMLStyles()}`;
461
+ }
462
+
463
+ _generateHTMLStyles() {
464
+ return `
465
+ <style>
466
+ body { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; margin: 0; padding: 20px; background: #f5f5f5; }
467
+ .dashboard { max-width: 1200px; margin: 0 auto; }
468
+ .header { background: white; padding: 20px; border-radius: 8px; margin-bottom: 20px; box-shadow: 0 2px 4px rgba(0,0,0,0.1); }
469
+ .metrics-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(250px, 1fr)); gap: 20px; margin-bottom: 20px; }
470
+ .metric-card { background: white; padding: 20px; border-radius: 8px; box-shadow: 0 2px 4px rgba(0,0,0,0.1); }
471
+ .metric-value { font-size: 2em; font-weight: bold; color: #2563eb; }
472
+ .metric-label { color: #6b7280; margin-top: 5px; }
473
+ .chart-container { background: white; padding: 20px; border-radius: 8px; margin-bottom: 20px; box-shadow: 0 2px 4px rgba(0,0,0,0.1); }
474
+ .activity-feed { background: white; padding: 20px; border-radius: 8px; box-shadow: 0 2px 4px rgba(0,0,0,0.1); }
475
+ .activity-item { padding: 10px 0; border-bottom: 1px solid #e5e7eb; }
476
+ .activity-time { color: #6b7280; font-size: 0.9em; }
477
+ .status-indicator { display: inline-block; width: 8px; height: 8px; border-radius: 50%; margin-right: 8px; }
478
+ .status-success { background: #10b981; }
479
+ .status-warning { background: #f59e0b; }
480
+ .status-error { background: #ef4444; }
481
+ </style>`;
482
+ }
483
+
484
+ _generateHTMLBody(data) {
485
+ return `
486
+ <div class="dashboard">
487
+ ${this._generateDashboardHeader(data)}
488
+ ${this._generateMetricsGrid(data)}
489
+ ${this._generateChartsSection()}
490
+ ${this._generateActivityFeed(data)}
491
+ </div>`;
492
+ }
493
+
494
+ _generateDashboardHeader(data) {
495
+ return `
496
+ <div class="header">
497
+ <h1>🔌 RAG Pipeline Plugin Analytics</h1>
498
+ <p>Real-time insights into plugin ecosystem performance and usage</p>
499
+ <p><strong>Last Updated:</strong> ${new Date(data.timestamp).toLocaleString()}</p>
500
+ </div>`;
501
+ }
502
+
503
+ _generateMetricsGrid(data) {
504
+ const metrics = [
505
+ {
506
+ value: data.summary.totalInstalls.toLocaleString(),
507
+ label: 'Total Installations',
508
+ },
509
+ {
510
+ value: data.summary.totalSearches.toLocaleString(),
511
+ label: 'Total Searches',
512
+ },
513
+ { value: data.summary.activePlugins, label: 'Active Plugins' },
514
+ { value: data.summary.certifiedPlugins, label: 'Certified Plugins' },
515
+ {
516
+ value: `${data.summary.avgRating.toFixed(1)}★`,
517
+ label: 'Average Rating',
518
+ },
519
+ { value: `${data.summary.securityScore}%`, label: 'Security Score' },
520
+ ];
521
+
522
+ const metricCards = metrics
523
+ .map(
524
+ (metric) =>
525
+ `<div class="metric-card">
526
+ <div class="metric-value">${metric.value}</div>
527
+ <div class="metric-label">${metric.label}</div>
528
+ </div>`,
529
+ )
530
+ .join('');
531
+
532
+ return `<div class="metrics-grid">${metricCards}</div>`;
533
+ }
534
+
535
+ _generateChartsSection() {
536
+ return `
537
+ <div class="chart-container">
538
+ <h3>Plugin Activity Over Time</h3>
539
+ <canvas id="activityChart" width="800" height="400"></canvas>
540
+ </div>
541
+
542
+ <div class="chart-container">
543
+ <h3>Top Plugins by Installations</h3>
544
+ <canvas id="topPluginsChart" width="800" height="400"></canvas>
545
+ </div>`;
546
+ }
547
+
548
+ _generateActivityFeed(data) {
549
+ const activityItems = data.recentActivity
550
+ .map(
551
+ (activity) => `
552
+ <div class="activity-item">
553
+ <span class="status-indicator status-${this.getActivityStatus(activity)}"></span>
554
+ <strong>${activity.type.replace('_', ' ').toUpperCase()}</strong>
555
+ ${this.formatActivityDescription(activity)}
556
+ <div class="activity-time">${new Date(activity.timestamp).toLocaleString()}</div>
557
+ </div>
558
+ `,
559
+ )
560
+ .join('');
561
+
562
+ return `
563
+ <div class="activity-feed">
564
+ <h3>Recent Activity</h3>
565
+ ${activityItems}
566
+ </div>`;
567
+ }
568
+
569
+ _generateHTMLScripts(data) {
570
+ return `
571
+ <script>
572
+ ${this._generateActivityChartScript(data)}
573
+ ${this._generateTopPluginsChartScript(data)}
574
+
575
+ // Auto-refresh every 30 seconds
576
+ setTimeout(() => location.reload(), 30000);
577
+ </script>`;
578
+ }
579
+
580
+ _generateActivityChartScript(data) {
581
+ return `
582
+ // Activity Chart
583
+ const activityCtx = document.getElementById('activityChart').getContext('2d');
584
+ new Chart(activityCtx, {
585
+ type: 'line',
586
+ data: {
587
+ labels: ${JSON.stringify(data.timeSeriesData.installs.map((d) => new Date(d.timestamp).toLocaleDateString()))},
588
+ datasets: [
589
+ {
590
+ label: 'Installations',
591
+ data: ${JSON.stringify(data.timeSeriesData.installs.map((d) => d.value))},
592
+ borderColor: '#2563eb',
593
+ backgroundColor: 'rgba(37, 99, 235, 0.1)'
594
+ },
595
+ {
596
+ label: 'Searches',
597
+ data: ${JSON.stringify(data.timeSeriesData.searches.map((d) => d.value))},
598
+ borderColor: '#10b981',
599
+ backgroundColor: 'rgba(16, 185, 129, 0.1)'
600
+ }
601
+ ]
602
+ },
603
+ options: {
604
+ responsive: true,
605
+ scales: {
606
+ y: { beginAtZero: true }
607
+ }
608
+ }
609
+ });`;
610
+ }
611
+
612
+ _generateTopPluginsChartScript(data) {
613
+ return `
614
+ // Top Plugins Chart
615
+ const topPluginsCtx = document.getElementById('topPluginsChart').getContext('2d');
616
+ new Chart(topPluginsCtx, {
617
+ type: 'bar',
618
+ data: {
619
+ labels: ${JSON.stringify(data.topPlugins.map((p) => p.pluginId))},
620
+ datasets: [{
621
+ label: 'Installations',
622
+ data: ${JSON.stringify(data.topPlugins.map((p) => p.installs))},
623
+ backgroundColor: 'rgba(37, 99, 235, 0.8)'
624
+ }]
625
+ },
626
+ options: {
627
+ responsive: true,
628
+ scales: {
629
+ y: { beginAtZero: true }
630
+ }
631
+ }
632
+ });`;
633
+ }
634
+
635
+ // Helper methods
636
+ incrementMetric(name, amount = 1) {
637
+ const metric = this.metrics.get(name);
638
+ if (metric && metric._type === 'counter') {
639
+ metric.value += amount;
640
+ metric.history.push({ timestamp: Date.now(), value: metric.value });
641
+ }
642
+ }
643
+
644
+ recordHistogram(name, value) {
645
+ const metric = this.metrics.get(name);
646
+ if (metric && metric._type === 'histogram') {
647
+ const bucket = Math.floor(value / 100) * 100; // 100ms buckets
648
+ metric.buckets[bucket] = (metric.buckets[bucket] || 0) + 1;
649
+ metric.history.push({ timestamp: Date.now(), value });
650
+ }
651
+ }
652
+
653
+ calculateAverageRating() {
654
+ const ratingMetric = this.metrics.get('plugin_ratings');
655
+ if (!ratingMetric) return 0;
656
+
657
+ let totalRatings = 0;
658
+ let weightedSum = 0;
659
+
660
+ Object.entries(ratingMetric.buckets).forEach(([rating, count]) => {
661
+ totalRatings += count;
662
+ weightedSum += parseInt(rating) * count;
663
+ });
664
+
665
+ return totalRatings > 0 ? weightedSum / totalRatings : 0;
666
+ }
667
+
668
+ calculateSecurityScore() {
669
+ const scans = this.metrics.get('security_scans')?.value || 0;
670
+ const issues = this.metrics.get('security_issues')?.value || 0;
671
+
672
+ if (scans === 0) return 100;
673
+
674
+ const issueRate = issues / scans;
675
+ return Math.max(0, Math.round(100 - issueRate * 20)); // Penalty for issues
676
+ }
677
+
678
+ calculatePercentile(values, percentile) {
679
+ if (values.length === 0) return 0;
680
+
681
+ const sorted = values.slice().sort((a, b) => a - b);
682
+ const index = Math.ceil((percentile / 100) * sorted.length) - 1;
683
+ return sorted[index] || 0;
684
+ }
685
+
686
+ getActivityStatus(activity) {
687
+ switch (activity.type) {
688
+ case 'installation':
689
+ return 'success';
690
+ case 'security_scan':
691
+ return activity.issueCount > 0 ? 'warning' : 'success';
692
+ case 'certification':
693
+ return activity.approved ? 'success' : 'error';
694
+ default:
695
+ return 'success';
696
+ }
697
+ }
698
+
699
+ formatActivityDescription(activity) {
700
+ switch (activity.type) {
701
+ case 'installation':
702
+ return `Plugin ${activity.pluginId}@${activity.version} installed`;
703
+ case 'search':
704
+ return `Search "${activity.query}" returned ${activity.resultCount} results`;
705
+ case 'rating':
706
+ return `Plugin ${activity.pluginId} rated ${activity.rating} stars`;
707
+ case 'security_scan':
708
+ return `Security scan for ${activity.pluginId} - ${activity.risk} risk, ${activity.issueCount} issues`;
709
+ case 'certification':
710
+ return `Certification ${activity.approved ? 'approved' : 'rejected'} for ${activity.pluginId} (${activity.level})`;
711
+ default:
712
+ return JSON.stringify(activity);
713
+ }
714
+ }
715
+
716
+ async collectMetrics() {
717
+ // Update active plugins count
718
+ try {
719
+ const pluginsDir = path.join(this._config.dataDir, 'plugins');
720
+ const entries = await fs.readdir(pluginsDir).catch(() => []);
721
+
722
+ const activeMetric = this.metrics.get('active_plugins');
723
+ if (activeMetric) {
724
+ activeMetric.value = entries.length;
725
+ activeMetric.history.push({
726
+ timestamp: Date.now(),
727
+ value: entries.length,
728
+ });
729
+ }
730
+ } catch (error) {
731
+ // Ignore errors
732
+ }
733
+ }
734
+
735
+ updateRealTimeData() {
736
+ // Clean up old real-time data (keep last 24 hours)
737
+ const cutoff = Date.now() - 24 * 60 * 60 * 1000;
738
+
739
+ for (const [key, data] of this.realTimeData) {
740
+ if (data.timestamp < cutoff) {
741
+ this.realTimeData.delete(key);
742
+ }
743
+ }
744
+ }
745
+
746
+ notifySubscribers() {
747
+ const data = this.getDashboardData();
748
+ this.subscribers.forEach((callback) => {
749
+ try {
750
+ callback(data);
751
+ } catch (error) {
752
+ console.error('Error notifying subscriber:', error);
753
+ // eslint-disable-line no-console
754
+ }
755
+ });
756
+ }
757
+
758
+ async cleanupOldData() {
759
+ const cutoff =
760
+ Date.now() - this._config.retentionDays * 24 * 60 * 60 * 1000;
761
+
762
+ // Clean up metric history
763
+ for (const metric of this.metrics.values()) {
764
+ if (metric.history) {
765
+ metric.history = metric.history.filter(
766
+ (entry) => entry.timestamp > cutoff,
767
+ );
768
+ }
769
+ }
770
+ }
771
+
772
+ subscribe(callback) {
773
+ this.subscribers.add(callback);
774
+ return () => this.subscribers.delete(callback);
775
+ }
776
+ }
777
+
778
+ module.exports = { PluginAnalyticsDashboard };