@serve.zone/dcrouter 13.1.3 → 13.3.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 +1202 -1133
- package/dist_ts/00_commitinfo_data.js +1 -1
- package/dist_ts_web/00_commitinfo_data.js +1 -1
- package/dist_ts_web/appstate.d.ts +1 -0
- package/dist_ts_web/appstate.js +9 -22
- package/dist_ts_web/elements/index.d.ts +2 -6
- package/dist_ts_web/elements/index.js +3 -7
- package/dist_ts_web/elements/network/index.d.ts +6 -0
- package/dist_ts_web/elements/network/index.js +7 -0
- package/dist_ts_web/elements/{ops-view-network.d.ts → network/ops-view-network-activity.d.ts} +3 -3
- package/dist_ts_web/elements/{ops-view-network.js → network/ops-view-network-activity.js} +20 -30
- package/dist_ts_web/elements/network/ops-view-network.d.ts +24 -0
- package/dist_ts_web/elements/network/ops-view-network.js +151 -0
- package/dist_ts_web/elements/{ops-view-networktargets.d.ts → network/ops-view-networktargets.d.ts} +1 -1
- package/dist_ts_web/elements/{ops-view-networktargets.js → network/ops-view-networktargets.js} +6 -6
- package/dist_ts_web/elements/{ops-view-routes.d.ts → network/ops-view-routes.d.ts} +1 -1
- package/dist_ts_web/elements/{ops-view-routes.js → network/ops-view-routes.js} +5 -6
- package/dist_ts_web/elements/{ops-view-sourceprofiles.d.ts → network/ops-view-sourceprofiles.d.ts} +1 -1
- package/dist_ts_web/elements/{ops-view-sourceprofiles.js → network/ops-view-sourceprofiles.js} +6 -6
- package/dist_ts_web/elements/{ops-view-targetprofiles.d.ts → network/ops-view-targetprofiles.d.ts} +2 -2
- package/dist_ts_web/elements/{ops-view-targetprofiles.js → network/ops-view-targetprofiles.js} +7 -7
- package/dist_ts_web/elements/ops-dashboard.js +4 -27
- package/dist_ts_web/elements/ops-view-apitokens.js +2 -1
- package/dist_ts_web/elements/ops-view-certificates.js +2 -1
- package/dist_ts_web/elements/ops-view-config.js +3 -3
- package/dist_ts_web/elements/ops-view-remoteingress.js +2 -1
- package/dist_ts_web/elements/ops-view-vpn.js +2 -1
- package/dist_ts_web/elements/security/index.d.ts +5 -0
- package/dist_ts_web/elements/security/index.js +6 -0
- package/dist_ts_web/elements/security/ops-view-security-authentication.d.ts +13 -0
- package/dist_ts_web/elements/security/ops-view-security-authentication.js +156 -0
- package/dist_ts_web/elements/security/ops-view-security-blocked.d.ts +15 -0
- package/dist_ts_web/elements/security/ops-view-security-blocked.js +152 -0
- package/dist_ts_web/elements/security/ops-view-security-emailsecurity.d.ts +14 -0
- package/dist_ts_web/elements/security/ops-view-security-emailsecurity.js +196 -0
- package/dist_ts_web/elements/security/ops-view-security-overview.d.ts +16 -0
- package/dist_ts_web/elements/security/ops-view-security-overview.js +204 -0
- package/dist_ts_web/elements/security/ops-view-security.d.ts +23 -0
- package/dist_ts_web/elements/security/ops-view-security.js +146 -0
- package/dist_ts_web/router.d.ts +5 -3
- package/dist_ts_web/router.js +69 -17
- package/package.json +1 -1
- package/ts/00_commitinfo_data.ts +1 -1
- package/ts_web/00_commitinfo_data.ts +1 -1
- package/ts_web/appstate.ts +10 -24
- package/ts_web/elements/index.ts +2 -6
- package/ts_web/elements/network/index.ts +6 -0
- package/ts_web/elements/{ops-view-network.ts → network/ops-view-network-activity.ts} +43 -53
- package/ts_web/elements/network/ops-view-network.ts +119 -0
- package/ts_web/elements/{ops-view-networktargets.ts → network/ops-view-networktargets.ts} +5 -5
- package/ts_web/elements/{ops-view-routes.ts → network/ops-view-routes.ts} +4 -5
- package/ts_web/elements/{ops-view-sourceprofiles.ts → network/ops-view-sourceprofiles.ts} +5 -5
- package/ts_web/elements/{ops-view-targetprofiles.ts → network/ops-view-targetprofiles.ts} +6 -6
- package/ts_web/elements/ops-dashboard.ts +3 -26
- package/ts_web/elements/ops-view-apitokens.ts +1 -0
- package/ts_web/elements/ops-view-certificates.ts +1 -0
- package/ts_web/elements/ops-view-config.ts +2 -2
- package/ts_web/elements/ops-view-remoteingress.ts +1 -0
- package/ts_web/elements/ops-view-vpn.ts +1 -0
- package/ts_web/elements/security/index.ts +5 -0
- package/ts_web/elements/security/ops-view-security-authentication.ts +120 -0
- package/ts_web/elements/security/ops-view-security-blocked.ts +117 -0
- package/ts_web/elements/security/ops-view-security-emailsecurity.ts +159 -0
- package/ts_web/elements/security/ops-view-security-overview.ts +171 -0
- package/ts_web/elements/security/ops-view-security.ts +114 -0
- package/ts_web/router.ts +75 -17
- package/dist_ts_web/elements/ops-view-security.d.ts +0 -24
- package/dist_ts_web/elements/ops-view-security.js +0 -481
- package/ts_web/elements/ops-view-security.ts +0 -453
|
@@ -14,19 +14,15 @@ import {
|
|
|
14
14
|
|
|
15
15
|
// Import view components
|
|
16
16
|
import { OpsViewOverview } from './ops-view-overview.js';
|
|
17
|
-
import { OpsViewNetwork } from './ops-view-network.js';
|
|
17
|
+
import { OpsViewNetwork } from './network/ops-view-network.js';
|
|
18
18
|
import { OpsViewEmails } from './ops-view-emails.js';
|
|
19
19
|
import { OpsViewLogs } from './ops-view-logs.js';
|
|
20
20
|
import { OpsViewConfig } from './ops-view-config.js';
|
|
21
|
-
import { OpsViewRoutes } from './ops-view-routes.js';
|
|
22
21
|
import { OpsViewApiTokens } from './ops-view-apitokens.js';
|
|
23
|
-
import { OpsViewSecurity } from './ops-view-security.js';
|
|
22
|
+
import { OpsViewSecurity } from './security/ops-view-security.js';
|
|
24
23
|
import { OpsViewCertificates } from './ops-view-certificates.js';
|
|
25
24
|
import { OpsViewRemoteIngress } from './ops-view-remoteingress.js';
|
|
26
25
|
import { OpsViewVpn } from './ops-view-vpn.js';
|
|
27
|
-
import { OpsViewSourceProfiles } from './ops-view-sourceprofiles.js';
|
|
28
|
-
import { OpsViewNetworkTargets } from './ops-view-networktargets.js';
|
|
29
|
-
import { OpsViewTargetProfiles } from './ops-view-targetprofiles.js';
|
|
30
26
|
|
|
31
27
|
@customElement('ops-dashboard')
|
|
32
28
|
export class OpsDashboard extends DeesElement {
|
|
@@ -37,6 +33,7 @@ export class OpsDashboard extends DeesElement {
|
|
|
37
33
|
|
|
38
34
|
@state() accessor uiState: appstate.IUiState = {
|
|
39
35
|
activeView: 'overview',
|
|
36
|
+
activeSubview: null,
|
|
40
37
|
sidebarCollapsed: false,
|
|
41
38
|
autoRefresh: true,
|
|
42
39
|
refreshInterval: 1000,
|
|
@@ -76,26 +73,6 @@ export class OpsDashboard extends DeesElement {
|
|
|
76
73
|
iconName: 'lucide:scrollText',
|
|
77
74
|
element: OpsViewLogs,
|
|
78
75
|
},
|
|
79
|
-
{
|
|
80
|
-
name: 'Routes',
|
|
81
|
-
iconName: 'lucide:route',
|
|
82
|
-
element: OpsViewRoutes,
|
|
83
|
-
},
|
|
84
|
-
{
|
|
85
|
-
name: 'SourceProfiles',
|
|
86
|
-
iconName: 'lucide:shieldCheck',
|
|
87
|
-
element: OpsViewSourceProfiles,
|
|
88
|
-
},
|
|
89
|
-
{
|
|
90
|
-
name: 'NetworkTargets',
|
|
91
|
-
iconName: 'lucide:server',
|
|
92
|
-
element: OpsViewNetworkTargets,
|
|
93
|
-
},
|
|
94
|
-
{
|
|
95
|
-
name: 'TargetProfiles',
|
|
96
|
-
iconName: 'lucide:target',
|
|
97
|
-
element: OpsViewTargetProfiles,
|
|
98
|
-
},
|
|
99
76
|
{
|
|
100
77
|
name: 'ApiTokens',
|
|
101
78
|
iconName: 'lucide:key',
|
|
@@ -109,6 +109,7 @@ export class OpsViewApiTokens extends DeesElement {
|
|
|
109
109
|
.data=${apiTokens}
|
|
110
110
|
.dataName=${'token'}
|
|
111
111
|
.searchable=${true}
|
|
112
|
+
.showColumnFilters=${true}
|
|
112
113
|
.displayFunction=${(token: interfaces.data.IApiTokenInfo) => ({
|
|
113
114
|
name: token.name,
|
|
114
115
|
scopes: this.renderScopePills(token.scopes),
|
|
@@ -228,6 +228,7 @@ export class OpsViewCertificates extends DeesElement {
|
|
|
228
228
|
return html`
|
|
229
229
|
<dees-table
|
|
230
230
|
.data=${this.certState.certificates}
|
|
231
|
+
.showColumnFilters=${true}
|
|
231
232
|
.displayFunction=${(cert: interfaces.requests.ICertificateInfo) => ({
|
|
232
233
|
Domain: cert.domain,
|
|
233
234
|
Routes: this.renderRoutePills(cert.routeNames),
|
|
@@ -86,7 +86,7 @@ export class OpsViewConfig extends DeesElement {
|
|
|
86
86
|
infoText="This view displays the current running configuration. DcRouter is configured through code or remote management."
|
|
87
87
|
@navigate=${(e: CustomEvent) => {
|
|
88
88
|
if (e.detail?.view) {
|
|
89
|
-
appRouter.navigateToView(e.detail.view);
|
|
89
|
+
appRouter.navigateToView(e.detail.view, e.detail.subview);
|
|
90
90
|
}
|
|
91
91
|
}}
|
|
92
92
|
>
|
|
@@ -149,7 +149,7 @@ export class OpsViewConfig extends DeesElement {
|
|
|
149
149
|
}
|
|
150
150
|
|
|
151
151
|
const actions: IConfigSectionAction[] = [
|
|
152
|
-
{ label: 'View Routes', icon: 'lucide:arrow-right', event: 'navigate', detail: { view: 'routes' } },
|
|
152
|
+
{ label: 'View Routes', icon: 'lucide:arrow-right', event: 'navigate', detail: { view: 'network', subview: 'routes' } },
|
|
153
153
|
];
|
|
154
154
|
|
|
155
155
|
return html`
|
|
@@ -220,6 +220,7 @@ export class OpsViewRemoteIngress extends DeesElement {
|
|
|
220
220
|
.heading1=${'Edge Nodes'}
|
|
221
221
|
.heading2=${'Manage remote ingress edge registrations'}
|
|
222
222
|
.data=${this.riState.edges}
|
|
223
|
+
.showColumnFilters=${true}
|
|
223
224
|
.displayFunction=${(edge: interfaces.data.IRemoteIngress) => ({
|
|
224
225
|
name: edge.name,
|
|
225
226
|
status: this.getEdgeStatusHtml(edge),
|
|
@@ -305,6 +305,7 @@ export class OpsViewVpn extends DeesElement {
|
|
|
305
305
|
.heading1=${'VPN Clients'}
|
|
306
306
|
.heading2=${'Manage WireGuard and SmartVPN client registrations'}
|
|
307
307
|
.data=${clients}
|
|
308
|
+
.showColumnFilters=${true}
|
|
308
309
|
.displayFunction=${(client: interfaces.data.IVpnClient) => {
|
|
309
310
|
const conn = this.getConnectedInfo(client);
|
|
310
311
|
let statusHtml;
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
import * as appstate from '../../appstate.js';
|
|
2
|
+
|
|
3
|
+
import {
|
|
4
|
+
DeesElement,
|
|
5
|
+
customElement,
|
|
6
|
+
html,
|
|
7
|
+
state,
|
|
8
|
+
css,
|
|
9
|
+
cssManager,
|
|
10
|
+
type TemplateResult,
|
|
11
|
+
} from '@design.estate/dees-element';
|
|
12
|
+
import { type IStatsTile } from '@design.estate/dees-catalog';
|
|
13
|
+
|
|
14
|
+
declare global {
|
|
15
|
+
interface HTMLElementTagNameMap {
|
|
16
|
+
'ops-view-security-authentication': OpsViewSecurityAuthentication;
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
@customElement('ops-view-security-authentication')
|
|
21
|
+
export class OpsViewSecurityAuthentication extends DeesElement {
|
|
22
|
+
@state()
|
|
23
|
+
accessor statsState: appstate.IStatsState = appstate.statsStatePart.getState()!;
|
|
24
|
+
|
|
25
|
+
constructor() {
|
|
26
|
+
super();
|
|
27
|
+
const sub = appstate.statsStatePart
|
|
28
|
+
.select((s) => s)
|
|
29
|
+
.subscribe((s) => {
|
|
30
|
+
this.statsState = s;
|
|
31
|
+
});
|
|
32
|
+
this.rxSubscriptions.push(sub);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
public static styles = [
|
|
36
|
+
cssManager.defaultStyles,
|
|
37
|
+
css`
|
|
38
|
+
:host { display: block; }
|
|
39
|
+
h2 {
|
|
40
|
+
margin: 32px 0 16px 0;
|
|
41
|
+
font-size: 24px;
|
|
42
|
+
font-weight: 600;
|
|
43
|
+
color: ${cssManager.bdTheme('#333', '#ccc')};
|
|
44
|
+
}
|
|
45
|
+
dees-statsgrid {
|
|
46
|
+
margin-bottom: 32px;
|
|
47
|
+
}
|
|
48
|
+
`,
|
|
49
|
+
];
|
|
50
|
+
|
|
51
|
+
public render(): TemplateResult {
|
|
52
|
+
const metrics = this.statsState.securityMetrics;
|
|
53
|
+
|
|
54
|
+
if (!metrics) {
|
|
55
|
+
return html`
|
|
56
|
+
<div class="loadingMessage">
|
|
57
|
+
<p>Loading security metrics...</p>
|
|
58
|
+
</div>
|
|
59
|
+
`;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
// Derive auth events from recentEvents
|
|
63
|
+
const allEvents: any[] = metrics.recentEvents || [];
|
|
64
|
+
const authEvents = allEvents.filter((evt: any) => evt.type === 'authentication');
|
|
65
|
+
const successfulLogins = authEvents.filter((evt: any) => evt.success === true).length;
|
|
66
|
+
|
|
67
|
+
const tiles: IStatsTile[] = [
|
|
68
|
+
{
|
|
69
|
+
id: 'authFailures',
|
|
70
|
+
title: 'Authentication Failures',
|
|
71
|
+
value: metrics.authenticationFailures,
|
|
72
|
+
type: 'number',
|
|
73
|
+
icon: 'lucide:LockOpen',
|
|
74
|
+
color: metrics.authenticationFailures > 10 ? '#ef4444' : '#f59e0b',
|
|
75
|
+
description: 'Failed authentication attempts today',
|
|
76
|
+
},
|
|
77
|
+
{
|
|
78
|
+
id: 'successfulLogins',
|
|
79
|
+
title: 'Successful Logins',
|
|
80
|
+
value: successfulLogins,
|
|
81
|
+
type: 'number',
|
|
82
|
+
icon: 'lucide:Lock',
|
|
83
|
+
color: '#22c55e',
|
|
84
|
+
description: 'Successful logins today',
|
|
85
|
+
},
|
|
86
|
+
];
|
|
87
|
+
|
|
88
|
+
// Map auth events to login history table data
|
|
89
|
+
const loginHistory = authEvents.map((evt: any) => ({
|
|
90
|
+
timestamp: evt.timestamp,
|
|
91
|
+
username: evt.details?.username || 'unknown',
|
|
92
|
+
ipAddress: evt.ipAddress || 'unknown',
|
|
93
|
+
success: evt.success ?? false,
|
|
94
|
+
reason: evt.success ? '' : evt.message || 'Authentication failed',
|
|
95
|
+
}));
|
|
96
|
+
|
|
97
|
+
return html`
|
|
98
|
+
<dees-heading level="hr">Authentication</dees-heading>
|
|
99
|
+
|
|
100
|
+
<dees-statsgrid
|
|
101
|
+
.tiles=${tiles}
|
|
102
|
+
.minTileWidth=${200}
|
|
103
|
+
></dees-statsgrid>
|
|
104
|
+
|
|
105
|
+
<h2>Recent Login Attempts</h2>
|
|
106
|
+
<dees-table
|
|
107
|
+
.heading1=${'Login History'}
|
|
108
|
+
.heading2=${'Recent authentication attempts'}
|
|
109
|
+
.data=${loginHistory}
|
|
110
|
+
.displayFunction=${(item) => ({
|
|
111
|
+
'Time': new Date(item.timestamp).toLocaleString(),
|
|
112
|
+
'Username': item.username,
|
|
113
|
+
'IP Address': item.ipAddress,
|
|
114
|
+
'Status': item.success ? 'Success' : 'Failed',
|
|
115
|
+
'Reason': item.reason || '-',
|
|
116
|
+
})}
|
|
117
|
+
></dees-table>
|
|
118
|
+
`;
|
|
119
|
+
}
|
|
120
|
+
}
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
import * as appstate from '../../appstate.js';
|
|
2
|
+
|
|
3
|
+
import {
|
|
4
|
+
DeesElement,
|
|
5
|
+
customElement,
|
|
6
|
+
html,
|
|
7
|
+
state,
|
|
8
|
+
css,
|
|
9
|
+
cssManager,
|
|
10
|
+
type TemplateResult,
|
|
11
|
+
} from '@design.estate/dees-element';
|
|
12
|
+
import { type IStatsTile } from '@design.estate/dees-catalog';
|
|
13
|
+
|
|
14
|
+
declare global {
|
|
15
|
+
interface HTMLElementTagNameMap {
|
|
16
|
+
'ops-view-security-blocked': OpsViewSecurityBlocked;
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
@customElement('ops-view-security-blocked')
|
|
21
|
+
export class OpsViewSecurityBlocked extends DeesElement {
|
|
22
|
+
@state()
|
|
23
|
+
accessor statsState: appstate.IStatsState = appstate.statsStatePart.getState()!;
|
|
24
|
+
|
|
25
|
+
constructor() {
|
|
26
|
+
super();
|
|
27
|
+
const sub = appstate.statsStatePart
|
|
28
|
+
.select((s) => s)
|
|
29
|
+
.subscribe((s) => {
|
|
30
|
+
this.statsState = s;
|
|
31
|
+
});
|
|
32
|
+
this.rxSubscriptions.push(sub);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
public static styles = [
|
|
36
|
+
cssManager.defaultStyles,
|
|
37
|
+
css`
|
|
38
|
+
:host { display: block; }
|
|
39
|
+
dees-statsgrid {
|
|
40
|
+
margin-bottom: 32px;
|
|
41
|
+
}
|
|
42
|
+
`,
|
|
43
|
+
];
|
|
44
|
+
|
|
45
|
+
public render(): TemplateResult {
|
|
46
|
+
const metrics = this.statsState.securityMetrics;
|
|
47
|
+
|
|
48
|
+
if (!metrics) {
|
|
49
|
+
return html`
|
|
50
|
+
<div class="loadingMessage">
|
|
51
|
+
<p>Loading security metrics...</p>
|
|
52
|
+
</div>
|
|
53
|
+
`;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
const blockedIPs: string[] = metrics.blockedIPs || [];
|
|
57
|
+
|
|
58
|
+
const tiles: IStatsTile[] = [
|
|
59
|
+
{
|
|
60
|
+
id: 'totalBlocked',
|
|
61
|
+
title: 'Blocked IPs',
|
|
62
|
+
value: blockedIPs.length,
|
|
63
|
+
type: 'number',
|
|
64
|
+
icon: 'lucide:ShieldBan',
|
|
65
|
+
color: blockedIPs.length > 0 ? '#ef4444' : '#22c55e',
|
|
66
|
+
description: 'Currently blocked addresses',
|
|
67
|
+
},
|
|
68
|
+
];
|
|
69
|
+
|
|
70
|
+
return html`
|
|
71
|
+
<dees-heading level="hr">Blocked IPs</dees-heading>
|
|
72
|
+
|
|
73
|
+
<dees-statsgrid
|
|
74
|
+
.tiles=${tiles}
|
|
75
|
+
.minTileWidth=${200}
|
|
76
|
+
></dees-statsgrid>
|
|
77
|
+
|
|
78
|
+
<dees-table
|
|
79
|
+
.heading1=${'Blocked IP Addresses'}
|
|
80
|
+
.heading2=${'IPs blocked due to suspicious activity'}
|
|
81
|
+
.data=${blockedIPs.map((ip) => ({ ip }))}
|
|
82
|
+
.displayFunction=${(item) => ({
|
|
83
|
+
'IP Address': item.ip,
|
|
84
|
+
'Reason': 'Suspicious activity',
|
|
85
|
+
})}
|
|
86
|
+
.dataActions=${[
|
|
87
|
+
{
|
|
88
|
+
name: 'Unblock',
|
|
89
|
+
iconName: 'lucide:shield-off',
|
|
90
|
+
type: ['contextmenu' as const],
|
|
91
|
+
actionFunc: async (item) => {
|
|
92
|
+
await this.unblockIP(item.ip);
|
|
93
|
+
},
|
|
94
|
+
},
|
|
95
|
+
{
|
|
96
|
+
name: 'Clear All',
|
|
97
|
+
iconName: 'lucide:trash-2',
|
|
98
|
+
type: ['header' as const],
|
|
99
|
+
actionFunc: async () => {
|
|
100
|
+
await this.clearBlockedIPs();
|
|
101
|
+
},
|
|
102
|
+
},
|
|
103
|
+
]}
|
|
104
|
+
></dees-table>
|
|
105
|
+
`;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
private async clearBlockedIPs() {
|
|
109
|
+
// SmartProxy manages IP blocking — not yet exposed via API
|
|
110
|
+
alert('Clearing blocked IPs is not yet supported from the UI.');
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
private async unblockIP(ip: string) {
|
|
114
|
+
// SmartProxy manages IP blocking — not yet exposed via API
|
|
115
|
+
alert(`Unblocking IP ${ip} is not yet supported from the UI.`);
|
|
116
|
+
}
|
|
117
|
+
}
|
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
import * as appstate from '../../appstate.js';
|
|
2
|
+
|
|
3
|
+
import {
|
|
4
|
+
DeesElement,
|
|
5
|
+
customElement,
|
|
6
|
+
html,
|
|
7
|
+
state,
|
|
8
|
+
css,
|
|
9
|
+
cssManager,
|
|
10
|
+
type TemplateResult,
|
|
11
|
+
} from '@design.estate/dees-element';
|
|
12
|
+
import { type IStatsTile } from '@design.estate/dees-catalog';
|
|
13
|
+
|
|
14
|
+
declare global {
|
|
15
|
+
interface HTMLElementTagNameMap {
|
|
16
|
+
'ops-view-security-emailsecurity': OpsViewSecurityEmailsecurity;
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
@customElement('ops-view-security-emailsecurity')
|
|
21
|
+
export class OpsViewSecurityEmailsecurity extends DeesElement {
|
|
22
|
+
@state()
|
|
23
|
+
accessor statsState: appstate.IStatsState = appstate.statsStatePart.getState()!;
|
|
24
|
+
|
|
25
|
+
constructor() {
|
|
26
|
+
super();
|
|
27
|
+
const sub = appstate.statsStatePart
|
|
28
|
+
.select((s) => s)
|
|
29
|
+
.subscribe((s) => {
|
|
30
|
+
this.statsState = s;
|
|
31
|
+
});
|
|
32
|
+
this.rxSubscriptions.push(sub);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
public static styles = [
|
|
36
|
+
cssManager.defaultStyles,
|
|
37
|
+
css`
|
|
38
|
+
:host { display: block; }
|
|
39
|
+
h2 {
|
|
40
|
+
margin: 32px 0 16px 0;
|
|
41
|
+
font-size: 24px;
|
|
42
|
+
font-weight: 600;
|
|
43
|
+
color: ${cssManager.bdTheme('#333', '#ccc')};
|
|
44
|
+
}
|
|
45
|
+
dees-statsgrid {
|
|
46
|
+
margin-bottom: 32px;
|
|
47
|
+
}
|
|
48
|
+
.securityCard {
|
|
49
|
+
background: ${cssManager.bdTheme('#fff', '#222')};
|
|
50
|
+
border: 1px solid ${cssManager.bdTheme('#e9ecef', '#333')};
|
|
51
|
+
border-radius: 8px;
|
|
52
|
+
padding: 24px;
|
|
53
|
+
position: relative;
|
|
54
|
+
overflow: hidden;
|
|
55
|
+
}
|
|
56
|
+
.actionButton {
|
|
57
|
+
margin-top: 16px;
|
|
58
|
+
}
|
|
59
|
+
`,
|
|
60
|
+
];
|
|
61
|
+
|
|
62
|
+
public render(): TemplateResult {
|
|
63
|
+
const metrics = this.statsState.securityMetrics;
|
|
64
|
+
|
|
65
|
+
if (!metrics) {
|
|
66
|
+
return html`
|
|
67
|
+
<div class="loadingMessage">
|
|
68
|
+
<p>Loading security metrics...</p>
|
|
69
|
+
</div>
|
|
70
|
+
`;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
const tiles: IStatsTile[] = [
|
|
74
|
+
{
|
|
75
|
+
id: 'malware',
|
|
76
|
+
title: 'Malware Detection',
|
|
77
|
+
value: metrics.malwareDetected,
|
|
78
|
+
type: 'number',
|
|
79
|
+
icon: 'lucide:BugOff',
|
|
80
|
+
color: metrics.malwareDetected > 0 ? '#ef4444' : '#22c55e',
|
|
81
|
+
description: 'Malware detected',
|
|
82
|
+
},
|
|
83
|
+
{
|
|
84
|
+
id: 'phishing',
|
|
85
|
+
title: 'Phishing Detection',
|
|
86
|
+
value: metrics.phishingDetected,
|
|
87
|
+
type: 'number',
|
|
88
|
+
icon: 'lucide:Fish',
|
|
89
|
+
color: metrics.phishingDetected > 0 ? '#ef4444' : '#22c55e',
|
|
90
|
+
description: 'Phishing attempts detected',
|
|
91
|
+
},
|
|
92
|
+
{
|
|
93
|
+
id: 'suspicious',
|
|
94
|
+
title: 'Suspicious Activities',
|
|
95
|
+
value: metrics.suspiciousActivities,
|
|
96
|
+
type: 'number',
|
|
97
|
+
icon: 'lucide:TriangleAlert',
|
|
98
|
+
color: metrics.suspiciousActivities > 5 ? '#ef4444' : '#f59e0b',
|
|
99
|
+
description: 'Suspicious activities detected',
|
|
100
|
+
},
|
|
101
|
+
{
|
|
102
|
+
id: 'spam',
|
|
103
|
+
title: 'Spam Detection',
|
|
104
|
+
value: metrics.spamDetected,
|
|
105
|
+
type: 'number',
|
|
106
|
+
icon: 'lucide:Ban',
|
|
107
|
+
color: '#f59e0b',
|
|
108
|
+
description: 'Spam emails blocked',
|
|
109
|
+
},
|
|
110
|
+
];
|
|
111
|
+
|
|
112
|
+
return html`
|
|
113
|
+
<dees-heading level="hr">Email Security</dees-heading>
|
|
114
|
+
|
|
115
|
+
<dees-statsgrid
|
|
116
|
+
.tiles=${tiles}
|
|
117
|
+
.minTileWidth=${200}
|
|
118
|
+
></dees-statsgrid>
|
|
119
|
+
|
|
120
|
+
<h2>Email Security Configuration</h2>
|
|
121
|
+
<div class="securityCard">
|
|
122
|
+
<dees-form>
|
|
123
|
+
<dees-input-checkbox
|
|
124
|
+
.key=${'enableSPF'}
|
|
125
|
+
.label=${'Enable SPF checking'}
|
|
126
|
+
.value=${true}
|
|
127
|
+
></dees-input-checkbox>
|
|
128
|
+
<dees-input-checkbox
|
|
129
|
+
.key=${'enableDKIM'}
|
|
130
|
+
.label=${'Enable DKIM validation'}
|
|
131
|
+
.value=${true}
|
|
132
|
+
></dees-input-checkbox>
|
|
133
|
+
<dees-input-checkbox
|
|
134
|
+
.key=${'enableDMARC'}
|
|
135
|
+
.label=${'Enable DMARC policy enforcement'}
|
|
136
|
+
.value=${true}
|
|
137
|
+
></dees-input-checkbox>
|
|
138
|
+
<dees-input-checkbox
|
|
139
|
+
.key=${'enableSpamFilter'}
|
|
140
|
+
.label=${'Enable spam filtering'}
|
|
141
|
+
.value=${true}
|
|
142
|
+
></dees-input-checkbox>
|
|
143
|
+
</dees-form>
|
|
144
|
+
<dees-button
|
|
145
|
+
class="actionButton"
|
|
146
|
+
type="highlighted"
|
|
147
|
+
@click=${() => this.saveEmailSecuritySettings()}
|
|
148
|
+
>
|
|
149
|
+
Save Settings
|
|
150
|
+
</dees-button>
|
|
151
|
+
</div>
|
|
152
|
+
`;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
private async saveEmailSecuritySettings() {
|
|
156
|
+
// Config is read-only from the UI for now
|
|
157
|
+
alert('Email security settings are read-only. Update the dcrouter configuration file to change these settings.');
|
|
158
|
+
}
|
|
159
|
+
}
|
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
import * as appstate from '../../appstate.js';
|
|
2
|
+
|
|
3
|
+
import {
|
|
4
|
+
DeesElement,
|
|
5
|
+
customElement,
|
|
6
|
+
html,
|
|
7
|
+
state,
|
|
8
|
+
css,
|
|
9
|
+
cssManager,
|
|
10
|
+
type TemplateResult,
|
|
11
|
+
} from '@design.estate/dees-element';
|
|
12
|
+
import { type IStatsTile } from '@design.estate/dees-catalog';
|
|
13
|
+
|
|
14
|
+
declare global {
|
|
15
|
+
interface HTMLElementTagNameMap {
|
|
16
|
+
'ops-view-security-overview': OpsViewSecurityOverview;
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
@customElement('ops-view-security-overview')
|
|
21
|
+
export class OpsViewSecurityOverview extends DeesElement {
|
|
22
|
+
@state()
|
|
23
|
+
accessor statsState: appstate.IStatsState = appstate.statsStatePart.getState()!;
|
|
24
|
+
|
|
25
|
+
constructor() {
|
|
26
|
+
super();
|
|
27
|
+
const sub = appstate.statsStatePart
|
|
28
|
+
.select((s) => s)
|
|
29
|
+
.subscribe((s) => {
|
|
30
|
+
this.statsState = s;
|
|
31
|
+
});
|
|
32
|
+
this.rxSubscriptions.push(sub);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
public static styles = [
|
|
36
|
+
cssManager.defaultStyles,
|
|
37
|
+
css`
|
|
38
|
+
:host { display: block; }
|
|
39
|
+
h2 {
|
|
40
|
+
margin: 32px 0 16px 0;
|
|
41
|
+
font-size: 24px;
|
|
42
|
+
font-weight: 600;
|
|
43
|
+
color: ${cssManager.bdTheme('#333', '#ccc')};
|
|
44
|
+
}
|
|
45
|
+
dees-statsgrid {
|
|
46
|
+
margin-bottom: 32px;
|
|
47
|
+
}
|
|
48
|
+
`,
|
|
49
|
+
];
|
|
50
|
+
|
|
51
|
+
public render(): TemplateResult {
|
|
52
|
+
const metrics = this.statsState.securityMetrics;
|
|
53
|
+
|
|
54
|
+
if (!metrics) {
|
|
55
|
+
return html`
|
|
56
|
+
<div class="loadingMessage">
|
|
57
|
+
<p>Loading security metrics...</p>
|
|
58
|
+
</div>
|
|
59
|
+
`;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
const threatLevel = this.calculateThreatLevel(metrics);
|
|
63
|
+
const threatScore = this.getThreatScore(metrics);
|
|
64
|
+
|
|
65
|
+
// Derive active sessions from recent successful auth events (last hour)
|
|
66
|
+
const allEvents: any[] = metrics.recentEvents || [];
|
|
67
|
+
const oneHourAgo = Date.now() - 3600000;
|
|
68
|
+
const recentAuthSuccesses = allEvents.filter(
|
|
69
|
+
(evt: any) => evt.type === 'authentication' && evt.success === true && evt.timestamp >= oneHourAgo
|
|
70
|
+
).length;
|
|
71
|
+
|
|
72
|
+
const tiles: IStatsTile[] = [
|
|
73
|
+
{
|
|
74
|
+
id: 'threatLevel',
|
|
75
|
+
title: 'Threat Level',
|
|
76
|
+
value: threatScore,
|
|
77
|
+
type: 'gauge',
|
|
78
|
+
icon: 'lucide:Shield',
|
|
79
|
+
gaugeOptions: {
|
|
80
|
+
min: 0,
|
|
81
|
+
max: 100,
|
|
82
|
+
thresholds: [
|
|
83
|
+
{ value: 0, color: '#ef4444' },
|
|
84
|
+
{ value: 30, color: '#f59e0b' },
|
|
85
|
+
{ value: 70, color: '#22c55e' },
|
|
86
|
+
],
|
|
87
|
+
},
|
|
88
|
+
description: `Status: ${threatLevel.toUpperCase()}`,
|
|
89
|
+
},
|
|
90
|
+
{
|
|
91
|
+
id: 'blockedThreats',
|
|
92
|
+
title: 'Blocked Threats',
|
|
93
|
+
value: (metrics.blockedIPs?.length || 0) + metrics.spamDetected,
|
|
94
|
+
type: 'number',
|
|
95
|
+
icon: 'lucide:ShieldCheck',
|
|
96
|
+
color: '#ef4444',
|
|
97
|
+
description: 'Total threats blocked today',
|
|
98
|
+
},
|
|
99
|
+
{
|
|
100
|
+
id: 'activeSessions',
|
|
101
|
+
title: 'Active Sessions',
|
|
102
|
+
value: recentAuthSuccesses,
|
|
103
|
+
type: 'number',
|
|
104
|
+
icon: 'lucide:Users',
|
|
105
|
+
color: '#22c55e',
|
|
106
|
+
description: 'Authenticated in last hour',
|
|
107
|
+
},
|
|
108
|
+
{
|
|
109
|
+
id: 'authFailures',
|
|
110
|
+
title: 'Auth Failures',
|
|
111
|
+
value: metrics.authenticationFailures,
|
|
112
|
+
type: 'number',
|
|
113
|
+
icon: 'lucide:LockOpen',
|
|
114
|
+
color: metrics.authenticationFailures > 10 ? '#ef4444' : '#f59e0b',
|
|
115
|
+
description: 'Failed login attempts today',
|
|
116
|
+
},
|
|
117
|
+
];
|
|
118
|
+
|
|
119
|
+
return html`
|
|
120
|
+
<dees-heading level="hr">Overview</dees-heading>
|
|
121
|
+
|
|
122
|
+
<dees-statsgrid
|
|
123
|
+
.tiles=${tiles}
|
|
124
|
+
.minTileWidth=${200}
|
|
125
|
+
></dees-statsgrid>
|
|
126
|
+
|
|
127
|
+
<h2>Recent Security Events</h2>
|
|
128
|
+
<dees-table
|
|
129
|
+
.heading1=${'Security Events'}
|
|
130
|
+
.heading2=${'Last 24 hours'}
|
|
131
|
+
.data=${this.getSecurityEvents(metrics)}
|
|
132
|
+
.displayFunction=${(item) => ({
|
|
133
|
+
'Time': new Date(item.timestamp).toLocaleTimeString(),
|
|
134
|
+
'Event': item.event,
|
|
135
|
+
'Severity': item.severity,
|
|
136
|
+
'Details': item.details,
|
|
137
|
+
})}
|
|
138
|
+
></dees-table>
|
|
139
|
+
`;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
private calculateThreatLevel(metrics: any): string {
|
|
143
|
+
const score = this.getThreatScore(metrics);
|
|
144
|
+
if (score < 30) return 'alert';
|
|
145
|
+
if (score < 70) return 'warning';
|
|
146
|
+
return 'success';
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
private getThreatScore(metrics: any): number {
|
|
150
|
+
// Simple scoring algorithm
|
|
151
|
+
let score = 100;
|
|
152
|
+
const blockedCount = Array.isArray(metrics.blockedIPs) ? metrics.blockedIPs.length : (metrics.blockedIPs || 0);
|
|
153
|
+
score -= blockedCount * 2;
|
|
154
|
+
score -= (metrics.authenticationFailures || 0) * 1;
|
|
155
|
+
score -= (metrics.spamDetected || 0) * 0.5;
|
|
156
|
+
score -= (metrics.malwareDetected || 0) * 3;
|
|
157
|
+
score -= (metrics.phishingDetected || 0) * 3;
|
|
158
|
+
score -= (metrics.suspiciousActivities || 0) * 2;
|
|
159
|
+
return Math.max(0, Math.min(100, Math.round(score)));
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
private getSecurityEvents(metrics: any): any[] {
|
|
163
|
+
const events: any[] = metrics.recentEvents || [];
|
|
164
|
+
return events.map((evt: any) => ({
|
|
165
|
+
timestamp: evt.timestamp,
|
|
166
|
+
event: evt.message,
|
|
167
|
+
severity: evt.level === 'critical' ? 'critical' : evt.level === 'error' ? 'high' : evt.level === 'warn' ? 'warning' : 'info',
|
|
168
|
+
details: evt.ipAddress ? `IP: ${evt.ipAddress}` : evt.domain ? `Domain: ${evt.domain}` : evt.type,
|
|
169
|
+
}));
|
|
170
|
+
}
|
|
171
|
+
}
|