@serve.zone/dcrouter 17.1.5 → 17.1.6
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/deno.json +1 -1
- package/dist_serve/bundle.js +559 -559
- package/dist_ts/00_commitinfo_data.js +1 -1
- package/dist_ts/classes.dcrouter.js +2 -2
- package/dist_ts/dns/manager.dns.js +23 -6
- package/dist_ts/email/classes.mail-dns-sync.d.ts +18 -0
- package/dist_ts/email/classes.mail-dns-sync.js +101 -2
- package/dist_ts/monitoring/classes.metricscache.d.ts +2 -0
- package/dist_ts/monitoring/classes.metricscache.js +28 -6
- package/dist_ts/monitoring/classes.metricsmanager.d.ts +10 -0
- package/dist_ts/monitoring/classes.metricsmanager.js +99 -31
- package/dist_ts/opsserver/handlers/security.handler.js +24 -6
- package/dist_ts/opsserver/handlers/stats.handler.js +2 -3
- package/dist_ts/remoteingress/classes.hub-lifecycle.js +2 -2
- package/dist_ts_interfaces/requests/stats.d.ts +7 -0
- package/dist_ts_migrations/index.js +277 -152
- package/dist_ts_web/00_commitinfo_data.js +1 -1
- package/dist_ts_web/appstate/network.js +11 -2
- package/dist_ts_web/appstate/ui.js +1 -8
- package/package.json +2 -2
- package/ts/00_commitinfo_data.ts +1 -1
- package/ts/classes.dcrouter.ts +3 -1
- package/ts/dns/manager.dns.ts +22 -5
- package/ts/email/classes.mail-dns-sync.ts +105 -1
- package/ts/monitoring/classes.metricscache.ts +36 -8
- package/ts/monitoring/classes.metricsmanager.ts +113 -32
- package/ts/opsserver/handlers/security.handler.ts +35 -6
- package/ts/opsserver/handlers/stats.handler.ts +1 -2
- package/ts/remoteingress/classes.hub-lifecycle.ts +3 -1
- package/ts_web/00_commitinfo_data.ts +1 -1
- package/ts_web/appstate/network.ts +12 -1
- package/ts_web/appstate/ui.ts +0 -9
|
@@ -5,11 +5,20 @@ import { SecurityLogger, SecurityEventType } from '../security/classes.securityl
|
|
|
5
5
|
import { logger } from '../logger.js';
|
|
6
6
|
import type { IAsnActivity } from '../../ts_interfaces/data/stats.js';
|
|
7
7
|
|
|
8
|
+
const NETWORK_STATS_CACHE_TTL_MS = 3_000;
|
|
9
|
+
const ASN_INTELLIGENCE_CACHE_TTL_MS = 60_000;
|
|
10
|
+
const ACTIVE_CONNECTION_SNAPSHOT_CACHE_TTL_MS = 3_000;
|
|
11
|
+
const OBSERVED_IP_SAMPLE_INTERVAL_MS = 60_000;
|
|
12
|
+
const MAX_ASN_CANDIDATE_IPS = 500;
|
|
13
|
+
const MAX_OBSERVED_IP_SAMPLE_SIZE = 20;
|
|
14
|
+
const MAX_SNAPSHOT_LIMIT = 1_000;
|
|
15
|
+
|
|
8
16
|
export class MetricsManager {
|
|
9
17
|
private metricsLogger: plugins.smartlog.Smartlog;
|
|
10
18
|
private smartMetrics: plugins.smartmetrics.SmartMetrics;
|
|
11
19
|
private dcRouter: DcRouter;
|
|
12
20
|
private resetInterval?: NodeJS.Timeout;
|
|
21
|
+
private observedIpSamplerInterval?: NodeJS.Timeout;
|
|
13
22
|
private metricsCache: MetricsCache;
|
|
14
23
|
|
|
15
24
|
// Constants
|
|
@@ -69,7 +78,7 @@ export class MetricsManager {
|
|
|
69
78
|
}
|
|
70
79
|
});
|
|
71
80
|
this.smartMetrics = new plugins.smartmetrics.SmartMetrics(this.metricsLogger, 'dcrouter');
|
|
72
|
-
//
|
|
81
|
+
// Individual metrics select an appropriate TTL where their collection cost differs.
|
|
73
82
|
this.metricsCache = new MetricsCache(500);
|
|
74
83
|
}
|
|
75
84
|
|
|
@@ -118,6 +127,11 @@ export class MetricsManager {
|
|
|
118
127
|
// Prune old time-series buckets every minute (don't wait for lazy query)
|
|
119
128
|
this.pruneOldBuckets();
|
|
120
129
|
}, 60000); // Check every minute
|
|
130
|
+
|
|
131
|
+
this.observedIpSamplerInterval = setInterval(() => {
|
|
132
|
+
this.sampleObservedIps();
|
|
133
|
+
}, OBSERVED_IP_SAMPLE_INTERVAL_MS);
|
|
134
|
+
this.observedIpSamplerInterval.unref();
|
|
121
135
|
|
|
122
136
|
logger.log('info', 'MetricsManager started');
|
|
123
137
|
}
|
|
@@ -128,6 +142,10 @@ export class MetricsManager {
|
|
|
128
142
|
clearInterval(this.resetInterval);
|
|
129
143
|
this.resetInterval = undefined;
|
|
130
144
|
}
|
|
145
|
+
if (this.observedIpSamplerInterval) {
|
|
146
|
+
clearInterval(this.observedIpSamplerInterval);
|
|
147
|
+
this.observedIpSamplerInterval = undefined;
|
|
148
|
+
}
|
|
131
149
|
|
|
132
150
|
this.smartMetrics.stop();
|
|
133
151
|
|
|
@@ -295,13 +313,35 @@ export class MetricsManager {
|
|
|
295
313
|
public async getActiveConnectionSnapshots(
|
|
296
314
|
options: plugins.smartproxy.IActiveConnectionSnapshotOptions = {},
|
|
297
315
|
): Promise<plugins.smartproxy.IActiveConnectionSnapshot[]> {
|
|
298
|
-
const
|
|
316
|
+
const normalizedOptions = {
|
|
317
|
+
...options,
|
|
318
|
+
limit: Math.min(Math.max(options.limit ?? MAX_SNAPSHOT_LIMIT, 1), MAX_SNAPSHOT_LIMIT),
|
|
319
|
+
};
|
|
320
|
+
const afterId = (normalizedOptions as typeof normalizedOptions & { afterId?: string }).afterId ?? '';
|
|
321
|
+
const cacheKey = `activeConnectionSnapshots:${normalizedOptions.limit}:${normalizedOptions.routeId ?? ''}:${afterId}`;
|
|
299
322
|
return await this.metricsCache.get<plugins.smartproxy.IActiveConnectionSnapshot[]>(cacheKey, async () => {
|
|
300
323
|
if (!this.dcRouter.smartProxy) {
|
|
301
324
|
return [];
|
|
302
325
|
}
|
|
303
|
-
return this.dcRouter.smartProxy.getActiveConnectionSnapshots(
|
|
304
|
-
},
|
|
326
|
+
return this.dcRouter.smartProxy.getActiveConnectionSnapshots(normalizedOptions);
|
|
327
|
+
}, ACTIVE_CONNECTION_SNAPSHOT_CACHE_TTL_MS);
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
public async getActiveConnectionSnapshotPage(
|
|
331
|
+
options: plugins.smartproxy.IActiveConnectionSnapshotOptions = {},
|
|
332
|
+
): Promise<plugins.smartproxy.IActiveConnectionSnapshotPage> {
|
|
333
|
+
const normalizedOptions = {
|
|
334
|
+
...options,
|
|
335
|
+
limit: Math.min(Math.max(options.limit ?? MAX_SNAPSHOT_LIMIT, 1), MAX_SNAPSHOT_LIMIT),
|
|
336
|
+
};
|
|
337
|
+
const afterId = normalizedOptions.afterId ?? '';
|
|
338
|
+
const cacheKey = `activeConnectionSnapshotPage:${normalizedOptions.limit}:${normalizedOptions.routeId ?? ''}:${afterId}`;
|
|
339
|
+
return await this.metricsCache.get<plugins.smartproxy.IActiveConnectionSnapshotPage>(cacheKey, async () => {
|
|
340
|
+
if (!this.dcRouter.smartProxy) {
|
|
341
|
+
return { snapshots: [], hasMore: false, nextCursor: null };
|
|
342
|
+
}
|
|
343
|
+
return this.dcRouter.smartProxy.getActiveConnectionSnapshotPage(normalizedOptions);
|
|
344
|
+
}, ACTIVE_CONNECTION_SNAPSHOT_CACHE_TTL_MS);
|
|
305
345
|
}
|
|
306
346
|
|
|
307
347
|
// Get connection info from SmartProxy
|
|
@@ -560,10 +600,59 @@ export class MetricsManager {
|
|
|
560
600
|
this.securityMetrics.incidents.shift();
|
|
561
601
|
}
|
|
562
602
|
}
|
|
603
|
+
|
|
604
|
+
/**
|
|
605
|
+
* Feed IP intelligence from maintained data-plane counters, independently of
|
|
606
|
+
* dashboard reads. Startup order is intentionally tolerated: until both
|
|
607
|
+
* SmartProxy and SecurityPolicyManager exist, the sample is a no-op.
|
|
608
|
+
*/
|
|
609
|
+
private sampleObservedIps(): void {
|
|
610
|
+
const smartProxy = this.dcRouter.smartProxy;
|
|
611
|
+
const securityPolicyManager = this.dcRouter.securityPolicyManager;
|
|
612
|
+
if (!smartProxy || !securityPolicyManager) {
|
|
613
|
+
return;
|
|
614
|
+
}
|
|
615
|
+
|
|
616
|
+
try {
|
|
617
|
+
const proxyMetrics = smartProxy.getMetrics();
|
|
618
|
+
const connectionsByIP = proxyMetrics.connections.byIP();
|
|
619
|
+
const throughputByIP = proxyMetrics.throughput.byIP();
|
|
620
|
+
const candidates = new Map<string, { connections: number; throughput: number }>();
|
|
621
|
+
|
|
622
|
+
for (const [ip, connections] of connectionsByIP) {
|
|
623
|
+
candidates.set(ip, { connections, throughput: 0 });
|
|
624
|
+
}
|
|
625
|
+
for (const [ip, throughput] of throughputByIP) {
|
|
626
|
+
const candidate = candidates.get(ip) || { connections: 0, throughput: 0 };
|
|
627
|
+
candidate.throughput = throughput.in + throughput.out;
|
|
628
|
+
candidates.set(ip, candidate);
|
|
629
|
+
}
|
|
630
|
+
for (const ip of proxyMetrics.connections.domainRequestsByIP().keys()) {
|
|
631
|
+
if (!candidates.has(ip)) {
|
|
632
|
+
candidates.set(ip, { connections: 0, throughput: 0 });
|
|
633
|
+
}
|
|
634
|
+
}
|
|
635
|
+
|
|
636
|
+
const observedIps = [...candidates.entries()]
|
|
637
|
+
.filter(([ip]) => Boolean(ip))
|
|
638
|
+
.sort((a, b) => (
|
|
639
|
+
b[1].connections - a[1].connections
|
|
640
|
+
|| b[1].throughput - a[1].throughput
|
|
641
|
+
|| a[0].localeCompare(b[0])
|
|
642
|
+
))
|
|
643
|
+
.slice(0, MAX_OBSERVED_IP_SAMPLE_SIZE)
|
|
644
|
+
.map(([ip]) => ip);
|
|
645
|
+
|
|
646
|
+
if (observedIps.length > 0) {
|
|
647
|
+
securityPolicyManager.queueObservedIps(observedIps);
|
|
648
|
+
}
|
|
649
|
+
} catch (error: unknown) {
|
|
650
|
+
logger.log('warn', `Observed-IP metrics sample failed: ${(error as Error).message}`);
|
|
651
|
+
}
|
|
652
|
+
}
|
|
563
653
|
|
|
564
654
|
// Get network metrics from SmartProxy
|
|
565
655
|
public async getNetworkStats() {
|
|
566
|
-
// Use shorter cache TTL for network stats to ensure real-time updates
|
|
567
656
|
return this.metricsCache.get('networkStats', async () => {
|
|
568
657
|
const smartProxy = this.dcRouter.smartProxy;
|
|
569
658
|
const proxyMetrics = smartProxy ? smartProxy.getMetrics() : null;
|
|
@@ -571,6 +660,7 @@ export class MetricsManager {
|
|
|
571
660
|
if (!proxyMetrics) {
|
|
572
661
|
return {
|
|
573
662
|
connectionsByIP: new Map<string, number>(),
|
|
663
|
+
activeConnections: 0,
|
|
574
664
|
throughputRate: { bytesInPerSecond: 0, bytesOutPerSecond: 0 },
|
|
575
665
|
topIPs: [] as Array<{ ip: string; count: number }>,
|
|
576
666
|
topIPsByBandwidth: [] as Array<{ ip: string; count: number; bwIn: number; bwOut: number }>,
|
|
@@ -587,21 +677,9 @@ export class MetricsManager {
|
|
|
587
677
|
};
|
|
588
678
|
}
|
|
589
679
|
|
|
590
|
-
const
|
|
591
|
-
|
|
592
|
-
const
|
|
593
|
-
const connectionsByRoute = new Map<string, number>();
|
|
594
|
-
const activeConnectionsByDomain = new Map<string, number>();
|
|
595
|
-
|
|
596
|
-
for (const snapshot of activeConnectionSnapshots) {
|
|
597
|
-
connectionsByIP.set(snapshot.sourceIp, (connectionsByIP.get(snapshot.sourceIp) || 0) + 1);
|
|
598
|
-
if (snapshot.routeId) {
|
|
599
|
-
connectionsByRoute.set(snapshot.routeId, (connectionsByRoute.get(snapshot.routeId) || 0) + 1);
|
|
600
|
-
}
|
|
601
|
-
if (snapshot.domain) {
|
|
602
|
-
activeConnectionsByDomain.set(snapshot.domain, (activeConnectionsByDomain.get(snapshot.domain) || 0) + 1);
|
|
603
|
-
}
|
|
604
|
-
}
|
|
680
|
+
const connectionsByIP = proxyMetrics.connections.byIP();
|
|
681
|
+
const connectionsByRoute = proxyMetrics.connections.byRoute();
|
|
682
|
+
const activeConnections = proxyMetrics.connections.active();
|
|
605
683
|
|
|
606
684
|
const instantThroughput = proxyMetrics.throughput.instant();
|
|
607
685
|
|
|
@@ -769,7 +847,6 @@ export class MetricsManager {
|
|
|
769
847
|
...topIPs.map((item) => item.ip),
|
|
770
848
|
...topIPsByBandwidth.map((item) => item.ip),
|
|
771
849
|
])];
|
|
772
|
-
this.dcRouter.securityPolicyManager?.queueObservedIps(observedIps);
|
|
773
850
|
|
|
774
851
|
const topASNs = await this.buildTopASNs(observedIps, allIPData);
|
|
775
852
|
|
|
@@ -808,10 +885,6 @@ export class MetricsManager {
|
|
|
808
885
|
for (const entry of protocolCache) {
|
|
809
886
|
if (entry.domain) allKnownDomains.add(entry.domain);
|
|
810
887
|
}
|
|
811
|
-
for (const snapshot of activeConnectionSnapshots) {
|
|
812
|
-
if (snapshot.domain) allKnownDomains.add(snapshot.domain);
|
|
813
|
-
}
|
|
814
|
-
|
|
815
888
|
// Build reverse map: concrete domain → canonical route key(s)
|
|
816
889
|
const domainToRoutes = new Map<string, string[]>();
|
|
817
890
|
for (const [routeKey, domains] of routeDomains) {
|
|
@@ -882,7 +955,7 @@ export class MetricsManager {
|
|
|
882
955
|
}
|
|
883
956
|
|
|
884
957
|
domainAgg.set(domain, {
|
|
885
|
-
activeConnections:
|
|
958
|
+
activeConnections: Math.round(totalConns),
|
|
886
959
|
bytesInPerSec: totalIn,
|
|
887
960
|
bytesOutPerSec: totalOut,
|
|
888
961
|
routeCount: routeKeys.length,
|
|
@@ -914,6 +987,7 @@ export class MetricsManager {
|
|
|
914
987
|
|
|
915
988
|
return {
|
|
916
989
|
connectionsByIP,
|
|
990
|
+
activeConnections,
|
|
917
991
|
throughputRate,
|
|
918
992
|
topIPs,
|
|
919
993
|
topIPsByBandwidth,
|
|
@@ -928,7 +1002,7 @@ export class MetricsManager {
|
|
|
928
1002
|
backendProtocols,
|
|
929
1003
|
domainActivity,
|
|
930
1004
|
};
|
|
931
|
-
},
|
|
1005
|
+
}, NETWORK_STATS_CACHE_TTL_MS);
|
|
932
1006
|
}
|
|
933
1007
|
|
|
934
1008
|
private async buildTopASNs(
|
|
@@ -936,14 +1010,21 @@ export class MetricsManager {
|
|
|
936
1010
|
allIPData: Map<string, { count: number; bwIn: number; bwOut: number }>,
|
|
937
1011
|
): Promise<IAsnActivity[]> {
|
|
938
1012
|
const manager = this.dcRouter.securityPolicyManager;
|
|
939
|
-
|
|
1013
|
+
const candidateIps = [...new Set(observedIps.filter(Boolean))]
|
|
1014
|
+
.sort()
|
|
1015
|
+
.slice(0, MAX_ASN_CANDIDATE_IPS);
|
|
1016
|
+
if (!manager || candidateIps.length === 0) {
|
|
940
1017
|
return [];
|
|
941
1018
|
}
|
|
942
1019
|
|
|
943
|
-
const intelligenceRecords = await
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
|
|
1020
|
+
const intelligenceRecords = await this.metricsCache.get(
|
|
1021
|
+
'networkAsnIntelligence',
|
|
1022
|
+
() => manager.listIpIntelligence({
|
|
1023
|
+
ipAddresses: candidateIps,
|
|
1024
|
+
limit: Math.max(100, candidateIps.length),
|
|
1025
|
+
}),
|
|
1026
|
+
ASN_INTELLIGENCE_CACHE_TTL_MS,
|
|
1027
|
+
);
|
|
947
1028
|
const asnActivity = new Map<number, IAsnActivity>();
|
|
948
1029
|
|
|
949
1030
|
for (const record of intelligenceRecords) {
|
|
@@ -45,7 +45,13 @@ export class SecurityHandler {
|
|
|
45
45
|
'getActiveConnections',
|
|
46
46
|
async (dataArg, toolsArg) => {
|
|
47
47
|
await requireOpsAuth(this.opsServerRef, dataArg, { scope: 'stats:read' });
|
|
48
|
-
const
|
|
48
|
+
const page = await this.getActiveConnections(
|
|
49
|
+
dataArg.protocol,
|
|
50
|
+
dataArg.state,
|
|
51
|
+
dataArg.limit,
|
|
52
|
+
dataArg.cursor,
|
|
53
|
+
);
|
|
54
|
+
const connectionInfos = page.connections;
|
|
49
55
|
const totalConnections = connectionInfos.reduce((sum, conn) => sum + (conn.connectionCount || 1), 0);
|
|
50
56
|
|
|
51
57
|
const summary = {
|
|
@@ -63,6 +69,7 @@ export class SecurityHandler {
|
|
|
63
69
|
return {
|
|
64
70
|
connections: connectionInfos,
|
|
65
71
|
summary,
|
|
72
|
+
pagination: page.pagination,
|
|
66
73
|
};
|
|
67
74
|
}
|
|
68
75
|
)
|
|
@@ -349,14 +356,27 @@ export class SecurityHandler {
|
|
|
349
356
|
|
|
350
357
|
private async getActiveConnections(
|
|
351
358
|
protocol?: 'http' | 'https' | 'smtp' | 'smtps',
|
|
352
|
-
state?: string
|
|
353
|
-
|
|
359
|
+
state?: string,
|
|
360
|
+
requestedLimit?: number,
|
|
361
|
+
cursor?: string,
|
|
362
|
+
): Promise<{
|
|
363
|
+
connections: interfaces.data.IConnectionInfo[];
|
|
364
|
+
pagination: { limit: number; hasMore: boolean; nextCursor?: string };
|
|
365
|
+
}> {
|
|
366
|
+
const limit = Math.min(Math.max(Math.floor(requestedLimit ?? 250), 1), 1_000);
|
|
354
367
|
const metricsManager = this.opsServerRef.dcRouterRef.metricsManager;
|
|
355
368
|
if (!metricsManager) {
|
|
356
|
-
return
|
|
369
|
+
return {
|
|
370
|
+
connections: [],
|
|
371
|
+
pagination: { limit, hasMore: false },
|
|
372
|
+
};
|
|
357
373
|
}
|
|
358
374
|
|
|
359
|
-
const
|
|
375
|
+
const page = await metricsManager.getActiveConnectionSnapshotPage({
|
|
376
|
+
limit,
|
|
377
|
+
afterId: cursor,
|
|
378
|
+
});
|
|
379
|
+
const snapshots = page.snapshots;
|
|
360
380
|
const connections = snapshots.map((snapshot): interfaces.data.IConnectionInfo => ({
|
|
361
381
|
id: String(snapshot.id),
|
|
362
382
|
remoteAddress: snapshot.sourcePort === null
|
|
@@ -372,7 +392,7 @@ export class SecurityHandler {
|
|
|
372
392
|
bytesSent: snapshot.bytesOut,
|
|
373
393
|
}));
|
|
374
394
|
|
|
375
|
-
|
|
395
|
+
const filteredConnections = connections.filter((connection) => {
|
|
376
396
|
if (protocol && connection.protocol !== protocol) {
|
|
377
397
|
return false;
|
|
378
398
|
}
|
|
@@ -381,6 +401,15 @@ export class SecurityHandler {
|
|
|
381
401
|
}
|
|
382
402
|
return true;
|
|
383
403
|
});
|
|
404
|
+
|
|
405
|
+
return {
|
|
406
|
+
connections: filteredConnections,
|
|
407
|
+
pagination: {
|
|
408
|
+
limit,
|
|
409
|
+
hasMore: page.hasMore,
|
|
410
|
+
nextCursor: page.nextCursor ?? undefined,
|
|
411
|
+
},
|
|
412
|
+
};
|
|
384
413
|
}
|
|
385
414
|
|
|
386
415
|
private mapSnapshotProtocol(
|
|
@@ -288,7 +288,6 @@ export class StatsHandler {
|
|
|
288
288
|
promises.push(
|
|
289
289
|
(async () => {
|
|
290
290
|
const stats = await this.opsServerRef.dcRouterRef.metricsManager!.getNetworkStats();
|
|
291
|
-
const serverStats = await this.collectServerStats();
|
|
292
291
|
|
|
293
292
|
// Build per-IP bandwidth lookup from throughputByIP
|
|
294
293
|
const ipBandwidth = new Map<string, { in: number; out: number }>();
|
|
@@ -322,7 +321,7 @@ export class StatsHandler {
|
|
|
322
321
|
in: stats.totalDataTransferred.bytesIn,
|
|
323
322
|
out: stats.totalDataTransferred.bytesOut,
|
|
324
323
|
},
|
|
325
|
-
activeConnections:
|
|
324
|
+
activeConnections: stats.activeConnections,
|
|
326
325
|
connectionDetails,
|
|
327
326
|
topEndpoints: stats.topIPs.map(ip => ({
|
|
328
327
|
endpoint: ip.ip,
|
|
@@ -223,7 +223,9 @@ export class RemoteIngressHubLifecycle {
|
|
|
223
223
|
tls: tlsConfig,
|
|
224
224
|
performance: manager.getHubPerformanceConfig(),
|
|
225
225
|
});
|
|
226
|
-
tunnelManager.setOnTopologyChanged((reason) =>
|
|
226
|
+
tunnelManager.setOnTopologyChanged((reason) => (
|
|
227
|
+
this.dcRouterRef.mailDnsSync.requestEdgeEligibilityCheck(reason)
|
|
228
|
+
));
|
|
227
229
|
try {
|
|
228
230
|
await tunnelManager.start();
|
|
229
231
|
} catch (err) {
|
|
@@ -4,6 +4,9 @@ import { appState } from './shared.js';
|
|
|
4
4
|
import { getActionContext } from './login.js';
|
|
5
5
|
import { runBackgroundRefresh } from './shared.js';
|
|
6
6
|
|
|
7
|
+
const IP_INTELLIGENCE_REFRESH_INTERVAL_MS = 60_000;
|
|
8
|
+
let lastIpIntelligenceRefreshAt = 0;
|
|
9
|
+
|
|
7
10
|
export interface INetworkState {
|
|
8
11
|
connections: interfaces.data.IConnectionInfo[];
|
|
9
12
|
connectionsByIP: { [ip: string]: number };
|
|
@@ -53,9 +56,17 @@ export const networkStatePart = await appState.getStatePart<INetworkState>(
|
|
|
53
56
|
);
|
|
54
57
|
|
|
55
58
|
export function refreshNetworkIpIntelligence(identity: interfaces.data.IIdentity, ipAddresses: string[]): void {
|
|
56
|
-
const ips = [...new Set(ipAddresses.filter(Boolean))]
|
|
59
|
+
const ips = [...new Set(ipAddresses.map((ip) => ip.trim()).filter(Boolean))]
|
|
60
|
+
.sort()
|
|
61
|
+
.slice(0, 100);
|
|
57
62
|
if (ips.length === 0) return;
|
|
58
63
|
|
|
64
|
+
const now = Date.now();
|
|
65
|
+
if (now - lastIpIntelligenceRefreshAt < IP_INTELLIGENCE_REFRESH_INTERVAL_MS) {
|
|
66
|
+
return;
|
|
67
|
+
}
|
|
68
|
+
lastIpIntelligenceRefreshAt = now;
|
|
69
|
+
|
|
59
70
|
runBackgroundRefresh('networkIpIntelligence', 'IP intelligence refresh failed:', async () => {
|
|
60
71
|
const intelligenceRequest = new plugins.domtools.plugins.typedrequest.TypedRequest<
|
|
61
72
|
interfaces.requests.IReq_ListIpIntelligence
|
package/ts_web/appstate/ui.ts
CHANGED
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import { appState } from './shared.js';
|
|
2
|
-
import { networkStatePart, fetchNetworkStatsAction } from './network.js';
|
|
3
2
|
import { certificateStatePart, fetchCertificateOverviewAction } from './certificates.js';
|
|
4
3
|
|
|
5
4
|
export interface IUiState {
|
|
@@ -52,13 +51,6 @@ export const toggleAutoRefreshAction = uiStatePart.createAction(async (statePart
|
|
|
52
51
|
export const setActiveViewAction = uiStatePart.createAction<string>(async (statePartArg, viewName): Promise<IUiState> => {
|
|
53
52
|
const currentState = statePartArg.getState()!;
|
|
54
53
|
|
|
55
|
-
// If switching to network view, ensure we fetch network data
|
|
56
|
-
if (viewName === 'network' && currentState.activeView !== 'network') {
|
|
57
|
-
setTimeout(() => {
|
|
58
|
-
networkStatePart.dispatchAction(fetchNetworkStatsAction, null);
|
|
59
|
-
}, 100);
|
|
60
|
-
}
|
|
61
|
-
|
|
62
54
|
// If switching to the Domains group, ensure we fetch certificate data
|
|
63
55
|
// (Certificates is a subview of Domains).
|
|
64
56
|
if (viewName === 'domains' && currentState.activeView !== 'domains') {
|
|
@@ -72,4 +64,3 @@ export const setActiveViewAction = uiStatePart.createAction<string>(async (state
|
|
|
72
64
|
activeView: viewName,
|
|
73
65
|
};
|
|
74
66
|
});
|
|
75
|
-
|