Skip to content

Commit 4d178b1

Browse files
authored
Merge pull request #1512 from utmstack/backlog/enhance-adversary-alerts-graph
Backlog/enhance adversary alerts graph
2 parents 8530f95 + 318b5c5 commit 4d178b1

File tree

2 files changed

+127
-7
lines changed

2 files changed

+127
-7
lines changed

frontend/src/app/data-management/adversary-management/adversary-alerts-graph/adversary-alerts-graph.component.html

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
<div class="d-flex flex-column h-100 m-h-0 overflow-auto">
1+
<div class="d-flex flex-column h-100 m-h-0 overflow-auto chart-container">
22
<div class="flex-grow-1 h-100">
33
<div echarts
44
[options]="option"
@@ -9,3 +9,26 @@
99
</div>
1010
</div>
1111

12+
<div *ngIf="viewAlertDetail" class="utm-right-container">
13+
<div (click)="closeDetail()" class="overlay overlay-lg col-md-6"></div>
14+
<div class="card utm-right-action utm-right-action-lg">
15+
<div class="title d-flex justify-content-between align-items-center border-bottom-1
16+
border-bottom-grey-100 p-3 ">
17+
<h6 class="card-title pb-0 mb-0 text-blue-800 font-weight-light">
18+
{{ alertDetail.name }}
19+
</h6>
20+
<div class="d-flex flex-row align-items-center gap-2">
21+
<app-alert-logs-related-action [logs]="alertDetail.events"></app-alert-logs-related-action>
22+
<button (click)="closeDetail()" aria-label="Close"
23+
class="" type="button">
24+
<div class="close-icon"></div>
25+
</button>
26+
</div>
27+
</div>
28+
<app-alert-view-detail [alert]="alertDetail"
29+
[isEcho] = "true"
30+
[hideEmptyField]= "true"
31+
[dataType]="EventDataTypeEnum.ALERT">
32+
</app-alert-view-detail>
33+
</div>
34+
</div>

frontend/src/app/data-management/adversary-management/adversary-alerts-graph/adversary-alerts-graph.component.ts

Lines changed: 103 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,8 @@
1-
import {Component, EventEmitter, Input, OnChanges, OnInit, Output} from '@angular/core';
1+
import {Component, Input, OnChanges} from '@angular/core';
22
import {ECharts} from 'echarts';
3+
import {UtmAlertType} from '../../../shared/types/alert/utm-alert.type';
4+
import {Side} from '../../../shared/types/event/event';
5+
import {EventDataTypeEnum} from '../../alert-management/shared/enums/event-data-type.enum';
36
import {AdversaryAlerts} from '../models';
47

