Skip to content

Commit 00cc50a

Browse files
authored
Merge pull request #1143 from utmstack/bugfix/10.7.1/alerts-stopping-tag-rules-exception
Bugfix/10.7.1/alerts stopping tag rules exception
2 parents 492c0e0 + c6a8021 commit 00cc50a

File tree

5 files changed

+163
-67
lines changed

5 files changed

+163
-67
lines changed

CHANGELOG.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,4 +4,5 @@
44
### Bug Fixes
55
-- Improved exception handling in `automaticReview` to prevent the process from stopping due to errors, ensuring the system continues evaluating alerts even if a specific rule fails.
66
-- Improved operator selection for more accurate and consistent filtering.
7-
-- Introduced new compliance reports aligned with the PCI DSS standard to expand auditing capabilities.
7+
-- Introduced new compliance reports aligned with the PCI DSS standard to expand auditing capabilities.
8+
-- Enabled creation and update of tag-based rules with dynamic conditions.

frontend/src/app/data-management/alert-management/alert-rules/alert-rules.component.html

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ <h5 class="card-title mb-0 label-header">Alerts rules</h5>
88
Manage alerts
99
</button>
1010
<button (click)="createRule()" class="btn utm-button utm-button-primary ml-2">
11-
Add tag rule
11+
Create tag rule
1212
</button>
1313
</div>
1414
</div>

frontend/src/app/data-management/alert-management/alert-rules/alert-rules.component.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,10 +5,12 @@ import {NgbModal} from '@ng-bootstrap/ng-bootstrap';
55
import {SourcesService} from '../../../admin/sources/sources.service';
66
import {UtmToastService} from '../../../shared/alert/utm-toast.service';
77
import {ModalConfirmationComponent} from '../../../shared/components/utm/util/modal-confirmation/modal-confirmation.component';
8+
import {FALSE_POSITIVE_OBJECT} from '../../../shared/constants/alert/alert-field.constant';
89
import {ITEMS_PER_PAGE} from '../../../shared/constants/pagination.constants';
910
import {SortEvent} from '../../../shared/directives/sortable/type/sort-event';
1011
import {AlertTags} from '../../../shared/types/alert/alert-tag.type';
1112
import {TimeFilterType} from '../../../shared/types/time-filter.type';
13+
import {AlertRuleCreateComponent} from '../shared/components/alert-rule-create/alert-rule-create.component';
1214
import {AlertRulesService} from '../shared/services/alert-rules.service';
1315
import {AlertTagService} from '../shared/services/alert-tag.service';
1416
import {AlertRuleType} from './alert-rule.type';

frontend/src/app/data-management/alert-management/shared/components/alert-rule-create/alert-rule-create.component.html

Lines changed: 71 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
1-
<app-utm-modal-header [name]="(action === 'update' ? 'Update' : 'Add new') + ' tag rule'" class="w-100"></app-utm-modal-header>
1+
<app-utm-modal-header [name]="(action === 'update' ? 'Update' : 'Add new') + ' tag rule'"
2+
class="w-100"></app-utm-modal-header>
23
<div class="container-fluid p-3" style="z-index: 99999999">
34
<div class="w-100">
45
<div *ngIf="!tags"
@@ -30,22 +31,22 @@
3031
</div>
3132
</div>
3233

33-
<ng-container *ngIf="action !== 'select'">
34-
<label class="pb-1">Alerts</label>
35-
<div class="d-flex justify-content-between align-items-center mb-3">
36-
<ng-select (change)="onAlertChange($event)"
37-
(search)="onSearch($event)"
38-
(scrollToEnd)="loadMoreAlerts()"
39-
[items]="(alerts$ | async)"
40-
[loading]="loading"
41-
[trackByFn]="trackByFn"
42-
[virtualScroll]="true"
43-
bindLabel="name"
44-
class="flex-item type-select w-100"
45-
placeholder="Choose an alert">
46-
</ng-select>
47-
</div>
48-
</ng-container>
34+
<!--<ng-container *ngIf="action !== 'select'">
35+
<label class="pb-1">Alerts</label>
36+
<div class="d-flex justify-content-between align-items-center mb-3">
37+
<ng-select (change)="onAlertChange($event)"
38+
(search)="onSearch($event)"
39+
(scrollToEnd)="loadMoreAlerts()"
40+
[items]="(alerts$ | async)"
41+
[loading]="loading"
42+
[trackByFn]="trackByFn"
43+
[virtualScroll]="true"
44+
bindLabel="name"
45+
class="flex-item type-select w-100"
46+
placeholder="Choose an alert">
47+
</ng-select>
48+
</div>
49+
</ng-container>-->
4950

