@serve.zone/dcrouter 13.23.0 → 13.25.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 +952 -792
- package/dist_ts/00_commitinfo_data.js +1 -1
- package/dist_ts/db/documents/classes.ip-intelligence.doc.d.ts +1 -0
- package/dist_ts/db/documents/classes.ip-intelligence.doc.js +8 -2
- package/dist_ts/opsserver/handlers/security.handler.js +22 -1
- package/dist_ts/security/classes.security-policy-manager.d.ts +15 -4
- package/dist_ts/security/classes.security-policy-manager.js +108 -11
- package/dist_ts_interfaces/requests/security-policy.d.ts +32 -1
- package/dist_ts_web/00_commitinfo_data.js +1 -1
- package/dist_ts_web/appstate.d.ts +28 -0
- package/dist_ts_web/appstate.js +171 -4
- package/dist_ts_web/elements/network/ops-view-network-activity.d.ts +9 -0
- package/dist_ts_web/elements/network/ops-view-network-activity.js +210 -3
- package/dist_ts_web/elements/security/ops-view-security-blocked.d.ts +12 -3
- package/dist_ts_web/elements/security/ops-view-security-blocked.js +407 -52
- package/package.json +2 -2
- package/ts/00_commitinfo_data.ts +1 -1
- package/ts/db/documents/classes.ip-intelligence.doc.ts +3 -0
- package/ts/opsserver/handlers/security.handler.ts +38 -0
- package/ts/security/classes.security-policy-manager.ts +119 -12
- package/ts_web/00_commitinfo_data.ts +1 -1
- package/ts_web/appstate.ts +236 -3
- package/ts_web/elements/network/ops-view-network-activity.ts +219 -2
- package/ts_web/elements/security/ops-view-security-blocked.ts +414 -51
|
@@ -255,6 +255,17 @@ export class OpsViewNetworkActivity extends DeesElement {
|
|
|
255
255
|
color: ${cssManager.bdTheme('#f57c00', '#ff9933')};
|
|
256
256
|
}
|
|
257
257
|
|
|
258
|
+
.intelligenceBadge {
|
|
259
|
+
display: inline-flex;
|
|
260
|
+
align-items: center;
|
|
261
|
+
padding: 4px 8px;
|
|
262
|
+
border-radius: 999px;
|
|
263
|
+
font-size: 12px;
|
|
264
|
+
font-weight: 500;
|
|
265
|
+
background: ${cssManager.bdTheme('#eef2ff', '#1e1b4b')};
|
|
266
|
+
color: ${cssManager.bdTheme('#4338ca', '#a5b4fc')};
|
|
267
|
+
}
|
|
268
|
+
|
|
258
269
|
.protocolChartGrid {
|
|
259
270
|
display: grid;
|
|
260
271
|
grid-template-columns: repeat(2, 1fr);
|
|
@@ -345,6 +356,100 @@ export class OpsViewNetworkActivity extends DeesElement {
|
|
|
345
356
|
return `${size.toFixed(1)} ${units[unitIndex]}`;
|
|
346
357
|
}
|
|
347
358
|
|
|
359
|
+
private formatOptional(value: unknown): string {
|
|
360
|
+
if (value === null || value === undefined || value === '') return '-';
|
|
361
|
+
return String(value);
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
private formatDateTime(timestamp?: number | null): string {
|
|
365
|
+
return timestamp ? new Date(timestamp).toLocaleString() : '-';
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
private getIpIntelligence(ip: string): interfaces.data.IIpIntelligenceRecord | undefined {
|
|
369
|
+
return this.networkState.ipIntelligence?.find((record) => record.ipAddress === ip);
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
private getIpOrganization(record?: interfaces.data.IIpIntelligenceRecord): string {
|
|
373
|
+
return record?.asnOrg || record?.registrantOrg || '';
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
private getIpIntelligenceColumns(ip: string): Record<string, unknown> {
|
|
377
|
+
const record = this.getIpIntelligence(ip);
|
|
378
|
+
const organization = this.getIpOrganization(record);
|
|
379
|
+
return {
|
|
380
|
+
'Intelligence': record
|
|
381
|
+
? html`<span class="intelligenceBadge">${this.formatOptional(organization || record.countryCode || 'Known')}</span>`
|
|
382
|
+
: html`<span class="statusBadge warning">Enriching...</span>`,
|
|
383
|
+
'ASN': record?.asn ? `AS${record.asn}` : '-',
|
|
384
|
+
'Organization': this.formatOptional(organization),
|
|
385
|
+
'Country': this.formatOptional(record?.countryCode || record?.country),
|
|
386
|
+
'Network Range': this.formatOptional(record?.networkRange),
|
|
387
|
+
'Last Seen': this.formatDateTime(record?.lastSeenAt),
|
|
388
|
+
};
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
private getIpDataActions() {
|
|
392
|
+
return [
|
|
393
|
+
{
|
|
394
|
+
name: 'Refresh Intelligence',
|
|
395
|
+
iconName: 'lucide:refresh-cw',
|
|
396
|
+
type: ['inRow', 'contextmenu'] as any,
|
|
397
|
+
actionFunc: async (actionData: any) => {
|
|
398
|
+
const ip = actionData.item.ip;
|
|
399
|
+
await appstate.securityPolicyStatePart.dispatchAction(appstate.refreshIpIntelligenceAction, ip);
|
|
400
|
+
await appstate.networkStatePart.dispatchAction(appstate.fetchNetworkStatsAction, null);
|
|
401
|
+
},
|
|
402
|
+
},
|
|
403
|
+
{
|
|
404
|
+
name: 'Block IP',
|
|
405
|
+
iconName: 'lucide:shield-ban',
|
|
406
|
+
type: ['inRow', 'contextmenu'] as any,
|
|
407
|
+
actionFunc: async (actionData: any) => {
|
|
408
|
+
await this.createBlockRuleDialog('ip', actionData.item.ip, 'Blocked from Network Activity');
|
|
409
|
+
},
|
|
410
|
+
},
|
|
411
|
+
{
|
|
412
|
+
name: 'Block Network Range',
|
|
413
|
+
iconName: 'lucide:network',
|
|
414
|
+
type: ['contextmenu'] as any,
|
|
415
|
+
actionRelevancyCheckFunc: (actionData: any) => Boolean(this.getIpIntelligence(actionData.item.ip)?.networkRange),
|
|
416
|
+
actionFunc: async (actionData: any) => {
|
|
417
|
+
const record = this.getIpIntelligence(actionData.item.ip);
|
|
418
|
+
await this.createBlockRuleDialog('cidr', record!.networkRange!, 'Blocked network range from Network Activity');
|
|
419
|
+
},
|
|
420
|
+
},
|
|
421
|
+
{
|
|
422
|
+
name: 'Block ASN',
|
|
423
|
+
iconName: 'lucide:radio-tower',
|
|
424
|
+
type: ['contextmenu'] as any,
|
|
425
|
+
actionRelevancyCheckFunc: (actionData: any) => Boolean(this.getIpIntelligence(actionData.item.ip)?.asn),
|
|
426
|
+
actionFunc: async (actionData: any) => {
|
|
427
|
+
const record = this.getIpIntelligence(actionData.item.ip);
|
|
428
|
+
await this.createBlockRuleDialog('asn', String(record!.asn), 'Blocked ASN from Network Activity');
|
|
429
|
+
},
|
|
430
|
+
},
|
|
431
|
+
{
|
|
432
|
+
name: 'Block Organization',
|
|
433
|
+
iconName: 'lucide:building-2',
|
|
434
|
+
type: ['contextmenu'] as any,
|
|
435
|
+
actionRelevancyCheckFunc: (actionData: any) => Boolean(this.getIpOrganization(this.getIpIntelligence(actionData.item.ip))),
|
|
436
|
+
actionFunc: async (actionData: any) => {
|
|
437
|
+
const record = this.getIpIntelligence(actionData.item.ip);
|
|
438
|
+
await this.createBlockRuleDialog('organization', this.getIpOrganization(record), 'Blocked organization from Network Activity');
|
|
439
|
+
},
|
|
440
|
+
},
|
|
441
|
+
{
|
|
442
|
+
name: 'View Intelligence',
|
|
443
|
+
iconName: 'lucide:info',
|
|
444
|
+
type: ['doubleClick', 'contextmenu'] as any,
|
|
445
|
+
actionRelevancyCheckFunc: (actionData: any) => Boolean(this.getIpIntelligence(actionData.item.ip)),
|
|
446
|
+
actionFunc: async (actionData: any) => {
|
|
447
|
+
await this.showIpIntelligenceDetails(actionData.item.ip);
|
|
448
|
+
},
|
|
449
|
+
},
|
|
450
|
+
];
|
|
451
|
+
}
|
|
452
|
+
|
|
348
453
|
private calculateThroughput(): { in: number; out: number } {
|
|
349
454
|
// Use real throughput data from network state
|
|
350
455
|
return {
|
|
@@ -500,10 +605,12 @@ export class OpsViewNetworkActivity extends DeesElement {
|
|
|
500
605
|
'Bandwidth In': bw ? this.formatBitsPerSecond(bw.in) : '0 bit/s',
|
|
501
606
|
'Bandwidth Out': bw ? this.formatBitsPerSecond(bw.out) : '0 bit/s',
|
|
502
607
|
'Share': totalConnections > 0 ? ((ipData.count / totalConnections) * 100).toFixed(1) + '%' : '0%',
|
|
608
|
+
...this.getIpIntelligenceColumns(ipData.ip),
|
|
503
609
|
};
|
|
504
610
|
}}
|
|
611
|
+
.dataActions=${this.getIpDataActions()}
|
|
505
612
|
heading1="Top Connected IPs"
|
|
506
|
-
heading2="IPs with most active connections and
|
|
613
|
+
heading2="IPs with most active connections, bandwidth, and intelligence"
|
|
507
614
|
searchable
|
|
508
615
|
.showColumnFilters=${true}
|
|
509
616
|
.pagination=${false}
|
|
@@ -529,10 +636,12 @@ export class OpsViewNetworkActivity extends DeesElement {
|
|
|
529
636
|
'Bandwidth Out': this.formatBitsPerSecond(ipData.bwOut),
|
|
530
637
|
'Total Bandwidth': this.formatBitsPerSecond(ipData.bwIn + ipData.bwOut),
|
|
531
638
|
'Connections': ipData.count,
|
|
639
|
+
...this.getIpIntelligenceColumns(ipData.ip),
|
|
532
640
|
};
|
|
533
641
|
}}
|
|
642
|
+
.dataActions=${this.getIpDataActions()}
|
|
534
643
|
heading1="Top IPs by Bandwidth"
|
|
535
|
-
heading2="IPs with highest throughput"
|
|
644
|
+
heading2="IPs with highest throughput and intelligence"
|
|
536
645
|
searchable
|
|
537
646
|
.showColumnFilters=${true}
|
|
538
647
|
.pagination=${false}
|
|
@@ -678,6 +787,114 @@ export class OpsViewNetworkActivity extends DeesElement {
|
|
|
678
787
|
});
|
|
679
788
|
}
|
|
680
789
|
|
|
790
|
+
private getDropdownKey(value: any): string {
|
|
791
|
+
return typeof value === 'string' ? value : value?.key || '';
|
|
792
|
+
}
|
|
793
|
+
|
|
794
|
+
private async createBlockRuleDialog(
|
|
795
|
+
type: interfaces.data.TSecurityBlockRuleType,
|
|
796
|
+
value: string,
|
|
797
|
+
reason: string,
|
|
798
|
+
): Promise<void> {
|
|
799
|
+
const { DeesModal } = await import('@design.estate/dees-catalog');
|
|
800
|
+
const typeOptions = [
|
|
801
|
+
{ key: 'ip', option: 'IP address' },
|
|
802
|
+
{ key: 'cidr', option: 'CIDR / network range' },
|
|
803
|
+
{ key: 'asn', option: 'ASN' },
|
|
804
|
+
{ key: 'organization', option: 'Organization' },
|
|
805
|
+
];
|
|
806
|
+
const matchModeOptions = [
|
|
807
|
+
{ key: 'contains', option: 'Organization contains value' },
|
|
808
|
+
{ key: 'exact', option: 'Organization exactly matches value' },
|
|
809
|
+
];
|
|
810
|
+
|
|
811
|
+
await DeesModal.createAndShow({
|
|
812
|
+
heading: 'Create Security Block Rule',
|
|
813
|
+
content: html`
|
|
814
|
+
<dees-form>
|
|
815
|
+
<dees-input-dropdown
|
|
816
|
+
.key=${'type'}
|
|
817
|
+
.label=${'Rule Type'}
|
|
818
|
+
.options=${typeOptions}
|
|
819
|
+
.selectedOption=${typeOptions.find((option) => option.key === type)}
|
|
820
|
+
></dees-input-dropdown>
|
|
821
|
+
<dees-input-text .key=${'value'} .label=${'Value'} .value=${value} .required=${true}></dees-input-text>
|
|
822
|
+
<dees-input-dropdown
|
|
823
|
+
.key=${'matchMode'}
|
|
824
|
+
.label=${'Organization Match Mode'}
|
|
825
|
+
.description=${'Only used for organization rules'}
|
|
826
|
+
.options=${matchModeOptions}
|
|
827
|
+
.selectedOption=${matchModeOptions[0]}
|
|
828
|
+
></dees-input-dropdown>
|
|
829
|
+
<dees-input-text .key=${'reason'} .label=${'Reason'} .value=${reason}></dees-input-text>
|
|
830
|
+
<dees-input-checkbox .key=${'enabled'} .label=${'Enable immediately'} .value=${true}></dees-input-checkbox>
|
|
831
|
+
</dees-form>
|
|
832
|
+
`,
|
|
833
|
+
menuOptions: [
|
|
834
|
+
{ name: 'Cancel', iconName: 'lucide:x', action: async (modalArg: any) => modalArg.destroy() },
|
|
835
|
+
{
|
|
836
|
+
name: 'Create',
|
|
837
|
+
iconName: 'lucide:shield-ban',
|
|
838
|
+
action: async (modalArg: any) => {
|
|
839
|
+
const form = modalArg.shadowRoot?.querySelector('.content')?.querySelector('dees-form');
|
|
840
|
+
if (!form) return;
|
|
841
|
+
const data = await form.collectFormData();
|
|
842
|
+
const selectedType = this.getDropdownKey(data.type) as interfaces.data.TSecurityBlockRuleType;
|
|
843
|
+
const selectedValue = String(data.value || '').trim();
|
|
844
|
+
if (!selectedType || !selectedValue) return;
|
|
845
|
+
const matchMode = selectedType === 'organization'
|
|
846
|
+
? this.getDropdownKey(data.matchMode) as interfaces.data.TSecurityBlockRuleMatchMode
|
|
847
|
+
: undefined;
|
|
848
|
+
await appstate.securityPolicyStatePart.dispatchAction(appstate.createSecurityBlockRuleAction, {
|
|
849
|
+
type: selectedType,
|
|
850
|
+
value: selectedValue,
|
|
851
|
+
matchMode,
|
|
852
|
+
reason: String(data.reason || '').trim() || undefined,
|
|
853
|
+
enabled: data.enabled !== false,
|
|
854
|
+
});
|
|
855
|
+
await appstate.networkStatePart.dispatchAction(appstate.fetchNetworkStatsAction, null);
|
|
856
|
+
await modalArg.destroy();
|
|
857
|
+
},
|
|
858
|
+
},
|
|
859
|
+
],
|
|
860
|
+
});
|
|
861
|
+
}
|
|
862
|
+
|
|
863
|
+
private async showIpIntelligenceDetails(ip: string): Promise<void> {
|
|
864
|
+
const record = this.getIpIntelligence(ip);
|
|
865
|
+
if (!record) return;
|
|
866
|
+
const { DeesModal } = await import('@design.estate/dees-catalog');
|
|
867
|
+
|
|
868
|
+
await DeesModal.createAndShow({
|
|
869
|
+
heading: `IP Intelligence: ${ip}`,
|
|
870
|
+
content: html`
|
|
871
|
+
<div style="padding: 20px;">
|
|
872
|
+
<dees-dataview-codebox
|
|
873
|
+
.heading=${'Intelligence Record'}
|
|
874
|
+
progLang="json"
|
|
875
|
+
.codeToDisplay=${JSON.stringify(record, null, 2)}
|
|
876
|
+
></dees-dataview-codebox>
|
|
877
|
+
</div>
|
|
878
|
+
`,
|
|
879
|
+
menuOptions: [
|
|
880
|
+
{
|
|
881
|
+
name: 'Copy Abuse Contact',
|
|
882
|
+
iconName: 'lucide:copy',
|
|
883
|
+
action: async () => {
|
|
884
|
+
if (record.abuseContact) await navigator.clipboard.writeText(record.abuseContact);
|
|
885
|
+
},
|
|
886
|
+
},
|
|
887
|
+
{
|
|
888
|
+
name: 'Block IP',
|
|
889
|
+
iconName: 'lucide:shield-ban',
|
|
890
|
+
action: async () => {
|
|
891
|
+
await this.createBlockRuleDialog('ip', record.ipAddress, 'Blocked from IP intelligence details');
|
|
892
|
+
},
|
|
893
|
+
},
|
|
894
|
+
],
|
|
895
|
+
});
|
|
896
|
+
}
|
|
897
|
+
|
|
681
898
|
private async updateNetworkData() {
|
|
682
899
|
// Track requests/sec history for the trend sparkline (moved out of render)
|
|
683
900
|
const reqPerSec = this.networkState.requestsPerSecond || 0;
|