@serve.zone/dcrouter 6.3.0 → 6.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.
Files changed (55) hide show
  1. package/dist_serve/bundle.js +559 -452
  2. package/dist_ts/00_commitinfo_data.js +1 -1
  3. package/dist_ts/classes.dcrouter.d.ts +22 -0
  4. package/dist_ts/classes.dcrouter.js +42 -1
  5. package/dist_ts/index.d.ts +1 -0
  6. package/dist_ts/index.js +3 -1
  7. package/dist_ts/opsserver/classes.opsserver.d.ts +1 -0
  8. package/dist_ts/opsserver/classes.opsserver.js +3 -1
  9. package/dist_ts/opsserver/handlers/index.d.ts +1 -0
  10. package/dist_ts/opsserver/handlers/index.js +2 -1
  11. package/dist_ts/opsserver/handlers/remoteingress.handler.d.ts +8 -0
  12. package/dist_ts/opsserver/handlers/remoteingress.handler.js +107 -0
  13. package/dist_ts/plugins.d.ts +2 -1
  14. package/dist_ts/plugins.js +3 -2
  15. package/dist_ts/remoteingress/classes.remoteingress-manager.d.ts +56 -0
  16. package/dist_ts/remoteingress/classes.remoteingress-manager.js +133 -0
  17. package/dist_ts/remoteingress/classes.tunnel-manager.d.ts +45 -0
  18. package/dist_ts/remoteingress/classes.tunnel-manager.js +107 -0
  19. package/dist_ts/remoteingress/index.d.ts +2 -0
  20. package/dist_ts/remoteingress/index.js +3 -0
  21. package/dist_ts_interfaces/data/index.d.ts +1 -0
  22. package/dist_ts_interfaces/data/index.js +2 -1
  23. package/dist_ts_interfaces/data/remoteingress.d.ts +24 -0
  24. package/dist_ts_interfaces/data/remoteingress.js +2 -0
  25. package/dist_ts_interfaces/requests/index.d.ts +1 -0
  26. package/dist_ts_interfaces/requests/index.js +2 -1
  27. package/dist_ts_interfaces/requests/remoteingress.d.ts +89 -0
  28. package/dist_ts_interfaces/requests/remoteingress.js +3 -0
  29. package/dist_ts_web/00_commitinfo_data.js +1 -1
  30. package/dist_ts_web/appstate.d.ts +19 -0
  31. package/dist_ts_web/appstate.js +124 -2
  32. package/dist_ts_web/elements/index.d.ts +1 -0
  33. package/dist_ts_web/elements/index.js +2 -1
  34. package/dist_ts_web/elements/ops-dashboard.js +6 -1
  35. package/dist_ts_web/elements/ops-view-remoteingress.d.ts +20 -0
  36. package/dist_ts_web/elements/ops-view-remoteingress.js +317 -0
  37. package/dist_ts_web/router.d.ts +1 -1
  38. package/dist_ts_web/router.js +2 -2
  39. package/package.json +2 -1
  40. package/ts/00_commitinfo_data.ts +1 -1
  41. package/ts/classes.dcrouter.ts +66 -0
  42. package/ts/index.ts +3 -0
  43. package/ts/opsserver/classes.opsserver.ts +2 -0
  44. package/ts/opsserver/handlers/index.ts +2 -1
  45. package/ts/opsserver/handlers/remoteingress.handler.ts +163 -0
  46. package/ts/plugins.ts +3 -1
  47. package/ts/remoteingress/classes.remoteingress-manager.ts +160 -0
  48. package/ts/remoteingress/classes.tunnel-manager.ts +126 -0
  49. package/ts/remoteingress/index.ts +2 -0
  50. package/ts_web/00_commitinfo_data.ts +1 -1
  51. package/ts_web/appstate.ts +180 -1
  52. package/ts_web/elements/index.ts +1 -0
  53. package/ts_web/elements/ops-dashboard.ts +5 -0
  54. package/ts_web/elements/ops-view-remoteingress.ts +290 -0
  55. package/ts_web/router.ts +1 -1
