Skip to content

Commit 576874f

Browse files
committed
feat(rule-view): enhance YAML export functionality and improve UI layout
Signed-off-by: Manuel Abascal <mjabascal10@gmail.com>
1 parent 29debbc commit 576874f

4 files changed

Lines changed: 163 additions & 23 deletions

File tree

Lines changed: 15 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,21 @@
1+
<app-utm-modal-header [name]="'Rule Details'"></app-utm-modal-header>
12

2-
<app-utm-modal-header [name]="'Rule Details'">
3+
<div class="sub-header px-4 d-flex align-items-center justify-content-between">
4+
<div class="sub-header-title">
5+
{{ 'YAML - ' + rowDocument.name }}
6+
</div>
37

4-
</app-utm-modal-header>
8+
<div class="d-flex gap-3">
9+
<button class="btn sub-btn mr-2" (click)="copyToClipboard()">
10+
<i class="icon-clipboard"></i>Copy
11+
</button>
512

6-
7-
<!-- utm-json-detail-view.component.html -->
8-
<div class="d-flex h-100 m-h-0 overflow-auto p-2 rule-view-container">
9-
<app-utm-json-detail-view [rowDocument]="rowDocument"></app-utm-json-detail-view>
13+
<button class="btn sub-btn" (click)="exportYaml()">
14+
<i class="icon-download"></i>Export
15+
</button>
16+
</div>
1017
</div>
1118

12-
<div class="header d-flex flex-row w-100 align-items-center justify-content-end">
13-
<button class="btn utm-button utm-button-primary floating-btn" (click)="copyToClipboard()" title="Copy YAML">
14-
<i class="icon-clipboard mr-2"></i>Copy
15-
</button>
16-
19+
<div class="d-flex h-100 m-h-0 overflow-auto p-2 rule-view-container">
20+
<pre class="yaml-preview w-100" [innerHTML]="yamlHighlighted"></pre>
1721
</div>

frontend/src/app/rule-management/app-rule/components/see-rule/rule-view.component.scss

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,3 +34,79 @@ app-utm-modal-header {
3434
transform: scale(1.05);
3535
}
3636
}
37+
38+
.yaml-container {
39+
width: 100%;
40+
background: #ffffff;
41+
padding: 16px;
42+
border-radius: 6px;
43+
border: 1px solid #e5e7eb;
44+
}
45+
46+
.yaml-preview {
47+
white-space: pre-wrap;
48+
font-family: "Fira Code", monospace;
49+
font-size: 14px;
50+
}
51+
52+
::ng-deep .yaml-key {
53+
color: #0277bd;
54+
font-weight: 600;
55+
}
56+
57+
::ng-deep .yaml-value {
58+
color: #FF6B6B;
59+
}
60+
61+
.sub-header {
62+
background: transparent;
63+
border: none;
64+
border-bottom: 1px solid #f0f0f0;
65+
padding: 12px 0;
66+
margin-bottom: 16px;
67+
box-shadow: none;
68+
}
69+
70+
.sub-header-title {
71+
font-size: 13px;
72+
font-weight: 600;
73+
color: #666666;
74+
letter-spacing: 0.3px;
75+
text-transform: uppercase;
76+
max-width: 70%;
77+
white-space: nowrap;
78+
overflow: hidden;
79+
text-overflow: ellipsis;
80+
}
81+
82+
.sub-btn {
83+
background: #232f3e;
84+
color: #ffffff;
85+
padding: .375rem .75rem;
86+
font-size: .75rem !important;
87+
border: none;
88+
font-weight: 400;
89+
cursor: pointer;
90+
transition: all 0.2s ease;
91+
display: inline-flex;
92+
align-items: center;
93+
gap: 6px;
94+
white-space: nowrap;
95+
border-radius: .25rem;
96+
97+
&:hover {
98+
background: #364556;
99+
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.2);
100+
transform: translateY(-1px);
101+
}
102+
103+
&:active {
104+
transform: translateY(0);
105+
box-shadow: 0 1px 4px rgba(0, 0, 0, 0.15);
106+
}
107+
108+
i {
109+
font-size: .75rem !important;
110+
}
111+
}
112+

