@serve.zone/dcrouter 6.5.0 → 6.7.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 +367 -367
- package/dist_ts/00_commitinfo_data.js +1 -1
- package/dist_ts/classes.dcrouter.js +8 -1
- package/dist_ts/opsserver/handlers/remoteingress.handler.js +4 -3
- package/dist_ts/remoteingress/classes.remoteingress-manager.d.ts +18 -2
- package/dist_ts/remoteingress/classes.remoteingress-manager.js +68 -2
- package/dist_ts_interfaces/data/remoteingress.d.ts +23 -0
- package/dist_ts_interfaces/requests/remoteingress.d.ts +1 -1
- package/dist_ts_web/00_commitinfo_data.js +1 -1
- package/dist_ts_web/appstate.d.ts +5 -1
- package/dist_ts_web/appstate.js +21 -1
- package/dist_ts_web/elements/ops-view-certificates.js +9 -9
- package/dist_ts_web/elements/ops-view-network.js +9 -9
- package/dist_ts_web/elements/ops-view-overview.js +16 -16
- package/dist_ts_web/elements/ops-view-remoteingress.js +67 -26
- package/dist_ts_web/elements/ops-view-security.js +11 -11
- package/package.json +2 -2
- package/ts/00_commitinfo_data.ts +1 -1
- package/ts/classes.dcrouter.ts +11 -2
- package/ts/opsserver/handlers/remoteingress.handler.ts +3 -2
- package/ts/remoteingress/classes.remoteingress-manager.ts +72 -2
- package/ts_web/00_commitinfo_data.ts +1 -1
- package/ts_web/appstate.ts +29 -1
- package/ts_web/elements/ops-view-certificates.ts +8 -8
- package/ts_web/elements/ops-view-network.ts +8 -8
- package/ts_web/elements/ops-view-overview.ts +15 -15
- package/ts_web/elements/ops-view-remoteingress.ts +72 -27
- package/ts_web/elements/ops-view-security.ts +10 -10
|
@@ -1,9 +1,30 @@
|
|
|
1
1
|
import * as plugins from '../plugins.js';
|
|
2
2
|
import type { StorageManager } from '../storage/classes.storagemanager.js';
|
|
3
|
-
import type { IRemoteIngress } from '../../ts_interfaces/data/remoteingress.js';
|
|
3
|
+
import type { IRemoteIngress, IDcRouterRouteConfig } from '../../ts_interfaces/data/remoteingress.js';
|
|
4
4
|
|
|
5
5
|
const STORAGE_PREFIX = '/remote-ingress/';
|
|
6
6
|
|
|
7
|
+
/**
|
|
8
|
+
* Flatten a port range (number | number[] | Array<{from, to}>) to a sorted unique number array.
|
|
9
|
+
*/
|
|
10
|
+
function extractPorts(portRange: number | number[] | Array<{ from: number; to: number }>): number[] {
|
|
11
|
+
const ports = new Set<number>();
|
|
12
|
+
if (typeof portRange === 'number') {
|
|
13
|
+
ports.add(portRange);
|
|
14
|
+
} else if (Array.isArray(portRange)) {
|
|
15
|
+
for (const entry of portRange) {
|
|
16
|
+
if (typeof entry === 'number') {
|
|
17
|
+
ports.add(entry);
|
|
18
|
+
} else if (typeof entry === 'object' && 'from' in entry && 'to' in entry) {
|
|
19
|
+
for (let p = entry.from; p <= entry.to; p++) {
|
|
20
|
+
ports.add(p);
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
return [...ports].sort((a, b) => a - b);
|
|
26
|
+
}
|
|
27
|
+
|
|
7
28
|
/**
|
|
8
29
|
* Manages CRUD for remote ingress edge registrations.
|
|
9
30
|
* Persists edge configs via StorageManager and provides
|
|
@@ -12,6 +33,7 @@ const STORAGE_PREFIX = '/remote-ingress/';
|
|
|
12
33
|
export class RemoteIngressManager {
|
|
13
34
|
private storageManager: StorageManager;
|
|
14
35
|
private edges: Map<string, IRemoteIngress> = new Map();
|
|
36
|
+
private routes: IDcRouterRouteConfig[] = [];
|
|
15
37
|
|
|
16
38
|
constructor(storageManager: StorageManager) {
|
|
17
39
|
this.storageManager = storageManager;
|
|
@@ -30,12 +52,60 @@ export class RemoteIngressManager {
|
|
|
30
52
|
}
|
|
31
53
|
}
|
|
32
54
|
|
|
55
|
+
/**
|
|
56
|
+
* Store the current route configs for port derivation.
|
|
57
|
+
*/
|
|
58
|
+
public setRoutes(routes: IDcRouterRouteConfig[]): void {
|
|
59
|
+
this.routes = routes;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Derive listen ports for an edge from routes tagged with remoteIngress.enabled.
|
|
64
|
+
* When a route specifies edgeFilter, only edges whose id or tags match get that route's ports.
|
|
65
|
+
* When edgeFilter is absent, the route applies to all edges.
|
|
66
|
+
*/
|
|
67
|
+
public derivePortsForEdge(edgeId: string, edgeTags?: string[]): number[] {
|
|
68
|
+
const ports = new Set<number>();
|
|
69
|
+
|
|
70
|
+
for (const route of this.routes) {
|
|
71
|
+
if (!route.remoteIngress?.enabled) continue;
|
|
72
|
+
|
|
73
|
+
// Apply edge filter if present
|
|
74
|
+
const filter = route.remoteIngress.edgeFilter;
|
|
75
|
+
if (filter && filter.length > 0) {
|
|
76
|
+
const idMatch = filter.includes(edgeId);
|
|
77
|
+
const tagMatch = edgeTags?.some((tag) => filter.includes(tag)) ?? false;
|
|
78
|
+
if (!idMatch && !tagMatch) continue;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
// Extract ports from the route match
|
|
82
|
+
if (route.match?.ports) {
|
|
83
|
+
for (const p of extractPorts(route.match.ports)) {
|
|
84
|
+
ports.add(p);
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
return [...ports].sort((a, b) => a - b);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* Get the effective listen ports for an edge.
|
|
94
|
+
* Returns manual listenPorts if non-empty, otherwise derives ports from tagged routes.
|
|
95
|
+
*/
|
|
96
|
+
public getEffectiveListenPorts(edge: IRemoteIngress): number[] {
|
|
97
|
+
if (edge.listenPorts && edge.listenPorts.length > 0) {
|
|
98
|
+
return edge.listenPorts;
|
|
99
|
+
}
|
|
100
|
+
return this.derivePortsForEdge(edge.id, edge.tags);
|
|
101
|
+
}
|
|
102
|
+
|
|
33
103
|
/**
|
|
34
104
|
* Create a new edge registration.
|
|
35
105
|
*/
|
|
36
106
|
public async createEdge(
|
|
37
107
|
name: string,
|
|
38
|
-
listenPorts: number[],
|
|
108
|
+
listenPorts: number[] = [],
|
|
39
109
|
tags?: string[],
|
|
40
110
|
): Promise<IRemoteIngress> {
|
|
41
111
|
const id = plugins.uuid.v4();
|
package/ts_web/appstate.ts
CHANGED
|
@@ -821,7 +821,7 @@ export const fetchRemoteIngressAction = remoteIngressStatePart.createAction(asyn
|
|
|
821
821
|
|
|
822
822
|
export const createRemoteIngressAction = remoteIngressStatePart.createAction<{
|
|
823
823
|
name: string;
|
|
824
|
-
listenPorts
|
|
824
|
+
listenPorts?: number[];
|
|
825
825
|
tags?: string[];
|
|
826
826
|
}>(async (statePartArg, dataArg) => {
|
|
827
827
|
const context = getActionContext();
|
|
@@ -924,6 +924,34 @@ export const clearNewEdgeSecretAction = remoteIngressStatePart.createAction(
|
|
|
924
924
|
}
|
|
925
925
|
);
|
|
926
926
|
|
|
927
|
+
export const toggleRemoteIngressAction = remoteIngressStatePart.createAction<{
|
|
928
|
+
id: string;
|
|
929
|
+
enabled: boolean;
|
|
930
|
+
}>(async (statePartArg, dataArg) => {
|
|
931
|
+
const context = getActionContext();
|
|
932
|
+
const currentState = statePartArg.getState();
|
|
933
|
+
|
|
934
|
+
try {
|
|
935
|
+
const request = new plugins.domtools.plugins.typedrequest.TypedRequest<
|
|
936
|
+
interfaces.requests.IReq_UpdateRemoteIngress
|
|
937
|
+
>('/typedrequest', 'updateRemoteIngress');
|
|
938
|
+
|
|
939
|
+
await request.fire({
|
|
940
|
+
identity: context.identity,
|
|
941
|
+
id: dataArg.id,
|
|
942
|
+
enabled: dataArg.enabled,
|
|
943
|
+
});
|
|
944
|
+
|
|
945
|
+
await remoteIngressStatePart.dispatchAction(fetchRemoteIngressAction, null);
|
|
946
|
+
return statePartArg.getState();
|
|
947
|
+
} catch (error) {
|
|
948
|
+
return {
|
|
949
|
+
...currentState,
|
|
950
|
+
error: error instanceof Error ? error.message : 'Failed to toggle edge',
|
|
951
|
+
};
|
|
952
|
+
}
|
|
953
|
+
});
|
|
954
|
+
|
|
927
955
|
// Combined refresh action for efficient polling
|
|
928
956
|
async function dispatchCombinedRefreshAction() {
|
|
929
957
|
const context = getActionContext();
|
|
@@ -175,7 +175,7 @@ export class OpsViewCertificates extends DeesElement {
|
|
|
175
175
|
title: 'Total Certificates',
|
|
176
176
|
value: summary.total,
|
|
177
177
|
type: 'number',
|
|
178
|
-
icon: '
|
|
178
|
+
icon: 'lucide:ShieldHalf',
|
|
179
179
|
color: '#3b82f6',
|
|
180
180
|
},
|
|
181
181
|
{
|
|
@@ -183,7 +183,7 @@ export class OpsViewCertificates extends DeesElement {
|
|
|
183
183
|
title: 'Valid',
|
|
184
184
|
value: summary.valid,
|
|
185
185
|
type: 'number',
|
|
186
|
-
icon: '
|
|
186
|
+
icon: 'lucide:Check',
|
|
187
187
|
color: '#22c55e',
|
|
188
188
|
},
|
|
189
189
|
{
|
|
@@ -191,7 +191,7 @@ export class OpsViewCertificates extends DeesElement {
|
|
|
191
191
|
title: 'Expiring Soon',
|
|
192
192
|
value: summary.expiring,
|
|
193
193
|
type: 'number',
|
|
194
|
-
icon: '
|
|
194
|
+
icon: 'lucide:Clock',
|
|
195
195
|
color: '#f59e0b',
|
|
196
196
|
},
|
|
197
197
|
{
|
|
@@ -199,7 +199,7 @@ export class OpsViewCertificates extends DeesElement {
|
|
|
199
199
|
title: 'Failed / Expired',
|
|
200
200
|
value: summary.failed + summary.expired,
|
|
201
201
|
type: 'number',
|
|
202
|
-
icon: '
|
|
202
|
+
icon: 'lucide:TriangleAlert',
|
|
203
203
|
color: '#ef4444',
|
|
204
204
|
},
|
|
205
205
|
];
|
|
@@ -211,7 +211,7 @@ export class OpsViewCertificates extends DeesElement {
|
|
|
211
211
|
.gridActions=${[
|
|
212
212
|
{
|
|
213
213
|
name: 'Refresh',
|
|
214
|
-
iconName: '
|
|
214
|
+
iconName: 'lucide:RefreshCw',
|
|
215
215
|
action: async () => {
|
|
216
216
|
await appstate.certificateStatePart.dispatchAction(
|
|
217
217
|
appstate.fetchCertificateOverviewAction,
|
|
@@ -243,7 +243,7 @@ export class OpsViewCertificates extends DeesElement {
|
|
|
243
243
|
.dataActions=${[
|
|
244
244
|
{
|
|
245
245
|
name: 'Reprovision',
|
|
246
|
-
iconName: '
|
|
246
|
+
iconName: 'lucide:RefreshCw',
|
|
247
247
|
type: ['inRow'],
|
|
248
248
|
actionFunc: async (actionData: { item: interfaces.requests.ICertificateInfo }) => {
|
|
249
249
|
const cert = actionData.item;
|
|
@@ -270,7 +270,7 @@ export class OpsViewCertificates extends DeesElement {
|
|
|
270
270
|
},
|
|
271
271
|
{
|
|
272
272
|
name: 'View Details',
|
|
273
|
-
iconName: '
|
|
273
|
+
iconName: 'lucide:Search',
|
|
274
274
|
type: ['doubleClick', 'contextmenu'],
|
|
275
275
|
actionFunc: async (actionData: { item: interfaces.requests.ICertificateInfo }) => {
|
|
276
276
|
const cert = actionData.item;
|
|
@@ -289,7 +289,7 @@ export class OpsViewCertificates extends DeesElement {
|
|
|
289
289
|
menuOptions: [
|
|
290
290
|
{
|
|
291
291
|
name: 'Copy Domain',
|
|
292
|
-
iconName: '
|
|
292
|
+
iconName: 'lucide:Copy',
|
|
293
293
|
action: async () => {
|
|
294
294
|
await navigator.clipboard.writeText(cert.domain);
|
|
295
295
|
},
|
|
@@ -287,7 +287,7 @@ export class OpsViewNetwork extends DeesElement {
|
|
|
287
287
|
.dataActions=${[
|
|
288
288
|
{
|
|
289
289
|
name: 'View Details',
|
|
290
|
-
iconName: '
|
|
290
|
+
iconName: 'lucide:Search',
|
|
291
291
|
type: ['inRow', 'doubleClick', 'contextmenu'],
|
|
292
292
|
actionFunc: async (actionData) => {
|
|
293
293
|
await this.showRequestDetails(actionData.item);
|
|
@@ -336,7 +336,7 @@ export class OpsViewNetwork extends DeesElement {
|
|
|
336
336
|
menuOptions: [
|
|
337
337
|
{
|
|
338
338
|
name: 'Copy Request ID',
|
|
339
|
-
iconName: '
|
|
339
|
+
iconName: 'lucide:Copy',
|
|
340
340
|
action: async () => {
|
|
341
341
|
await navigator.clipboard.writeText(request.id);
|
|
342
342
|
}
|
|
@@ -429,13 +429,13 @@ export class OpsViewNetwork extends DeesElement {
|
|
|
429
429
|
title: 'Active Connections',
|
|
430
430
|
value: activeConnections,
|
|
431
431
|
type: 'number',
|
|
432
|
-
icon: '
|
|
432
|
+
icon: 'lucide:Plug',
|
|
433
433
|
color: activeConnections > 100 ? '#f59e0b' : '#22c55e',
|
|
434
434
|
description: `Total: ${this.networkState.requestsTotal || this.statsState.serverStats?.totalConnections || 0}`,
|
|
435
435
|
actions: [
|
|
436
436
|
{
|
|
437
437
|
name: 'View Details',
|
|
438
|
-
iconName: '
|
|
438
|
+
iconName: 'lucide:Search',
|
|
439
439
|
action: async () => {
|
|
440
440
|
},
|
|
441
441
|
},
|
|
@@ -446,7 +446,7 @@ export class OpsViewNetwork extends DeesElement {
|
|
|
446
446
|
title: 'Requests/sec',
|
|
447
447
|
value: reqPerSec,
|
|
448
448
|
type: 'trend',
|
|
449
|
-
icon: '
|
|
449
|
+
icon: 'lucide:ChartLine',
|
|
450
450
|
color: '#3b82f6',
|
|
451
451
|
trendData: trendData,
|
|
452
452
|
description: `Total: ${this.formatNumber(this.networkState.requestsTotal || 0)} requests`,
|
|
@@ -457,7 +457,7 @@ export class OpsViewNetwork extends DeesElement {
|
|
|
457
457
|
value: this.formatBitsPerSecond(throughput.in),
|
|
458
458
|
unit: '',
|
|
459
459
|
type: 'number',
|
|
460
|
-
icon: '
|
|
460
|
+
icon: 'lucide:Download',
|
|
461
461
|
color: '#22c55e',
|
|
462
462
|
description: `Total: ${this.formatBytes(this.networkState.totalBytes?.in || 0)}`,
|
|
463
463
|
},
|
|
@@ -467,7 +467,7 @@ export class OpsViewNetwork extends DeesElement {
|
|
|
467
467
|
value: this.formatBitsPerSecond(throughput.out),
|
|
468
468
|
unit: '',
|
|
469
469
|
type: 'number',
|
|
470
|
-
icon: '
|
|
470
|
+
icon: 'lucide:Upload',
|
|
471
471
|
color: '#8b5cf6',
|
|
472
472
|
description: `Total: ${this.formatBytes(this.networkState.totalBytes?.out || 0)}`,
|
|
473
473
|
},
|
|
@@ -480,7 +480,7 @@ export class OpsViewNetwork extends DeesElement {
|
|
|
480
480
|
.gridActions=${[
|
|
481
481
|
{
|
|
482
482
|
name: 'Export Data',
|
|
483
|
-
iconName: '
|
|
483
|
+
iconName: 'lucide:FileOutput',
|
|
484
484
|
action: async () => {
|
|
485
485
|
console.log('Export feature coming soon');
|
|
486
486
|
},
|
|
@@ -163,7 +163,7 @@ export class OpsViewOverview extends DeesElement {
|
|
|
163
163
|
title: 'Server Status',
|
|
164
164
|
value: this.statsState.serverStats.uptime ? 'Online' : 'Offline',
|
|
165
165
|
type: 'text',
|
|
166
|
-
icon: '
|
|
166
|
+
icon: 'lucide:Server',
|
|
167
167
|
color: this.statsState.serverStats.uptime ? '#22c55e' : '#ef4444',
|
|
168
168
|
description: `Uptime: ${this.formatUptime(this.statsState.serverStats.uptime)}`,
|
|
169
169
|
},
|
|
@@ -172,7 +172,7 @@ export class OpsViewOverview extends DeesElement {
|
|
|
172
172
|
title: 'Active Connections',
|
|
173
173
|
value: this.statsState.serverStats.activeConnections,
|
|
174
174
|
type: 'number',
|
|
175
|
-
icon: '
|
|
175
|
+
icon: 'lucide:Network',
|
|
176
176
|
color: '#3b82f6',
|
|
177
177
|
description: `Total: ${this.statsState.serverStats.totalConnections}`,
|
|
178
178
|
},
|
|
@@ -181,7 +181,7 @@ export class OpsViewOverview extends DeesElement {
|
|
|
181
181
|
title: 'Throughput In',
|
|
182
182
|
value: this.formatBitsPerSecond(this.statsState.serverStats.throughput?.bytesInPerSecond || 0),
|
|
183
183
|
type: 'text',
|
|
184
|
-
icon: '
|
|
184
|
+
icon: 'lucide:Download',
|
|
185
185
|
color: '#22c55e',
|
|
186
186
|
description: `Total: ${this.formatBytes(this.statsState.serverStats.throughput?.bytesIn || 0)}`,
|
|
187
187
|
},
|
|
@@ -190,7 +190,7 @@ export class OpsViewOverview extends DeesElement {
|
|
|
190
190
|
title: 'Throughput Out',
|
|
191
191
|
value: this.formatBitsPerSecond(this.statsState.serverStats.throughput?.bytesOutPerSecond || 0),
|
|
192
192
|
type: 'text',
|
|
193
|
-
icon: '
|
|
193
|
+
icon: 'lucide:Upload',
|
|
194
194
|
color: '#8b5cf6',
|
|
195
195
|
description: `Total: ${this.formatBytes(this.statsState.serverStats.throughput?.bytesOut || 0)}`,
|
|
196
196
|
},
|
|
@@ -199,7 +199,7 @@ export class OpsViewOverview extends DeesElement {
|
|
|
199
199
|
title: 'CPU Usage',
|
|
200
200
|
value: cpuUsage,
|
|
201
201
|
type: 'gauge',
|
|
202
|
-
icon: '
|
|
202
|
+
icon: 'lucide:Cpu',
|
|
203
203
|
gaugeOptions: {
|
|
204
204
|
min: 0,
|
|
205
205
|
max: 100,
|
|
@@ -215,7 +215,7 @@ export class OpsViewOverview extends DeesElement {
|
|
|
215
215
|
title: 'Memory Usage',
|
|
216
216
|
value: memoryUsage,
|
|
217
217
|
type: 'percentage',
|
|
218
|
-
icon: '
|
|
218
|
+
icon: 'lucide:MemoryStick',
|
|
219
219
|
color: memoryUsage > 80 ? '#ef4444' : memoryUsage > 60 ? '#f59e0b' : '#22c55e',
|
|
220
220
|
description: this.statsState.serverStats.memoryUsage.actualUsageBytes !== undefined && this.statsState.serverStats.memoryUsage.maxMemoryMB !== undefined
|
|
221
221
|
? `${this.formatBytes(this.statsState.serverStats.memoryUsage.actualUsageBytes)} / ${this.formatBytes(this.statsState.serverStats.memoryUsage.maxMemoryMB * 1024 * 1024)}`
|
|
@@ -229,7 +229,7 @@ export class OpsViewOverview extends DeesElement {
|
|
|
229
229
|
.gridActions=${[
|
|
230
230
|
{
|
|
231
231
|
name: 'Refresh',
|
|
232
|
-
iconName: '
|
|
232
|
+
iconName: 'lucide:RefreshCw',
|
|
233
233
|
action: async () => {
|
|
234
234
|
await appstate.statsStatePart.dispatchAction(appstate.fetchAllStatsAction, null);
|
|
235
235
|
},
|
|
@@ -251,7 +251,7 @@ export class OpsViewOverview extends DeesElement {
|
|
|
251
251
|
title: 'Emails Sent',
|
|
252
252
|
value: this.statsState.emailStats.sent,
|
|
253
253
|
type: 'number',
|
|
254
|
-
icon: '
|
|
254
|
+
icon: 'lucide:Send',
|
|
255
255
|
color: '#22c55e',
|
|
256
256
|
description: `Delivery rate: ${(deliveryRate * 100).toFixed(1)}%`,
|
|
257
257
|
},
|
|
@@ -260,7 +260,7 @@ export class OpsViewOverview extends DeesElement {
|
|
|
260
260
|
title: 'Emails Received',
|
|
261
261
|
value: this.statsState.emailStats.received,
|
|
262
262
|
type: 'number',
|
|
263
|
-
icon: '
|
|
263
|
+
icon: 'lucide:Mail',
|
|
264
264
|
color: '#3b82f6',
|
|
265
265
|
},
|
|
266
266
|
{
|
|
@@ -268,7 +268,7 @@ export class OpsViewOverview extends DeesElement {
|
|
|
268
268
|
title: 'Queued',
|
|
269
269
|
value: this.statsState.emailStats.queued,
|
|
270
270
|
type: 'number',
|
|
271
|
-
icon: '
|
|
271
|
+
icon: 'lucide:Clock',
|
|
272
272
|
color: '#f59e0b',
|
|
273
273
|
description: 'Pending delivery',
|
|
274
274
|
},
|
|
@@ -277,7 +277,7 @@ export class OpsViewOverview extends DeesElement {
|
|
|
277
277
|
title: 'Failed',
|
|
278
278
|
value: this.statsState.emailStats.failed,
|
|
279
279
|
type: 'number',
|
|
280
|
-
icon: '
|
|
280
|
+
icon: 'lucide:TriangleAlert',
|
|
281
281
|
color: '#ef4444',
|
|
282
282
|
description: `Bounce rate: ${(bounceRate * 100).toFixed(1)}%`,
|
|
283
283
|
},
|
|
@@ -300,7 +300,7 @@ export class OpsViewOverview extends DeesElement {
|
|
|
300
300
|
title: 'DNS Queries',
|
|
301
301
|
value: this.statsState.dnsStats.totalQueries,
|
|
302
302
|
type: 'number',
|
|
303
|
-
icon: '
|
|
303
|
+
icon: 'lucide:Globe',
|
|
304
304
|
color: '#3b82f6',
|
|
305
305
|
description: 'Total queries handled',
|
|
306
306
|
},
|
|
@@ -309,7 +309,7 @@ export class OpsViewOverview extends DeesElement {
|
|
|
309
309
|
title: 'Cache Hit Rate',
|
|
310
310
|
value: cacheHitRate,
|
|
311
311
|
type: 'percentage',
|
|
312
|
-
icon: '
|
|
312
|
+
icon: 'lucide:Database',
|
|
313
313
|
color: cacheHitRate > 80 ? '#22c55e' : cacheHitRate > 60 ? '#f59e0b' : '#ef4444',
|
|
314
314
|
description: `${this.statsState.dnsStats.cacheHits} hits / ${this.statsState.dnsStats.cacheMisses} misses`,
|
|
315
315
|
},
|
|
@@ -318,7 +318,7 @@ export class OpsViewOverview extends DeesElement {
|
|
|
318
318
|
title: 'Active Domains',
|
|
319
319
|
value: this.statsState.dnsStats.activeDomains,
|
|
320
320
|
type: 'number',
|
|
321
|
-
icon: '
|
|
321
|
+
icon: 'lucide:Network',
|
|
322
322
|
color: '#8b5cf6',
|
|
323
323
|
},
|
|
324
324
|
{
|
|
@@ -327,7 +327,7 @@ export class OpsViewOverview extends DeesElement {
|
|
|
327
327
|
value: this.statsState.dnsStats.averageResponseTime.toFixed(1),
|
|
328
328
|
unit: 'ms',
|
|
329
329
|
type: 'number',
|
|
330
|
-
icon: '
|
|
330
|
+
icon: 'lucide:History',
|
|
331
331
|
color: this.statsState.dnsStats.averageResponseTime < 50 ? '#22c55e' : '#f59e0b',
|
|
332
332
|
},
|
|
333
333
|
];
|
|
@@ -187,7 +187,7 @@ export class OpsViewRemoteIngress extends DeesElement {
|
|
|
187
187
|
name: edge.name,
|
|
188
188
|
status: this.getEdgeStatusHtml(edge),
|
|
189
189
|
publicIp: this.getEdgePublicIp(edge.id),
|
|
190
|
-
ports: this.getPortsHtml(edge
|
|
190
|
+
ports: this.getPortsHtml(edge),
|
|
191
191
|
tunnels: this.getEdgeTunnelCount(edge.id),
|
|
192
192
|
lastHeartbeat: this.getLastHeartbeat(edge.id),
|
|
193
193
|
})}
|
|
@@ -198,42 +198,80 @@ export class OpsViewRemoteIngress extends DeesElement {
|
|
|
198
198
|
type: ['header'],
|
|
199
199
|
actionFunc: async () => {
|
|
200
200
|
const { DeesModal } = await import('@design.estate/dees-catalog');
|
|
201
|
-
const
|
|
201
|
+
const modal = await DeesModal.createAndShow({
|
|
202
202
|
heading: 'Create Edge Node',
|
|
203
203
|
content: html`
|
|
204
204
|
<dees-form>
|
|
205
205
|
<dees-input-text .key=${'name'} .label=${'Name'} .required=${true}></dees-input-text>
|
|
206
|
-
<dees-input-text .key=${'listenPorts'} .label=${'Listen Ports (comma-separated
|
|
206
|
+
<dees-input-text .key=${'listenPorts'} .label=${'Listen Ports (comma-separated, auto-derived if empty)'}></dees-input-text>
|
|
207
207
|
<dees-input-text .key=${'tags'} .label=${'Tags (comma-separated, optional)'}></dees-input-text>
|
|
208
208
|
</dees-form>
|
|
209
209
|
`,
|
|
210
|
-
menuOptions: [
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
.map((p: string) => parseInt(p.trim(), 10))
|
|
217
|
-
.filter((p: number) => !isNaN(p));
|
|
218
|
-
const tags = formData.tags
|
|
219
|
-
? formData.tags.split(',').map((t: string) => t.trim()).filter(Boolean)
|
|
220
|
-
: undefined;
|
|
221
|
-
await appstate.remoteIngressStatePart.dispatchAction(
|
|
222
|
-
appstate.createRemoteIngressAction,
|
|
210
|
+
menuOptions: [
|
|
211
|
+
{
|
|
212
|
+
name: 'Cancel',
|
|
213
|
+
iconName: 'lucide:x',
|
|
214
|
+
action: async (modalArg: any) => await modalArg.destroy(),
|
|
215
|
+
},
|
|
223
216
|
{
|
|
224
|
-
name:
|
|
225
|
-
|
|
226
|
-
|
|
217
|
+
name: 'Create',
|
|
218
|
+
iconName: 'lucide:plus',
|
|
219
|
+
action: async (modalArg: any) => {
|
|
220
|
+
const form = modalArg.shadowRoot?.querySelector('.content')?.querySelector('dees-form');
|
|
221
|
+
if (!form) return;
|
|
222
|
+
const formData = await form.collectFormData();
|
|
223
|
+
const name = formData.name;
|
|
224
|
+
if (!name) return;
|
|
225
|
+
const portsStr = formData.listenPorts?.trim();
|
|
226
|
+
const listenPorts = portsStr
|
|
227
|
+
? portsStr.split(',').map((p: string) => parseInt(p.trim(), 10)).filter((p: number) => !isNaN(p))
|
|
228
|
+
: undefined;
|
|
229
|
+
const tags = formData.tags
|
|
230
|
+
? formData.tags.split(',').map((t: string) => t.trim()).filter(Boolean)
|
|
231
|
+
: undefined;
|
|
232
|
+
await appstate.remoteIngressStatePart.dispatchAction(
|
|
233
|
+
appstate.createRemoteIngressAction,
|
|
234
|
+
{ name, listenPorts, tags },
|
|
235
|
+
);
|
|
236
|
+
await modalArg.destroy();
|
|
237
|
+
},
|
|
227
238
|
},
|
|
228
|
-
|
|
229
|
-
}
|
|
239
|
+
],
|
|
240
|
+
});
|
|
241
|
+
},
|
|
242
|
+
},
|
|
243
|
+
{
|
|
244
|
+
name: 'Enable',
|
|
245
|
+
iconName: 'lucide:play',
|
|
246
|
+
type: ['inRow', 'contextmenu'] as any,
|
|
247
|
+
actionRelevancyCheckFunc: (actionData: any) => !actionData.item.enabled,
|
|
248
|
+
actionFunc: async (actionData: any) => {
|
|
249
|
+
const edge = actionData.item as interfaces.data.IRemoteIngress;
|
|
250
|
+
await appstate.remoteIngressStatePart.dispatchAction(
|
|
251
|
+
appstate.toggleRemoteIngressAction,
|
|
252
|
+
{ id: edge.id, enabled: true },
|
|
253
|
+
);
|
|
254
|
+
},
|
|
255
|
+
},
|
|
256
|
+
{
|
|
257
|
+
name: 'Disable',
|
|
258
|
+
iconName: 'lucide:pause',
|
|
259
|
+
type: ['inRow', 'contextmenu'] as any,
|
|
260
|
+
actionRelevancyCheckFunc: (actionData: any) => actionData.item.enabled,
|
|
261
|
+
actionFunc: async (actionData: any) => {
|
|
262
|
+
const edge = actionData.item as interfaces.data.IRemoteIngress;
|
|
263
|
+
await appstate.remoteIngressStatePart.dispatchAction(
|
|
264
|
+
appstate.toggleRemoteIngressAction,
|
|
265
|
+
{ id: edge.id, enabled: false },
|
|
266
|
+
);
|
|
230
267
|
},
|
|
231
268
|
},
|
|
232
269
|
{
|
|
233
270
|
name: 'Regenerate Secret',
|
|
234
271
|
iconName: 'lucide:key',
|
|
235
|
-
type: ['
|
|
236
|
-
|
|
272
|
+
type: ['inRow', 'contextmenu'] as any,
|
|
273
|
+
actionFunc: async (actionData: any) => {
|
|
274
|
+
const edge = actionData.item as interfaces.data.IRemoteIngress;
|
|
237
275
|
await appstate.remoteIngressStatePart.dispatchAction(
|
|
238
276
|
appstate.regenerateRemoteIngressSecretAction,
|
|
239
277
|
edge.id,
|
|
@@ -243,8 +281,9 @@ export class OpsViewRemoteIngress extends DeesElement {
|
|
|
243
281
|
{
|
|
244
282
|
name: 'Delete',
|
|
245
283
|
iconName: 'lucide:trash2',
|
|
246
|
-
type: ['
|
|
247
|
-
|
|
284
|
+
type: ['inRow', 'contextmenu'] as any,
|
|
285
|
+
actionFunc: async (actionData: any) => {
|
|
286
|
+
const edge = actionData.item as interfaces.data.IRemoteIngress;
|
|
248
287
|
await appstate.remoteIngressStatePart.dispatchAction(
|
|
249
288
|
appstate.deleteRemoteIngressAction,
|
|
250
289
|
edge.id,
|
|
@@ -277,8 +316,14 @@ export class OpsViewRemoteIngress extends DeesElement {
|
|
|
277
316
|
return status?.publicIp || '-';
|
|
278
317
|
}
|
|
279
318
|
|
|
280
|
-
private getPortsHtml(
|
|
281
|
-
|
|
319
|
+
private getPortsHtml(edge: interfaces.data.IRemoteIngress): TemplateResult {
|
|
320
|
+
const hasManualPorts = edge.listenPorts && edge.listenPorts.length > 0;
|
|
321
|
+
const ports = hasManualPorts ? edge.listenPorts : (edge.effectiveListenPorts || []);
|
|
322
|
+
const isAuto = !hasManualPorts && ports.length > 0;
|
|
323
|
+
if (ports.length === 0) {
|
|
324
|
+
return html`<span style="color: var(--text-muted, #6b7280); font-size: 12px;">none</span>`;
|
|
325
|
+
}
|
|
326
|
+
return html`<div class="portsDisplay">${ports.map(p => html`<span class="portBadge">${p}</span>`)}${isAuto ? html`<span style="font-size: 11px; color: var(--text-muted, #6b7280); align-self: center;">(auto)</span>` : ''}</div>`;
|
|
282
327
|
}
|
|
283
328
|
|
|
284
329
|
private getEdgeTunnelCount(edgeId: string): number {
|
|
@@ -256,7 +256,7 @@ export class OpsViewSecurity extends DeesElement {
|
|
|
256
256
|
title: 'Threat Level',
|
|
257
257
|
value: threatScore,
|
|
258
258
|
type: 'gauge',
|
|
259
|
-
icon: '
|
|
259
|
+
icon: 'lucide:Shield',
|
|
260
260
|
gaugeOptions: {
|
|
261
261
|
min: 0,
|
|
262
262
|
max: 100,
|
|
@@ -273,7 +273,7 @@ export class OpsViewSecurity extends DeesElement {
|
|
|
273
273
|
title: 'Blocked Threats',
|
|
274
274
|
value: metrics.blockedIPs.length + metrics.spamDetected,
|
|
275
275
|
type: 'number',
|
|
276
|
-
icon: '
|
|
276
|
+
icon: 'lucide:ShieldCheck',
|
|
277
277
|
color: '#ef4444',
|
|
278
278
|
description: 'Total threats blocked today',
|
|
279
279
|
},
|
|
@@ -282,7 +282,7 @@ export class OpsViewSecurity extends DeesElement {
|
|
|
282
282
|
title: 'Active Sessions',
|
|
283
283
|
value: 0,
|
|
284
284
|
type: 'number',
|
|
285
|
-
icon: '
|
|
285
|
+
icon: 'lucide:Users',
|
|
286
286
|
color: '#22c55e',
|
|
287
287
|
description: 'Current authenticated sessions',
|
|
288
288
|
},
|
|
@@ -291,7 +291,7 @@ export class OpsViewSecurity extends DeesElement {
|
|
|
291
291
|
title: 'Auth Failures',
|
|
292
292
|
value: metrics.authenticationFailures,
|
|
293
293
|
type: 'number',
|
|
294
|
-
icon: '
|
|
294
|
+
icon: 'lucide:LockOpen',
|
|
295
295
|
color: metrics.authenticationFailures > 10 ? '#ef4444' : '#f59e0b',
|
|
296
296
|
description: 'Failed login attempts today',
|
|
297
297
|
},
|
|
@@ -355,7 +355,7 @@ export class OpsViewSecurity extends DeesElement {
|
|
|
355
355
|
title: 'Authentication Failures',
|
|
356
356
|
value: metrics.authenticationFailures,
|
|
357
357
|
type: 'number',
|
|
358
|
-
icon: '
|
|
358
|
+
icon: 'lucide:LockOpen',
|
|
359
359
|
color: metrics.authenticationFailures > 10 ? '#ef4444' : '#f59e0b',
|
|
360
360
|
description: 'Failed authentication attempts today',
|
|
361
361
|
},
|
|
@@ -364,7 +364,7 @@ export class OpsViewSecurity extends DeesElement {
|
|
|
364
364
|
title: 'Successful Logins',
|
|
365
365
|
value: 0,
|
|
366
366
|
type: 'number',
|
|
367
|
-
icon: '
|
|
367
|
+
icon: 'lucide:Lock',
|
|
368
368
|
color: '#22c55e',
|
|
369
369
|
description: 'Successful logins today',
|
|
370
370
|
},
|
|
@@ -399,7 +399,7 @@ export class OpsViewSecurity extends DeesElement {
|
|
|
399
399
|
title: 'Malware Detection',
|
|
400
400
|
value: metrics.malwareDetected,
|
|
401
401
|
type: 'number',
|
|
402
|
-
icon: '
|
|
402
|
+
icon: 'lucide:BugOff',
|
|
403
403
|
color: metrics.malwareDetected > 0 ? '#ef4444' : '#22c55e',
|
|
404
404
|
description: 'Malware detected',
|
|
405
405
|
},
|
|
@@ -408,7 +408,7 @@ export class OpsViewSecurity extends DeesElement {
|
|
|
408
408
|
title: 'Phishing Detection',
|
|
409
409
|
value: metrics.phishingDetected,
|
|
410
410
|
type: 'number',
|
|
411
|
-
icon: '
|
|
411
|
+
icon: 'lucide:Fish',
|
|
412
412
|
color: metrics.phishingDetected > 0 ? '#ef4444' : '#22c55e',
|
|
413
413
|
description: 'Phishing attempts detected',
|
|
414
414
|
},
|
|
@@ -417,7 +417,7 @@ export class OpsViewSecurity extends DeesElement {
|
|
|
417
417
|
title: 'Suspicious Activities',
|
|
418
418
|
value: metrics.suspiciousActivities,
|
|
419
419
|
type: 'number',
|
|
420
|
-
icon: '
|
|
420
|
+
icon: 'lucide:TriangleAlert',
|
|
421
421
|
color: metrics.suspiciousActivities > 5 ? '#ef4444' : '#f59e0b',
|
|
422
422
|
description: 'Suspicious activities detected',
|
|
423
423
|
},
|
|
@@ -426,7 +426,7 @@ export class OpsViewSecurity extends DeesElement {
|
|
|
426
426
|
title: 'Spam Detection',
|
|
427
427
|
value: metrics.spamDetected,
|
|
428
428
|
type: 'number',
|
|
429
|
-
icon: '
|
|
429
|
+
icon: 'lucide:Ban',
|
|
430
430
|
color: '#f59e0b',
|
|
431
431
|
description: 'Spam emails blocked',
|
|
432
432
|
},
|