@@ -0,0 +1,163 @@
1
+ import * as plugins from '../../plugins.js';
2
+ import type { OpsServer } from '../classes.opsserver.js';
3
+ import * as interfaces from '../../../ts_interfaces/index.js';
4
+
5
+ export class RemoteIngressHandler {
6
+ public typedrouter = new plugins.typedrequest.TypedRouter();
7
+
8
+ constructor(private opsServerRef: OpsServer) {
9
+ this.opsServerRef.typedrouter.addTypedRouter(this.typedrouter);
10
+ this.registerHandlers();
11
+ }
12
+
13
+ private registerHandlers(): void {
14
+ // Get all remote ingress edges
15
+ this.typedrouter.addTypedHandler(
16
+ new plugins.typedrequest.TypedHandler<interfaces.requests.IReq_GetRemoteIngresses>(
17
+ 'getRemoteIngresses',
18
+ async (dataArg, toolsArg) => {
19
+ const manager = this.opsServerRef.dcRouterRef.remoteIngressManager;
20
+ if (!manager) {
21
+ return { edges: [] };
22
+ }
23
+ // Return edges without secrets
24
+ const edges = manager.getAllEdges().map((e) => ({
25
+ ...e,
26
+ secret: '********', // Never expose secrets via API
27
+ }));
28
+ return { edges };
29
+ },
30
+ ),
31
+ );
32
+
33
+ // Create a new remote ingress edge
34
+ this.typedrouter.addTypedHandler(
35
+ new plugins.typedrequest.TypedHandler<interfaces.requests.IReq_CreateRemoteIngress>(
36
+ 'createRemoteIngress',
37
+ async (dataArg, toolsArg) => {
38
+ const manager = this.opsServerRef.dcRouterRef.remoteIngressManager;
39
+ const tunnelManager = this.opsServerRef.dcRouterRef.tunnelManager;
40
+
41
+ if (!manager) {
42
+ return {
43
+ success: false,
44
+ edge: null as any,
45
+ };
46
+ }
47
+
48
+ const edge = await manager.createEdge(
49
+ dataArg.name,
50
+ dataArg.listenPorts,
51
+ dataArg.tags,
52
+ );
53
+
54
+ // Sync allowed edges with the hub
55
+ if (tunnelManager) {
56
+ await tunnelManager.syncAllowedEdges();
57
+ }
58
+
59
+ return { success: true, edge };
60
+ },
61
+ ),
62
+ );
63
+
64
+ // Delete a remote ingress edge
65
+ this.typedrouter.addTypedHandler(
66
+ new plugins.typedrequest.TypedHandler<interfaces.requests.IReq_DeleteRemoteIngress>(
67
+ 'deleteRemoteIngress',
68
+ async (dataArg, toolsArg) => {
69
+ const manager = this.opsServerRef.dcRouterRef.remoteIngressManager;
70
+ const tunnelManager = this.opsServerRef.dcRouterRef.tunnelManager;
71
+
72
+ if (!manager) {
73
+ return { success: false, message: 'RemoteIngress not configured' };
74
+ }
75
+
76
+ const deleted = await manager.deleteEdge(dataArg.id);
77
+ if (deleted && tunnelManager) {
78
+ await tunnelManager.syncAllowedEdges();
79
+ }
80
+
81
+ return {
82
+ success: deleted,
83
+ message: deleted ? undefined : 'Edge not found',
84
+ };
85
+ },
86
+ ),
87
+ );
88
+
89
+ // Update a remote ingress edge
90
+ this.typedrouter.addTypedHandler(
91
+ new plugins.typedrequest.TypedHandler<interfaces.requests.IReq_UpdateRemoteIngress>(
92
+ 'updateRemoteIngress',
93
+ async (dataArg, toolsArg) => {
94
+ const manager = this.opsServerRef.dcRouterRef.remoteIngressManager;
95
+ const tunnelManager = this.opsServerRef.dcRouterRef.tunnelManager;
96
+
97
+ if (!manager) {
98
+ return { success: false, edge: null as any };
99
+ }
100
+
101
+ const edge = await manager.updateEdge(dataArg.id, {
102
+ name: dataArg.name,
103
+ listenPorts: dataArg.listenPorts,
104
+ enabled: dataArg.enabled,
105
+ tags: dataArg.tags,
106
+ });
107
+
108
+ if (!edge) {
109
+ return { success: false, edge: null as any };
110
+ }
111
+
112
+ // Sync allowed edges if enabled status changed
113
+ if (tunnelManager && dataArg.enabled !== undefined) {
114
+ await tunnelManager.syncAllowedEdges();
115
+ }
116
+
117
+ return { success: true, edge: { ...edge, secret: '********' } };
118
+ },
119
+ ),
120
+ );
121
+
122
+ // Regenerate secret for an edge
123
+ this.typedrouter.addTypedHandler(
124
+ new plugins.typedrequest.TypedHandler<interfaces.requests.IReq_RegenerateRemoteIngressSecret>(
125
+ 'regenerateRemoteIngressSecret',
126
+ async (dataArg, toolsArg) => {
127
+ const manager = this.opsServerRef.dcRouterRef.remoteIngressManager;
128
+ const tunnelManager = this.opsServerRef.dcRouterRef.tunnelManager;
129
+
130
+ if (!manager) {
131
+ return { success: false, secret: '' };
132
+ }
133
+
134
+ const secret = await manager.regenerateSecret(dataArg.id);
135
+ if (!secret) {
136
+ return { success: false, secret: '' };
137
+ }
138
+
139
+ // Sync allowed edges since secret changed
140
+ if (tunnelManager) {
141
+ await tunnelManager.syncAllowedEdges();
142
+ }
143
+
144
+ return { success: true, secret };
145
+ },
146
+ ),
147
+ );
148
+
149
+ // Get runtime status of all edges
150
+ this.typedrouter.addTypedHandler(
151
+ new plugins.typedrequest.TypedHandler<interfaces.requests.IReq_GetRemoteIngressStatus>(
152
+ 'getRemoteIngressStatus',
153
+ async (dataArg, toolsArg) => {
154
+ const tunnelManager = this.opsServerRef.dcRouterRef.tunnelManager;
155
+ if (!tunnelManager) {
156
+ return { statuses: [] };
157
+ }
158
+ return { statuses: tunnelManager.getEdgeStatuses() };
159
+ },
160
+ ),
161
+ );
162
+ }
163
+ }
package/ts/plugins.ts CHANGED
@@ -23,9 +23,11 @@ export {
23
23
 
24
24
  // @serve.zone scope
25
25
  import * as servezoneInterfaces from '@serve.zone/interfaces';
26
+ import * as remoteingress from '@serve.zone/remoteingress';
26
27
 
27
28
  export {
28
- servezoneInterfaces
29
+ servezoneInterfaces,
30
+ remoteingress,
29
31
  }
30
32
 
31
33
  // @api.global scope
@@ -0,0 +1,160 @@
1
+ import * as plugins from '../plugins.js';
2
+ import type { StorageManager } from '../storage/classes.storagemanager.js';
3
+ import type { IRemoteIngress } from '../../ts_interfaces/data/remoteingress.js';
4
+
5
+ const STORAGE_PREFIX = '/remote-ingress/';
6
+
7
+ /**
8
+ * Manages CRUD for remote ingress edge registrations.
9
+ * Persists edge configs via StorageManager and provides
10
+ * the allowed edges list for the Rust hub.
11
+ */
12
+ export class RemoteIngressManager {
13
+ private storageManager: StorageManager;
14
+ private edges: Map<string, IRemoteIngress> = new Map();
15
+
16
+ constructor(storageManager: StorageManager) {
17
+ this.storageManager = storageManager;
18
+ }
19
+
20
+ /**
21
+ * Load all edge registrations from storage into memory.
22
+ */
23
+ public async initialize(): Promise<void> {
24
+ const keys = await this.storageManager.list(STORAGE_PREFIX);
25
+ for (const key of keys) {
26
+ const edge = await this.storageManager.getJSON<IRemoteIngress>(key);
27
+ if (edge) {
28
+ this.edges.set(edge.id, edge);
29
+ }
30
+ }
31
+ }
32
+
33
+ /**
34
+ * Create a new edge registration.
35
+ */
36
+ public async createEdge(
37
+ name: string,
38
+ listenPorts: number[],
39
+ tags?: string[],
40
+ ): Promise<IRemoteIngress> {
41
+ const id = plugins.uuid.v4();
42
+ const secret = plugins.crypto.randomBytes(32).toString('hex');
43
+ const now = Date.now();
44
+
45
+ const edge: IRemoteIngress = {
46
+ id,
47
+ name,
48
+ secret,
49
+ listenPorts,
50
+ enabled: true,
51
+ tags: tags || [],
52
+ createdAt: now,
53
+ updatedAt: now,
54
+ };
55
+
56
+ await this.storageManager.setJSON(`${STORAGE_PREFIX}${id}`, edge);
57
+ this.edges.set(id, edge);
58
+ return edge;
59
+ }
60
+
61
+ /**
62
+ * Get an edge by ID.
63
+ */
64
+ public getEdge(id: string): IRemoteIngress | undefined {
65
+ return this.edges.get(id);
66
+ }
67
+
68
+ /**
69
+ * Get all edge registrations.
70
+ */
71
+ public getAllEdges(): IRemoteIngress[] {
72
+ return Array.from(this.edges.values());
73
+ }
74
+
75
+ /**
76
+ * Update an edge registration.
77
+ */
78
+ public async updateEdge(
79
+ id: string,
80
+ updates: {
81
+ name?: string;
82
+ listenPorts?: number[];
83
+ enabled?: boolean;
84
+ tags?: string[];
85
+ },
86
+ ): Promise<IRemoteIngress | null> {
87
+ const edge = this.edges.get(id);
88
+ if (!edge) {
89
+ return null;
90
+ }
91
+
92
+ if (updates.name !== undefined) edge.name = updates.name;
93
+ if (updates.listenPorts !== undefined) edge.listenPorts = updates.listenPorts;
94
+ if (updates.enabled !== undefined) edge.enabled = updates.enabled;
95
+ if (updates.tags !== undefined) edge.tags = updates.tags;
96
+ edge.updatedAt = Date.now();
97
+
98
+ await this.storageManager.setJSON(`${STORAGE_PREFIX}${id}`, edge);
99
+ this.edges.set(id, edge);
100
+ return edge;
101
+ }
102
+
103
+ /**
104
+ * Delete an edge registration.
105
+ */
106
+ public async deleteEdge(id: string): Promise<boolean> {
107
+ if (!this.edges.has(id)) {
108
+ return false;
109
+ }
110
+ await this.storageManager.delete(`${STORAGE_PREFIX}${id}`);
111
+ this.edges.delete(id);
112
+ return true;
113
+ }
114
+
115
+ /**
116
+ * Regenerate the secret for an edge.
117
+ */
118
+ public async regenerateSecret(id: string): Promise<string | null> {
119
+ const edge = this.edges.get(id);
120
+ if (!edge) {
121
+ return null;
122
+ }
123
+
124
+ edge.secret = plugins.crypto.randomBytes(32).toString('hex');
125
+ edge.updatedAt = Date.now();
126
+
127
+ await this.storageManager.setJSON(`${STORAGE_PREFIX}${id}`, edge);
128
+ this.edges.set(id, edge);
129
+ return edge.secret;
130
+ }
131
+
132
+ /**
133
+ * Verify an edge's secret using constant-time comparison.
134
+ */
135
+ public verifySecret(id: string, secret: string): boolean {
136
+ const edge = this.edges.get(id);
137
+ if (!edge) {
138
+ return false;
139
+ }
140
+ const expected = Buffer.from(edge.secret);
141
+ const provided = Buffer.from(secret);
142
+ if (expected.length !== provided.length) {
143
+ return false;
144
+ }
145
+ return plugins.crypto.timingSafeEqual(expected, provided);
146
+ }
147
+
148
+ /**
149
+ * Get the list of allowed edges (enabled only) for the Rust hub.
150
+ */
151
+ public getAllowedEdges(): Array<{ id: string; secret: string }> {
152
+ const result: Array<{ id: string; secret: string }> = [];
153
+ for (const edge of this.edges.values()) {
154
+ if (edge.enabled) {
155
+ result.push({ id: edge.id, secret: edge.secret });
156
+ }
157
+ }
158
+ return result;
159
+ }
160
+ }
@@ -0,0 +1,126 @@
1
+ import * as plugins from '../plugins.js';
2
+ import type { IRemoteIngressStatus } from '../../ts_interfaces/data/remoteingress.js';
3
+ import type { RemoteIngressManager } from './classes.remoteingress-manager.js';
4
+
5
+ export interface ITunnelManagerConfig {
6
+ tunnelPort?: number;
7
+ targetHost?: string;
8
+ }
9
+
10
+ /**
11
+ * Manages the RemoteIngressHub instance and tracks connected edge statuses.
12
+ */
13
+ export class TunnelManager {
14
+ private hub: InstanceType<typeof plugins.remoteingress.RemoteIngressHub>;
15
+ private manager: RemoteIngressManager;
16
+ private config: ITunnelManagerConfig;
17
+ private edgeStatuses: Map<string, IRemoteIngressStatus> = new Map();
18
+
19
+ constructor(manager: RemoteIngressManager, config: ITunnelManagerConfig = {}) {
20
+ this.manager = manager;
21
+ this.config = config;
22
+ this.hub = new plugins.remoteingress.RemoteIngressHub();
23
+
24
+ // Listen for edge connect/disconnect events
25
+ this.hub.on('edgeConnected', (data: { edgeId: string }) => {
26
+ const existing = this.edgeStatuses.get(data.edgeId);
27
+ this.edgeStatuses.set(data.edgeId, {
28
+ edgeId: data.edgeId,
29
+ connected: true,
30
+ publicIp: existing?.publicIp ?? null,
31
+ activeTunnels: 0,
32
+ lastHeartbeat: Date.now(),
33
+ connectedAt: Date.now(),
34
+ });
35
+ });
36
+
37
+ this.hub.on('edgeDisconnected', (data: { edgeId: string }) => {
38
+ const existing = this.edgeStatuses.get(data.edgeId);
39
+ if (existing) {
40
+ existing.connected = false;
41
+ existing.activeTunnels = 0;
42
+ }
43
+ });
44
+
45
+ this.hub.on('streamOpened', (data: { edgeId: string; streamId: number }) => {
46
+ const existing = this.edgeStatuses.get(data.edgeId);
47
+ if (existing) {
48
+ existing.activeTunnels++;
49
+ existing.lastHeartbeat = Date.now();
50
+ }
51
+ });
52
+
53
+ this.hub.on('streamClosed', (data: { edgeId: string; streamId: number }) => {
54
+ const existing = this.edgeStatuses.get(data.edgeId);
55
+ if (existing && existing.activeTunnels > 0) {
56
+ existing.activeTunnels--;
57
+ }
58
+ });
59
+ }
60
+
61
+ /**
62
+ * Start the tunnel hub and load allowed edges.
63
+ */
64
+ public async start(): Promise<void> {
65
+ await this.hub.start({
66
+ tunnelPort: this.config.tunnelPort ?? 8443,
67
+ targetHost: this.config.targetHost ?? '127.0.0.1',
68
+ });
69
+
70
+ // Send allowed edges to the hub
71
+ await this.syncAllowedEdges();
72
+ }
73
+
74
+ /**
75
+ * Stop the tunnel hub.
76
+ */
77
+ public async stop(): Promise<void> {
78
+ await this.hub.stop();
79
+ this.edgeStatuses.clear();
80
+ }
81
+
82
+ /**
83
+ * Sync allowed edges from the manager to the hub.
84
+ * Call this after creating/deleting/updating edges.
85
+ */
86
+ public async syncAllowedEdges(): Promise<void> {
87
+ const edges = this.manager.getAllowedEdges();
88
+ await this.hub.updateAllowedEdges(edges);
89
+ }
90
+
91
+ /**
92
+ * Get runtime statuses for all known edges.
93
+ */
94
+ public getEdgeStatuses(): IRemoteIngressStatus[] {
95
+ return Array.from(this.edgeStatuses.values());
96
+ }
97
+
98
+ /**
99
+ * Get status for a specific edge.
100
+ */
101
+ public getEdgeStatus(edgeId: string): IRemoteIngressStatus | undefined {
102
+ return this.edgeStatuses.get(edgeId);
103
+ }
104
+
105
+ /**
106
+ * Get the count of connected edges.
107
+ */
108
+ public getConnectedCount(): number {
109
+ let count = 0;
110
+ for (const status of this.edgeStatuses.values()) {
111
+ if (status.connected) count++;
112
+ }
113
+ return count;
114
+ }
115
+
116
+ /**
117
+ * Get the total number of active tunnels across all edges.
118
+ */
119
+ public getTotalActiveTunnels(): number {
120
+ let total = 0;
121
+ for (const status of this.edgeStatuses.values()) {
122
+ total += status.activeTunnels;
123
+ }
124
+ return total;
125
+ }
126
+ }
@@ -0,0 +1,2 @@
1
+ export * from './classes.remoteingress-manager.js';
2
+ export * from './classes.tunnel-manager.js';
@@ -3,6 +3,6 @@
3
3
  */
4
4
  export const commitinfo = {
5
5
  name: '@serve.zone/dcrouter',
6
- version: '6.3.0',
6
+ version: '6.4.0',
7
7
  description: 'A multifaceted routing service handling mail and SMS delivery functions.'
8
8
  }
@@ -116,7 +116,7 @@ export const configStatePart = await appState.getStatePart<IConfigState>(
116
116
  // Determine initial view from URL path
117
117
  const getInitialView = (): string => {
118
118
  const path = typeof window !== 'undefined' ? window.location.pathname : '/';
119
- const validViews = ['overview', 'network', 'emails', 'logs', 'configuration', 'security', 'certificates'];
119
+ const validViews = ['overview', 'network', 'emails', 'logs', 'configuration', 'security', 'certificates', 'remoteingress'];
120
120
  const segments = path.split('/').filter(Boolean);
121
121
  const view = segments[0];
122
122
  return validViews.includes(view) ? view : 'overview';
@@ -192,6 +192,34 @@ export const certificateStatePart = await appState.getStatePart<ICertificateStat
192
192
  'soft'
193
193
  );
194
194
 
195
+ // ============================================================================
196
+ // Remote Ingress State
197
+ // ============================================================================
198
+
199
+ export interface IRemoteIngressState {
200
+ edges: interfaces.data.IRemoteIngress[];
201
+ statuses: interfaces.data.IRemoteIngressStatus[];
202
+ selectedEdgeId: string | null;
203
+ newEdgeSecret: string | null;
204
+ isLoading: boolean;
205
+ error: string | null;
206
+ lastUpdated: number;
207
+ }
208
+
209
+ export const remoteIngressStatePart = await appState.getStatePart<IRemoteIngressState>(
210
+ 'remoteIngress',
211
+ {
212
+ edges: [],
213
+ statuses: [],
214
+ selectedEdgeId: null,
215
+ newEdgeSecret: null,
216
+ isLoading: false,
217
+ error: null,
218
+ lastUpdated: 0,
219
+ },
220
+ 'soft'
221
+ );
222
+
195
223
  // Actions for state management
196
224
  interface IActionContext {
197
225
  identity: interfaces.data.IIdentity | null;
@@ -378,6 +406,13 @@ export const setActiveViewAction = uiStatePart.createAction<string>(async (state
378
406
  }, 100);
379
407
  }
380
408
 
409
+ // If switching to remoteingress view, ensure we fetch edge data
410
+ if (viewName === 'remoteingress' && currentState.activeView !== 'remoteingress') {
411
+ setTimeout(() => {
412
+ remoteIngressStatePart.dispatchAction(fetchRemoteIngressAction, null);
413
+ }, 100);
414
+ }
415
+
381
416
  return {
382
417
  ...currentState,
383
418
  activeView: viewName,
@@ -745,6 +780,150 @@ export const reprovisionCertificateAction = certificateStatePart.createAction<st
745
780
  }
746
781
  );
747
782
 
783
+ // ============================================================================
784
+ // Remote Ingress Actions
785
+ // ============================================================================
786
+
787
+ export const fetchRemoteIngressAction = remoteIngressStatePart.createAction(async (statePartArg) => {
788
+ const context = getActionContext();
789
+ const currentState = statePartArg.getState();
790
+
791
+ try {
792
+ const edgesRequest = new plugins.domtools.plugins.typedrequest.TypedRequest<
793
+ interfaces.requests.IReq_GetRemoteIngresses
794
+ >('/typedrequest', 'getRemoteIngresses');
795
+
796
+ const statusRequest = new plugins.domtools.plugins.typedrequest.TypedRequest<
797
+ interfaces.requests.IReq_GetRemoteIngressStatus
798
+ >('/typedrequest', 'getRemoteIngressStatus');
799
+
800
+ const [edgesResponse, statusResponse] = await Promise.all([
801
+ edgesRequest.fire({ identity: context.identity }),
802
+ statusRequest.fire({ identity: context.identity }),
803
+ ]);
804
+
805
+ return {
806
+ ...currentState,
807
+ edges: edgesResponse.edges,
808
+ statuses: statusResponse.statuses,
809
+ isLoading: false,
810
+ error: null,
811
+ lastUpdated: Date.now(),
812
+ };
813
+ } catch (error) {
814
+ return {
815
+ ...currentState,
816
+ isLoading: false,
817
+ error: error instanceof Error ? error.message : 'Failed to fetch remote ingress data',
818
+ };
819
+ }
820
+ });
821
+
822
+ export const createRemoteIngressAction = remoteIngressStatePart.createAction<{
823
+ name: string;
824
+ listenPorts: number[];
825
+ tags?: string[];
826
+ }>(async (statePartArg, dataArg) => {
827
+ const context = getActionContext();
828
+ const currentState = statePartArg.getState();
829
+
830
+ try {
831
+ const request = new plugins.domtools.plugins.typedrequest.TypedRequest<
832
+ interfaces.requests.IReq_CreateRemoteIngress
833
+ >('/typedrequest', 'createRemoteIngress');
834
+
835
+ const response = await request.fire({
836
+ identity: context.identity,
837
+ name: dataArg.name,
838
+ listenPorts: dataArg.listenPorts,
839
+ tags: dataArg.tags,
840
+ });
841
+
842
+ if (response.success) {
843
+ // Refresh the list and store the new secret for display
844
+ await remoteIngressStatePart.dispatchAction(fetchRemoteIngressAction, null);
845
+ return {
846
+ ...statePartArg.getState(),
847
+ newEdgeSecret: response.edge.secret,
848
+ };
849
+ }
850
+
851
+ return currentState;
852
+ } catch (error) {
853
+ return {
854
+ ...currentState,
855
+ error: error instanceof Error ? error.message : 'Failed to create edge',
856
+ };
857
+ }
858
+ });
859
+
860
+ export const deleteRemoteIngressAction = remoteIngressStatePart.createAction<string>(
861
+ async (statePartArg, edgeId) => {
862
+ const context = getActionContext();
863
+ const currentState = statePartArg.getState();
864
+
865
+ try {
866
+ const request = new plugins.domtools.plugins.typedrequest.TypedRequest<
867
+ interfaces.requests.IReq_DeleteRemoteIngress
868
+ >('/typedrequest', 'deleteRemoteIngress');
869
+
870
+ await request.fire({
871
+ identity: context.identity,
872
+ id: edgeId,
873
+ });
874
+
875
+ await remoteIngressStatePart.dispatchAction(fetchRemoteIngressAction, null);
876
+ return statePartArg.getState();
877
+ } catch (error) {
878
+ return {
879
+ ...currentState,
880
+ error: error instanceof Error ? error.message : 'Failed to delete edge',
881
+ };
882
+ }
883
+ }
884
+ );
885
+
886
+ export const regenerateRemoteIngressSecretAction = remoteIngressStatePart.createAction<string>(
887
+ async (statePartArg, edgeId) => {
888
+ const context = getActionContext();
889
+ const currentState = statePartArg.getState();
890
+
891
+ try {
892
+ const request = new plugins.domtools.plugins.typedrequest.TypedRequest<
893
+ interfaces.requests.IReq_RegenerateRemoteIngressSecret
894
+ >('/typedrequest', 'regenerateRemoteIngressSecret');
895
+
896
+ const response = await request.fire({
897
+ identity: context.identity,
898
+ id: edgeId,
899
+ });
900
+
901
+ if (response.success) {
902
+ return {
903
+ ...currentState,
904
+ newEdgeSecret: response.secret,
905
+ };
906
+ }
907
+
908
+ return currentState;
909
+ } catch (error) {
910
+ return {
911
+ ...currentState,
912
+ error: error instanceof Error ? error.message : 'Failed to regenerate secret',
913
+ };
914
+ }
915
+ }
916
+ );
917
+
918
+ export const clearNewEdgeSecretAction = remoteIngressStatePart.createAction(
919
+ async (statePartArg) => {
920
+ return {
921
+ ...statePartArg.getState(),
922
+ newEdgeSecret: null,
923
+ };
924
+ }
925
+ );
926
+
748
927
  // Combined refresh action for efficient polling
749
928
  async function dispatchCombinedRefreshAction() {
750
929
  const context = getActionContext();
@@ -6,4 +6,5 @@ export * from './ops-view-logs.js';
6
6
  export * from './ops-view-config.js';
7
7
  export * from './ops-view-security.js';
8
8
  export * from './ops-view-certificates.js';
9
+ export * from './ops-view-remoteingress.js';
9
10
  export * from './shared/index.js';