Skip to content

Commit 9ef75b3

Browse files
committed
feat: enhance playbook component with improved layout and functionality
Signed-off-by: Manuel Abascal <mjabascal10@gmail.com>
1 parent bccd1c6 commit 9ef75b3

11 files changed

Lines changed: 201 additions & 85 deletions

frontend/src/app/incident-response/incident-response.module.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,8 @@ import {IncidentResponseSharedModule} from './shared/incident-response-shared.mo
2020
@NgModule({
2121
declarations:
2222
[
23-
IncidentResponseViewComponent, IncidentResponseCommandComponent,
23+
IncidentResponseViewComponent,
24+
IncidentResponseCommandComponent,
2425
IncidentResponseAutomationComponent,
2526
PlaybookBuilderComponent,
2627
PlaybooksComponent,
@@ -40,6 +41,7 @@ import {IncidentResponseSharedModule} from './shared/incident-response-shared.mo
4041
ReactiveFormsModule
4142
],
4243
entryComponents: [],
44+
providers: []
4345
})
4446
export class IncidentResponseModule {
4547
}

frontend/src/app/incident-response/playbook-builder/playbook-builder.component.html

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -86,7 +86,7 @@ <h5 class="card-title mb-0 text-uppercase label-header">Add flow</h5>
8686
<button (click)="createRule()" class="btn utm-button utm-button-primary ml-2"
8787
[disabled]="!formRule.valid">
8888
<i class="icon-terminal mr-2"></i>
89-
{{ (rule ? 'Update' : 'Create') + ' flow' }}
89+
{{ (rule.id ? 'Update' : 'Create') + ' flow' }}
9090
</button>
9191
</div>
9292
</div>

frontend/src/app/incident-response/playbook-builder/playbook-builder.component.spec.ts

Lines changed: 0 additions & 25 deletions
This file was deleted.

frontend/src/app/incident-response/playbook-builder/playbook-builder.component.ts