58
@Component({
@@ -13,14 +16,17 @@ export class AdversaryAlertsGraphComponent implements OnChanges {
1316
baseHeight = 600;
1417
nodeGap = 6;
1518
option: any;
19+
viewAlertDetail = false;
20+
alertDetail: UtmAlertType;
21+
EventDataTypeEnum = EventDataTypeEnum;
1622

1723
ngOnChanges(): void {
1824
if (this.data) {
1925
this.option = this.buildOption(this.data);
2026
}
2127
}
2228

23-
onChartInit(chart: ECharts) {
29+
onChartInit(chart: any) {
2430

2531
chart.on('mouseover', (params) => {
2632
if (params.dataType === 'node') {
@@ -35,11 +41,72 @@ export class AdversaryAlertsGraphComponent implements OnChanges {
3541
});
3642

3743
chart.on('click', (params) => {
38-
if (params.dataType === 'node') {
44+
if (params.componentType === 'series'
45+
&& params.seriesType === 'sankey'
46+
&& (params.dataType === 'node' || params.dataType === 'label' || params.dataType === 'edge')) {
47+
this.alertDetail = params.data.meta.alert || null;
48+
this.viewAlertDetail = !!this.alertDetail;
3949
}
4050
});
4151
}
4252

53+
private getGraphicElements() {
54+
const sankey = {
55+
left: 10,
56+
right: 180,
57+
top: 20,
58+
bottom: 40
59+
};
60+
61+
const chartElement = document.querySelector('.chart-container');
62+
const chartContainerWidth = chartElement ? chartElement.clientWidth : 1000;
63+
const columnWidth = chartContainerWidth / 3;
64+
65+
const depthPositions = [
66+
{ left: sankey.left, width: columnWidth },
67+
{ left: sankey.left + columnWidth, width: columnWidth },
68+
{ left: sankey.left + (columnWidth * 2), width: columnWidth }
69+
];
70+
71+
const graphicElements: any[] = [];
72+
const labels = ['Adversary', 'Alerts', 'Echoes'];
73+
const colors = [
74+
{ fill: 'rgba(31, 119, 180, 0.06)', text: '#1F77B4' },
75+
{ fill: 'rgba(255, 127, 14, 0.06)', text: '#FF7F0E' },
76+
{ fill: 'rgba(44, 160, 44, 0.06)', text: '#2CA02C' }
77+
];
78+
79+
depthPositions.forEach((pos, index) => {
80+
const color = colors[index];
81+
82+
graphicElements.push({
83+
type: 'rect',
84+
left: pos.left,
85+
top: sankey.top,
86+
shape: { width: pos.width - 10, height: this.chartHeight - sankey.top - sankey.bottom },
87+
style: {
88+
fill: color.fill,
89+
stroke: 'none'
90+
}
91+
});
92+
93+
graphicElements.push({
94+
type: 'text',
95+
left: pos.left + (pos.width / 2),
96+
top: 0,
97+
style: {
98+
text: labels[index],
99+
fontSize: 12,
100+
fontWeight: 'bold',
101+
fill: color.text,
102+
textAlign: 'center'
103+
}
104+
});
105+
});
106+
107+
return graphicElements;
108+
}
109+
43110
private buildOption(adversaryAlerts: AdversaryAlerts[]): any {
44111
const nodes: any[] = [];
45112
const links: any[] = [];
@@ -112,6 +179,10 @@ export class AdversaryAlertsGraphComponent implements OnChanges {
112179
depth: 1,
113180
symbolSize: getNodeSize(childCount),
114181
meta: {
182+
alert: {
183+
...alert,
184+
hasChildren: true
185+
},
115186
severity: alert.severityLabel,
116187
timestamp: alert.timestamp,
117188
dataSource: alert.dataSource
@@ -133,7 +204,7 @@ export class AdversaryAlertsGraphComponent implements OnChanges {
133204

134205
alertWithChildren.children.forEach(child => {
135206
const childKey = nodeKey(child.id, child.name);
136-
const childLabel = truncate(child.name);
207+
const childLabel = truncate(this.getLabel(child));
137208
const childSeverityColor = severityColors[child.severityLabel.toLowerCase()] || severityColors.default;
138209

139210
if (!nodeSet.has(childKey)) {
@@ -149,6 +220,7 @@ export class AdversaryAlertsGraphComponent implements OnChanges {
149220
depth: 2,
150221
symbolSize: getNodeSize(1),
151222
meta: {
223+
alert: child,
152224
severity: child.severityLabel,
153225
timestamp: child.timestamp,
154226
dataSource: child.dataSource
@@ -165,6 +237,9 @@ export class AdversaryAlertsGraphComponent implements OnChanges {
165237
color: gradientColor(severityColor, childSeverityColor),
166238
opacity: 0.5,
167239
curveness: 0.2
240+
},
241+
meta: {
242+
alert: child,
168243
}
169244
});
170245
});
@@ -190,6 +265,7 @@ export class AdversaryAlertsGraphComponent implements OnChanges {
190265
`;
191266
}
192267
},
268+
graphic: this.getGraphicElements(),
193269
series: [{
194270
type: 'sankey',
195271
orient: 'horizontal',
@@ -214,14 +290,35 @@ export class AdversaryAlertsGraphComponent implements OnChanges {
214290
layoutIterations: 32,
215291
left: 20,
216292
right: 180,
217-
top: 20,
218-
bottom: 40,
293+
top: 30,
294+
bottom: 50,
219295
label: {
296+
show: true,
220297
position: 'right',
221298
fontSize: 10,
222299
formatter: (params: any) => params.name.split('::')[1] || params.name
223300
}
224301
}]
225302
};
226303
}
304+
305+
private getLabel(alert: UtmAlertType) {
306+
if (alert.target) {
307+
const target: any = alert.target as Side;
308+
if (target.ip) {
309+
return target.ip;
310+
} else if (target.host) {
311+
return target.host;
312+
} else if (target.user) {
313+
return target.user;
314+
}
315+
} else {
316+
return alert.dataSource;
317+
}
318+
}
319+
320+
closeDetail() {
321+
this.alertDetail = null;
322+
this.viewAlertDetail = false;
323+
}
227324
}

0 commit comments

Comments
 (0)