@serve.zone/dcrouter 7.0.1 → 7.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.
@@ -291,6 +291,10 @@ let OpsViewSecurity = (() => {
291
291
  renderOverview(metrics) {
292
292
  const threatLevel = this.calculateThreatLevel(metrics);
293
293
  const threatScore = this.getThreatScore(metrics);
294
+ // Derive active sessions from recent successful auth events (last hour)
295
+ const allEvents = metrics.recentEvents || [];
296
+ const oneHourAgo = Date.now() - 3600000;
297
+ const recentAuthSuccesses = allEvents.filter((evt) => evt.type === 'authentication' && evt.success === true && evt.timestamp >= oneHourAgo).length;
294
298
  const tiles = [
295
299
  {
296
300
  id: 'threatLevel',
@@ -312,7 +316,7 @@ let OpsViewSecurity = (() => {
312
316
  {
313
317
  id: 'blockedThreats',
314
318
  title: 'Blocked Threats',
315
- value: metrics.blockedIPs.length + metrics.spamDetected,
319
+ value: (metrics.blockedIPs?.length || 0) + metrics.spamDetected,
316
320
  type: 'number',
317
321
  icon: 'lucide:ShieldCheck',
318
322
  color: '#ef4444',
@@ -321,11 +325,11 @@ let OpsViewSecurity = (() => {
321
325
  {
322
326
  id: 'activeSessions',
323
327
  title: 'Active Sessions',
324
- value: 0,
328
+ value: recentAuthSuccesses,
325
329
  type: 'number',
326
330
  icon: 'lucide:Users',
327
331
  color: '#22c55e',
328
- description: 'Current authenticated sessions',
332
+ description: 'Authenticated in last hour',
329
333
  },
330
334
  {
331
335
  id: 'authFailures',
@@ -387,6 +391,10 @@ let OpsViewSecurity = (() => {
387
391
  `;
388
392
  }
389
393
  renderAuthentication(metrics) {
394
+ // Derive auth events from recentEvents
395
+ const allEvents = metrics.recentEvents || [];
396
+ const authEvents = allEvents.filter((evt) => evt.type === 'authentication');
397
+ const successfulLogins = authEvents.filter((evt) => evt.success === true).length;
390
398
  const tiles = [
391
399
  {
392
400
  id: 'authFailures',
@@ -400,13 +408,21 @@ let OpsViewSecurity = (() => {
400
408
  {
401
409
  id: 'successfulLogins',
402
410
  title: 'Successful Logins',
403
- value: 0,
411
+ value: successfulLogins,
404
412
  type: 'number',
405
413
  icon: 'lucide:Lock',
406
414
  color: '#22c55e',
407
415
  description: 'Successful logins today',
408
416
  },
409
417
  ];
418
+ // Map auth events to login history table data
419
+ const loginHistory = authEvents.map((evt) => ({
420
+ timestamp: evt.timestamp,
421
+ username: evt.details?.username || 'unknown',
422
+ ipAddress: evt.ipAddress || 'unknown',
423
+ success: evt.success ?? false,
424
+ reason: evt.success ? '' : evt.message || 'Authentication failed',
425
+ }));
410
426
  return html `
411
427
  <dees-statsgrid
412
428
  .tiles=${tiles}
@@ -417,7 +433,7 @@ let OpsViewSecurity = (() => {
417
433
  <dees-table
418
434
  .heading1=${'Login History'}
419
435
  .heading2=${'Recent authentication attempts'}
420
- .data=${[]}
436
+ .data=${loginHistory}
421
437
  .displayFunction=${(item) => ({
422
438
  'Time': new Date(item.timestamp).toLocaleString(),
423
439
  'Username': item.username,
@@ -518,45 +534,35 @@ let OpsViewSecurity = (() => {
518
534
  getThreatScore(metrics) {
519
535
  // Simple scoring algorithm
520
536
  let score = 100;
521
- score -= metrics.blockedIPs.length * 2;
522
- score -= metrics.authenticationFailures * 1;
523
- score -= metrics.spamDetected * 0.5;
524
- score -= metrics.malwareDetected * 3;
525
- score -= metrics.phishingDetected * 3;
526
- score -= metrics.suspiciousActivities * 2;
537
+ const blockedCount = Array.isArray(metrics.blockedIPs) ? metrics.blockedIPs.length : (metrics.blockedIPs || 0);
538
+ score -= blockedCount * 2;
539
+ score -= (metrics.authenticationFailures || 0) * 1;
540
+ score -= (metrics.spamDetected || 0) * 0.5;
541
+ score -= (metrics.malwareDetected || 0) * 3;
542
+ score -= (metrics.phishingDetected || 0) * 3;
543
+ score -= (metrics.suspiciousActivities || 0) * 2;
527
544
  return Math.max(0, Math.min(100, Math.round(score)));
528
545
  }
529
546
  getSecurityEvents(metrics) {
530
- // Mock data - in real implementation, this would come from the server
531
- return [
532
- {
533
- timestamp: Date.now() - 1000 * 60 * 5,
534
- event: 'Multiple failed login attempts',
535
- severity: 'warning',
536
- details: 'IP: 192.168.1.100',
537
- },
538
- {
539
- timestamp: Date.now() - 1000 * 60 * 15,
540
- event: 'SPF check failed',
541
- severity: 'medium',
542
- details: 'Domain: example.com',
543
- },
544
- {
545
- timestamp: Date.now() - 1000 * 60 * 30,
546
- event: 'IP blocked due to spam',
547
- severity: 'high',
548
- details: 'IP: 10.0.0.1',
549
- },
550
- ];
547
+ const events = metrics.recentEvents || [];
548
+ return events.map((evt) => ({
549
+ timestamp: evt.timestamp,
550
+ event: evt.message,
551
+ severity: evt.level === 'critical' ? 'critical' : evt.level === 'error' ? 'high' : evt.level === 'warn' ? 'warning' : 'info',
552
+ details: evt.ipAddress ? `IP: ${evt.ipAddress}` : evt.domain ? `Domain: ${evt.domain}` : evt.type,
553
+ }));
551
554
  }
552
555
  async clearBlockedIPs() {
553
- console.log('Clear blocked IPs');
556
+ // SmartProxy manages IP blocking — not yet exposed via API
557
+ alert('Clearing blocked IPs is not yet supported from the UI.');
554
558
  }
555
559
  async unblockIP(ip) {
556
- console.log('Unblock IP:', ip);
560
+ // SmartProxy manages IP blocking — not yet exposed via API
561
+ alert(`Unblocking IP ${ip} is not yet supported from the UI.`);
557
562
  }
558
563
  async saveEmailSecuritySettings() {
559
- console.log('Save email security settings');
564
+ // Config is read-only from the UI for now
565
+ alert('Email security settings are read-only. Update the dcrouter configuration file to change these settings.');
560
566
  }
561
567
  static {
562
568
  __runInitializers(_classThis, _classExtraInitializers);
@@ -565,4 +571,4 @@ let OpsViewSecurity = (() => {
565
571
  return OpsViewSecurity = _classThis;
566
572
  })();
567
573
  export { OpsViewSecurity };
568
- //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoib3BzLXZpZXctc2VjdXJpdHkuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi90c193ZWIvZWxlbWVudHMvb3BzLXZpZXctc2VjdXJpdHkudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6Ijs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7OztBQUFBLE9BQU8sS0FBSyxPQUFPLE1BQU0sZUFBZSxDQUFDO0FBQ3pDLE9BQU8sS0FBSyxNQUFNLE1BQU0sbUJBQW1CLENBQUM7QUFDNUMsT0FBTyxLQUFLLFFBQVEsTUFBTSxnQkFBZ0IsQ0FBQztBQUUzQyxPQUFPLEVBQ0wsV0FBVyxFQUNYLGFBQWEsRUFDYixJQUFJLEVBQ0osS0FBSyxFQUNMLEdBQUcsRUFDSCxVQUFVLEdBQ1gsTUFBTSw2QkFBNkIsQ0FBQztBQUNyQyxPQUFPLEVBQW1CLE1BQU0sNkJBQTZCLENBQUM7SUFHakQsZUFBZTs0QkFEM0IsYUFBYSxDQUFDLG1CQUFtQixDQUFDOzs7O3NCQUNFLFdBQVc7Ozs7Ozs7K0JBQW5CLFNBQVEsV0FBVzs7OztzQ0FDN0MsS0FBSyxFQUFFO3VDQVdQLEtBQUssRUFBRTtZQVZSLG1MQUFTLFVBQVUsNkJBQVYsVUFBVSwrRkFRakI7WUFHRixzTEFBUyxXQUFXLDZCQUFYLFdBQVcsaUdBQTRFO1lBYmxHLDZLQWtnQkM7Ozs7UUFoZ0JDLGlGQUE0QztZQUMxQyxXQUFXLEVBQUUsSUFBSTtZQUNqQixVQUFVLEVBQUUsSUFBSTtZQUNoQixRQUFRLEVBQUUsSUFBSTtZQUNkLGVBQWUsRUFBRSxJQUFJO1lBQ3JCLFdBQVcsRUFBRSxDQUFDO1lBQ2QsU0FBUyxFQUFFLEtBQUs7WUFDaEIsS0FBSyxFQUFFLElBQUk7U0FDWixFQUFDO1FBUkYsSUFBUyxVQUFVLGdEQVFqQjtRQVJGLElBQVMsVUFBVSxzREFRakI7UUFHRiw0SUFBcUYsVUFBVSxHQUFDO1FBQWhHLElBQVMsV0FBVyxpREFBNEU7UUFBaEcsSUFBUyxXQUFXLHVEQUE0RTtRQUVoRztZQUNF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
574
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoib3BzLXZpZXctc2VjdXJpdHkuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi90c193ZWIvZWxlbWVudHMvb3BzLXZpZXctc2VjdXJpdHkudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6Ijs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7OztBQUFBLE9BQU8sS0FBSyxPQUFPLE1BQU0sZUFBZSxDQUFDO0FBQ3pDLE9BQU8sS0FBSyxNQUFNLE1BQU0sbUJBQW1CLENBQUM7QUFDNUMsT0FBTyxLQUFLLFFBQVEsTUFBTSxnQkFBZ0IsQ0FBQztBQUUzQyxPQUFPLEVBQ0wsV0FBVyxFQUNYLGFBQWEsRUFDYixJQUFJLEVBQ0osS0FBSyxFQUNMLEdBQUcsRUFDSCxVQUFVLEdBQ1gsTUFBTSw2QkFBNkIsQ0FBQztBQUNyQyxPQUFPLEVBQW1CLE1BQU0sNkJBQTZCLENBQUM7SUFHakQsZUFBZTs0QkFEM0IsYUFBYSxDQUFDLG1CQUFtQixDQUFDOzs7O3NCQUNFLFdBQVc7Ozs7Ozs7K0JBQW5CLFNBQVEsV0FBVzs7OztzQ0FDN0MsS0FBSyxFQUFFO3VDQVdQLEtBQUssRUFBRTtZQVZSLG1MQUFTLFVBQVUsNkJBQVYsVUFBVSwrRkFRakI7WUFHRixzTEFBUyxXQUFXLDZCQUFYLFdBQVcsaUdBQTRFO1lBYmxHLDZLQTZnQkM7Ozs7UUEzZ0JDLGlGQUE0QztZQUMxQyxXQUFXLEVBQUUsSUFBSTtZQUNqQixVQUFVLEVBQUUsSUFBSTtZQUNoQixRQUFRLEVBQUUsSUFBSTtZQUNkLGVBQWUsRUFBRSxJQUFJO1lBQ3JCLFdBQVcsRUFBRSxDQUFDO1lBQ2QsU0FBUyxFQUFFLEtBQUs7WUFDaEIsS0FBSyxFQUFFLElBQUk7U0FDWixFQUFDO1FBUkYsSUFBUyxVQUFVLGdEQVFqQjtRQVJGLElBQVMsVUFBVSxzREFRakI7UUFHRiw0SUFBcUYsVUFBVSxHQUFDO1FBQWhHLElBQVMsV0FBVyxpREFBNEU7UUFBaEcsSUFBUyxXQUFXLHVEQUE0RTtRQUVoRztZQUNF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
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@serve.zone/dcrouter",
3
3
  "private": false,
4
- "version": "7.0.1",
4
+ "version": "7.2.0",
5
5
  "description": "A multifaceted routing service handling mail and SMS delivery functions.",
6
6
  "type": "module",
7
7
  "exports": {
@@ -42,7 +42,7 @@
42
42
  "@push.rocks/smartfile": "^13.1.2",
43
43
  "@push.rocks/smartguard": "^3.1.0",
44
44
  "@push.rocks/smartjwt": "^2.2.1",
45
- "@push.rocks/smartlog": "^3.1.11",
45
+ "@push.rocks/smartlog": "^3.2.1",
46
46
  "@push.rocks/smartmetrics": "^3.0.1",
47
47
  "@push.rocks/smartmongo": "^5.1.0",
48
48
  "@push.rocks/smartmta": "^5.2.2",
@@ -3,6 +3,6 @@
3
3
  */
4
4
  export const commitinfo = {
5
5
  name: '@serve.zone/dcrouter',
6
- version: '7.0.1',
6
+ version: '7.2.0',
7
7
  description: 'A multifaceted routing service handling mail and SMS delivery functions.'
8
8
  }
@@ -1055,7 +1055,25 @@ export class DcRouter {
1055
1055
 
1056
1056
  // Start the server
1057
1057
  await this.emailServer.start();
1058
-
1058
+
1059
+ // Wire delivery events to MetricsManager for time-series tracking
1060
+ if (this.metricsManager && this.emailServer.deliverySystem) {
1061
+ this.emailServer.deliverySystem.on('deliveryStart', (item: any) => {
1062
+ this.metricsManager.trackEmailReceived(item?.from);
1063
+ });
1064
+ this.emailServer.deliverySystem.on('deliverySuccess', (item: any) => {
1065
+ this.metricsManager.trackEmailSent(item?.to);
1066
+ });
1067
+ this.emailServer.deliverySystem.on('deliveryFailed', (item: any, error: any) => {
1068
+ this.metricsManager.trackEmailFailed(item?.to, error?.message);
1069
+ });
1070
+ }
1071
+ if (this.metricsManager && this.emailServer) {
1072
+ this.emailServer.on('bounceProcessed', () => {
1073
+ this.metricsManager.trackEmailBounced();
1074
+ });
1075
+ }
1076
+
1059
1077
  logger.log('info', `Email server started on ports: ${emailConfig.ports.join(', ')}`);
1060
1078
  }
1061
1079
 
package/ts/logger.ts CHANGED
@@ -1,5 +1,6 @@
1
1
  import * as plugins from './plugins.js';
2
2
  import { randomUUID } from 'node:crypto';
3
+ import { SmartlogDestinationBuffer } from '@push.rocks/smartlog/destination-buffer';
3
4
 
4
5
  // Map NODE_ENV to valid TEnvironment
5
6
  const nodeEnv = process.env.NODE_ENV || 'production';
@@ -10,6 +11,9 @@ const envMap: Record<string, 'local' | 'test' | 'staging' | 'production'> = {
10
11
  'production': 'production'
11
12
  };
12
13
 
14
+ // In-memory log buffer for the OpsServer UI
15
+ export const logBuffer = new SmartlogDestinationBuffer({ maxEntries: 2000 });
16
+
13
17
  // Default Smartlog instance
14
18
  const baseLogger = new plugins.smartlog.Smartlog({
15
19
  logContext: {
@@ -19,6 +23,9 @@ const baseLogger = new plugins.smartlog.Smartlog({
19
23
  }
20
24
  });
21
25
 
26
+ // Wire the buffer destination so all logs are captured
27
+ baseLogger.addLogDestination(logBuffer);
28
+
22
29
  // Extended logger compatible with the original enhanced logger API
23
30
  class StandardLogger {
24
31
  private defaultContext: Record<string, any> = {};
@@ -1,6 +1,7 @@
1
1
  import * as plugins from '../plugins.js';
2
2
  import { DcRouter } from '../classes.dcrouter.js';
3
3
  import { MetricsCache } from './classes.metricscache.js';
4
+ import { SecurityLogger, SecurityEventType } from '../security/classes.securitylogger.js';
4
5
 
5
6
  export class MetricsManager {
6
7
  private logger: plugins.smartlog.Smartlog;
@@ -37,6 +38,10 @@ export class MetricsManager {
37
38
  responseTimes: [] as number[], // Track response times in ms
38
39
  };
39
40
 
41
+ // Per-minute time-series buckets for charts
42
+ private emailMinuteBuckets = new Map<number, { sent: number; received: number; failed: number }>();
43
+ private dnsMinuteBuckets = new Map<number, { queries: number }>();
44
+
40
45
  // Track security-specific metrics
41
46
  private securityMetrics = {
42
47
  blockedIPs: 0,
@@ -227,20 +232,45 @@ export class MetricsManager {
227
232
  });
228
233
  }
229
234
 
235
+ /**
236
+ * Sync security metrics from the SecurityLogger singleton (last 24h).
237
+ * Called before returning security stats so counters reflect real events.
238
+ */
239
+ private syncFromSecurityLogger(): void {
240
+ try {
241
+ const securityLogger = SecurityLogger.getInstance();
242
+ const summary = securityLogger.getEventsSummary(86400000); // last 24h
243
+
244
+ this.securityMetrics.spamDetected = summary.byType[SecurityEventType.SPAM] || 0;
245
+ this.securityMetrics.malwareDetected = summary.byType[SecurityEventType.MALWARE] || 0;
246
+ this.securityMetrics.phishingDetected = summary.byType[SecurityEventType.DMARC] || 0; // phishing via DMARC
247
+ this.securityMetrics.authFailures =
248
+ summary.byType[SecurityEventType.AUTHENTICATION] || 0;
249
+ this.securityMetrics.blockedIPs =
250
+ (summary.byType[SecurityEventType.IP_REPUTATION] || 0) +
251
+ (summary.byType[SecurityEventType.REJECTED_CONNECTION] || 0);
252
+ } catch {
253
+ // SecurityLogger may not be initialized yet — ignore
254
+ }
255
+ }
256
+
230
257
  // Get security metrics
231
258
  public async getSecurityStats() {
232
259
  return this.metricsCache.get('securityStats', () => {
260
+ // Sync counters from the real SecurityLogger events
261
+ this.syncFromSecurityLogger();
262
+
233
263
  // Get recent incidents (last 20)
234
264
  const recentIncidents = this.securityMetrics.incidents.slice(-20);
235
-
265
+
236
266
  return {
237
267
  blockedIPs: this.securityMetrics.blockedIPs,
238
268
  authFailures: this.securityMetrics.authFailures,
239
269
  spamDetected: this.securityMetrics.spamDetected,
240
270
  malwareDetected: this.securityMetrics.malwareDetected,
241
271
  phishingDetected: this.securityMetrics.phishingDetected,
242
- totalThreatsBlocked: this.securityMetrics.spamDetected +
243
- this.securityMetrics.malwareDetected +
272
+ totalThreatsBlocked: this.securityMetrics.spamDetected +
273
+ this.securityMetrics.malwareDetected +
244
274
  this.securityMetrics.phishingDetected,
245
275
  recentIncidents,
246
276
  };
@@ -275,6 +305,7 @@ export class MetricsManager {
275
305
  // Email event tracking methods
276
306
  public trackEmailSent(recipient?: string, deliveryTimeMs?: number): void {
277
307
  this.emailMetrics.sentToday++;
308
+ this.incrementEmailBucket('sent');
278
309
 
279
310
  if (recipient) {
280
311
  const count = this.emailMetrics.recipients.get(recipient) || 0;
@@ -311,6 +342,7 @@ export class MetricsManager {
311
342
 
312
343
  public trackEmailReceived(sender?: string): void {
313
344
  this.emailMetrics.receivedToday++;
345
+ this.incrementEmailBucket('received');
314
346
 
315
347
  this.emailMetrics.recentActivity.push({
316
348
  timestamp: Date.now(),
@@ -326,6 +358,7 @@ export class MetricsManager {
326
358
 
327
359
  public trackEmailFailed(recipient?: string, reason?: string): void {
328
360
  this.emailMetrics.failedToday++;
361
+ this.incrementEmailBucket('failed');
329
362
 
330
363
  this.emailMetrics.recentActivity.push({
331
364
  timestamp: Date.now(),
@@ -361,6 +394,7 @@ export class MetricsManager {
361
394
  // DNS event tracking methods
362
395
  public trackDnsQuery(queryType: string, domain: string, cacheHit: boolean, responseTimeMs?: number): void {
363
396
  this.dnsMetrics.totalQueries++;
397
+ this.incrementDnsBucket();
364
398
 
365
399
  if (cacheHit) {
366
400
  this.dnsMetrics.cacheHits++;
@@ -547,4 +581,90 @@ export class MetricsManager {
547
581
  };
548
582
  }, 200); // Use 200ms cache for more frequent updates
549
583
  }
584
+
585
+ // --- Time-series helpers ---
586
+
587
+ private static minuteKey(ts: number = Date.now()): number {
588
+ return Math.floor(ts / 60000) * 60000;
589
+ }
590
+
591
+ private incrementEmailBucket(field: 'sent' | 'received' | 'failed'): void {
592
+ const key = MetricsManager.minuteKey();
593
+ let bucket = this.emailMinuteBuckets.get(key);
594
+ if (!bucket) {
595
+ bucket = { sent: 0, received: 0, failed: 0 };
596
+ this.emailMinuteBuckets.set(key, bucket);
597
+ }
598
+ bucket[field]++;
599
+ }
600
+
601
+ private incrementDnsBucket(): void {
602
+ const key = MetricsManager.minuteKey();
603
+ let bucket = this.dnsMinuteBuckets.get(key);
604
+ if (!bucket) {
605
+ bucket = { queries: 0 };
606
+ this.dnsMinuteBuckets.set(key, bucket);
607
+ }
608
+ bucket.queries++;
609
+ }
610
+
611
+ private pruneOldBuckets(): void {
612
+ const cutoff = Date.now() - 86400000; // 24h
613
+ for (const key of this.emailMinuteBuckets.keys()) {
614
+ if (key < cutoff) this.emailMinuteBuckets.delete(key);
615
+ }
616
+ for (const key of this.dnsMinuteBuckets.keys()) {
617
+ if (key < cutoff) this.dnsMinuteBuckets.delete(key);
618
+ }
619
+ }
620
+
621
+ /**
622
+ * Get email time-series data for the last N hours, aggregated per minute.
623
+ */
624
+ public getEmailTimeSeries(hours: number = 24): {
625
+ sent: Array<{ timestamp: number; value: number }>;
626
+ received: Array<{ timestamp: number; value: number }>;
627
+ failed: Array<{ timestamp: number; value: number }>;
628
+ } {
629
+ this.pruneOldBuckets();
630
+ const cutoff = Date.now() - hours * 3600000;
631
+ const sent: Array<{ timestamp: number; value: number }> = [];
632
+ const received: Array<{ timestamp: number; value: number }> = [];
633
+ const failed: Array<{ timestamp: number; value: number }> = [];
634
+
635
+ const sortedKeys = Array.from(this.emailMinuteBuckets.keys())
636
+ .filter((k) => k >= cutoff)
637
+ .sort((a, b) => a - b);
638
+
639
+ for (const key of sortedKeys) {
640
+ const bucket = this.emailMinuteBuckets.get(key)!;
641
+ sent.push({ timestamp: key, value: bucket.sent });
642
+ received.push({ timestamp: key, value: bucket.received });
643
+ failed.push({ timestamp: key, value: bucket.failed });
644
+ }
645
+
646
+ return { sent, received, failed };
647
+ }
648
+
649
+ /**
650
+ * Get DNS time-series data for the last N hours, aggregated per minute.
651
+ */
652
+ public getDnsTimeSeries(hours: number = 24): {
653
+ queries: Array<{ timestamp: number; value: number }>;
654
+ } {
655
+ this.pruneOldBuckets();
656
+ const cutoff = Date.now() - hours * 3600000;
657
+ const queries: Array<{ timestamp: number; value: number }> = [];
658
+
659
+ const sortedKeys = Array.from(this.dnsMinuteBuckets.keys())
660
+ .filter((k) => k >= cutoff)
661
+ .sort((a, b) => a - b);
662
+
663
+ for (const key of sortedKeys) {
664
+ const bucket = this.dnsMinuteBuckets.get(key)!;
665
+ queries.push({ timestamp: key, value: bucket.queries });
666
+ }
667
+
668
+ return { queries };
669
+ }
550
670
  }
@@ -1,6 +1,7 @@
1
1
  import * as plugins from '../../plugins.js';
2
2
  import type { OpsServer } from '../classes.opsserver.js';
3
3
  import * as interfaces from '../../../ts_interfaces/index.js';
4
+ import { logBuffer } from '../../logger.js';
4
5
 
5
6
  export class LogsHandler {
6
7
  public typedrouter = new plugins.typedrequest.TypedRouter();
@@ -64,6 +65,32 @@ export class LogsHandler {
64
65
  );
65
66
  }
66
67
 
68
+ private static mapLogLevel(smartlogLevel: string): 'debug' | 'info' | 'warn' | 'error' {
69
+ switch (smartlogLevel) {
70
+ case 'silly':
71
+ case 'debug':
72
+ return 'debug';
73
+ case 'warn':
74
+ return 'warn';
75
+ case 'error':
76
+ return 'error';
77
+ default:
78
+ return 'info';
79
+ }
80
+ }
81
+
82
+ private static deriveCategory(
83
+ zone?: string,
84
+ message?: string
85
+ ): 'smtp' | 'dns' | 'security' | 'system' | 'email' {
86
+ const msg = (message || '').toLowerCase();
87
+ if (msg.includes('[security:') || msg.includes('security')) return 'security';
88
+ if (zone === 'email' || msg.includes('email') || msg.includes('smtp') || msg.includes('mta')) return 'email';
89
+ if (zone === 'dns' || msg.includes('dns')) return 'dns';
90
+ if (msg.includes('smtp')) return 'smtp';
91
+ return 'system';
92
+ }
93
+
67
94
  private async getRecentLogs(
68
95
  level?: 'error' | 'warn' | 'info' | 'debug',
69
96
  category?: 'smtp' | 'dns' | 'security' | 'system' | 'email',
@@ -78,42 +105,64 @@ export class LogsHandler {
78
105
  message: string;
79
106
  metadata?: any;
80
107
  }>> {
81
- // TODO: Implement actual log retrieval from storage or logger
82
- // For now, return mock data
83
- const mockLogs: Array<{
108
+ // Compute a timestamp cutoff from timeRange
109
+ let since: number | undefined;
110
+ if (timeRange) {
111
+ const rangeMs: Record<string, number> = {
112
+ '1h': 3600000,
113
+ '6h': 21600000,
114
+ '24h': 86400000,
115
+ '7d': 604800000,
116
+ '30d': 2592000000,
117
+ };
118
+ since = Date.now() - (rangeMs[timeRange] || 86400000);
119
+ }
120
+
121
+ // Map the UI level to smartlog levels for filtering
122
+ const smartlogLevels: string[] | undefined = level
123
+ ? level === 'debug'
124
+ ? ['debug', 'silly']
125
+ : level === 'info'
126
+ ? ['info', 'ok', 'success', 'note', 'lifecycle']
127
+ : [level]
128
+ : undefined;
129
+
130
+ // Fetch a larger batch from buffer, then apply category filter client-side
131
+ const rawEntries = logBuffer.getEntries({
132
+ level: smartlogLevels as any,
133
+ search,
134
+ since,
135
+ limit: limit * 3, // over-fetch to compensate for category filtering
136
+ offset: 0,
137
+ });
138
+
139
+ // Map ILogPackage → UI log format and apply category filter
140
+ const mapped: Array<{
84
141
  timestamp: number;
85
142
  level: 'debug' | 'info' | 'warn' | 'error';
86
143
  category: 'smtp' | 'dns' | 'security' | 'system' | 'email';
87
144
  message: string;
88
145
  metadata?: any;
89
146
  }> = [];
90
-
91
- const categories: Array<'smtp' | 'dns' | 'security' | 'system' | 'email'> = ['smtp', 'dns', 'security', 'system', 'email'];
92
- const levels: Array<'debug' | 'info' | 'warn' | 'error'> = ['info', 'warn', 'error', 'debug'];
93
- const now = Date.now();
94
-
95
- // Generate some mock log entries
96
- for (let i = 0; i < 50; i++) {
97
- const mockCategory = categories[Math.floor(Math.random() * categories.length)];
98
- const mockLevel = levels[Math.floor(Math.random() * levels.length)];
99
-
100
- // Filter by requested criteria
101
- if (level && mockLevel !== level) continue;
102
- if (category && mockCategory !== category) continue;
103
-
104
- mockLogs.push({
105
- timestamp: now - (i * 60000), // 1 minute apart
106
- level: mockLevel,
107
- category: mockCategory,
108
- message: `Sample log message ${i} from ${mockCategory}`,
109
- metadata: {
110
- requestId: plugins.uuid.v4(),
111
- },
147
+
148
+ for (const pkg of rawEntries) {
149
+ const uiLevel = LogsHandler.mapLogLevel(pkg.level);
150
+ const uiCategory = LogsHandler.deriveCategory(pkg.context?.zone, pkg.message);
151
+
152
+ if (category && uiCategory !== category) continue;
153
+
154
+ mapped.push({
155
+ timestamp: pkg.timestamp,
156
+ level: uiLevel,
157
+ category: uiCategory,
158
+ message: pkg.message,
159
+ metadata: pkg.data,
112
160
  });
161
+
162
+ if (mapped.length >= limit) break;
113
163
  }
114
-
115
- // Apply pagination
116
- return mockLogs.slice(offset, offset + limit);
164
+
165
+ return mapped;
117
166
  }
118
167
 
119
168
  private setupLogStream(