@serve.zone/dcrouter 13.23.0 → 13.25.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist_serve/bundle.js +952 -792
- package/dist_ts/00_commitinfo_data.js +1 -1
- package/dist_ts/db/documents/classes.ip-intelligence.doc.d.ts +1 -0
- package/dist_ts/db/documents/classes.ip-intelligence.doc.js +8 -2
- package/dist_ts/opsserver/handlers/security.handler.js +22 -1
- package/dist_ts/security/classes.security-policy-manager.d.ts +15 -4
- package/dist_ts/security/classes.security-policy-manager.js +108 -11
- package/dist_ts_interfaces/requests/security-policy.d.ts +32 -1
- package/dist_ts_web/00_commitinfo_data.js +1 -1
- package/dist_ts_web/appstate.d.ts +28 -0
- package/dist_ts_web/appstate.js +171 -4
- package/dist_ts_web/elements/network/ops-view-network-activity.d.ts +9 -0
- package/dist_ts_web/elements/network/ops-view-network-activity.js +210 -3
- package/dist_ts_web/elements/security/ops-view-security-blocked.d.ts +12 -3
- package/dist_ts_web/elements/security/ops-view-security-blocked.js +407 -52
- package/package.json +2 -2
- package/ts/00_commitinfo_data.ts +1 -1
- package/ts/db/documents/classes.ip-intelligence.doc.ts +3 -0
- package/ts/opsserver/handlers/security.handler.ts +38 -0
- package/ts/security/classes.security-policy-manager.ts +119 -12
- package/ts_web/00_commitinfo_data.ts +1 -1
- package/ts_web/appstate.ts +236 -3
- package/ts_web/elements/network/ops-view-network-activity.ts +219 -2
- package/ts_web/elements/security/ops-view-security-blocked.ts +414 -51
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import * as appstate from '../../appstate.js';
|
|
2
|
+
import * as interfaces from '../../../dist_ts_interfaces/index.js';
|
|
2
3
|
import { viewHostCss } from '../shared/css.js';
|
|
3
4
|
|
|
4
5
|
import {
|
|
@@ -21,18 +22,23 @@ declare global {
|
|
|
21
22
|
@customElement('ops-view-security-blocked')
|
|
22
23
|
export class OpsViewSecurityBlocked extends DeesElement {
|
|
23
24
|
@state()
|
|
24
|
-
accessor
|
|
25
|
+
accessor securityPolicyState: appstate.ISecurityPolicyState = appstate.securityPolicyStatePart.getState()!;
|
|
25
26
|
|
|
26
27
|
constructor() {
|
|
27
28
|
super();
|
|
28
|
-
const sub = appstate.
|
|
29
|
+
const sub = appstate.securityPolicyStatePart
|
|
29
30
|
.select((s) => s)
|
|
30
31
|
.subscribe((s) => {
|
|
31
|
-
this.
|
|
32
|
+
this.securityPolicyState = s;
|
|
32
33
|
});
|
|
33
34
|
this.rxSubscriptions.push(sub);
|
|
34
35
|
}
|
|
35
36
|
|
|
37
|
+
public async connectedCallback() {
|
|
38
|
+
await super.connectedCallback();
|
|
39
|
+
await appstate.securityPolicyStatePart.dispatchAction(appstate.fetchSecurityPolicyAction, null);
|
|
40
|
+
}
|
|
41
|
+
|
|
36
42
|
public static styles = [
|
|
37
43
|
cssManager.defaultStyles,
|
|
38
44
|
viewHostCss,
|
|
@@ -40,79 +46,436 @@ export class OpsViewSecurityBlocked extends DeesElement {
|
|
|
40
46
|
dees-statsgrid {
|
|
41
47
|
margin-bottom: 32px;
|
|
42
48
|
}
|
|
49
|
+
|
|
50
|
+
.sectionStack {
|
|
51
|
+
display: flex;
|
|
52
|
+
flex-direction: column;
|
|
53
|
+
gap: 32px;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
.statusBadge {
|
|
57
|
+
display: inline-flex;
|
|
58
|
+
align-items: center;
|
|
59
|
+
padding: 4px 8px;
|
|
60
|
+
border-radius: 4px;
|
|
61
|
+
font-size: 12px;
|
|
62
|
+
font-weight: 500;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
.statusBadge.enabled {
|
|
66
|
+
background: ${cssManager.bdTheme('#e8f5e9', '#1a3a1a')};
|
|
67
|
+
color: ${cssManager.bdTheme('#388e3c', '#66bb6a')};
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
.statusBadge.disabled {
|
|
71
|
+
background: ${cssManager.bdTheme('#f5f5f5', '#2a2a2a')};
|
|
72
|
+
color: ${cssManager.bdTheme('#757575', '#999')};
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
.typeBadge {
|
|
76
|
+
display: inline-flex;
|
|
77
|
+
align-items: center;
|
|
78
|
+
padding: 4px 8px;
|
|
79
|
+
border-radius: 999px;
|
|
80
|
+
font-size: 12px;
|
|
81
|
+
font-weight: 500;
|
|
82
|
+
background: ${cssManager.bdTheme('#eef2ff', '#1e1b4b')};
|
|
83
|
+
color: ${cssManager.bdTheme('#4338ca', '#a5b4fc')};
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
.errorMessage {
|
|
87
|
+
padding: 12px 16px;
|
|
88
|
+
border-radius: 8px;
|
|
89
|
+
background: ${cssManager.bdTheme('#fef2f2', '#450a0a')};
|
|
90
|
+
color: ${cssManager.bdTheme('#b91c1c', '#fca5a5')};
|
|
91
|
+
}
|
|
43
92
|
`,
|
|
44
93
|
];
|
|
45
94
|
|
|
46
95
|
public render(): TemplateResult {
|
|
47
|
-
const
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
<div class="loadingMessage">
|
|
52
|
-
<p>Loading security metrics...</p>
|
|
53
|
-
</div>
|
|
54
|
-
`;
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
const blockedIPs: string[] = metrics.blockedIPs || [];
|
|
96
|
+
const state = this.securityPolicyState;
|
|
97
|
+
const activeRules = state.rules.filter((rule) => rule.enabled);
|
|
98
|
+
const disabledRules = state.rules.length - activeRules.length;
|
|
99
|
+
const compiledPolicy = state.compiledPolicy || { blockedIps: [], blockedCidrs: [] };
|
|
58
100
|
|
|
59
101
|
const tiles: IStatsTile[] = [
|
|
60
102
|
{
|
|
61
|
-
id: '
|
|
62
|
-
title: '
|
|
63
|
-
value:
|
|
103
|
+
id: 'activeRules',
|
|
104
|
+
title: 'Active Rules',
|
|
105
|
+
value: activeRules.length,
|
|
64
106
|
type: 'number',
|
|
65
|
-
icon: 'lucide:
|
|
66
|
-
color:
|
|
67
|
-
description:
|
|
107
|
+
icon: 'lucide:shield-check',
|
|
108
|
+
color: activeRules.length > 0 ? '#ef4444' : '#22c55e',
|
|
109
|
+
description: `${disabledRules} disabled`,
|
|
110
|
+
},
|
|
111
|
+
{
|
|
112
|
+
id: 'compiledIps',
|
|
113
|
+
title: 'Compiled IPs',
|
|
114
|
+
value: compiledPolicy.blockedIps.length,
|
|
115
|
+
type: 'number',
|
|
116
|
+
icon: 'lucide:server-off',
|
|
117
|
+
color: '#ef4444',
|
|
118
|
+
description: 'Direct IP blocks enforced by SmartProxy',
|
|
119
|
+
},
|
|
120
|
+
{
|
|
121
|
+
id: 'compiledCidrs',
|
|
122
|
+
title: 'Compiled CIDRs',
|
|
123
|
+
value: compiledPolicy.blockedCidrs.length,
|
|
124
|
+
type: 'number',
|
|
125
|
+
icon: 'lucide:network',
|
|
126
|
+
color: '#f97316',
|
|
127
|
+
description: 'Network ranges pushed to enforcement layers',
|
|
128
|
+
},
|
|
129
|
+
{
|
|
130
|
+
id: 'intelligenceRecords',
|
|
131
|
+
title: 'IP Intelligence',
|
|
132
|
+
value: state.ipIntelligence.length,
|
|
133
|
+
type: 'number',
|
|
134
|
+
icon: 'lucide:radar',
|
|
135
|
+
color: '#6366f1',
|
|
136
|
+
description: 'Observed public IPs with enrichment',
|
|
68
137
|
},
|
|
69
138
|
];
|
|
70
139
|
|
|
71
140
|
return html`
|
|
72
|
-
<dees-heading level="3">
|
|
141
|
+
<dees-heading level="3">Security Blocking</dees-heading>
|
|
142
|
+
|
|
143
|
+
${state.error ? html`<div class="errorMessage">${state.error}</div>` : html``}
|
|
73
144
|
|
|
74
145
|
<dees-statsgrid
|
|
75
146
|
.tiles=${tiles}
|
|
76
147
|
.minTileWidth=${200}
|
|
77
148
|
></dees-statsgrid>
|
|
78
149
|
|
|
150
|
+
<div class="sectionStack">
|
|
151
|
+
${this.renderRulesTable()}
|
|
152
|
+
${this.renderCompiledPolicyTable()}
|
|
153
|
+
${this.renderIpIntelligenceTable()}
|
|
154
|
+
${this.renderAuditTable()}
|
|
155
|
+
</div>
|
|
156
|
+
`;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
private renderRulesTable(): TemplateResult {
|
|
160
|
+
return html`
|
|
79
161
|
<dees-table
|
|
80
|
-
.heading1=${'
|
|
81
|
-
.heading2=${'
|
|
82
|
-
.data=${
|
|
83
|
-
.
|
|
84
|
-
|
|
85
|
-
'
|
|
162
|
+
.heading1=${'Managed Block Rules'}
|
|
163
|
+
.heading2=${'Rules compiled into SmartProxy policy and remote ingress edge firewall snapshots'}
|
|
164
|
+
.data=${this.securityPolicyState.rules}
|
|
165
|
+
.rowKey=${'id'}
|
|
166
|
+
.displayFunction=${(rule: interfaces.data.ISecurityBlockRule) => ({
|
|
167
|
+
'Type': html`<span class="typeBadge">${rule.type}</span>`,
|
|
168
|
+
'Value': rule.value,
|
|
169
|
+
'Match': rule.type === 'organization' ? (rule.matchMode || 'contains') : '-',
|
|
170
|
+
'Reason': rule.reason || '-',
|
|
171
|
+
'Status': html`<span class="statusBadge ${rule.enabled ? 'enabled' : 'disabled'}">${rule.enabled ? 'Enabled' : 'Disabled'}</span>`,
|
|
172
|
+
'Created': this.formatDateTime(rule.createdAt),
|
|
173
|
+
'Updated': this.formatDateTime(rule.updatedAt),
|
|
86
174
|
})}
|
|
87
|
-
.dataActions=${
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
175
|
+
.dataActions=${this.getRuleActions()}
|
|
176
|
+
searchable
|
|
177
|
+
.showColumnFilters=${true}
|
|
178
|
+
dataName="rule"
|
|
179
|
+
></dees-table>
|
|
180
|
+
`;
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
private renderCompiledPolicyTable(): TemplateResult {
|
|
184
|
+
const policy = this.securityPolicyState.compiledPolicy || { blockedIps: [], blockedCidrs: [] };
|
|
185
|
+
const rows = [
|
|
186
|
+
...policy.blockedIps.map((value) => ({ type: 'ip', value })),
|
|
187
|
+
...policy.blockedCidrs.map((value) => ({ type: 'cidr', value })),
|
|
188
|
+
];
|
|
189
|
+
|
|
190
|
+
return html`
|
|
191
|
+
<dees-table
|
|
192
|
+
.heading1=${'Compiled Enforcement Policy'}
|
|
193
|
+
.heading2=${'Concrete IPs and CIDRs currently sent to SmartProxy and remote ingress'}
|
|
194
|
+
.data=${rows}
|
|
195
|
+
.rowKey=${'value'}
|
|
196
|
+
.displayFunction=${(row: { type: string; value: string }) => ({
|
|
197
|
+
'Enforcement Type': html`<span class="typeBadge">${row.type}</span>`,
|
|
198
|
+
'Value': row.value,
|
|
199
|
+
})}
|
|
200
|
+
searchable
|
|
201
|
+
.showColumnFilters=${true}
|
|
202
|
+
dataName="compiled rule"
|
|
105
203
|
></dees-table>
|
|
106
204
|
`;
|
|
107
205
|
}
|
|
108
206
|
|
|
109
|
-
private
|
|
110
|
-
|
|
111
|
-
|
|
207
|
+
private renderIpIntelligenceTable(): TemplateResult {
|
|
208
|
+
return html`
|
|
209
|
+
<dees-table
|
|
210
|
+
.heading1=${'Observed IP Intelligence'}
|
|
211
|
+
.heading2=${'Public IPs observed in network metrics and enriched for ASN / organization matching'}
|
|
212
|
+
.data=${this.securityPolicyState.ipIntelligence}
|
|
213
|
+
.rowKey=${'ipAddress'}
|
|
214
|
+
.displayFunction=${(record: interfaces.data.IIpIntelligenceRecord) => ({
|
|
215
|
+
'IP Address': record.ipAddress,
|
|
216
|
+
'ASN': record.asn ? `AS${record.asn}` : '-',
|
|
217
|
+
'ASN Org': record.asnOrg || '-',
|
|
218
|
+
'Registrant Org': record.registrantOrg || '-',
|
|
219
|
+
'Country': record.countryCode || record.country || '-',
|
|
220
|
+
'Network Range': record.networkRange || '-',
|
|
221
|
+
'Abuse Contact': record.abuseContact || '-',
|
|
222
|
+
'Seen': record.seenCount,
|
|
223
|
+
'Last Seen': this.formatDateTime(record.lastSeenAt),
|
|
224
|
+
})}
|
|
225
|
+
.dataActions=${this.getIpIntelligenceActions()}
|
|
226
|
+
searchable
|
|
227
|
+
.showColumnFilters=${true}
|
|
228
|
+
dataName="ip intelligence record"
|
|
229
|
+
></dees-table>
|
|
230
|
+
`;
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
private renderAuditTable(): TemplateResult {
|
|
234
|
+
return html`
|
|
235
|
+
<dees-table
|
|
236
|
+
.heading1=${'Policy Audit'}
|
|
237
|
+
.heading2=${'Recent security policy changes'}
|
|
238
|
+
.data=${this.securityPolicyState.auditEvents}
|
|
239
|
+
.rowKey=${'id'}
|
|
240
|
+
.displayFunction=${(event: interfaces.data.ISecurityPolicyAuditEvent) => ({
|
|
241
|
+
'Time': this.formatDateTime(event.createdAt),
|
|
242
|
+
'Action': event.action,
|
|
243
|
+
'Actor': event.actor,
|
|
244
|
+
'Details': this.formatAuditDetails(event.details),
|
|
245
|
+
})}
|
|
246
|
+
searchable
|
|
247
|
+
.showColumnFilters=${true}
|
|
248
|
+
dataName="audit event"
|
|
249
|
+
></dees-table>
|
|
250
|
+
`;
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
private getRuleActions() {
|
|
254
|
+
return [
|
|
255
|
+
{
|
|
256
|
+
name: 'Create Rule',
|
|
257
|
+
iconName: 'lucide:plus',
|
|
258
|
+
type: ['header'] as any,
|
|
259
|
+
actionFunc: async () => this.showRuleDialog(),
|
|
260
|
+
},
|
|
261
|
+
{
|
|
262
|
+
name: 'Edit',
|
|
263
|
+
iconName: 'lucide:pencil',
|
|
264
|
+
type: ['inRow', 'contextmenu'] as any,
|
|
265
|
+
actionFunc: async (actionData: any) => this.showRuleDialog(actionData.item),
|
|
266
|
+
},
|
|
267
|
+
{
|
|
268
|
+
name: 'Enable',
|
|
269
|
+
iconName: 'lucide:play',
|
|
270
|
+
type: ['contextmenu'] as any,
|
|
271
|
+
actionRelevancyCheckFunc: (actionData: any) => !actionData.item.enabled,
|
|
272
|
+
actionFunc: async (actionData: any) => {
|
|
273
|
+
const rule = actionData.item as interfaces.data.ISecurityBlockRule;
|
|
274
|
+
await appstate.securityPolicyStatePart.dispatchAction(appstate.updateSecurityBlockRuleAction, {
|
|
275
|
+
id: rule.id,
|
|
276
|
+
enabled: true,
|
|
277
|
+
});
|
|
278
|
+
},
|
|
279
|
+
},
|
|
280
|
+
{
|
|
281
|
+
name: 'Disable',
|
|
282
|
+
iconName: 'lucide:pause',
|
|
283
|
+
type: ['contextmenu'] as any,
|
|
284
|
+
actionRelevancyCheckFunc: (actionData: any) => actionData.item.enabled,
|
|
285
|
+
actionFunc: async (actionData: any) => {
|
|
286
|
+
const rule = actionData.item as interfaces.data.ISecurityBlockRule;
|
|
287
|
+
await appstate.securityPolicyStatePart.dispatchAction(appstate.updateSecurityBlockRuleAction, {
|
|
288
|
+
id: rule.id,
|
|
289
|
+
enabled: false,
|
|
290
|
+
});
|
|
291
|
+
},
|
|
292
|
+
},
|
|
293
|
+
{
|
|
294
|
+
name: 'Delete',
|
|
295
|
+
iconName: 'lucide:trash-2',
|
|
296
|
+
type: ['contextmenu'] as any,
|
|
297
|
+
actionFunc: async (actionData: any) => {
|
|
298
|
+
const rule = actionData.item as interfaces.data.ISecurityBlockRule;
|
|
299
|
+
if (!window.confirm(`Delete block rule ${rule.type}:${rule.value}?`)) return;
|
|
300
|
+
await appstate.securityPolicyStatePart.dispatchAction(appstate.deleteSecurityBlockRuleAction, rule.id);
|
|
301
|
+
},
|
|
302
|
+
},
|
|
303
|
+
];
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
private getIpIntelligenceActions() {
|
|
307
|
+
return [
|
|
308
|
+
{
|
|
309
|
+
name: 'Refresh Intelligence',
|
|
310
|
+
iconName: 'lucide:refresh-cw',
|
|
311
|
+
type: ['inRow', 'contextmenu'] as any,
|
|
312
|
+
actionFunc: async (actionData: any) => {
|
|
313
|
+
const record = actionData.item as interfaces.data.IIpIntelligenceRecord;
|
|
314
|
+
await appstate.securityPolicyStatePart.dispatchAction(appstate.refreshIpIntelligenceAction, record.ipAddress);
|
|
315
|
+
},
|
|
316
|
+
},
|
|
317
|
+
{
|
|
318
|
+
name: 'Block IP',
|
|
319
|
+
iconName: 'lucide:shield-ban',
|
|
320
|
+
type: ['contextmenu'] as any,
|
|
321
|
+
actionFunc: async (actionData: any) => {
|
|
322
|
+
const record = actionData.item as interfaces.data.IIpIntelligenceRecord;
|
|
323
|
+
await this.showRuleDialog(undefined, {
|
|
324
|
+
type: 'ip',
|
|
325
|
+
value: record.ipAddress,
|
|
326
|
+
reason: 'Blocked from IP intelligence table',
|
|
327
|
+
});
|
|
328
|
+
},
|
|
329
|
+
},
|
|
330
|
+
{
|
|
331
|
+
name: 'Block Network Range',
|
|
332
|
+
iconName: 'lucide:network',
|
|
333
|
+
type: ['contextmenu'] as any,
|
|
334
|
+
actionRelevancyCheckFunc: (actionData: any) => Boolean(actionData.item.networkRange),
|
|
335
|
+
actionFunc: async (actionData: any) => {
|
|
336
|
+
const record = actionData.item as interfaces.data.IIpIntelligenceRecord;
|
|
337
|
+
await this.showRuleDialog(undefined, {
|
|
338
|
+
type: 'cidr',
|
|
339
|
+
value: record.networkRange || '',
|
|
340
|
+
reason: 'Blocked network range from IP intelligence table',
|
|
341
|
+
});
|
|
342
|
+
},
|
|
343
|
+
},
|
|
344
|
+
{
|
|
345
|
+
name: 'Block ASN',
|
|
346
|
+
iconName: 'lucide:radio-tower',
|
|
347
|
+
type: ['contextmenu'] as any,
|
|
348
|
+
actionRelevancyCheckFunc: (actionData: any) => Boolean(actionData.item.asn),
|
|
349
|
+
actionFunc: async (actionData: any) => {
|
|
350
|
+
const record = actionData.item as interfaces.data.IIpIntelligenceRecord;
|
|
351
|
+
await this.showRuleDialog(undefined, {
|
|
352
|
+
type: 'asn',
|
|
353
|
+
value: String(record.asn),
|
|
354
|
+
reason: 'Blocked ASN from IP intelligence table',
|
|
355
|
+
});
|
|
356
|
+
},
|
|
357
|
+
},
|
|
358
|
+
{
|
|
359
|
+
name: 'Block Organization',
|
|
360
|
+
iconName: 'lucide:building-2',
|
|
361
|
+
type: ['contextmenu'] as any,
|
|
362
|
+
actionRelevancyCheckFunc: (actionData: any) => Boolean(actionData.item.asnOrg || actionData.item.registrantOrg),
|
|
363
|
+
actionFunc: async (actionData: any) => {
|
|
364
|
+
const record = actionData.item as interfaces.data.IIpIntelligenceRecord;
|
|
365
|
+
await this.showRuleDialog(undefined, {
|
|
366
|
+
type: 'organization',
|
|
367
|
+
value: record.asnOrg || record.registrantOrg || '',
|
|
368
|
+
reason: 'Blocked organization from IP intelligence table',
|
|
369
|
+
});
|
|
370
|
+
},
|
|
371
|
+
},
|
|
372
|
+
];
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
private async showRuleDialog(
|
|
376
|
+
rule?: interfaces.data.ISecurityBlockRule,
|
|
377
|
+
defaults: Partial<interfaces.data.ISecurityBlockRule> = {},
|
|
378
|
+
): Promise<void> {
|
|
379
|
+
const { DeesModal } = await import('@design.estate/dees-catalog');
|
|
380
|
+
const typeOptions = [
|
|
381
|
+
{ key: 'ip', option: 'IP address' },
|
|
382
|
+
{ key: 'cidr', option: 'CIDR / network range' },
|
|
383
|
+
{ key: 'asn', option: 'ASN' },
|
|
384
|
+
{ key: 'organization', option: 'Organization' },
|
|
385
|
+
];
|
|
386
|
+
const matchModeOptions = [
|
|
387
|
+
{ key: 'contains', option: 'Organization contains value' },
|
|
388
|
+
{ key: 'exact', option: 'Organization exactly matches value' },
|
|
389
|
+
];
|
|
390
|
+
const selectedType = rule?.type || defaults.type || 'ip';
|
|
391
|
+
const selectedMatchMode = rule?.matchMode || defaults.matchMode || 'contains';
|
|
392
|
+
|
|
393
|
+
await DeesModal.createAndShow({
|
|
394
|
+
heading: rule ? `Edit Block Rule: ${rule.type}:${rule.value}` : 'Create Block Rule',
|
|
395
|
+
content: html`
|
|
396
|
+
<dees-form>
|
|
397
|
+
${rule ? html`` : html`
|
|
398
|
+
<dees-input-dropdown
|
|
399
|
+
.key=${'type'}
|
|
400
|
+
.label=${'Rule Type'}
|
|
401
|
+
.options=${typeOptions}
|
|
402
|
+
.selectedOption=${typeOptions.find((option) => option.key === selectedType)}
|
|
403
|
+
></dees-input-dropdown>
|
|
404
|
+
`}
|
|
405
|
+
<dees-input-text
|
|
406
|
+
.key=${'value'}
|
|
407
|
+
.label=${'Value'}
|
|
408
|
+
.value=${rule?.value || defaults.value || ''}
|
|
409
|
+
.required=${true}
|
|
410
|
+
></dees-input-text>
|
|
411
|
+
<dees-input-dropdown
|
|
412
|
+
.key=${'matchMode'}
|
|
413
|
+
.label=${'Organization Match Mode'}
|
|
414
|
+
.description=${'Only used for organization rules'}
|
|
415
|
+
.options=${matchModeOptions}
|
|
416
|
+
.selectedOption=${matchModeOptions.find((option) => option.key === selectedMatchMode)}
|
|
417
|
+
></dees-input-dropdown>
|
|
418
|
+
<dees-input-text
|
|
419
|
+
.key=${'reason'}
|
|
420
|
+
.label=${'Reason'}
|
|
421
|
+
.value=${rule?.reason || defaults.reason || ''}
|
|
422
|
+
></dees-input-text>
|
|
423
|
+
<dees-input-checkbox
|
|
424
|
+
.key=${'enabled'}
|
|
425
|
+
.label=${'Enabled'}
|
|
426
|
+
.value=${rule ? rule.enabled : defaults.enabled !== false}
|
|
427
|
+
></dees-input-checkbox>
|
|
428
|
+
</dees-form>
|
|
429
|
+
`,
|
|
430
|
+
menuOptions: [
|
|
431
|
+
{ name: 'Cancel', iconName: 'lucide:x', action: async (modalArg: any) => modalArg.destroy() },
|
|
432
|
+
{
|
|
433
|
+
name: rule ? 'Save' : 'Create',
|
|
434
|
+
iconName: rule ? 'lucide:check' : 'lucide:plus',
|
|
435
|
+
action: async (modalArg: any) => {
|
|
436
|
+
const form = modalArg.shadowRoot?.querySelector('.content')?.querySelector('dees-form');
|
|
437
|
+
if (!form) return;
|
|
438
|
+
const data = await form.collectFormData();
|
|
439
|
+
const type = (rule?.type || this.getDropdownKey(data.type)) as interfaces.data.TSecurityBlockRuleType;
|
|
440
|
+
const value = String(data.value || '').trim();
|
|
441
|
+
if (!type || !value) return;
|
|
442
|
+
const matchMode = type === 'organization'
|
|
443
|
+
? this.getDropdownKey(data.matchMode) as interfaces.data.TSecurityBlockRuleMatchMode
|
|
444
|
+
: undefined;
|
|
445
|
+
const payload = {
|
|
446
|
+
value,
|
|
447
|
+
matchMode,
|
|
448
|
+
reason: String(data.reason || '').trim() || undefined,
|
|
449
|
+
enabled: data.enabled !== false,
|
|
450
|
+
};
|
|
451
|
+
if (rule) {
|
|
452
|
+
await appstate.securityPolicyStatePart.dispatchAction(appstate.updateSecurityBlockRuleAction, {
|
|
453
|
+
id: rule.id,
|
|
454
|
+
...payload,
|
|
455
|
+
});
|
|
456
|
+
} else {
|
|
457
|
+
await appstate.securityPolicyStatePart.dispatchAction(appstate.createSecurityBlockRuleAction, {
|
|
458
|
+
type,
|
|
459
|
+
...payload,
|
|
460
|
+
});
|
|
461
|
+
}
|
|
462
|
+
await modalArg.destroy();
|
|
463
|
+
},
|
|
464
|
+
},
|
|
465
|
+
],
|
|
466
|
+
});
|
|
467
|
+
}
|
|
468
|
+
|
|
469
|
+
private getDropdownKey(value: any): string {
|
|
470
|
+
return typeof value === 'string' ? value : value?.key || '';
|
|
471
|
+
}
|
|
472
|
+
|
|
473
|
+
private formatDateTime(timestamp?: number): string {
|
|
474
|
+
return timestamp ? new Date(timestamp).toLocaleString() : '-';
|
|
112
475
|
}
|
|
113
476
|
|
|
114
|
-
private
|
|
115
|
-
|
|
116
|
-
|
|
477
|
+
private formatAuditDetails(details: Record<string, unknown>): string {
|
|
478
|
+
const text = JSON.stringify(details);
|
|
479
|
+
return text.length > 160 ? `${text.slice(0, 157)}...` : text;
|
|
117
480
|
}
|
|
118
481
|
}
|