frontend/src/app/rule-management/app-rule/components/see-rule/rule-view.component.ts

Lines changed: 49 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,34 +1,71 @@
1-
2-
import { Component, Input } from '@angular/core';
3-
import { NgbActiveModal } from '@ng-bootstrap/ng-bootstrap';
4-
import { Rule } from '../../../models/rule.model';
1+
import {Component, Input, OnInit} from '@angular/core';
52
import * as yaml from 'js-yaml';
3+
import {mapRuleToYaml} from '../../../../shared/components/utm/util/utm-file-upload/shared/rule-yaml.mapper';
4+
import {Rule} from '../../../models/rule.model';
65

76
@Component({
87
selector: 'app-rule-view',
98
templateUrl: './rule-view.component.html',
109
styleUrls: ['./rule-view.component.scss'],
1110
})
12-
export class RuleViewComponent {
13-
@Input() rowDocument: Rule;
11+
export class RuleViewComponent implements OnInit {
12+
@Input() rowDocument: Rule;
1413

1514
copied = false;
1615

16+
ngOnInit() {
17+
console.log('Rule received:', this.rowDocument);
18+
}
19+
1720
get yamlString(): string {
1821
try {
19-
return yaml.dump(this.rowDocument, { indent: 2 });
22+
const yamlModel = mapRuleToYaml(this.rowDocument);
23+
24+
return yaml.dump(yamlModel, {
25+
indent: 2,
26+
lineWidth: -1,
27+
styles: {
28+
'!!null': 'empty'
29+
}
30+
});
2031
} catch (e) {
21-
return 'Error parsing YAML';
32+
return 'Error generating YAML';
2233
}
2334
}
2435

36+
get yamlHighlighted(): string {
37+
return this.yamlString
38+
.replace(/^(\s*)([a-zA-Z0-9_]+):/gm, '$1<span class="yaml-key">$2</span>:')
39+
.replace(/: (.*)/g, ': <span class="yaml-value">$1</span>')
40+
.replace(/-\s+(.*)/g, '- <span class="yaml-value">$1</span>')
41+
.replace(/^\s{2,}(.+)/gm, ' <span class="yaml-value">$1</span>');
42+
}
43+
44+
exportYaml() {
45+
const yamlContent = this.yamlString;
46+
47+
const blob = new Blob([yamlContent], {
48+
type: 'text/yaml;charset=utf-8'
49+
});
50+
51+
const url = window.URL.createObjectURL(blob);
52+
53+
const a = document.createElement('a');
54+
a.href = url;
55+
56+
a.download = `${this.rowDocument.name || 'rule'}.yml`
57+
.replace(/\s+/g, '-')
58+
.toLowerCase();
59+
a.click();
60+
61+
window.URL.revokeObjectURL(url);
62+
}
63+
64+
2565
copyToClipboard() {
26-
window.navigator['clipboard'].writeText(this.yamlString).then(() => {
66+
(navigator as any).clipboard.writeText(this.yamlString).then(() => {
2767
this.copied = true;
2868
setTimeout(() => (this.copied = false), 1500);
2969
});
3070
}
3171
}
32-
33-
34-
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
import {Rule} from '../../../../../../rule-management/models/rule.model';
2+
3+
4+
export function mapRuleToYaml(rule: Rule): any {
5+
return {
6+
name: rule.name,
7+
dataTypes: rule.dataTypes ? rule.dataTypes.map(d => d.dataType) : [],
8+
impact: {
9+
confidentiality: rule.confidentiality,
10+
integrity: rule.integrity,
11+
availability: rule.availability
12+
},
13+
category: rule.category,
14+
technique: rule.technique,
15+
adversary: rule.adversary,
16+
references: rule.references,
17+
description: rule.description,
18+
where: rule.definition,
19+
afterEvents: rule.afterEvents && rule.afterEvents.length ? rule.afterEvents : [],
20+
groupBy: rule.groupBy && rule.groupBy.length ? rule.groupBy : [],
21+
deduplicateBy: rule.deduplicateBy && rule.deduplicateBy.length ? rule.deduplicateBy : []
22+
};
23+
}

0 commit comments

Comments
 (0)