@serve.zone/dcrouter 13.12.0 → 13.14.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/dist_serve/bundle.js +809 -779
- package/dist_ts/00_commitinfo_data.js +1 -1
- package/dist_ts/classes.dcrouter.js +6 -5
- package/dist_ts/dns/manager.dns.d.ts +46 -8
- package/dist_ts/dns/manager.dns.js +189 -36
- package/dist_ts/monitoring/classes.metricsmanager.d.ts +26 -0
- package/dist_ts/monitoring/classes.metricsmanager.js +72 -2
- package/dist_ts/opsserver/handlers/config.handler.js +2 -2
- package/dist_ts/opsserver/handlers/domain.handler.js +14 -1
- package/dist_ts/opsserver/handlers/security.handler.js +27 -23
- package/dist_ts/opsserver/handlers/stats.handler.js +22 -3
- package/dist_ts_interfaces/data/stats.d.ts +17 -1
- package/dist_ts_interfaces/requests/domains.d.ts +24 -0
- package/dist_ts_web/00_commitinfo_data.js +1 -1
- package/dist_ts_web/appstate.d.ts +13 -0
- package/dist_ts_web/appstate.js +62 -54
- package/dist_ts_web/elements/domains/ops-view-domains.d.ts +1 -0
- package/dist_ts_web/elements/domains/ops-view-domains.js +95 -1
- package/dist_ts_web/elements/network/ops-view-network-activity.d.ts +2 -20
- package/dist_ts_web/elements/network/ops-view-network-activity.js +65 -115
- package/package.json +1 -1
- package/ts/00_commitinfo_data.ts +1 -1
- package/ts/classes.dcrouter.ts +5 -4
- package/ts/dns/manager.dns.ts +219 -35
- package/ts/monitoring/classes.metricsmanager.ts +77 -1
- package/ts/opsserver/handlers/config.handler.ts +1 -1
- package/ts/opsserver/handlers/domain.handler.ts +18 -0
- package/ts/opsserver/handlers/security.handler.ts +27 -23
- package/ts/opsserver/handlers/stats.handler.ts +22 -2
- package/ts_web/00_commitinfo_data.ts +1 -1
- package/ts_web/appstate.ts +74 -57
- package/ts_web/elements/domains/ops-view-domains.ts +97 -0
- package/ts_web/elements/network/ops-view-network-activity.ts +67 -132
package/ts_web/appstate.ts
CHANGED
|
@@ -52,7 +52,9 @@ export interface INetworkState {
|
|
|
52
52
|
throughputRate: { bytesInPerSecond: number; bytesOutPerSecond: number };
|
|
53
53
|
totalBytes: { in: number; out: number };
|
|
54
54
|
topIPs: Array<{ ip: string; count: number }>;
|
|
55
|
+
topIPsByBandwidth: Array<{ ip: string; count: number; bwIn: number; bwOut: number }>;
|
|
55
56
|
throughputByIP: Array<{ ip: string; in: number; out: number }>;
|
|
57
|
+
domainActivity: interfaces.data.IDomainActivity[];
|
|
56
58
|
throughputHistory: Array<{ timestamp: number; in: number; out: number }>;
|
|
57
59
|
requestsPerSecond: number;
|
|
58
60
|
requestsTotal: number;
|
|
@@ -160,7 +162,9 @@ export const networkStatePart = await appState.getStatePart<INetworkState>(
|
|
|
160
162
|
throughputRate: { bytesInPerSecond: 0, bytesOutPerSecond: 0 },
|
|
161
163
|
totalBytes: { in: 0, out: 0 },
|
|
162
164
|
topIPs: [],
|
|
165
|
+
topIPsByBandwidth: [],
|
|
163
166
|
throughputByIP: [],
|
|
167
|
+
domainActivity: [],
|
|
164
168
|
throughputHistory: [],
|
|
165
169
|
requestsPerSecond: 0,
|
|
166
170
|
requestsTotal: 0,
|
|
@@ -552,7 +556,9 @@ export const fetchNetworkStatsAction = networkStatePart.createAction(async (stat
|
|
|
552
556
|
? { in: networkStatsResponse.totalDataTransferred.bytesIn, out: networkStatsResponse.totalDataTransferred.bytesOut }
|
|
553
557
|
: { in: 0, out: 0 },
|
|
554
558
|
topIPs: networkStatsResponse.topIPs || [],
|
|
559
|
+
topIPsByBandwidth: networkStatsResponse.topIPsByBandwidth || [],
|
|
555
560
|
throughputByIP: networkStatsResponse.throughputByIP || [],
|
|
561
|
+
domainActivity: networkStatsResponse.domainActivity || [],
|
|
556
562
|
throughputHistory: networkStatsResponse.throughputHistory || [],
|
|
557
563
|
requestsPerSecond: networkStatsResponse.requestsPerSecond || 0,
|
|
558
564
|
requestsTotal: networkStatsResponse.requestsTotal || 0,
|
|
@@ -1887,6 +1893,32 @@ export const syncDomainAction = domainsStatePart.createAction<{ id: string }>(
|
|
|
1887
1893
|
},
|
|
1888
1894
|
);
|
|
1889
1895
|
|
|
1896
|
+
export const migrateDomainAction = domainsStatePart.createAction<{
|
|
1897
|
+
id: string;
|
|
1898
|
+
targetSource: interfaces.data.TDomainSource;
|
|
1899
|
+
targetProviderId?: string;
|
|
1900
|
+
deleteExistingProviderRecords?: boolean;
|
|
1901
|
+
}>(
|
|
1902
|
+
async (statePartArg, dataArg, actionContext): Promise<IDomainsState> => {
|
|
1903
|
+
const context = getActionContext();
|
|
1904
|
+
try {
|
|
1905
|
+
const request = new plugins.domtools.plugins.typedrequest.TypedRequest<
|
|
1906
|
+
interfaces.requests.IReq_MigrateDomain
|
|
1907
|
+
>('/typedrequest', 'migrateDomain');
|
|
1908
|
+
const response = await request.fire({ identity: context.identity!, ...dataArg });
|
|
1909
|
+
if (!response.success) {
|
|
1910
|
+
return { ...statePartArg.getState()!, error: response.message || 'Migration failed' };
|
|
1911
|
+
}
|
|
1912
|
+
return await actionContext!.dispatch(fetchDomainsAndProvidersAction, null);
|
|
1913
|
+
} catch (error: unknown) {
|
|
1914
|
+
return {
|
|
1915
|
+
...statePartArg.getState()!,
|
|
1916
|
+
error: error instanceof Error ? error.message : 'Migration failed',
|
|
1917
|
+
};
|
|
1918
|
+
}
|
|
1919
|
+
},
|
|
1920
|
+
);
|
|
1921
|
+
|
|
1890
1922
|
export const createDnsRecordAction = domainsStatePart.createAction<{
|
|
1891
1923
|
domainId: string;
|
|
1892
1924
|
name: string;
|
|
@@ -2623,67 +2655,52 @@ async function dispatchCombinedRefreshActionInner() {
|
|
|
2623
2655
|
if (combinedResponse.metrics.network && currentView === 'network') {
|
|
2624
2656
|
const network = combinedResponse.metrics.network;
|
|
2625
2657
|
const connectionsByIP: { [ip: string]: number } = {};
|
|
2626
|
-
|
|
2627
|
-
//
|
|
2658
|
+
|
|
2659
|
+
// Build connectionsByIP from connectionDetails (now populated with real per-IP data)
|
|
2628
2660
|
network.connectionDetails.forEach(conn => {
|
|
2629
2661
|
connectionsByIP[conn.remoteAddress] = (connectionsByIP[conn.remoteAddress] || 0) + 1;
|
|
2630
2662
|
});
|
|
2631
2663
|
|
|
2632
|
-
//
|
|
2633
|
-
|
|
2634
|
-
|
|
2635
|
-
|
|
2636
|
-
|
|
2637
|
-
|
|
2638
|
-
|
|
2639
|
-
|
|
2640
|
-
|
|
2641
|
-
|
|
2642
|
-
|
|
2643
|
-
|
|
2644
|
-
|
|
2645
|
-
|
|
2646
|
-
|
|
2647
|
-
|
|
2648
|
-
|
|
2649
|
-
|
|
2650
|
-
|
|
2651
|
-
|
|
2652
|
-
|
|
2653
|
-
|
|
2654
|
-
|
|
2655
|
-
|
|
2656
|
-
|
|
2657
|
-
|
|
2658
|
-
|
|
2659
|
-
|
|
2660
|
-
|
|
2661
|
-
|
|
2662
|
-
|
|
2663
|
-
|
|
2664
|
-
|
|
2665
|
-
|
|
2666
|
-
|
|
2667
|
-
|
|
2668
|
-
|
|
2669
|
-
|
|
2670
|
-
|
|
2671
|
-
|
|
2672
|
-
},
|
|
2673
|
-
totalBytes: network.totalBytes || { in: 0, out: 0 },
|
|
2674
|
-
topIPs: network.topEndpoints.map(e => ({ ip: e.endpoint, count: e.requests })),
|
|
2675
|
-
throughputByIP: network.topEndpoints.map(e => ({ ip: e.endpoint, in: e.bandwidth?.in || 0, out: e.bandwidth?.out || 0 })),
|
|
2676
|
-
throughputHistory: network.throughputHistory || [],
|
|
2677
|
-
requestsPerSecond: network.requestsPerSecond || 0,
|
|
2678
|
-
requestsTotal: network.requestsTotal || 0,
|
|
2679
|
-
backends: network.backends || [],
|
|
2680
|
-
frontendProtocols: network.frontendProtocols || null,
|
|
2681
|
-
backendProtocols: network.backendProtocols || null,
|
|
2682
|
-
lastUpdated: Date.now(),
|
|
2683
|
-
isLoading: false,
|
|
2684
|
-
error: null,
|
|
2685
|
-
});
|
|
2686
|
-
}
|
|
2664
|
+
// Build connections from connectionDetails (real per-IP aggregates)
|
|
2665
|
+
const connections: interfaces.data.IConnectionInfo[] = network.connectionDetails.map((conn, i) => ({
|
|
2666
|
+
id: `ip-${conn.remoteAddress}`,
|
|
2667
|
+
remoteAddress: conn.remoteAddress,
|
|
2668
|
+
localAddress: 'server',
|
|
2669
|
+
startTime: conn.startTime,
|
|
2670
|
+
protocol: conn.protocol as any,
|
|
2671
|
+
state: conn.state as any,
|
|
2672
|
+
bytesReceived: conn.bytesIn,
|
|
2673
|
+
bytesSent: conn.bytesOut,
|
|
2674
|
+
}));
|
|
2675
|
+
|
|
2676
|
+
networkStatePart.setState({
|
|
2677
|
+
...networkStatePart.getState()!,
|
|
2678
|
+
connections,
|
|
2679
|
+
connectionsByIP,
|
|
2680
|
+
throughputRate: {
|
|
2681
|
+
bytesInPerSecond: network.totalBandwidth.in,
|
|
2682
|
+
bytesOutPerSecond: network.totalBandwidth.out,
|
|
2683
|
+
},
|
|
2684
|
+
totalBytes: network.totalBytes || { in: 0, out: 0 },
|
|
2685
|
+
topIPs: network.topEndpoints.map(e => ({ ip: e.endpoint, count: e.connections })),
|
|
2686
|
+
topIPsByBandwidth: (network.topEndpointsByBandwidth || []).map(e => ({
|
|
2687
|
+
ip: e.endpoint,
|
|
2688
|
+
count: e.connections,
|
|
2689
|
+
bwIn: e.bandwidth?.in || 0,
|
|
2690
|
+
bwOut: e.bandwidth?.out || 0,
|
|
2691
|
+
})),
|
|
2692
|
+
throughputByIP: network.topEndpoints.map(e => ({ ip: e.endpoint, in: e.bandwidth?.in || 0, out: e.bandwidth?.out || 0 })),
|
|
2693
|
+
domainActivity: network.domainActivity || [],
|
|
2694
|
+
throughputHistory: network.throughputHistory || [],
|
|
2695
|
+
requestsPerSecond: network.requestsPerSecond || 0,
|
|
2696
|
+
requestsTotal: network.requestsTotal || 0,
|
|
2697
|
+
backends: network.backends || [],
|
|
2698
|
+
frontendProtocols: network.frontendProtocols || null,
|
|
2699
|
+
backendProtocols: network.backendProtocols || null,
|
|
2700
|
+
lastUpdated: Date.now(),
|
|
2701
|
+
isLoading: false,
|
|
2702
|
+
error: null,
|
|
2703
|
+
});
|
|
2687
2704
|
}
|
|
2688
2705
|
|
|
2689
2706
|
// Refresh certificate data if on Domains > Certificates subview
|
|
@@ -149,6 +149,15 @@ export class OpsViewDomains extends DeesElement {
|
|
|
149
149
|
});
|
|
150
150
|
},
|
|
151
151
|
},
|
|
152
|
+
{
|
|
153
|
+
name: 'Migrate',
|
|
154
|
+
iconName: 'lucide:arrow-right-left',
|
|
155
|
+
type: ['inRow', 'contextmenu'] as any,
|
|
156
|
+
actionFunc: async (actionData: any) => {
|
|
157
|
+
const domain = actionData.item as interfaces.data.IDomain;
|
|
158
|
+
await this.showMigrateDialog(domain);
|
|
159
|
+
},
|
|
160
|
+
},
|
|
152
161
|
{
|
|
153
162
|
name: 'Delete',
|
|
154
163
|
iconName: 'lucide:trash2',
|
|
@@ -308,6 +317,94 @@ export class OpsViewDomains extends DeesElement {
|
|
|
308
317
|
});
|
|
309
318
|
}
|
|
310
319
|
|
|
320
|
+
private async showMigrateDialog(domain: interfaces.data.IDomain) {
|
|
321
|
+
const { DeesModal, DeesToast } = await import('@design.estate/dees-catalog');
|
|
322
|
+
const providers = this.domainsState.providers;
|
|
323
|
+
|
|
324
|
+
// Build target options based on current source
|
|
325
|
+
const targetOptions: { option: string; key: string }[] = [];
|
|
326
|
+
for (const p of providers) {
|
|
327
|
+
// Skip current source
|
|
328
|
+
if (p.builtIn && domain.source === 'dcrouter') continue;
|
|
329
|
+
if (!p.builtIn && domain.source === 'provider' && domain.providerId === p.id) continue;
|
|
330
|
+
|
|
331
|
+
const label = p.builtIn ? 'DcRouter (self)' : `${p.name} (${p.type})`;
|
|
332
|
+
const key = p.builtIn ? 'dcrouter' : `provider:${p.id}`;
|
|
333
|
+
targetOptions.push({ option: label, key });
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
if (targetOptions.length === 0) {
|
|
337
|
+
DeesToast.show({
|
|
338
|
+
message: 'No migration targets available. Add a DNS provider first.',
|
|
339
|
+
type: 'warning',
|
|
340
|
+
duration: 3000,
|
|
341
|
+
});
|
|
342
|
+
return;
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
const currentLabel = domain.source === 'dcrouter'
|
|
346
|
+
? 'DcRouter (self)'
|
|
347
|
+
: providers.find((p) => p.id === domain.providerId)?.name || 'Provider';
|
|
348
|
+
|
|
349
|
+
DeesModal.createAndShow({
|
|
350
|
+
heading: `Migrate: ${domain.name}`,
|
|
351
|
+
content: html`
|
|
352
|
+
<dees-form>
|
|
353
|
+
<dees-input-text
|
|
354
|
+
.key=${'currentSource'}
|
|
355
|
+
.label=${'Current source'}
|
|
356
|
+
.value=${currentLabel}
|
|
357
|
+
.disabled=${true}
|
|
358
|
+
></dees-input-text>
|
|
359
|
+
<dees-input-dropdown
|
|
360
|
+
.key=${'target'}
|
|
361
|
+
.label=${'Migrate to'}
|
|
362
|
+
.description=${'Select the target DNS management'}
|
|
363
|
+
.options=${targetOptions}
|
|
364
|
+
.required=${true}
|
|
365
|
+
></dees-input-dropdown>
|
|
366
|
+
<dees-input-checkbox
|
|
367
|
+
.key=${'deleteExisting'}
|
|
368
|
+
.label=${'Delete existing records at provider first'}
|
|
369
|
+
.description=${'Removes all records at the provider before pushing migrated records'}
|
|
370
|
+
.value=${true}
|
|
371
|
+
></dees-input-checkbox>
|
|
372
|
+
</dees-form>
|
|
373
|
+
`,
|
|
374
|
+
menuOptions: [
|
|
375
|
+
{ name: 'Cancel', action: async (m: any) => m.destroy() },
|
|
376
|
+
{
|
|
377
|
+
name: 'Migrate',
|
|
378
|
+
action: async (m: any) => {
|
|
379
|
+
const form = m.shadowRoot?.querySelector('.content')?.querySelector('dees-form');
|
|
380
|
+
if (!form) return;
|
|
381
|
+
const data = await form.collectFormData();
|
|
382
|
+
const targetKey = typeof data.target === 'object' ? data.target.key : data.target;
|
|
383
|
+
if (!targetKey) return;
|
|
384
|
+
|
|
385
|
+
let targetSource: interfaces.data.TDomainSource;
|
|
386
|
+
let targetProviderId: string | undefined;
|
|
387
|
+
if (targetKey === 'dcrouter') {
|
|
388
|
+
targetSource = 'dcrouter';
|
|
389
|
+
} else {
|
|
390
|
+
targetSource = 'provider';
|
|
391
|
+
targetProviderId = targetKey.replace('provider:', '');
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
await appstate.domainsStatePart.dispatchAction(appstate.migrateDomainAction, {
|
|
395
|
+
id: domain.id,
|
|
396
|
+
targetSource,
|
|
397
|
+
targetProviderId,
|
|
398
|
+
deleteExistingProviderRecords: targetSource === 'provider' ? Boolean(data.deleteExisting) : false,
|
|
399
|
+
});
|
|
400
|
+
DeesToast.show({ message: `Domain ${domain.name} migrated successfully`, type: 'success', duration: 3000 });
|
|
401
|
+
m.destroy();
|
|
402
|
+
},
|
|
403
|
+
},
|
|
404
|
+
],
|
|
405
|
+
});
|
|
406
|
+
}
|
|
407
|
+
|
|
311
408
|
private async deleteDomain(domain: interfaces.data.IDomain) {
|
|
312
409
|
const { DeesModal } = await import('@design.estate/dees-catalog');
|
|
313
410
|
DeesModal.createAndShow({
|
|
@@ -10,22 +10,6 @@ declare global {
|
|
|
10
10
|
}
|
|
11
11
|
}
|
|
12
12
|
|
|
13
|
-
interface INetworkRequest {
|
|
14
|
-
id: string;
|
|
15
|
-
timestamp: number;
|
|
16
|
-
method: string;
|
|
17
|
-
url: string;
|
|
18
|
-
hostname: string;
|
|
19
|
-
port: number;
|
|
20
|
-
protocol: 'http' | 'https' | 'tcp' | 'udp';
|
|
21
|
-
statusCode?: number;
|
|
22
|
-
duration: number;
|
|
23
|
-
bytesIn: number;
|
|
24
|
-
bytesOut: number;
|
|
25
|
-
remoteIp: string;
|
|
26
|
-
route?: string;
|
|
27
|
-
}
|
|
28
|
-
|
|
29
13
|
@customElement('ops-view-network-activity')
|
|
30
14
|
export class OpsViewNetworkActivity extends DeesElement {
|
|
31
15
|
/** How far back the traffic chart shows */
|
|
@@ -42,9 +26,6 @@ export class OpsViewNetworkActivity extends DeesElement {
|
|
|
42
26
|
accessor networkState = appstate.networkStatePart.getState()!;
|
|
43
27
|
|
|
44
28
|
|
|
45
|
-
@state()
|
|
46
|
-
accessor networkRequests: INetworkRequest[] = [];
|
|
47
|
-
|
|
48
29
|
@state()
|
|
49
30
|
accessor trafficDataIn: Array<{ x: string | number; y: number }> = [];
|
|
50
31
|
|
|
@@ -314,108 +295,21 @@ export class OpsViewNetworkActivity extends DeesElement {
|
|
|
314
295
|
<!-- Protocol Distribution Charts -->
|
|
315
296
|
${this.renderProtocolCharts()}
|
|
316
297
|
|
|
317
|
-
<!-- Top IPs
|
|
298
|
+
<!-- Top IPs by Connection Count -->
|
|
318
299
|
${this.renderTopIPs()}
|
|
319
300
|
|
|
301
|
+
<!-- Top IPs by Bandwidth -->
|
|
302
|
+
${this.renderTopIPsByBandwidth()}
|
|
303
|
+
|
|
304
|
+
<!-- Domain Activity -->
|
|
305
|
+
${this.renderDomainActivity()}
|
|
306
|
+
|
|
320
307
|
<!-- Backend Protocols Section -->
|
|
321
308
|
${this.renderBackendProtocols()}
|
|
322
|
-
|
|
323
|
-
<!-- Requests Table -->
|
|
324
|
-
<dees-table
|
|
325
|
-
.data=${this.networkRequests}
|
|
326
|
-
.rowKey=${'id'}
|
|
327
|
-
.highlightUpdates=${'flash'}
|
|
328
|
-
.displayFunction=${(req: INetworkRequest) => ({
|
|
329
|
-
Time: new Date(req.timestamp).toLocaleTimeString(),
|
|
330
|
-
Protocol: html`<span class="protocolBadge ${req.protocol}">${req.protocol.toUpperCase()}</span>`,
|
|
331
|
-
Method: req.method,
|
|
332
|
-
'Host:Port': `${req.hostname}:${req.port}`,
|
|
333
|
-
Path: this.truncateUrl(req.url),
|
|
334
|
-
Status: this.renderStatus(req.statusCode),
|
|
335
|
-
Duration: `${req.duration}ms`,
|
|
336
|
-
'In/Out': `${this.formatBytes(req.bytesIn)} / ${this.formatBytes(req.bytesOut)}`,
|
|
337
|
-
'Remote IP': req.remoteIp,
|
|
338
|
-
})}
|
|
339
|
-
.dataActions=${[
|
|
340
|
-
{
|
|
341
|
-
name: 'View Details',
|
|
342
|
-
iconName: 'fa:magnifyingGlass',
|
|
343
|
-
type: ['inRow', 'doubleClick', 'contextmenu'],
|
|
344
|
-
actionFunc: async (actionData) => {
|
|
345
|
-
await this.showRequestDetails(actionData.item);
|
|
346
|
-
}
|
|
347
|
-
}
|
|
348
|
-
]}
|
|
349
|
-
heading1="Recent Network Activity"
|
|
350
|
-
heading2="Recent network requests"
|
|
351
|
-
searchable
|
|
352
|
-
.showColumnFilters=${true}
|
|
353
|
-
.pagination=${true}
|
|
354
|
-
.paginationSize=${50}
|
|
355
|
-
dataName="request"
|
|
356
|
-
></dees-table>
|
|
357
309
|
</div>
|
|
358
310
|
`;
|
|
359
311
|
}
|
|
360
312
|
|
|
361
|
-
private async showRequestDetails(request: INetworkRequest) {
|
|
362
|
-
const { DeesModal } = await import('@design.estate/dees-catalog');
|
|
363
|
-
|
|
364
|
-
await DeesModal.createAndShow({
|
|
365
|
-
heading: 'Request Details',
|
|
366
|
-
content: html`
|
|
367
|
-
<div style="padding: 20px;">
|
|
368
|
-
<dees-dataview-codebox
|
|
369
|
-
.heading=${'Request Information'}
|
|
370
|
-
progLang="json"
|
|
371
|
-
.codeToDisplay=${JSON.stringify({
|
|
372
|
-
id: request.id,
|
|
373
|
-
timestamp: new Date(request.timestamp).toISOString(),
|
|
374
|
-
protocol: request.protocol,
|
|
375
|
-
method: request.method,
|
|
376
|
-
url: request.url,
|
|
377
|
-
hostname: request.hostname,
|
|
378
|
-
port: request.port,
|
|
379
|
-
statusCode: request.statusCode,
|
|
380
|
-
duration: `${request.duration}ms`,
|
|
381
|
-
bytesIn: request.bytesIn,
|
|
382
|
-
bytesOut: request.bytesOut,
|
|
383
|
-
remoteIp: request.remoteIp,
|
|
384
|
-
route: request.route,
|
|
385
|
-
}, null, 2)}
|
|
386
|
-
></dees-dataview-codebox>
|
|
387
|
-
</div>
|
|
388
|
-
`,
|
|
389
|
-
menuOptions: [
|
|
390
|
-
{
|
|
391
|
-
name: 'Copy Request ID',
|
|
392
|
-
iconName: 'lucide:Copy',
|
|
393
|
-
action: async () => {
|
|
394
|
-
await navigator.clipboard.writeText(request.id);
|
|
395
|
-
}
|
|
396
|
-
}
|
|
397
|
-
]
|
|
398
|
-
});
|
|
399
|
-
}
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
private renderStatus(statusCode?: number): TemplateResult {
|
|
403
|
-
if (!statusCode) {
|
|
404
|
-
return html`<span class="statusBadge warning">N/A</span>`;
|
|
405
|
-
}
|
|
406
|
-
|
|
407
|
-
const statusClass = statusCode >= 200 && statusCode < 300 ? 'success' :
|
|
408
|
-
statusCode >= 400 ? 'error' : 'warning';
|
|
409
|
-
|
|
410
|
-
return html`<span class="statusBadge ${statusClass}">${statusCode}</span>`;
|
|
411
|
-
}
|
|
412
|
-
|
|
413
|
-
private truncateUrl(url: string, maxLength = 50): string {
|
|
414
|
-
if (url.length <= maxLength) return url;
|
|
415
|
-
return url.substring(0, maxLength - 3) + '...';
|
|
416
|
-
}
|
|
417
|
-
|
|
418
|
-
|
|
419
313
|
private formatNumber(num: number): string {
|
|
420
314
|
if (num >= 1000000) {
|
|
421
315
|
return (num / 1000000).toFixed(1) + 'M';
|
|
@@ -619,6 +513,66 @@ export class OpsViewNetworkActivity extends DeesElement {
|
|
|
619
513
|
`;
|
|
620
514
|
}
|
|
621
515
|
|
|
516
|
+
private renderTopIPsByBandwidth(): TemplateResult {
|
|
517
|
+
if (!this.networkState.topIPsByBandwidth || this.networkState.topIPsByBandwidth.length === 0) {
|
|
518
|
+
return html``;
|
|
519
|
+
}
|
|
520
|
+
|
|
521
|
+
return html`
|
|
522
|
+
<dees-table
|
|
523
|
+
.data=${this.networkState.topIPsByBandwidth}
|
|
524
|
+
.rowKey=${'ip'}
|
|
525
|
+
.highlightUpdates=${'flash'}
|
|
526
|
+
.displayFunction=${(ipData: { ip: string; count: number; bwIn: number; bwOut: number }) => {
|
|
527
|
+
return {
|
|
528
|
+
'IP Address': ipData.ip,
|
|
529
|
+
'Bandwidth In': this.formatBitsPerSecond(ipData.bwIn),
|
|
530
|
+
'Bandwidth Out': this.formatBitsPerSecond(ipData.bwOut),
|
|
531
|
+
'Total Bandwidth': this.formatBitsPerSecond(ipData.bwIn + ipData.bwOut),
|
|
532
|
+
'Connections': ipData.count,
|
|
533
|
+
};
|
|
534
|
+
}}
|
|
535
|
+
heading1="Top IPs by Bandwidth"
|
|
536
|
+
heading2="IPs with highest throughput"
|
|
537
|
+
searchable
|
|
538
|
+
.showColumnFilters=${true}
|
|
539
|
+
.pagination=${false}
|
|
540
|
+
dataName="ip"
|
|
541
|
+
></dees-table>
|
|
542
|
+
`;
|
|
543
|
+
}
|
|
544
|
+
|
|
545
|
+
private renderDomainActivity(): TemplateResult {
|
|
546
|
+
if (!this.networkState.domainActivity || this.networkState.domainActivity.length === 0) {
|
|
547
|
+
return html``;
|
|
548
|
+
}
|
|
549
|
+
|
|
550
|
+
return html`
|
|
551
|
+
<dees-table
|
|
552
|
+
.data=${this.networkState.domainActivity}
|
|
553
|
+
.rowKey=${'domain'}
|
|
554
|
+
.highlightUpdates=${'flash'}
|
|
555
|
+
.displayFunction=${(item: interfaces.data.IDomainActivity) => {
|
|
556
|
+
const totalBytesPerMin = (item.bytesInPerSecond + item.bytesOutPerSecond) * 60;
|
|
557
|
+
return {
|
|
558
|
+
'Domain': item.domain,
|
|
559
|
+
'Throughput In': this.formatBitsPerSecond(item.bytesInPerSecond),
|
|
560
|
+
'Throughput Out': this.formatBitsPerSecond(item.bytesOutPerSecond),
|
|
561
|
+
'Transferred / min': this.formatBytes(totalBytesPerMin),
|
|
562
|
+
'Connections': item.activeConnections,
|
|
563
|
+
'Routes': item.routeCount,
|
|
564
|
+
};
|
|
565
|
+
}}
|
|
566
|
+
heading1="Domain Activity"
|
|
567
|
+
heading2="Per-domain network activity aggregated from route metrics"
|
|
568
|
+
searchable
|
|
569
|
+
.showColumnFilters=${true}
|
|
570
|
+
.pagination=${false}
|
|
571
|
+
dataName="domain"
|
|
572
|
+
></dees-table>
|
|
573
|
+
`;
|
|
574
|
+
}
|
|
575
|
+
|
|
622
576
|
private renderBackendProtocols(): TemplateResult {
|
|
623
577
|
const backends = this.networkState.backends;
|
|
624
578
|
if (!backends || backends.length === 0) {
|
|
@@ -730,25 +684,6 @@ export class OpsViewNetworkActivity extends DeesElement {
|
|
|
730
684
|
this.requestsPerSecHistory.shift();
|
|
731
685
|
}
|
|
732
686
|
|
|
733
|
-
// Reassign unconditionally so dees-table's flash diff can compare per-cell
|
|
734
|
-
// values against the previous snapshot. Row identity is preserved via
|
|
735
|
-
// rowKey='id', so DOM nodes are reused across ticks.
|
|
736
|
-
this.networkRequests = this.networkState.connections.map((conn) => ({
|
|
737
|
-
id: conn.id,
|
|
738
|
-
timestamp: conn.startTime,
|
|
739
|
-
method: 'GET', // Default method for proxy connections
|
|
740
|
-
url: '/',
|
|
741
|
-
hostname: conn.remoteAddress,
|
|
742
|
-
port: conn.protocol === 'https' ? 443 : 80,
|
|
743
|
-
protocol: conn.protocol === 'https' || conn.protocol === 'http' ? conn.protocol : 'tcp',
|
|
744
|
-
statusCode: conn.state === 'connected' ? 200 : undefined,
|
|
745
|
-
duration: Date.now() - conn.startTime,
|
|
746
|
-
bytesIn: conn.bytesReceived,
|
|
747
|
-
bytesOut: conn.bytesSent,
|
|
748
|
-
remoteIp: conn.remoteAddress,
|
|
749
|
-
route: 'proxy',
|
|
750
|
-
}));
|
|
751
|
-
|
|
752
687
|
// Load server-side throughput history into chart (once)
|
|
753
688
|
if (!this.historyLoaded && this.networkState.throughputHistory && this.networkState.throughputHistory.length > 0) {
|
|
754
689
|
this.loadThroughputHistory();
|