5051
<form [formGroup]="formRule">
5152
<div class="form-group">
@@ -81,48 +82,70 @@
8182
rows="2"></textarea>
8283
<app-formcontrol-error [formcontrol]="formRule.get('description')"></app-formcontrol-error>
8384
</div>
84-
</form>
8585

86-
<div *ngIf="isFalsePositive()" class="w-100">
87-
<div class="alert bg-info-400 text-white alert-styled-right mt-3">
86+
<div *ngIf="isFalsePositive()" class="w-100">
87+
<div class="alert bg-info-400 text-white alert-styled-right mt-3">
8888
<span>By creating a False Positive rule, future alerts where the fields
8989
and values match the operator defined will be tagged as <b>False positive</b>.
9090
The alerts that match with this rule will be marked as <b>Completed</b>.</span>
91+
</div>
9192
</div>
92-
</div>
9393

94-
<div class="d-flex justify-content-start align-items-center mt-3">
94+
<div class="d-flex justify-content-start align-items-center mt-3">
9595
<span (click)="viewFieldDetail=!viewFieldDetail"
9696
class="text-center cursor-pointer">
9797
<i [ngClass]="viewFieldDetail?'icon-arrow-up32':'icon-arrow-down32'"></i>
9898
<span class="font-weight-semibold">View rule fields</span>&nbsp;
9999
</span>&nbsp;
100-
</div>
101-
<div *ngIf="viewFieldDetail" class="alert-history mt-2">
102-
<div class="d-flex justify-content-between align-items-center pb-2" *ngFor="let filter of filters">
103-
<span class="text-blue-800 rule-label">{{ getFieldName(filter.field) }}:&nbsp;</span>
104-
<ng-select
105-
[clearable]="false"
106-
[items]="getOperators(filter)"
107-
[(ngModel)]="filter.operator"
108-
[loadingText]="'Loading operators....'"
109-
[loading]="!operators"
110-
[searchable]="true"
111-
bindLabel="name"
112-
class="rule-operator"
113-
dropdownPosition="top"
114-
bindValue="operator"
115-
id="operators">
116-
</ng-select>
117-
<input
118-
[style.visibility]="filter.operator == ElasticOperatorsEnum.EXIST || filter.operator == ElasticOperatorsEnum.DOES_NOT_EXIST ? 'hidden' : 'visible'"
119-
[(ngModel)]="filter.value" class="form-control">
120-
<i class="icon-cross2 cursor-pointer ml-3" ngbTooltip="Delete filter field"
121-
placement="left"
122-
(click)="deleteFilter(filter)"></i>
123100
</div>
124-
</div>
125-
101+
<div *ngIf="viewFieldDetail" class="alert-history mt-2">
102+
<div formArrayName="conditions">
103+
<div class="d-flex justify-content-between align-items-center pb-2"
104+
*ngFor="let condition of ruleConditions.controls; let i = index" [formGroupName]="i">
105+
<span *ngIf="action === 'select'"
106+
class="text-blue-800 rule-label">{{ getFieldName(condition.get('field').value) }}:&nbsp;</span>
107+
<ng-select *ngIf="action !== 'select'"
108+
formControlName="field"
109+
[clearable]="false"
110+
[items]="fields"
111+
[loadingText]="'Loading fields....'"
112+
[loading]="!operators"
113+
[searchable]="true"
114+
bindLabel="label"
115+
class="rule-operator"
116+
dropdownPosition="top"
117+
bindValue="field"
118+
id="fields">
119+
</ng-select>
120+
<ng-select
121+
formControlName="operator"
122+
[clearable]="false"
123+
[items]="getOperators(condition.get('field').value)"
124+
[loadingText]="'Loading operators....'"
125+
[loading]="!operators"
126+
[searchable]="true"
127+
bindLabel="name"
128+
class="rule-operator"
129+
dropdownPosition="top"
130+
bindValue="operator"
131+
id="operators">
132+
</ng-select>
133+
<input formControlName="value"
134+
[style.visibility]="condition.get('operator').value == ElasticOperatorsEnum.EXIST || condition.get('operator').value == ElasticOperatorsEnum.DOES_NOT_EXIST ? 'hidden' : 'visible'"
135+
class="form-control">
136+
<i *ngIf="ruleConditions.length > 1" class="icon-cross2 cursor-pointer ml-3" ngbTooltip="Delete filter field"
137+
placement="left"
138+
(click)="removeRuleCondition(i)"></i>
139+
</div>
140+
<div class="d-flex justify-content-end mt-2">
141+
<button class="btn utm-button btn-success align-self-end" (click)="addRuleCondition()">
142+
<i class="icon-plus22"></i>&nbsp;
143+
Add
144+
</button>
145+
</div>
146+
</div>
147+
</div>
148+
</form>
126149

