@arela/uploader 0.2.12 → 0.3.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.
- package/.env.template +66 -0
- package/.vscode/settings.json +1 -0
- package/README.md +134 -58
- package/SUPABASE_UPLOAD_FIX.md +157 -0
- package/package.json +3 -2
- package/scripts/cleanup-ds-store.js +109 -0
- package/scripts/cleanup-system-files.js +69 -0
- package/scripts/tests/phase-7-features.test.js +415 -0
- package/scripts/tests/signal-handling.test.js +275 -0
- package/scripts/tests/smart-watch-integration.test.js +554 -0
- package/scripts/tests/watch-service-integration.test.js +584 -0
- package/src/commands/UploadCommand.js +36 -2
- package/src/commands/WatchCommand.js +1305 -0
- package/src/config/config.js +113 -0
- package/src/document-type-shared.js +2 -0
- package/src/document-types/support-document.js +201 -0
- package/src/file-detection.js +2 -1
- package/src/index.js +44 -0
- package/src/services/AdvancedFilterService.js +505 -0
- package/src/services/AutoProcessingService.js +639 -0
- package/src/services/BenchmarkingService.js +381 -0
- package/src/services/DatabaseService.js +723 -170
- package/src/services/ErrorMonitor.js +275 -0
- package/src/services/LoggingService.js +419 -1
- package/src/services/MonitoringService.js +401 -0
- package/src/services/PerformanceOptimizer.js +511 -0
- package/src/services/ReportingService.js +511 -0
- package/src/services/SignalHandler.js +255 -0
- package/src/services/SmartWatchDatabaseService.js +527 -0
- package/src/services/WatchService.js +783 -0
- package/src/services/upload/ApiUploadService.js +30 -4
- package/src/services/upload/SupabaseUploadService.js +28 -6
- package/src/utils/CleanupManager.js +262 -0
- package/src/utils/FileOperations.js +41 -0
- package/src/utils/WatchEventHandler.js +517 -0
- package/supabase/migrations/001_create_initial_schema.sql +366 -0
- package/supabase/migrations/002_align_with_arela_api_schema.sql +145 -0
- package/commands.md +0 -6
|
@@ -0,0 +1,401 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* MonitoringService.js
|
|
3
|
+
* Phase 7 - Task 4: Advanced Monitoring & Alerting
|
|
4
|
+
*
|
|
5
|
+
* Provides advanced monitoring and alerting including:
|
|
6
|
+
* - Real-time alerts
|
|
7
|
+
* - Threshold-based notifications
|
|
8
|
+
* - Health monitoring
|
|
9
|
+
* - Alert history and escalation
|
|
10
|
+
* - Custom alert rules
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
class MonitoringService {
|
|
14
|
+
constructor(logger) {
|
|
15
|
+
this.logger = logger;
|
|
16
|
+
this.alerts = [];
|
|
17
|
+
this.alertRules = [];
|
|
18
|
+
this.subscribers = [];
|
|
19
|
+
|
|
20
|
+
this.metrics = {
|
|
21
|
+
cpuUsage: 0,
|
|
22
|
+
memoryUsage: 0,
|
|
23
|
+
diskUsage: 0,
|
|
24
|
+
activeConnections: 0,
|
|
25
|
+
errorRate: 0,
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
this.thresholds = {
|
|
29
|
+
memoryWarning: 75, // 75% heap used
|
|
30
|
+
memoryCritical: 90, // 90% heap used
|
|
31
|
+
errorRateWarning: 5, // 5% error rate
|
|
32
|
+
errorRateCritical: 10, // 10% error rate
|
|
33
|
+
responseTimeWarning: 5000, // 5 seconds
|
|
34
|
+
responseTimeCritical: 10000, // 10 seconds
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
this._initializeDefaultRules();
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Initialize default alert rules
|
|
42
|
+
*/
|
|
43
|
+
_initializeDefaultRules() {
|
|
44
|
+
this.addAlertRule({
|
|
45
|
+
name: 'High Memory Usage',
|
|
46
|
+
metric: 'memoryUsage',
|
|
47
|
+
operator: 'greaterThan',
|
|
48
|
+
threshold: this.thresholds.memoryWarning,
|
|
49
|
+
severity: 'warning',
|
|
50
|
+
enabled: true,
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
this.addAlertRule({
|
|
54
|
+
name: 'Critical Memory Usage',
|
|
55
|
+
metric: 'memoryUsage',
|
|
56
|
+
operator: 'greaterThan',
|
|
57
|
+
threshold: this.thresholds.memoryCritical,
|
|
58
|
+
severity: 'critical',
|
|
59
|
+
enabled: true,
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
this.addAlertRule({
|
|
63
|
+
name: 'High Error Rate',
|
|
64
|
+
metric: 'errorRate',
|
|
65
|
+
operator: 'greaterThan',
|
|
66
|
+
threshold: this.thresholds.errorRateWarning,
|
|
67
|
+
severity: 'warning',
|
|
68
|
+
enabled: true,
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
this.addAlertRule({
|
|
72
|
+
name: 'Critical Error Rate',
|
|
73
|
+
metric: 'errorRate',
|
|
74
|
+
operator: 'greaterThan',
|
|
75
|
+
threshold: this.thresholds.errorRateCritical,
|
|
76
|
+
severity: 'critical',
|
|
77
|
+
enabled: true,
|
|
78
|
+
});
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* Add alert rule
|
|
83
|
+
*/
|
|
84
|
+
addAlertRule(rule) {
|
|
85
|
+
const fullRule = {
|
|
86
|
+
id: `rule_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`,
|
|
87
|
+
...rule,
|
|
88
|
+
createdAt: new Date().toISOString(),
|
|
89
|
+
triggeredCount: 0,
|
|
90
|
+
lastTriggered: null,
|
|
91
|
+
};
|
|
92
|
+
|
|
93
|
+
this.alertRules.push(fullRule);
|
|
94
|
+
return fullRule;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* Subscribe to alerts
|
|
99
|
+
*/
|
|
100
|
+
subscribe(severity, callback) {
|
|
101
|
+
const subscription = {
|
|
102
|
+
id: `sub_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`,
|
|
103
|
+
severity,
|
|
104
|
+
callback,
|
|
105
|
+
subscribed: true,
|
|
106
|
+
};
|
|
107
|
+
|
|
108
|
+
this.subscribers.push(subscription);
|
|
109
|
+
return subscription.id;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/**
|
|
113
|
+
* Unsubscribe from alerts
|
|
114
|
+
*/
|
|
115
|
+
unsubscribe(subscriptionId) {
|
|
116
|
+
const index = this.subscribers.findIndex((s) => s.id === subscriptionId);
|
|
117
|
+
if (index > -1) {
|
|
118
|
+
this.subscribers.splice(index, 1);
|
|
119
|
+
return true;
|
|
120
|
+
}
|
|
121
|
+
return false;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
/**
|
|
125
|
+
* Update metrics
|
|
126
|
+
*/
|
|
127
|
+
updateMetrics(newMetrics) {
|
|
128
|
+
const previousMetrics = { ...this.metrics };
|
|
129
|
+
this.metrics = { ...this.metrics, ...newMetrics };
|
|
130
|
+
|
|
131
|
+
// Check rules
|
|
132
|
+
this._checkAlertRules();
|
|
133
|
+
|
|
134
|
+
return {
|
|
135
|
+
previous: previousMetrics,
|
|
136
|
+
current: this.metrics,
|
|
137
|
+
changed: Object.keys(newMetrics),
|
|
138
|
+
};
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
/**
|
|
142
|
+
* Check all alert rules
|
|
143
|
+
*/
|
|
144
|
+
_checkAlertRules() {
|
|
145
|
+
for (const rule of this.alertRules) {
|
|
146
|
+
if (!rule.enabled) continue;
|
|
147
|
+
|
|
148
|
+
const metricValue = this.metrics[rule.metric];
|
|
149
|
+
let triggered = false;
|
|
150
|
+
|
|
151
|
+
switch (rule.operator) {
|
|
152
|
+
case 'greaterThan':
|
|
153
|
+
triggered = metricValue > rule.threshold;
|
|
154
|
+
break;
|
|
155
|
+
case 'lessThan':
|
|
156
|
+
triggered = metricValue < rule.threshold;
|
|
157
|
+
break;
|
|
158
|
+
case 'equals':
|
|
159
|
+
triggered = metricValue === rule.threshold;
|
|
160
|
+
break;
|
|
161
|
+
case 'between':
|
|
162
|
+
triggered =
|
|
163
|
+
metricValue >= rule.threshold[0] &&
|
|
164
|
+
metricValue <= rule.threshold[1];
|
|
165
|
+
break;
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
if (triggered) {
|
|
169
|
+
this._triggerAlert(rule, metricValue);
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
/**
|
|
175
|
+
* Trigger an alert
|
|
176
|
+
*/
|
|
177
|
+
_triggerAlert(rule, value) {
|
|
178
|
+
const alert = {
|
|
179
|
+
id: `alert_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`,
|
|
180
|
+
ruleId: rule.id,
|
|
181
|
+
ruleName: rule.name,
|
|
182
|
+
severity: rule.severity,
|
|
183
|
+
metric: rule.metric,
|
|
184
|
+
value,
|
|
185
|
+
threshold: rule.threshold,
|
|
186
|
+
timestamp: new Date().toISOString(),
|
|
187
|
+
message: `${rule.name}: ${rule.metric} = ${value} (threshold: ${rule.threshold})`,
|
|
188
|
+
acknowledged: false,
|
|
189
|
+
};
|
|
190
|
+
|
|
191
|
+
this.alerts.push(alert);
|
|
192
|
+
rule.triggeredCount++;
|
|
193
|
+
rule.lastTriggered = new Date().toISOString();
|
|
194
|
+
|
|
195
|
+
// Notify subscribers
|
|
196
|
+
this._notifySubscribers(alert);
|
|
197
|
+
|
|
198
|
+
this.logger.warn(`Alert triggered: ${alert.message}`);
|
|
199
|
+
|
|
200
|
+
return alert;
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
/**
|
|
204
|
+
* Notify subscribers
|
|
205
|
+
*/
|
|
206
|
+
_notifySubscribers(alert) {
|
|
207
|
+
const severityOrder = { info: 0, warning: 1, critical: 2 };
|
|
208
|
+
const alertSeverityLevel = severityOrder[alert.severity] || 0;
|
|
209
|
+
|
|
210
|
+
for (const subscriber of this.subscribers) {
|
|
211
|
+
const subscriberSeverityLevel = severityOrder[subscriber.severity] || 0;
|
|
212
|
+
|
|
213
|
+
if (alertSeverityLevel >= subscriberSeverityLevel) {
|
|
214
|
+
try {
|
|
215
|
+
subscriber.callback(alert);
|
|
216
|
+
} catch (error) {
|
|
217
|
+
this.logger.error(`Subscriber callback error:`, error);
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
/**
|
|
224
|
+
* Acknowledge alert
|
|
225
|
+
*/
|
|
226
|
+
acknowledgeAlert(alertId) {
|
|
227
|
+
const alert = this.alerts.find((a) => a.id === alertId);
|
|
228
|
+
if (alert) {
|
|
229
|
+
alert.acknowledged = true;
|
|
230
|
+
alert.acknowledgedAt = new Date().toISOString();
|
|
231
|
+
return true;
|
|
232
|
+
}
|
|
233
|
+
return false;
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
/**
|
|
237
|
+
* Get active alerts
|
|
238
|
+
*/
|
|
239
|
+
getActiveAlerts(severity = null) {
|
|
240
|
+
let alerts = this.alerts.filter((a) => !a.acknowledged);
|
|
241
|
+
|
|
242
|
+
if (severity) {
|
|
243
|
+
alerts = alerts.filter((a) => a.severity === severity);
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
return alerts;
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
/**
|
|
250
|
+
* Get alert history
|
|
251
|
+
*/
|
|
252
|
+
getAlertHistory(limit = 50) {
|
|
253
|
+
return this.alerts.slice(-limit).reverse();
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
/**
|
|
257
|
+
* Get health status
|
|
258
|
+
*/
|
|
259
|
+
getHealthStatus() {
|
|
260
|
+
const activeAlerts = this.getActiveAlerts();
|
|
261
|
+
const criticalAlerts = activeAlerts.filter(
|
|
262
|
+
(a) => a.severity === 'critical',
|
|
263
|
+
);
|
|
264
|
+
|
|
265
|
+
let healthStatus = 'healthy';
|
|
266
|
+
if (criticalAlerts.length > 0) {
|
|
267
|
+
healthStatus = 'critical';
|
|
268
|
+
} else if (activeAlerts.length > 0) {
|
|
269
|
+
healthStatus = 'warning';
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
return {
|
|
273
|
+
status: healthStatus,
|
|
274
|
+
timestamp: new Date().toISOString(),
|
|
275
|
+
metrics: this.metrics,
|
|
276
|
+
activeAlerts: activeAlerts.length,
|
|
277
|
+
criticalAlerts: criticalAlerts.length,
|
|
278
|
+
totalAlerts: this.alerts.length,
|
|
279
|
+
uptime: process.uptime() + 's',
|
|
280
|
+
};
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
/**
|
|
284
|
+
* Generate health report
|
|
285
|
+
*/
|
|
286
|
+
generateHealthReport() {
|
|
287
|
+
const health = this.getHealthStatus();
|
|
288
|
+
const recentAlerts = this.getAlertHistory(10);
|
|
289
|
+
const ruleStats = this.alertRules.map((rule) => ({
|
|
290
|
+
name: rule.name,
|
|
291
|
+
severity: rule.severity,
|
|
292
|
+
triggeredCount: rule.triggeredCount,
|
|
293
|
+
lastTriggered: rule.lastTriggered,
|
|
294
|
+
}));
|
|
295
|
+
|
|
296
|
+
return {
|
|
297
|
+
timestamp: new Date().toISOString(),
|
|
298
|
+
health,
|
|
299
|
+
recentAlerts,
|
|
300
|
+
ruleStatistics: ruleStats,
|
|
301
|
+
alertsSummary: {
|
|
302
|
+
total: this.alerts.length,
|
|
303
|
+
active: health.activeAlerts,
|
|
304
|
+
critical: health.criticalAlerts,
|
|
305
|
+
acknowledged: this.alerts.filter((a) => a.acknowledged).length,
|
|
306
|
+
},
|
|
307
|
+
};
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
/**
|
|
311
|
+
* Set metric threshold
|
|
312
|
+
*/
|
|
313
|
+
setThreshold(metric, level, value) {
|
|
314
|
+
const thresholdKey = `${metric}${level.charAt(0).toUpperCase() + level.slice(1)}`;
|
|
315
|
+
if (thresholdKey in this.thresholds) {
|
|
316
|
+
this.thresholds[thresholdKey] = value;
|
|
317
|
+
return true;
|
|
318
|
+
}
|
|
319
|
+
return false;
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
/**
|
|
323
|
+
* Get alert rules
|
|
324
|
+
*/
|
|
325
|
+
getAlertRules(enabled = null) {
|
|
326
|
+
let rules = this.alertRules;
|
|
327
|
+
|
|
328
|
+
if (enabled !== null) {
|
|
329
|
+
rules = rules.filter((r) => r.enabled === enabled);
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
return rules;
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
/**
|
|
336
|
+
* Enable/disable alert rule
|
|
337
|
+
*/
|
|
338
|
+
toggleAlertRule(ruleId, enabled) {
|
|
339
|
+
const rule = this.alertRules.find((r) => r.id === ruleId);
|
|
340
|
+
if (rule) {
|
|
341
|
+
rule.enabled = enabled;
|
|
342
|
+
return true;
|
|
343
|
+
}
|
|
344
|
+
return false;
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
/**
|
|
348
|
+
* Clear old alerts
|
|
349
|
+
*/
|
|
350
|
+
clearOldAlerts(daysOld = 30) {
|
|
351
|
+
const cutoffTime = Date.now() - daysOld * 24 * 60 * 60 * 1000;
|
|
352
|
+
const beforeCount = this.alerts.length;
|
|
353
|
+
|
|
354
|
+
this.alerts = this.alerts.filter(
|
|
355
|
+
(alert) => new Date(alert.timestamp).getTime() > cutoffTime,
|
|
356
|
+
);
|
|
357
|
+
|
|
358
|
+
const removed = beforeCount - this.alerts.length;
|
|
359
|
+
this.logger.info(`Cleared ${removed} alerts older than ${daysOld} days`);
|
|
360
|
+
|
|
361
|
+
return { removed, remaining: this.alerts.length };
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
/**
|
|
365
|
+
* Simulate monitoring scenario for testing
|
|
366
|
+
*/
|
|
367
|
+
async simulateMonitoring(duration = 5000, intervals = 10) {
|
|
368
|
+
const intervalMs = duration / intervals;
|
|
369
|
+
const results = [];
|
|
370
|
+
|
|
371
|
+
for (let i = 0; i < intervals; i++) {
|
|
372
|
+
await new Promise((resolve) => setTimeout(resolve, intervalMs));
|
|
373
|
+
|
|
374
|
+
// Simulate changing metrics
|
|
375
|
+
const memoryUsage = 50 + Math.random() * 50;
|
|
376
|
+
const errorRate = Math.random() * 12;
|
|
377
|
+
const activeConnections = Math.floor(Math.random() * 100);
|
|
378
|
+
|
|
379
|
+
const update = this.updateMetrics({
|
|
380
|
+
memoryUsage: Math.round(memoryUsage),
|
|
381
|
+
errorRate: Math.round(errorRate * 10) / 10,
|
|
382
|
+
activeConnections,
|
|
383
|
+
});
|
|
384
|
+
|
|
385
|
+
results.push({
|
|
386
|
+
timestamp: new Date().toISOString(),
|
|
387
|
+
metrics: update.current,
|
|
388
|
+
alerts: this.getActiveAlerts(),
|
|
389
|
+
});
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
return {
|
|
393
|
+
duration,
|
|
394
|
+
intervals,
|
|
395
|
+
results,
|
|
396
|
+
finalHealth: this.getHealthStatus(),
|
|
397
|
+
};
|
|
398
|
+
}
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
export default MonitoringService;
|