Skip to content

Commit 75842a8

Browse files
committed
feat: implement timeline visualization for compliance evaluations
1 parent 1e4b6d6 commit 75842a8

3 files changed

Lines changed: 267 additions & 0 deletions

File tree

Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
<div class="container-fluid p-3">
2+
<div class="card-header p-2">
3+
<h6 class="d-flex align-items-center text-blue-800 font-weight-light">
4+
Control Status Timeline
5+
</h6>
6+
</div>
7+
<div class="card-body p-2 h-10">
8+
<div *ngIf="evaluations.length > 0 && !loading"
9+
class="m-0 h-100" id="eventsTimeline">
10+
<div class="timeline-scroll-wrapper">
11+
<ul class="timeline">
12+
<li *ngFor="let evaluation of evaluations" class="cursor-pointer">
13+
<div class="timeline-item" (click)="selectEvaluation(evaluation)">
14+
<!--<div class="utm_tmlabel timeline-label cursor-pointer"
15+
[ngClass]="selected === evaluation ?
16+
evaluation.status === 'COMPLIANT' ? 'selected compliant': 'selected non-compliant'
17+
: ''"
18+
(click)="selectEvaluation(evaluation)">
19+
<p class="vertical-text">{{ evaluation.status }}</p>
20+
</div>
21+
22+
<div class="timeline-line" [ngClass]="evaluation.status === 'COMPLIANT' ? 'compliant' : 'non-compliant'"></div>-->
23+
24+
<div class="timeline-icons" [ngClass]="evaluation.status === 'COMPLIANT' ? 'compliant' : 'non-compliant'"></div>
25+
26+
<time class="timeline-time">
27+
{{ evaluation.timestamp | date:formatDateEnum.UTM_SHORT:'UTC' }}
28+
</time>
29+
</div>
30+
31+
</li>
32+
</ul>
33+
<!--<div *ngIf="loadingMore && !noMoreResult"
34+
class="d-flex justify-content-center align-items-center p-3 loading">
35+
<app-utm-spinner [height]="'35px'" [loading]="true"
36+
[width]="'35px'" label="Loading more events"></app-utm-spinner>
37+
</div>
38+
<div *ngIf="noMoreResult"
39+
class="d-flex justify-content-center align-items-center p-3 loading">
40+
<i class="icon-three-bars font-size-lg text-blue-800"></i>
41+
<span class="text-blue-800 mt-2">No more data</span>
42+
</div>-->
43+
</div>
44+
<!--<div *ngIf="evaluations && evaluations.length>0" class="my-3 mt-1">
45+
<div class="row justify-content-center">
46+
<ngb-pagination (pageChange)="loadPage($event)"
47+
[(page)]="page"
48+
[boundaryLinks]="true"
49+
[collectionSize]="totalItems"
50+
[maxSize]="3"
51+
[pageSize]="itemsPerPage"eastc
52+
[rotate]="true"
53+
[size]="'sm'"></ngb-pagination>
54+
<app-utm-items-per-page (itemsInPage)="onItemsPerPageChange($event)"
55+
class="ml-3">
56+
</app-utm-items-per-page>
57+
</div>-->
58+
59+
</div>
60+
</div>
61+
<div *ngIf="loading"
62+
class=" h-100 d-flex justify-content-center align-items-center p-3 loading">
63+
<app-utm-spinner [height]="'35px'" [loading]="true"
64+
[width]="'35px'" label="Loading"></app-utm-spinner>
65+
</div>
66+
<div *ngIf="evaluations.length===0 && !loading"
67+
class="card-body message-container p-2 timeline-container h-100">
68+
<div class="h-100 w-100 d-flex justify-content-center align-items-center flex-column">
69+
<div class="event-icon utm-icon-lg utm-icon-light"></div>
70+
<h6 class="text-blue-800 mt-2 text-justify font-weight-light">
71+
No data found
72+
</h6>
73+
</div>
74+
</div>
75+
</div>
Lines changed: 113 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,113 @@
1+
2+
.timeline-scroll-wrapper {
3+
width: 100%;
4+
overflow-x: auto;
5+
overflow-y: hidden;
6+
padding-bottom: 8px;
7+
}
8+
9+
.timeline {
10+
display: flex;
11+
flex-direction: row;
12+
gap: 25px;
13+
list-style: none;
14+
padding: 10px 0;
15+
align-items: center;
16+
position: relative;
17+
min-width: max-content;
18+
}
19+
20+
.timeline::before {
21+
position: absolute;
22+
top: 25px;
23+
left: 0;
24+
right: 0;
25+
height: 1px;
26+
z-index: 0;
27+
width: 100%;
28+
border-radius: unset;
29+
}
30+
31+
.timeline-item {
32+
display: flex;
33+
flex-direction: column;
34+
align-items: center;
35+
text-align: center;
36+
width: 100px;
37+
}
38+
39+
.timeline-label {
40+
font-size: 14px;
41+
border-bottom: 3px solid #4caf50 !important;
42+
height: 150px;
43+
display: flex;
44+
align-items: center;
45+
width: 50px;
46+
justify-content: center;
47+
box-shadow: 2px 0 1px 0 rgba(0, 0, 0, 0.12), 0 1px 1px 0 rgba(0, 0, 0, 0.24);
48+
}
49+
50+
.timeline-line {
51+
width: 2px;
52+
height: 30px;
53+
margin: 4px 0;
54+
}
55+
56+
.timeline-icons {
57+
width: 25px;
58+
height: 25px;
59+
border-radius: 50%;
60+
display: flex;
61+
align-items: center;
62+
justify-content: center;
63+
color: white;
64+
font-size: 20px;
65+
z-index: 2;
66+
border-color: whitesmoke !important;
67+
box-shadow: 0 0 0 4px rgba(104, 159, 56, .25);
68+
}
69+
70+
.timeline-icons::before {
71+
content: '\ea16';
72+
font-family: icomoon, sans-serif;
73+
color: white;
74+
font-size: 12px;
75+
}
76+
77+
.timeline-icons::after {
78+
display: none;
79+
}
80+
81+
.timeline-line.compliant,
82+
.timeline-icons.compliant,
83+
.timeline-label.compliant,
84+
.timeline-label.compliant:hover {
85+
background: #4CAF50;
86+
}
87+
88+
.timeline-line.non-compliant,
89+
.timeline-icons.non-compliant,
90+
.timeline-label.non-compliant,
91+
.timeline-label.non-compliant:hover {
92+
background: #DC3545;
93+
}
94+
95+
.timeline-time {
96+
font-size: 12px;
97+
color: #666;
98+
margin-top: 6px;
99+
margin-bottom: 0 !important;
100+
}
101+
102+
.vertical-text {
103+
transform: rotate(270deg);
104+
}
105+
106+
.selected p{
107+
color: white;
108+
}
109+
110+
.timeline-label:hover {
111+
background-color: #f0f0f0;
112+
box-shadow: 2px 0 8px 0 rgba(0, 0, 0, 0.12);
113+
}
Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
import {Component, EventEmitter, Input, OnInit, Output} from '@angular/core';
2+
import {WinlogbeatService} from '../../../../active-directory/shared/services/winlogbeat.service';
3+
import {ActiveDirectoryTreeType} from '../../../../active-directory/shared/types/active-directory-tree.type';
4+
import {ITEMS_PER_PAGE} from '../../../../shared/constants/pagination.constants';
5+
import {UtmDateFormatEnum} from '../../../../shared/enums/utm-date-format.enum';
6+
import {TimeFilterType} from '../../../../shared/types/time-filter.type';
7+
import {ComplianceControlEvaluationsType} from '../../type/compliance-control-evaluations.type';
8+
9+
10+
@Component({
11+
selector: 'app-compliance-timeline',
12+
templateUrl: './compliance-timeline.component.html',
13+
styleUrls: ['./compliance-timeline.component.scss']
14+
})
15+
export class ComplianceTimelineComponent implements OnInit {
16+
@Input() evaluations: ComplianceControlEvaluationsType[];
17+
@Input() time: TimeFilterType;
18+
@Output() evaluationSelected = new EventEmitter<ComplianceControlEvaluationsType>();
19+
objectId: ActiveDirectoryTreeType;
20+
loadingMore = false;
21+
totalItems: any;
22+
page = 1;
23+
itemsPerPage = ITEMS_PER_PAGE;
24+
filterTime: TimeFilterType;
25+
loading = true;
26+
selected: ComplianceControlEvaluationsType;
27+
formatDateEnum = UtmDateFormatEnum;
28+
noMoreResult = false;
29+
30+
constructor(private winlogbeatService: WinlogbeatService) {
31+
}
32+
33+
ngOnInit(): void {
34+
this.selected = null;
35+
this.page = 1;
36+
this.totalItems = this.evaluations.length;
37+
this.loading = false;
38+
}
39+
40+
getEvents() {
41+
if (this.filterTime && this.objectId.objectSid) {
42+
const req = {
43+
page: this.page,
44+
size: this.itemsPerPage,
45+
sort: '@timestamp,desc',
46+
sid: this.objectId.objectSid,
47+
indexPattern: this.objectId.indexPattern,
48+
from: this.filterTime.timeFrom,
49+
to: this.filterTime.timeTo,
50+
'eventId.in': this.evaluations ? this.evaluations.toString() : undefined
51+
};
52+
this.winlogbeatService.query(req).subscribe(response => {
53+
this.loadingMore = false;
54+
this.loading = false;
55+
if (response.body === null || response.body.length === 0) {
56+
this.evaluationSelected.emit(null);
57+
} else {
58+
this.evaluations = response.body;
59+
this.totalItems = Number(response.headers.get('X-Total-Count'));
60+
}
61+
});
62+
} else {
63+
this.loading = false;
64+
}
65+
}
66+
67+
onScroll() {
68+
this.loadingMore = true;
69+
this.page += 1;
70+
this.getEvents();
71+
}
72+
73+
selectEvaluation(evaluation: ComplianceControlEvaluationsType) {
74+
this.selected = evaluation;
75+
this.evaluationSelected.emit(evaluation);
76+
}
77+
}
78+
79+

0 commit comments

Comments
 (0)