@serve.zone/dcrouter 13.2.2 → 13.4.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 +1499 -1413
- 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 +14 -38
- package/dist_ts_web/elements/access/index.d.ts +1 -0
- package/dist_ts_web/elements/access/index.js +2 -0
- package/dist_ts_web/elements/{ops-view-apitokens.d.ts → access/ops-view-apitokens.d.ts} +1 -1
- package/dist_ts_web/elements/{ops-view-apitokens.js → access/ops-view-apitokens.js} +4 -4
- package/dist_ts_web/elements/email/index.d.ts +2 -0
- package/dist_ts_web/elements/email/index.js +3 -0
- package/dist_ts_web/elements/email/ops-view-email-security.d.ts +14 -0
- package/dist_ts_web/elements/email/ops-view-email-security.js +197 -0
- package/dist_ts_web/elements/{ops-view-emails.d.ts → email/ops-view-emails.d.ts} +2 -2
- package/dist_ts_web/elements/{ops-view-emails.js → email/ops-view-emails.js} +5 -5
- package/dist_ts_web/elements/index.d.ts +5 -12
- package/dist_ts_web/elements/index.js +6 -13
- package/dist_ts_web/elements/network/index.d.ts +7 -0
- package/dist_ts_web/elements/network/index.js +8 -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 -32
- 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} +5 -5
- package/dist_ts_web/elements/{ops-view-remoteingress.d.ts → network/ops-view-remoteingress.d.ts} +1 -1
- package/dist_ts_web/elements/{ops-view-remoteingress.js → network/ops-view-remoteingress.js} +5 -5
- 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 -5
- 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} +5 -5
- 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} +6 -6
- package/dist_ts_web/elements/{ops-view-vpn.d.ts → network/ops-view-vpn.d.ts} +2 -2
- package/dist_ts_web/elements/{ops-view-vpn.js → network/ops-view-vpn.js} +6 -6
- package/dist_ts_web/elements/ops-dashboard.d.ts +8 -2
- package/dist_ts_web/elements/ops-dashboard.js +101 -83
- package/dist_ts_web/elements/overview/index.d.ts +2 -0
- package/dist_ts_web/elements/overview/index.js +3 -0
- package/dist_ts_web/elements/{ops-view-config.d.ts → overview/ops-view-config.d.ts} +2 -2
- package/dist_ts_web/elements/{ops-view-config.js → overview/ops-view-config.js} +9 -9
- package/dist_ts_web/elements/{ops-view-overview.d.ts → overview/ops-view-overview.d.ts} +2 -2
- package/dist_ts_web/elements/{ops-view-overview.js → overview/ops-view-overview.js} +4 -4
- package/dist_ts_web/elements/security/index.d.ts +3 -0
- package/dist_ts_web/elements/security/index.js +4 -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 +157 -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 +153 -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 +205 -0
- package/dist_ts_web/router.d.ts +5 -3
- package/dist_ts_web/router.js +75 -17
- package/package.json +2 -2
- package/ts/00_commitinfo_data.ts +1 -1
- package/ts_web/00_commitinfo_data.ts +1 -1
- package/ts_web/appstate.ts +15 -42
- package/ts_web/elements/access/index.ts +1 -0
- package/ts_web/elements/{ops-view-apitokens.ts → access/ops-view-apitokens.ts} +3 -3
- package/ts_web/elements/email/index.ts +2 -0
- package/ts_web/elements/email/ops-view-email-security.ts +160 -0
- package/ts_web/elements/{ops-view-emails.ts → email/ops-view-emails.ts} +4 -4
- package/ts_web/elements/index.ts +6 -13
- package/ts_web/elements/network/index.ts +7 -0
- package/ts_web/elements/{ops-view-network.ts → network/ops-view-network-activity.ts} +43 -55
- package/ts_web/elements/{ops-view-networktargets.ts → network/ops-view-networktargets.ts} +4 -4
- package/ts_web/elements/{ops-view-remoteingress.ts → network/ops-view-remoteingress.ts} +4 -4
- package/ts_web/elements/{ops-view-routes.ts → network/ops-view-routes.ts} +4 -4
- package/ts_web/elements/{ops-view-sourceprofiles.ts → network/ops-view-sourceprofiles.ts} +4 -4
- package/ts_web/elements/{ops-view-targetprofiles.ts → network/ops-view-targetprofiles.ts} +5 -5
- package/ts_web/elements/{ops-view-vpn.ts → network/ops-view-vpn.ts} +5 -5
- package/ts_web/elements/ops-dashboard.ts +125 -90
- package/ts_web/elements/overview/index.ts +2 -0
- package/ts_web/elements/{ops-view-config.ts → overview/ops-view-config.ts} +8 -8
- package/ts_web/elements/{ops-view-overview.ts → overview/ops-view-overview.ts} +3 -3
- package/ts_web/elements/security/index.ts +3 -0
- package/ts_web/elements/security/ops-view-security-authentication.ts +121 -0
- package/ts_web/elements/security/ops-view-security-blocked.ts +118 -0
- package/ts_web/elements/security/ops-view-security-overview.ts +172 -0
- package/ts_web/router.ts +81 -17
- package/dist_ts_web/elements/ops-view-security.d.ts +0 -24
- package/dist_ts_web/elements/ops-view-security.js +0 -484
- package/ts_web/elements/ops-view-security.ts +0 -456
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
import * as appstate from '../../appstate.js';
|
|
2
|
+
import { viewHostCss } from '../shared/css.js';
|
|
3
|
+
|
|
4
|
+
import {
|
|
5
|
+
DeesElement,
|
|
6
|
+
customElement,
|
|
7
|
+
html,
|
|
8
|
+
state,
|
|
9
|
+
css,
|
|
10
|
+
cssManager,
|
|
11
|
+
type TemplateResult,
|
|
12
|
+
} from '@design.estate/dees-element';
|
|
13
|
+
import { type IStatsTile } from '@design.estate/dees-catalog';
|
|
14
|
+
|
|
15
|
+
declare global {
|
|
16
|
+
interface HTMLElementTagNameMap {
|
|
17
|
+
'ops-view-security-overview': OpsViewSecurityOverview;
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
@customElement('ops-view-security-overview')
|
|
22
|
+
export class OpsViewSecurityOverview extends DeesElement {
|
|
23
|
+
@state()
|
|
24
|
+
accessor statsState: appstate.IStatsState = appstate.statsStatePart.getState()!;
|
|
25
|
+
|
|
26
|
+
constructor() {
|
|
27
|
+
super();
|
|
28
|
+
const sub = appstate.statsStatePart
|
|
29
|
+
.select((s) => s)
|
|
30
|
+
.subscribe((s) => {
|
|
31
|
+
this.statsState = s;
|
|
32
|
+
});
|
|
33
|
+
this.rxSubscriptions.push(sub);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
public static styles = [
|
|
37
|
+
cssManager.defaultStyles,
|
|
38
|
+
viewHostCss,
|
|
39
|
+
css`
|
|
40
|
+
h2 {
|
|
41
|
+
margin: 32px 0 16px 0;
|
|
42
|
+
font-size: 24px;
|
|
43
|
+
font-weight: 600;
|
|
44
|
+
color: ${cssManager.bdTheme('#333', '#ccc')};
|
|
45
|
+
}
|
|
46
|
+
dees-statsgrid {
|
|
47
|
+
margin-bottom: 32px;
|
|
48
|
+
}
|
|
49
|
+
`,
|
|
50
|
+
];
|
|
51
|
+
|
|
52
|
+
public render(): TemplateResult {
|
|
53
|
+
const metrics = this.statsState.securityMetrics;
|
|
54
|
+
|
|
55
|
+
if (!metrics) {
|
|
56
|
+
return html`
|
|
57
|
+
<div class="loadingMessage">
|
|
58
|
+
<p>Loading security metrics...</p>
|
|
59
|
+
</div>
|
|
60
|
+
`;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
const threatLevel = this.calculateThreatLevel(metrics);
|
|
64
|
+
const threatScore = this.getThreatScore(metrics);
|
|
65
|
+
|
|
66
|
+
// Derive active sessions from recent successful auth events (last hour)
|
|
67
|
+
const allEvents: any[] = metrics.recentEvents || [];
|
|
68
|
+
const oneHourAgo = Date.now() - 3600000;
|
|
69
|
+
const recentAuthSuccesses = allEvents.filter(
|
|
70
|
+
(evt: any) => evt.type === 'authentication' && evt.success === true && evt.timestamp >= oneHourAgo
|
|
71
|
+
).length;
|
|
72
|
+
|
|
73
|
+
const tiles: IStatsTile[] = [
|
|
74
|
+
{
|
|
75
|
+
id: 'threatLevel',
|
|
76
|
+
title: 'Threat Level',
|
|
77
|
+
value: threatScore,
|
|
78
|
+
type: 'gauge',
|
|
79
|
+
icon: 'lucide:Shield',
|
|
80
|
+
gaugeOptions: {
|
|
81
|
+
min: 0,
|
|
82
|
+
max: 100,
|
|
83
|
+
thresholds: [
|
|
84
|
+
{ value: 0, color: '#ef4444' },
|
|
85
|
+
{ value: 30, color: '#f59e0b' },
|
|
86
|
+
{ value: 70, color: '#22c55e' },
|
|
87
|
+
],
|
|
88
|
+
},
|
|
89
|
+
description: `Status: ${threatLevel.toUpperCase()}`,
|
|
90
|
+
},
|
|
91
|
+
{
|
|
92
|
+
id: 'blockedThreats',
|
|
93
|
+
title: 'Blocked Threats',
|
|
94
|
+
value: (metrics.blockedIPs?.length || 0) + metrics.spamDetected,
|
|
95
|
+
type: 'number',
|
|
96
|
+
icon: 'lucide:ShieldCheck',
|
|
97
|
+
color: '#ef4444',
|
|
98
|
+
description: 'Total threats blocked today',
|
|
99
|
+
},
|
|
100
|
+
{
|
|
101
|
+
id: 'activeSessions',
|
|
102
|
+
title: 'Active Sessions',
|
|
103
|
+
value: recentAuthSuccesses,
|
|
104
|
+
type: 'number',
|
|
105
|
+
icon: 'lucide:Users',
|
|
106
|
+
color: '#22c55e',
|
|
107
|
+
description: 'Authenticated in last hour',
|
|
108
|
+
},
|
|
109
|
+
{
|
|
110
|
+
id: 'authFailures',
|
|
111
|
+
title: 'Auth Failures',
|
|
112
|
+
value: metrics.authenticationFailures,
|
|
113
|
+
type: 'number',
|
|
114
|
+
icon: 'lucide:LockOpen',
|
|
115
|
+
color: metrics.authenticationFailures > 10 ? '#ef4444' : '#f59e0b',
|
|
116
|
+
description: 'Failed login attempts today',
|
|
117
|
+
},
|
|
118
|
+
];
|
|
119
|
+
|
|
120
|
+
return html`
|
|
121
|
+
<dees-heading level="hr">Overview</dees-heading>
|
|
122
|
+
|
|
123
|
+
<dees-statsgrid
|
|
124
|
+
.tiles=${tiles}
|
|
125
|
+
.minTileWidth=${200}
|
|
126
|
+
></dees-statsgrid>
|
|
127
|
+
|
|
128
|
+
<h2>Recent Security Events</h2>
|
|
129
|
+
<dees-table
|
|
130
|
+
.heading1=${'Security Events'}
|
|
131
|
+
.heading2=${'Last 24 hours'}
|
|
132
|
+
.data=${this.getSecurityEvents(metrics)}
|
|
133
|
+
.displayFunction=${(item) => ({
|
|
134
|
+
'Time': new Date(item.timestamp).toLocaleTimeString(),
|
|
135
|
+
'Event': item.event,
|
|
136
|
+
'Severity': item.severity,
|
|
137
|
+
'Details': item.details,
|
|
138
|
+
})}
|
|
139
|
+
></dees-table>
|
|
140
|
+
`;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
private calculateThreatLevel(metrics: any): string {
|
|
144
|
+
const score = this.getThreatScore(metrics);
|
|
145
|
+
if (score < 30) return 'alert';
|
|
146
|
+
if (score < 70) return 'warning';
|
|
147
|
+
return 'success';
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
private getThreatScore(metrics: any): number {
|
|
151
|
+
// Simple scoring algorithm
|
|
152
|
+
let score = 100;
|
|
153
|
+
const blockedCount = Array.isArray(metrics.blockedIPs) ? metrics.blockedIPs.length : (metrics.blockedIPs || 0);
|
|
154
|
+
score -= blockedCount * 2;
|
|
155
|
+
score -= (metrics.authenticationFailures || 0) * 1;
|
|
156
|
+
score -= (metrics.spamDetected || 0) * 0.5;
|
|
157
|
+
score -= (metrics.malwareDetected || 0) * 3;
|
|
158
|
+
score -= (metrics.phishingDetected || 0) * 3;
|
|
159
|
+
score -= (metrics.suspiciousActivities || 0) * 2;
|
|
160
|
+
return Math.max(0, Math.min(100, Math.round(score)));
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
private getSecurityEvents(metrics: any): any[] {
|
|
164
|
+
const events: any[] = metrics.recentEvents || [];
|
|
165
|
+
return events.map((evt: any) => ({
|
|
166
|
+
timestamp: evt.timestamp,
|
|
167
|
+
event: evt.message,
|
|
168
|
+
severity: evt.level === 'critical' ? 'critical' : evt.level === 'error' ? 'high' : evt.level === 'warn' ? 'warning' : 'info',
|
|
169
|
+
details: evt.ipAddress ? `IP: ${evt.ipAddress}` : evt.domain ? `Domain: ${evt.domain}` : evt.type,
|
|
170
|
+
}));
|
|
171
|
+
}
|
|
172
|
+
}
|
package/ts_web/router.ts
CHANGED
|
@@ -3,9 +3,37 @@ import * as appstate from './appstate.js';
|
|
|
3
3
|
|
|
4
4
|
const SmartRouter = plugins.domtools.plugins.smartrouter.SmartRouter;
|
|
5
5
|
|
|
6
|
-
|
|
6
|
+
// Flat top-level views (no subviews)
|
|
7
|
+
const flatViews = ['logs', 'certificates'] as const;
|
|
8
|
+
|
|
9
|
+
// Tabbed views and their valid subviews
|
|
10
|
+
const subviewMap: Record<string, readonly string[]> = {
|
|
11
|
+
overview: ['stats', 'configuration'] as const,
|
|
12
|
+
network: ['activity', 'routes', 'sourceprofiles', 'networktargets', 'targetprofiles', 'remoteingress', 'vpn'] as const,
|
|
13
|
+
email: ['log', 'security'] as const,
|
|
14
|
+
access: ['apitokens'] as const,
|
|
15
|
+
security: ['overview', 'blocked', 'authentication'] as const,
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
// Default subview when user visits the bare parent URL
|
|
19
|
+
const defaultSubview: Record<string, string> = {
|
|
20
|
+
overview: 'stats',
|
|
21
|
+
network: 'activity',
|
|
22
|
+
email: 'log',
|
|
23
|
+
access: 'apitokens',
|
|
24
|
+
security: 'overview',
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
export const validTopLevelViews = [...flatViews, ...Object.keys(subviewMap)] as const;
|
|
28
|
+
export type TValidView = typeof validTopLevelViews[number];
|
|
29
|
+
|
|
30
|
+
export function isValidView(view: string): boolean {
|
|
31
|
+
return (validTopLevelViews as readonly string[]).includes(view);
|
|
32
|
+
}
|
|
7
33
|
|
|
8
|
-
export
|
|
34
|
+
export function isValidSubview(view: string, subview: string): boolean {
|
|
35
|
+
return subviewMap[view]?.includes(subview) ?? false;
|
|
36
|
+
}
|
|
9
37
|
|
|
10
38
|
class AppRouter {
|
|
11
39
|
private router: InstanceType<typeof SmartRouter>;
|
|
@@ -25,10 +53,25 @@ class AppRouter {
|
|
|
25
53
|
}
|
|
26
54
|
|
|
27
55
|
private setupRoutes(): void {
|
|
28
|
-
|
|
56
|
+
// Flat views
|
|
57
|
+
for (const view of flatViews) {
|
|
58
|
+
this.router.on(`/${view}`, async () => {
|
|
59
|
+
this.updateViewState(view, null);
|
|
60
|
+
});
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
// Tabbed views
|
|
64
|
+
for (const view of Object.keys(subviewMap)) {
|
|
65
|
+
// Bare parent → redirect to default subview
|
|
29
66
|
this.router.on(`/${view}`, async () => {
|
|
30
|
-
this.
|
|
67
|
+
this.navigateTo(`/${view}/${defaultSubview[view]}`);
|
|
31
68
|
});
|
|
69
|
+
// Each valid subview
|
|
70
|
+
for (const sub of subviewMap[view]) {
|
|
71
|
+
this.router.on(`/${view}/${sub}`, async () => {
|
|
72
|
+
this.updateViewState(view, sub);
|
|
73
|
+
});
|
|
74
|
+
}
|
|
32
75
|
}
|
|
33
76
|
|
|
34
77
|
// Root redirect
|
|
@@ -42,7 +85,9 @@ class AppRouter {
|
|
|
42
85
|
if (this.suppressStateUpdate) return;
|
|
43
86
|
|
|
44
87
|
const currentPath = window.location.pathname;
|
|
45
|
-
const expectedPath =
|
|
88
|
+
const expectedPath = uiState.activeSubview
|
|
89
|
+
? `/${uiState.activeView}/${uiState.activeSubview}`
|
|
90
|
+
: `/${uiState.activeView}`;
|
|
46
91
|
|
|
47
92
|
if (currentPath !== expectedPath) {
|
|
48
93
|
this.suppressStateUpdate = true;
|
|
@@ -57,25 +102,38 @@ class AppRouter {
|
|
|
57
102
|
|
|
58
103
|
if (!path || path === '/') {
|
|
59
104
|
this.router.pushUrl('/overview');
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
105
|
+
return;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
const segments = path.split('/').filter(Boolean);
|
|
109
|
+
const view = segments[0];
|
|
110
|
+
const sub = segments[1];
|
|
63
111
|
|
|
64
|
-
|
|
65
|
-
|
|
112
|
+
if (!isValidView(view)) {
|
|
113
|
+
this.router.pushUrl('/overview');
|
|
114
|
+
return;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
if (subviewMap[view]) {
|
|
118
|
+
if (sub && isValidSubview(view, sub)) {
|
|
119
|
+
this.updateViewState(view, sub);
|
|
66
120
|
} else {
|
|
67
|
-
|
|
121
|
+
// Bare parent or invalid sub → default subview
|
|
122
|
+
this.router.pushUrl(`/${view}/${defaultSubview[view]}`);
|
|
68
123
|
}
|
|
124
|
+
} else {
|
|
125
|
+
this.updateViewState(view, null);
|
|
69
126
|
}
|
|
70
127
|
}
|
|
71
128
|
|
|
72
|
-
private updateViewState(view: string): void {
|
|
129
|
+
private updateViewState(view: string, subview: string | null): void {
|
|
73
130
|
this.suppressStateUpdate = true;
|
|
74
131
|
const currentState = appstate.uiStatePart.getState()!;
|
|
75
|
-
if (currentState.activeView !== view) {
|
|
132
|
+
if (currentState.activeView !== view || currentState.activeSubview !== subview) {
|
|
76
133
|
appstate.uiStatePart.setState({
|
|
77
134
|
...currentState,
|
|
78
135
|
activeView: view,
|
|
136
|
+
activeSubview: subview,
|
|
79
137
|
} as appstate.IUiState);
|
|
80
138
|
}
|
|
81
139
|
this.suppressStateUpdate = false;
|
|
@@ -85,11 +143,17 @@ class AppRouter {
|
|
|
85
143
|
this.router.pushUrl(path);
|
|
86
144
|
}
|
|
87
145
|
|
|
88
|
-
public navigateToView(view: string): void {
|
|
89
|
-
if (
|
|
90
|
-
this.navigateTo(`/${view}`);
|
|
91
|
-
} else {
|
|
146
|
+
public navigateToView(view: string, subview?: string): void {
|
|
147
|
+
if (!isValidView(view)) {
|
|
92
148
|
this.navigateTo('/overview');
|
|
149
|
+
return;
|
|
150
|
+
}
|
|
151
|
+
if (subview && isValidSubview(view, subview)) {
|
|
152
|
+
this.navigateTo(`/${view}/${subview}`);
|
|
153
|
+
} else if (subviewMap[view]) {
|
|
154
|
+
this.navigateTo(`/${view}/${defaultSubview[view]}`);
|
|
155
|
+
} else {
|
|
156
|
+
this.navigateTo(`/${view}`);
|
|
93
157
|
}
|
|
94
158
|
}
|
|
95
159
|
|
|
@@ -1,24 +0,0 @@
|
|
|
1
|
-
import * as plugins from '../plugins.js';
|
|
2
|
-
import * as appstate from '../appstate.js';
|
|
3
|
-
import { DeesElement } from '@design.estate/dees-element';
|
|
4
|
-
export declare class OpsViewSecurity extends DeesElement {
|
|
5
|
-
accessor statsState: appstate.IStatsState;
|
|
6
|
-
accessor selectedTab: 'overview' | 'blocked' | 'authentication' | 'email-security';
|
|
7
|
-
private tabLabelMap;
|
|
8
|
-
private labelToTab;
|
|
9
|
-
constructor();
|
|
10
|
-
firstUpdated(): Promise<void>;
|
|
11
|
-
static styles: plugins.deesElement.CSSResult[];
|
|
12
|
-
render(): plugins.deesElement.TemplateResult<1>;
|
|
13
|
-
private renderTabContent;
|
|
14
|
-
private renderOverview;
|
|
15
|
-
private renderBlockedIPs;
|
|
16
|
-
private renderAuthentication;
|
|
17
|
-
private renderEmailSecurity;
|
|
18
|
-
private calculateThreatLevel;
|
|
19
|
-
private getThreatScore;
|
|
20
|
-
private getSecurityEvents;
|
|
21
|
-
private clearBlockedIPs;
|
|
22
|
-
private unblockIP;
|
|
23
|
-
private saveEmailSecuritySettings;
|
|
24
|
-
}
|