Lines changed: 20 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -89,12 +89,26 @@ export class PlaybookBuilderComponent implements OnInit, OnDestroy {
8989
map(res => res.body));
9090
})
9191
).subscribe(rule => {
92-
console.log(rule.conditions);
93-
this.rule = rule;
92+
93+
const isTemplate = this.route.snapshot.queryParams.template === 'true';
94+
const ruleData = isTemplate ? { ...rule, id: null } : rule;
95+
const actions = isTemplate ? ruleData.actions.map(item => {
96+
return {
97+
...item,
98+
id: null
99+
};
100+
}) : ruleData.actions;
101+
102+
this.rule = ruleData;
103+
this.rule.actions = actions;
104+
94105
this.exist = false;
95106
this.typing = false;
96107
this.rulePrefix = getElementPrefix(this.rule.name);
97-
this.formRule.patchValue(this.rule, {emitEvent: false});
108+
109+
110+
this.formRule.patchValue(ruleData, { emitEvent: false });
111+
98112
const name = this.formRule.get('name').value;
99113
this.formRule.get('name').setValue(this.replacePrefixInName(name));
100114

@@ -122,9 +136,9 @@ export class PlaybookBuilderComponent implements OnInit, OnDestroy {
122136
this.route.queryParams
123137
.pipe(
124138
filter(params => !!params && !!params.alertName),
125-
map(params => params.alertName)).subscribe((alertName)=>{
139+
map(params => params.alertName)).subscribe((alertName) => {
126140
this.addRuleCondition();
127-
const rc = this.ruleConditions.at(this.ruleConditions.length-1);
141+
const rc = this.ruleConditions.at(this.ruleConditions.length - 1);
128142
rc.patchValue({
129143
field: 'name',
130144
value: alertName,
@@ -180,7 +194,7 @@ export class PlaybookBuilderComponent implements OnInit, OnDestroy {
180194
}
181195

182196
createRule() {
183-
if (this.rule) {
197+
if (this.rule.id) {
184198
this.editRule();
185199
} else {
186200
this.saveRule();

frontend/src/app/incident-response/playbooks/playbooks.component.spec.ts

Lines changed: 0 additions & 25 deletions
This file was deleted.

frontend/src/app/incident-response/playbooks/playbooks.component.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,13 +11,14 @@ import {UtmAlertType} from '../../shared/types/alert/utm-alert.type';
1111
import {TimeFilterType} from '../../shared/types/time-filter.type';
1212
import {IncidentResponseRuleService} from '../shared/services/incident-response-rule.service';
1313
import {IncidentRuleType} from '../shared/type/incident-rule.type';
14-
import {PlaybookService} from './playbook.service';
14+
import {PlaybookService} from '../shared/services/playbook.service';
1515
import {NewPlaybookComponent} from "../shared/component/new-playbook/new-playbook.component";
1616

1717
@Component({
1818
selector: 'app-playbooks',
1919
templateUrl: './playbooks.component.html',
20-
styleUrls: ['./playbooks.component.scss']
20+
styleUrls: ['./playbooks.component.scss'],
21+
providers: [PlaybookService]
2122
})
2223
export class PlaybooksComponent implements OnInit, AfterViewInit, OnDestroy {
2324
loading = true;

frontend/src/app/incident-response/shared/component/new-playbook/new-playbook.component.html

Lines changed: 41 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,42 +1,69 @@
11
<div class="d-flex flex-1 flex-column gap-8 overflow-hidden p-4 ma-h-620 h-50">
22
<div class="d-flex flex-1 flex-column gap-6 overflow-hidden">
3-
<div class="d-flex flex-1 flex-grow-0 flex-column space-y-1 text-left">
3+
<div class="d-flex flex-1 flex-grow-0 flex-column space-y-1 text-left h-100">
44
<h2 id="radix-:rf8:" class="font-semibold tracking-tight line-clamp-1 flex items-center pb-0.5 text-base">Get started</h2>
55
<p id="radix-:rf9:" class="text-muted-foreground line-clamp-2 text-sm">Start with templates showcasing common response playbooks and workflows.</p>
66

7-
<div class="d-flex w-100 mt-3">
7+
<div class="d-flex flex-column mt-3 px-1">
88
<app-utm-search-input
99
(searchFor)="searchByRule($event)"
1010
[placeholder]="'Search workflows '"
1111
class="w-100">
1212
</app-utm-search-input>
1313
</div>
1414

15-
<div class="col-12 text-center py-5">
16-
<div class="d-flex flex-column align-items-center justify-content-center">
17-
<i class="bi bi-diagram-3 text-muted mb-3" style="font-size: 48px;"></i>
18-
<h4 class="mb-2 fw-semibold text-muted">Let’s get started</h4>
19-
<p class="text-muted mb-0">No predefined flows were found. Create your first one to begin.</p>
15+
<div class="playbook-list-container m-h-0 d-flex flex-column gap-3 overflow-auto mt-3 px-1">
16+
<div *ngFor="let item of playbookService.playbooks$ | async; trackBy: trackByFn"
17+
class="playbook-item-card d-flex align-items-center rounded border p-3 cursor-pointer"
18+
[routerLink]="['/soar/create-flow']"
19+
[queryParams]="{ id: item.id, template: true }">
20+
21+
<div class="playbook-icon-small d-flex justify-content-center align-items-center flex-shrink-0">
22+
<i class="fa fa-sitemap"></i>
23+
</div>
24+
25+
<div class="playbook-content d-flex flex-column flex-grow-1 min-w-0">
26+
<div class="d-flex align-items-center justify-content-between mb-1">
27+
<span class="playbook-title">{{ item.name }}</span>
28+
<i class="fa fa-chevron-right playbook-arrow"></i>
29+
</div>
30+
<p class="playbook-description">
31+
{{ item.description }}
32+
</p>
33+
<div class="playbook-date">
34+
<i class="fa fa-clock-o"></i>
35+
<span>Edited {{ item.createdDate | relativeTime }}</span>
36+
</div>
37+
</div>
2038
</div>
21-
</div>
2239

40+
<div *ngIf="(playbookService.playbooks$ | async)?.length === 0" class="col-12 text-center py-5">
41+
<div class="d-flex flex-column align-items-center justify-content-center">
42+
<i class="fa fa-sitemap text-muted mb-3" style="font-size: 48px;"></i>
43+
<h4 class="mb-2 fw-semibold text-muted">Let's get started</h4>
44+
<p class="text-muted mb-0">No predefined flows were found. Create your first one to begin.</p>
45+
</div>
46+
</div>
47+
</div>
2348
</div>
2449
</div>
50+
2551
<div class="d-flex flex-shrink-0 flex-row-reverse">
26-
<div class="d-flex w-100 justify-content-between gap-4 pb-4">
52+
<div class="d-flex w-100 justify-content-between align-items-center gap-4 pb-4 border-top pt-4">
2753
<div class="d-flex flex-column items-start justify-center">
28-
<div class="font-semibold">Start from scratch</div>
54+
<div class="font-semibold text-dark">Start from scratch</div>
2955
<div class="text-sm text-muted-foreground">Begin with a fresh flow to build from scratch.</div>
3056
</div>
31-
<button (click)="createNewPlaybook()" class="btn utm-button utm-button-primary">
32-
<i class="icon-plus2 mr-1"></i>
33-
New blank flow </button>
57+
<button (click)="createNewPlaybook()" class="btn utm-button utm-button-primary d-flex align-items-center">
58+
<i class="fa fa-plus mr-2"></i>
59+
New blank flow
60+
</button>
3461
</div>
3562
</div>
3663
</div>
64+
3765
<button (click)="activeModal.dismiss('closed')" aria-label="Close"
3866
class="position-absolute right-2 top-2 h-8 w-8" data-dismiss="modal"
3967
type="button">
4068
<div class="close-modal-icon"></div>
4169
</button>
42-

frontend/src/app/incident-response/shared/component/new-playbook/new-playbook.component.scss

Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,3 +64,92 @@
6464
.h-8 {
6565
height: 2rem;
6666
}
67+
68+
.playbook-list-container {
69+
padding-right: 8px;
70+
71+
&::-webkit-scrollbar {
72+
width: 6px;
73+
}
74+
75+
&::-webkit-scrollbar-track {
76+
background: transparent;
77+
}
78+
79+
&::-webkit-scrollbar-thumb {
80+
background: #d1d5db;
81+
border-radius: 3px;
82+
83+
&:hover {
84+
background: #9ca3af;
85+
}
86+
}
87+
}
88+
89+
.playbook-item-card {
90+
background: #f9fafb;
91+
border-color: #e5e7eb;
92+
transition: all 0.2s ease;
93+
margin-bottom: 8px; // Separación entre cards
94+
95+
&:hover {
96+
background: #ffffff;
97+
border-color: #cbd5e1;
98+
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.06);
99+
}
100+
101+
&:last-child {
102+
margin-bottom: 0;
103+
}
104+
}
105+
106+
.playbook-icon-small {
107+
width: 36px;
108+
height: 36px;
109+
color: #6b7280;
110+
font-size: 16px;
111+
margin-right: 16px; // Mejor separación del icono al contenido
112+
flex-shrink: 0;
113+
}
114+
115+
.playbook-content {
116+
min-width: 0;
117+
}
118+
119+
.playbook-title {
120+
font-size: 14px;
121+
font-weight: 500;
122+
color: #111827;
123+
line-height: 1.5;
124+
}
125+
126+
.playbook-description {
127+
font-size: 13px;
128+
color: #6b7280;
129+
line-height: 1.5;
130+
display: -webkit-box;
131+
-webkit-line-clamp: 2;
132+
-webkit-box-orient: vertical;
133+
overflow: hidden;
134+
margin-bottom: 8px;
135+
}
136+
137+
.playbook-date {
138+
font-size: 12px;
139+
color: #9ca3af;
140+
display: flex;
141+
align-items: center;
142+
gap: 6px; // Separación entre icono y texto
143+
144+
i {
145+
font-size: 11px;
146+
}
147+
}
148+
149+
.playbook-arrow {
150+
color: #9ca3af;
151+
font-size: 14px;
152+
flex-shrink: 0;
153+
margin-left: 8px;
154+
}
155+
Lines changed: 37 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,27 +1,51 @@
1-
import {Component, EventEmitter, Input, OnInit, Output} from '@angular/core';
2-
import {Router} from "@angular/router";
1+
import {AfterViewInit, Component, EventEmitter, Input, OnInit, Output} from '@angular/core';
2+
import {Router} from '@angular/router';
33
import {NgbActiveModal} from '@ng-bootstrap/ng-bootstrap';
4+
import {Observable, of} from 'rxjs';
45
import {UtmToastService} from '../../../../shared/alert/utm-toast.service';
56
import {InputClassResolve} from '../../../../shared/util/input-class-resolve';
7+
import {PlaybookService} from '../../services/playbook.service';
8+
import {IncidentResponseRuleService} from '../../services/incident-response-rule.service';
69
import {IncidentActionType} from '../../type/incident-action.type';
710

811
@Component({
912
selector: 'app-incident-response-command-create',
1013
templateUrl: './new-playbook.component.html',
11-
styleUrls: ['./new-playbook.component.scss']
14+
styleUrls: ['./new-playbook.component.scss'],
15+
providers: [PlaybookService]
1216
})
13-
export class NewPlaybookComponent implements OnInit {
17+
export class NewPlaybookComponent implements OnInit, AfterViewInit {
1418
@Input() action: IncidentActionType;
1519
@Output() actionCreated = new EventEmitter<IncidentActionType>();
1620

21+
request = {
22+
page: 0,
23+
size: 25,
24+
sort: '',
25+
'active.equals': null,
26+
'agentPlatform.equals': null,
27+
'createdBy.equals': null,
28+
'systemOwner.equals': true
29+
};
30+
platforms: string[];
31+
32+
platforms$: Observable<string[]>;
33+
loadingPlatform = false;
34+
1735
constructor(public activeModal: NgbActiveModal,
1836
public inputClass: InputClassResolve,
19-
public utmToastService: UtmToastService,
37+
private utmToastService: UtmToastService,
38+
private incidentResponseRuleService: IncidentResponseRuleService,
39+
public playbookService: PlaybookService,
2040
private router: Router) {
2141
}
2242

2343
ngOnInit() {}
2444

45+
ngAfterViewInit(): void {
46+
this.playbookService.loadData({...this.request});
47+
}
48+
2549
createNewPlaybook() {
2650
this.router.navigate(['/soar/create-flow']);
2751
this.activeModal.close();
@@ -30,4 +54,12 @@ export class NewPlaybookComponent implements OnInit {
3054
searchByRule($event: string | number) {
3155

3256
}
57+
58+
selectPlatform(platform: string) {
59+
60+
}
61+
62+
trackByFn(index: number, item: any) {
63+
return item.id || index;
64+
}
3365
}

0 commit comments

Comments
 (0)