@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
|
@@ -11,22 +11,45 @@ import {
|
|
|
11
11
|
state,
|
|
12
12
|
type TemplateResult
|
|
13
13
|
} from '@design.estate/dees-element';
|
|
14
|
+
import type { IView } from '@design.estate/dees-catalog';
|
|
14
15
|
|
|
15
|
-
//
|
|
16
|
-
import { OpsViewOverview } from './ops-view-overview.js';
|
|
17
|
-
import { OpsViewNetwork } from './ops-view-network.js';
|
|
18
|
-
import { OpsViewEmails } from './ops-view-emails.js';
|
|
16
|
+
// Top-level / flat views
|
|
19
17
|
import { OpsViewLogs } from './ops-view-logs.js';
|
|
20
|
-
import { OpsViewConfig } from './ops-view-config.js';
|
|
21
|
-
import { OpsViewRoutes } from './ops-view-routes.js';
|
|
22
|
-
import { OpsViewApiTokens } from './ops-view-apitokens.js';
|
|
23
|
-
import { OpsViewSecurity } from './ops-view-security.js';
|
|
24
18
|
import { OpsViewCertificates } from './ops-view-certificates.js';
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
import {
|
|
28
|
-
import {
|
|
29
|
-
|
|
19
|
+
|
|
20
|
+
// Overview group
|
|
21
|
+
import { OpsViewOverview } from './overview/ops-view-overview.js';
|
|
22
|
+
import { OpsViewConfig } from './overview/ops-view-config.js';
|
|
23
|
+
|
|
24
|
+
// Network group
|
|
25
|
+
import { OpsViewNetworkActivity } from './network/ops-view-network-activity.js';
|
|
26
|
+
import { OpsViewRoutes } from './network/ops-view-routes.js';
|
|
27
|
+
import { OpsViewSourceProfiles } from './network/ops-view-sourceprofiles.js';
|
|
28
|
+
import { OpsViewNetworkTargets } from './network/ops-view-networktargets.js';
|
|
29
|
+
import { OpsViewTargetProfiles } from './network/ops-view-targetprofiles.js';
|
|
30
|
+
import { OpsViewRemoteIngress } from './network/ops-view-remoteingress.js';
|
|
31
|
+
import { OpsViewVpn } from './network/ops-view-vpn.js';
|
|
32
|
+
|
|
33
|
+
// Email group
|
|
34
|
+
import { OpsViewEmails } from './email/ops-view-emails.js';
|
|
35
|
+
import { OpsViewEmailSecurity } from './email/ops-view-email-security.js';
|
|
36
|
+
|
|
37
|
+
// Access group
|
|
38
|
+
import { OpsViewApiTokens } from './access/ops-view-apitokens.js';
|
|
39
|
+
|
|
40
|
+
// Security group
|
|
41
|
+
import { OpsViewSecurityOverview } from './security/ops-view-security-overview.js';
|
|
42
|
+
import { OpsViewSecurityBlocked } from './security/ops-view-security-blocked.js';
|
|
43
|
+
import { OpsViewSecurityAuthentication } from './security/ops-view-security-authentication.js';
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Extended IView with explicit URL slug. Without an explicit `slug`, the URL
|
|
47
|
+
* slug is derived from `name.toLowerCase().replace(/\s+/g, '')`.
|
|
48
|
+
*/
|
|
49
|
+
interface ITabbedView extends IView {
|
|
50
|
+
slug?: string;
|
|
51
|
+
subViews?: ITabbedView[];
|
|
52
|
+
}
|
|
30
53
|
|
|
31
54
|
@customElement('ops-dashboard')
|
|
32
55
|
export class OpsDashboard extends DeesElement {
|
|
@@ -37,6 +60,7 @@ export class OpsDashboard extends DeesElement {
|
|
|
37
60
|
|
|
38
61
|
@state() accessor uiState: appstate.IUiState = {
|
|
39
62
|
activeView: 'overview',
|
|
63
|
+
activeSubview: null,
|
|
40
64
|
sidebarCollapsed: false,
|
|
41
65
|
autoRefresh: true,
|
|
42
66
|
refreshInterval: 1000,
|
|
@@ -49,27 +73,36 @@ export class OpsDashboard extends DeesElement {
|
|
|
49
73
|
error: null,
|
|
50
74
|
};
|
|
51
75
|
|
|
52
|
-
// Store viewTabs as a property to maintain object references
|
|
53
|
-
private viewTabs = [
|
|
76
|
+
// Store viewTabs as a property to maintain object references (used for === selectedView identity)
|
|
77
|
+
private viewTabs: ITabbedView[] = [
|
|
54
78
|
{
|
|
55
79
|
name: 'Overview',
|
|
56
80
|
iconName: 'lucide:layoutDashboard',
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
iconName: 'lucide:settings',
|
|
62
|
-
element: OpsViewConfig,
|
|
81
|
+
subViews: [
|
|
82
|
+
{ slug: 'stats', name: 'Stats', iconName: 'lucide:activity', element: OpsViewOverview },
|
|
83
|
+
{ slug: 'configuration', name: 'Configuration', iconName: 'lucide:settings', element: OpsViewConfig },
|
|
84
|
+
],
|
|
63
85
|
},
|
|
64
86
|
{
|
|
65
87
|
name: 'Network',
|
|
66
88
|
iconName: 'lucide:network',
|
|
67
|
-
|
|
89
|
+
subViews: [
|
|
90
|
+
{ slug: 'activity', name: 'Network Activity', iconName: 'lucide:activity', element: OpsViewNetworkActivity },
|
|
91
|
+
{ slug: 'routes', name: 'Routes', iconName: 'lucide:route', element: OpsViewRoutes },
|
|
92
|
+
{ slug: 'sourceprofiles', name: 'Source Profiles', iconName: 'lucide:shieldCheck', element: OpsViewSourceProfiles },
|
|
93
|
+
{ slug: 'networktargets', name: 'Network Targets', iconName: 'lucide:server', element: OpsViewNetworkTargets },
|
|
94
|
+
{ slug: 'targetprofiles', name: 'Target Profiles', iconName: 'lucide:target', element: OpsViewTargetProfiles },
|
|
95
|
+
{ slug: 'remoteingress', name: 'Remote Ingress', iconName: 'lucide:globe', element: OpsViewRemoteIngress },
|
|
96
|
+
{ slug: 'vpn', name: 'VPN', iconName: 'lucide:shield', element: OpsViewVpn },
|
|
97
|
+
],
|
|
68
98
|
},
|
|
69
99
|
{
|
|
70
|
-
name: '
|
|
100
|
+
name: 'Email',
|
|
71
101
|
iconName: 'lucide:mail',
|
|
72
|
-
|
|
102
|
+
subViews: [
|
|
103
|
+
{ slug: 'log', name: 'Email Log', iconName: 'lucide:scrollText', element: OpsViewEmails },
|
|
104
|
+
{ slug: 'security', name: 'Email Security', iconName: 'lucide:shieldCheck', element: OpsViewEmailSecurity },
|
|
105
|
+
],
|
|
73
106
|
},
|
|
74
107
|
{
|
|
75
108
|
name: 'Logs',
|
|
@@ -77,52 +110,48 @@ export class OpsDashboard extends DeesElement {
|
|
|
77
110
|
element: OpsViewLogs,
|
|
78
111
|
},
|
|
79
112
|
{
|
|
80
|
-
name: '
|
|
81
|
-
iconName: 'lucide:
|
|
82
|
-
|
|
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
|
-
{
|
|
100
|
-
name: 'ApiTokens',
|
|
101
|
-
iconName: 'lucide:key',
|
|
102
|
-
element: OpsViewApiTokens,
|
|
113
|
+
name: 'Access',
|
|
114
|
+
iconName: 'lucide:keyRound',
|
|
115
|
+
subViews: [
|
|
116
|
+
{ slug: 'apitokens', name: 'API Tokens', iconName: 'lucide:key', element: OpsViewApiTokens },
|
|
117
|
+
],
|
|
103
118
|
},
|
|
104
119
|
{
|
|
105
120
|
name: 'Security',
|
|
106
121
|
iconName: 'lucide:shield',
|
|
107
|
-
|
|
122
|
+
subViews: [
|
|
123
|
+
{ slug: 'overview', name: 'Overview', iconName: 'lucide:eye', element: OpsViewSecurityOverview },
|
|
124
|
+
{ slug: 'blocked', name: 'Blocked IPs', iconName: 'lucide:shieldBan', element: OpsViewSecurityBlocked },
|
|
125
|
+
{ slug: 'authentication', name: 'Authentication', iconName: 'lucide:lock', element: OpsViewSecurityAuthentication },
|
|
126
|
+
],
|
|
108
127
|
},
|
|
109
128
|
{
|
|
110
129
|
name: 'Certificates',
|
|
111
130
|
iconName: 'lucide:badgeCheck',
|
|
112
131
|
element: OpsViewCertificates,
|
|
113
132
|
},
|
|
114
|
-
{
|
|
115
|
-
name: 'RemoteIngress',
|
|
116
|
-
iconName: 'lucide:globe',
|
|
117
|
-
element: OpsViewRemoteIngress,
|
|
118
|
-
},
|
|
119
|
-
{
|
|
120
|
-
name: 'VPN',
|
|
121
|
-
iconName: 'lucide:shield',
|
|
122
|
-
element: OpsViewVpn,
|
|
123
|
-
},
|
|
124
133
|
];
|
|
125
134
|
|
|
135
|
+
/** URL slug for a view (explicit `slug` field, or lowercased name with spaces stripped). */
|
|
136
|
+
private slugFor(view: ITabbedView): string {
|
|
137
|
+
return view.slug ?? view.name.toLowerCase().replace(/\s+/g, '');
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
/** Find the parent group of a subview, or undefined for top-level views. */
|
|
141
|
+
private findParent(view: ITabbedView): ITabbedView | undefined {
|
|
142
|
+
return this.viewTabs.find((v) => v.subViews?.includes(view));
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
/** Look up a view (or subview) by its URL slug pair. */
|
|
146
|
+
private findViewBySlug(viewSlug: string, subSlug: string | null): ITabbedView | undefined {
|
|
147
|
+
const top = this.viewTabs.find((v) => this.slugFor(v) === viewSlug);
|
|
148
|
+
if (!top) return undefined;
|
|
149
|
+
if (subSlug && top.subViews) {
|
|
150
|
+
return top.subViews.find((sv) => this.slugFor(sv) === subSlug) ?? top;
|
|
151
|
+
}
|
|
152
|
+
return top;
|
|
153
|
+
}
|
|
154
|
+
|
|
126
155
|
private get globalMessages() {
|
|
127
156
|
const messages: Array<{ id: string; type: string; message: string; dismissible?: boolean }> = [];
|
|
128
157
|
const config = this.configState.config;
|
|
@@ -138,17 +167,19 @@ export class OpsDashboard extends DeesElement {
|
|
|
138
167
|
}
|
|
139
168
|
|
|
140
169
|
/**
|
|
141
|
-
* Get the current view tab based on the UI state's activeView.
|
|
170
|
+
* Get the current view tab based on the UI state's activeView/activeSubview.
|
|
142
171
|
* Used to pass the correct selectedView to dees-simple-appdash on initial render.
|
|
143
172
|
*/
|
|
144
|
-
private get currentViewTab() {
|
|
145
|
-
return
|
|
173
|
+
private get currentViewTab(): ITabbedView {
|
|
174
|
+
return (
|
|
175
|
+
this.findViewBySlug(this.uiState.activeView, this.uiState.activeSubview) ?? this.viewTabs[0]
|
|
176
|
+
);
|
|
146
177
|
}
|
|
147
178
|
|
|
148
179
|
constructor() {
|
|
149
180
|
super();
|
|
150
181
|
document.title = 'DCRouter OpsServer';
|
|
151
|
-
|
|
182
|
+
|
|
152
183
|
// Subscribe to login state
|
|
153
184
|
const loginSubscription = appstate.loginStatePart
|
|
154
185
|
.select((stateArg) => stateArg)
|
|
@@ -161,7 +192,7 @@ export class OpsDashboard extends DeesElement {
|
|
|
161
192
|
}
|
|
162
193
|
});
|
|
163
194
|
this.rxSubscriptions.push(loginSubscription);
|
|
164
|
-
|
|
195
|
+
|
|
165
196
|
// Subscribe to config state (for global warnings)
|
|
166
197
|
const configSubscription = appstate.configStatePart
|
|
167
198
|
.select((stateArg) => stateArg)
|
|
@@ -176,38 +207,27 @@ export class OpsDashboard extends DeesElement {
|
|
|
176
207
|
.subscribe((uiState) => {
|
|
177
208
|
this.uiState = uiState;
|
|
178
209
|
// Sync appdash view when state changes (e.g., from URL navigation)
|
|
179
|
-
this.syncAppdashView(uiState.activeView);
|
|
210
|
+
this.syncAppdashView(uiState.activeView, uiState.activeSubview);
|
|
180
211
|
});
|
|
181
212
|
this.rxSubscriptions.push(uiSubscription);
|
|
182
213
|
}
|
|
183
214
|
|
|
184
215
|
/**
|
|
185
216
|
* Sync the dees-simple-appdash view selection with the current state.
|
|
186
|
-
* This is needed when the URL changes
|
|
217
|
+
* This is needed when the URL changes externally (back/forward, deep link).
|
|
187
218
|
*/
|
|
188
|
-
private syncAppdashView(
|
|
219
|
+
private syncAppdashView(viewSlug: string, subviewSlug: string | null): void {
|
|
189
220
|
const appDash = this.shadowRoot?.querySelector('dees-simple-appdash') as any;
|
|
190
221
|
if (!appDash) return;
|
|
191
222
|
|
|
192
|
-
const
|
|
193
|
-
if (!
|
|
223
|
+
const targetView = this.findViewBySlug(viewSlug, subviewSlug);
|
|
224
|
+
if (!targetView) return;
|
|
194
225
|
|
|
195
|
-
|
|
196
|
-
if (appDash.selectedView === targetTab) return;
|
|
226
|
+
if (appDash.selectedView === targetView) return;
|
|
197
227
|
|
|
198
|
-
//
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
// Update the displayed content
|
|
202
|
-
const content = appDash.shadowRoot?.querySelector('.appcontent');
|
|
203
|
-
if (content) {
|
|
204
|
-
if (appDash.currentView) {
|
|
205
|
-
appDash.currentView.remove();
|
|
206
|
-
}
|
|
207
|
-
const view = new targetTab.element();
|
|
208
|
-
content.appendChild(view);
|
|
209
|
-
appDash.currentView = view;
|
|
210
|
-
}
|
|
228
|
+
// Use loadView to update both selectedView and the mounted element.
|
|
229
|
+
// It will dispatch view-select; our handler skips when state already matches.
|
|
230
|
+
appDash.loadView(targetView);
|
|
211
231
|
}
|
|
212
232
|
|
|
213
233
|
public static styles = [
|
|
@@ -249,7 +269,7 @@ export class OpsDashboard extends DeesElement {
|
|
|
249
269
|
public async firstUpdated() {
|
|
250
270
|
const simpleLogin = this.shadowRoot!.querySelector('dees-simple-login') as any;
|
|
251
271
|
simpleLogin.addEventListener('login', (e: Event) => {
|
|
252
|
-
// Handle
|
|
272
|
+
// Handle login event
|
|
253
273
|
const detail = (e as CustomEvent).detail;
|
|
254
274
|
this.login(detail.data.username, detail.data.password);
|
|
255
275
|
});
|
|
@@ -258,9 +278,24 @@ export class OpsDashboard extends DeesElement {
|
|
|
258
278
|
const appDash = this.shadowRoot!.querySelector('dees-simple-appdash');
|
|
259
279
|
if (appDash) {
|
|
260
280
|
appDash.addEventListener('view-select', (e: Event) => {
|
|
261
|
-
const
|
|
262
|
-
|
|
263
|
-
|
|
281
|
+
const view = (e as CustomEvent).detail.view as ITabbedView;
|
|
282
|
+
const parent = this.findParent(view);
|
|
283
|
+
const currentState = appstate.uiStatePart.getState();
|
|
284
|
+
if (parent) {
|
|
285
|
+
const parentSlug = this.slugFor(parent);
|
|
286
|
+
const subSlug = this.slugFor(view);
|
|
287
|
+
// Skip if already on this exact subview — preserves URL on initial mount
|
|
288
|
+
if (currentState?.activeView === parentSlug && currentState?.activeSubview === subSlug) {
|
|
289
|
+
return;
|
|
290
|
+
}
|
|
291
|
+
appRouter.navigateToView(parentSlug, subSlug);
|
|
292
|
+
} else {
|
|
293
|
+
const slug = this.slugFor(view);
|
|
294
|
+
if (currentState?.activeView === slug && !currentState?.activeSubview) {
|
|
295
|
+
return;
|
|
296
|
+
}
|
|
297
|
+
appRouter.navigateToView(slug);
|
|
298
|
+
}
|
|
264
299
|
});
|
|
265
300
|
|
|
266
301
|
// Handle logout event
|
|
@@ -306,12 +341,12 @@ export class OpsDashboard extends DeesElement {
|
|
|
306
341
|
const simpleLogin = this.shadowRoot!.querySelector('dees-simple-login') as any;
|
|
307
342
|
const form = simpleLogin.shadowRoot!.querySelector('dees-form') as any;
|
|
308
343
|
form.setStatus('pending', 'Logging in...');
|
|
309
|
-
|
|
344
|
+
|
|
310
345
|
const state = await appstate.loginStatePart.dispatchAction(appstate.loginAction, {
|
|
311
346
|
username,
|
|
312
347
|
password,
|
|
313
348
|
});
|
|
314
|
-
|
|
349
|
+
|
|
315
350
|
if (state.identity) {
|
|
316
351
|
console.log('Login successful');
|
|
317
352
|
this.loginState = state;
|
|
@@ -325,4 +360,4 @@ export class OpsDashboard extends DeesElement {
|
|
|
325
360
|
form!.reset();
|
|
326
361
|
}
|
|
327
362
|
}
|
|
328
|
-
}
|
|
363
|
+
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import * as plugins from '
|
|
2
|
-
import * as shared from '
|
|
3
|
-
import * as appstate from '
|
|
4
|
-
import { appRouter } from '
|
|
1
|
+
import * as plugins from '../../plugins.js';
|
|
2
|
+
import * as shared from '../shared/index.js';
|
|
3
|
+
import * as appstate from '../../appstate.js';
|
|
4
|
+
import { appRouter } from '../../router.js';
|
|
5
5
|
|
|
6
6
|
import {
|
|
7
7
|
DeesElement,
|
|
@@ -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`
|
|
@@ -181,7 +181,7 @@ export class OpsViewConfig extends DeesElement {
|
|
|
181
181
|
}
|
|
182
182
|
|
|
183
183
|
const actions: IConfigSectionAction[] = [
|
|
184
|
-
{ label: 'View Emails', icon: 'lucide:arrow-right', event: 'navigate', detail: { view: '
|
|
184
|
+
{ label: 'View Emails', icon: 'lucide:arrow-right', event: 'navigate', detail: { view: 'email', subview: 'log' } },
|
|
185
185
|
];
|
|
186
186
|
|
|
187
187
|
return html`
|
|
@@ -305,7 +305,7 @@ export class OpsViewConfig extends DeesElement {
|
|
|
305
305
|
];
|
|
306
306
|
|
|
307
307
|
const actions: IConfigSectionAction[] = [
|
|
308
|
-
{ label: 'View Remote Ingress', icon: 'lucide:arrow-right', event: 'navigate', detail: { view: 'remoteingress' } },
|
|
308
|
+
{ label: 'View Remote Ingress', icon: 'lucide:arrow-right', event: 'navigate', detail: { view: 'network', subview: 'remoteingress' } },
|
|
309
309
|
];
|
|
310
310
|
|
|
311
311
|
return html`
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import * as plugins from '
|
|
2
|
-
import * as shared from '
|
|
3
|
-
import * as appstate from '
|
|
1
|
+
import * as plugins from '../../plugins.js';
|
|
2
|
+
import * as shared from '../shared/index.js';
|
|
3
|
+
import * as appstate from '../../appstate.js';
|
|
4
4
|
|
|
5
5
|
import {
|
|
6
6
|
DeesElement,
|
|
@@ -0,0 +1,121 @@
|
|
|
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-authentication': OpsViewSecurityAuthentication;
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
@customElement('ops-view-security-authentication')
|
|
22
|
+
export class OpsViewSecurityAuthentication 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
|
+
// Derive auth events from recentEvents
|
|
64
|
+
const allEvents: any[] = metrics.recentEvents || [];
|
|
65
|
+
const authEvents = allEvents.filter((evt: any) => evt.type === 'authentication');
|
|
66
|
+
const successfulLogins = authEvents.filter((evt: any) => evt.success === true).length;
|
|
67
|
+
|
|
68
|
+
const tiles: IStatsTile[] = [
|
|
69
|
+
{
|
|
70
|
+
id: 'authFailures',
|
|
71
|
+
title: 'Authentication Failures',
|
|
72
|
+
value: metrics.authenticationFailures,
|
|
73
|
+
type: 'number',
|
|
74
|
+
icon: 'lucide:LockOpen',
|
|
75
|
+
color: metrics.authenticationFailures > 10 ? '#ef4444' : '#f59e0b',
|
|
76
|
+
description: 'Failed authentication attempts today',
|
|
77
|
+
},
|
|
78
|
+
{
|
|
79
|
+
id: 'successfulLogins',
|
|
80
|
+
title: 'Successful Logins',
|
|
81
|
+
value: successfulLogins,
|
|
82
|
+
type: 'number',
|
|
83
|
+
icon: 'lucide:Lock',
|
|
84
|
+
color: '#22c55e',
|
|
85
|
+
description: 'Successful logins today',
|
|
86
|
+
},
|
|
87
|
+
];
|
|
88
|
+
|
|
89
|
+
// Map auth events to login history table data
|
|
90
|
+
const loginHistory = authEvents.map((evt: any) => ({
|
|
91
|
+
timestamp: evt.timestamp,
|
|
92
|
+
username: evt.details?.username || 'unknown',
|
|
93
|
+
ipAddress: evt.ipAddress || 'unknown',
|
|
94
|
+
success: evt.success ?? false,
|
|
95
|
+
reason: evt.success ? '' : evt.message || 'Authentication failed',
|
|
96
|
+
}));
|
|
97
|
+
|
|
98
|
+
return html`
|
|
99
|
+
<dees-heading level="hr">Authentication</dees-heading>
|
|
100
|
+
|
|
101
|
+
<dees-statsgrid
|
|
102
|
+
.tiles=${tiles}
|
|
103
|
+
.minTileWidth=${200}
|
|
104
|
+
></dees-statsgrid>
|
|
105
|
+
|
|
106
|
+
<h2>Recent Login Attempts</h2>
|
|
107
|
+
<dees-table
|
|
108
|
+
.heading1=${'Login History'}
|
|
109
|
+
.heading2=${'Recent authentication attempts'}
|
|
110
|
+
.data=${loginHistory}
|
|
111
|
+
.displayFunction=${(item) => ({
|
|
112
|
+
'Time': new Date(item.timestamp).toLocaleString(),
|
|
113
|
+
'Username': item.username,
|
|
114
|
+
'IP Address': item.ipAddress,
|
|
115
|
+
'Status': item.success ? 'Success' : 'Failed',
|
|
116
|
+
'Reason': item.reason || '-',
|
|
117
|
+
})}
|
|
118
|
+
></dees-table>
|
|
119
|
+
`;
|
|
120
|
+
}
|
|
121
|
+
}
|
|
@@ -0,0 +1,118 @@
|
|
|
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-blocked': OpsViewSecurityBlocked;
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
@customElement('ops-view-security-blocked')
|
|
22
|
+
export class OpsViewSecurityBlocked 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
|
+
dees-statsgrid {
|
|
41
|
+
margin-bottom: 32px;
|
|
42
|
+
}
|
|
43
|
+
`,
|
|
44
|
+
];
|
|
45
|
+
|
|
46
|
+
public render(): TemplateResult {
|
|
47
|
+
const metrics = this.statsState.securityMetrics;
|
|
48
|
+
|
|
49
|
+
if (!metrics) {
|
|
50
|
+
return html`
|
|
51
|
+
<div class="loadingMessage">
|
|
52
|
+
<p>Loading security metrics...</p>
|
|
53
|
+
</div>
|
|
54
|
+
`;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
const blockedIPs: string[] = metrics.blockedIPs || [];
|
|
58
|
+
|
|
59
|
+
const tiles: IStatsTile[] = [
|
|
60
|
+
{
|
|
61
|
+
id: 'totalBlocked',
|
|
62
|
+
title: 'Blocked IPs',
|
|
63
|
+
value: blockedIPs.length,
|
|
64
|
+
type: 'number',
|
|
65
|
+
icon: 'lucide:ShieldBan',
|
|
66
|
+
color: blockedIPs.length > 0 ? '#ef4444' : '#22c55e',
|
|
67
|
+
description: 'Currently blocked addresses',
|
|
68
|
+
},
|
|
69
|
+
];
|
|
70
|
+
|
|
71
|
+
return html`
|
|
72
|
+
<dees-heading level="hr">Blocked IPs</dees-heading>
|
|
73
|
+
|
|
74
|
+
<dees-statsgrid
|
|
75
|
+
.tiles=${tiles}
|
|
76
|
+
.minTileWidth=${200}
|
|
77
|
+
></dees-statsgrid>
|
|
78
|
+
|
|
79
|
+
<dees-table
|
|
80
|
+
.heading1=${'Blocked IP Addresses'}
|
|
81
|
+
.heading2=${'IPs blocked due to suspicious activity'}
|
|
82
|
+
.data=${blockedIPs.map((ip) => ({ ip }))}
|
|
83
|
+
.displayFunction=${(item) => ({
|
|
84
|
+
'IP Address': item.ip,
|
|
85
|
+
'Reason': 'Suspicious activity',
|
|
86
|
+
})}
|
|
87
|
+
.dataActions=${[
|
|
88
|
+
{
|
|
89
|
+
name: 'Unblock',
|
|
90
|
+
iconName: 'lucide:shield-off',
|
|
91
|
+
type: ['contextmenu' as const],
|
|
92
|
+
actionFunc: async (item) => {
|
|
93
|
+
await this.unblockIP(item.ip);
|
|
94
|
+
},
|
|
95
|
+
},
|
|
96
|
+
{
|
|
97
|
+
name: 'Clear All',
|
|
98
|
+
iconName: 'lucide:trash-2',
|
|
99
|
+
type: ['header' as const],
|
|
100
|
+
actionFunc: async () => {
|
|
101
|
+
await this.clearBlockedIPs();
|
|
102
|
+
},
|
|
103
|
+
},
|
|
104
|
+
]}
|
|
105
|
+
></dees-table>
|
|
106
|
+
`;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
private async clearBlockedIPs() {
|
|
110
|
+
// SmartProxy manages IP blocking — not yet exposed via API
|
|
111
|
+
alert('Clearing blocked IPs is not yet supported from the UI.');
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
private async unblockIP(ip: string) {
|
|
115
|
+
// SmartProxy manages IP blocking — not yet exposed via API
|
|
116
|
+
alert(`Unblocking IP ${ip} is not yet supported from the UI.`);
|
|
117
|
+
}
|
|
118
|
+
}
|