127150
<div class="button-container d-flex justify-content-end mt-3">
128151
<button (click)="activeModal.close()" class="btn utm-button utm-button-grey mr-3">
@@ -132,7 +155,7 @@
132155
<button (click)="saveRule()" class="btn utm-button utm-button-primary"
133156
[disabled]="!formRule.valid || exist || selected.length === 0">
134157
<i class="icon-stack-text"></i>&nbsp;
135-
Add rule & Tag
158+
{{ (action === 'update' ? 'Update ' : 'Add ') + 'rule & Tag' }}
136159
</button>
137160
</div>
138161
</div>

frontend/src/app/data-management/alert-management/shared/components/alert-rule-create/alert-rule-create.component.ts

Lines changed: 87 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import {HttpResponse} from '@angular/common/http';
22
import {Component, EventEmitter, Input, OnDestroy, OnInit, Output} from '@angular/core';
3-
import {FormBuilder, FormGroup, Validators} from '@angular/forms';
3+
import {AbstractControl, FormArray, FormBuilder, FormGroup, Validators} from '@angular/forms';
44
import {NgbActiveModal, NgbModal} from '@ng-bootstrap/ng-bootstrap';
55
import {UUID} from 'angular2-uuid';
66
import {Observable, Subject} from 'rxjs';
@@ -20,7 +20,7 @@ import {
2020
ALERT_INCIDENT_FLAG_FIELD,
2121
ALERT_INCIDENT_MODULE_FIELD,
2222
ALERT_INCIDENT_OBSERVATION_FIELD,
23-
ALERT_INCIDENT_USER_FIELD,
23+
ALERT_INCIDENT_USER_FIELD, ALERT_NAME_FIELD,
2424
ALERT_NOTE_FIELD,
2525
ALERT_OBSERVATION_FIELD,
2626
ALERT_REFERENCE_FIELD,
@@ -142,18 +142,12 @@ export class AlertRuleCreateComponent implements OnInit, OnDestroy {
142142
ngOnInit() {
143143

144144
this.initForm();
145+
this.createDefaultFilters();
145146
this.getTags();
146147
this.formRule.get('name').valueChanges.pipe(debounceTime(3000)).subscribe(ruleName => {
147148
this.searchRule(ruleName);
148149
});
149150

150-
if (!this.alert) {
151-
this.loading = true;
152-
this.alertService.notifyRefresh(true);
153-
} else {
154-
this.createDefaultFilters();
155-
}
156-
157151
if (this.rule) {
158152
this.filters = [... this.rule.conditions];
159153
this.selected = this.rule.tags.length > 0 ? [...this.rule.tags] : [];
@@ -173,18 +167,52 @@ export class AlertRuleCreateComponent implements OnInit, OnDestroy {
173167
id: [this.rule ? this.rule.id : null],
174168
name: [ this.rule ? this.rule.name : '', Validators.required],
175169
description: [this.rule ? this.rule.description : '', Validators.required],
176-
conditions: [ this.rule ? this.rule.conditions : [], Validators.required],
170+
conditions: this.fb.array([], Validators.required),
177171
tags: [this.rule ? this.rule.tags : null, Validators.required],
178172
});
179173
}
180174

181175
createDefaultFilters() {
176+
if (this.alert) {
177+
this.initConditionsFromAlert();
178+
} else {
179+
this.initConditionsFromAction();
180+
}
181+
182+
this.subscribeToFieldChanges();
183+
}
184+
185+
private initConditionsFromAlert() {
182186
for (const field of this.fields) {
183-
if (this.getFieldValue(field.field)) {
184-
this.filters.push({field: field.field, operator: ElasticOperatorsEnum.IS, value: this.getFieldValue(field.field)});
187+
const value = this.getFieldValue(field.field);
188+
if (value) {
189+
const condition = this.buildCondition(field.field, value);
190+
this.filters.push(condition);
191+
this.ruleConditions.push(this.createConditionGroup(condition));
185192
}
186193
}
187-
this.formRule.get('conditions').setValue(this.filters);
194+
}
195+
196+
private initConditionsFromAction() {
197+
if (this.action === 'create') {
198+
const field = this.fields[0];
199+
const condition = this.buildCondition(field.field, '');
200+
this.filters.push(condition);
201+
this.ruleConditions.push(this.createConditionGroup(condition));
202+
} else if (this.action === 'update') {
203+
this.filters = [...this.rule.conditions];
204+
this.rule.conditions.forEach(condition => {
205+
this.ruleConditions.push(this.createConditionGroup(condition));
206+
});
207+
}
208+
}
209+
210+
private buildCondition(field: string, value: any) {
211+
return {
212+
field,
213+
operator: ElasticOperatorsEnum.IS,
214+
value
215+
};
188216
}
189217

190218
getFieldValue(field: string): any {
@@ -262,8 +290,8 @@ export class AlertRuleCreateComponent implements OnInit, OnDestroy {
262290

263291
}
264292

265-
deleteFilter(filter: ElasticFilterType) {
266-
const index = this.filters.indexOf(filter);
293+
deleteFilter(elasticFilterType: ElasticFilterType) {
294+
const index = this.filters.indexOf(elasticFilterType);
267295
if (index !== -1) {
268296
this.filters.splice(index, 1);
269297
this.formRule.get('conditions').setValue(this.filters);
@@ -326,8 +354,8 @@ export class AlertRuleCreateComponent implements OnInit, OnDestroy {
326354
return this.selected.findIndex(value => value.tagName.includes('False positive')) !== -1;
327355
}
328356

329-
getOperators(filter: ElasticFilterType) {
330-
const field = this.fields.find(f => f.field === filter.field);
357+
getOperators(conditionField: string) {
358+
const field = this.fields.find(f => f.field === conditionField);
331359
if (field) {
332360
return this.operatorService.getOperators({name: field.field, type: field.type}, this.operators);
333361
}
@@ -391,6 +419,48 @@ export class AlertRuleCreateComponent implements OnInit, OnDestroy {
391419
this.activeModal.close();
392420
}
393421

422+
get ruleConditions() {
423+
return this.formRule.get('conditions') as FormArray;
424+
}
425+
426+
removeRuleCondition(index: number) {
427+
this.ruleConditions.removeAt(index);
428+
}
429+
430+
createConditionGroup(condition: any): FormGroup {
431+
return this.fb.group({
432+
field: [condition.field, Validators.required],
433+
operator: [condition.operator, Validators.required],
434+
value: [condition.value]
435+
});
436+
}
437+
438+
subscribeToFieldChanges() {
439+
this.ruleConditions.controls.forEach((group: AbstractControl, index: number) => {
440+
const fieldControl = group.get('field');
441+
const operatorControl = group.get('operator');
442+
443+
fieldControl.valueChanges
444+
.pipe(takeUntil(this.destroy$))
445+
.subscribe(newField => {
446+
const validOperators = this.getOperators(newField).map(op => op.operator);
447+
if (!validOperators.includes(operatorControl.value)) {
448+
operatorControl.setValue(null);
449+
}
450+
});
451+
});
452+
}
453+
454+
addRuleCondition() {
455+
this.ruleConditions.push(
456+
this.createConditionGroup({
457+
field: this.fields[0].field,
458+
operator: null,
459+
value: null
460+
}));
461+
this.subscribeToFieldChanges();
462+
}
463+
394464
ngOnDestroy(): void {
395465
this.destroy$.next();
396466
this.destroy$.complete();

0 commit comments

Comments
 (0)