@serve.zone/dcrouter 15.0.2 → 15.0.4

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 (72) hide show
  1. package/deno.json +1 -1
  2. package/dist_serve/bundle.js +768 -768
  3. package/dist_ts/00_commitinfo_data.js +1 -1
  4. package/dist_ts/classes.dcrouter.js +9 -1
  5. package/dist_ts_web/00_commitinfo_data.js +1 -1
  6. package/dist_ts_web/appstate/acme.d.ts +17 -0
  7. package/dist_ts_web/appstate/acme.js +64 -0
  8. package/dist_ts_web/appstate/certificates.d.ts +37 -0
  9. package/dist_ts_web/appstate/certificates.js +107 -0
  10. package/dist_ts_web/appstate/config.d.ts +9 -0
  11. package/dist_ts_web/appstate/config.js +35 -0
  12. package/dist_ts_web/appstate/domains.d.ts +80 -0
  13. package/dist_ts_web/appstate/domains.js +324 -0
  14. package/dist_ts_web/appstate/email-domains.d.ts +25 -0
  15. package/dist_ts_web/appstate/email-domains.js +104 -0
  16. package/dist_ts_web/appstate/email-ops.d.ts +10 -0
  17. package/dist_ts_web/appstate/email-ops.js +40 -0
  18. package/dist_ts_web/appstate/login.d.ts +30 -0
  19. package/dist_ts_web/appstate/login.js +83 -0
  20. package/dist_ts_web/appstate/logs.d.ts +16 -0
  21. package/dist_ts_web/appstate/logs.js +27 -0
  22. package/dist_ts_web/appstate/network.d.ts +50 -0
  23. package/dist_ts_web/appstate/network.js +122 -0
  24. package/dist_ts_web/appstate/profiles-targets.d.ts +45 -0
  25. package/dist_ts_web/appstate/profiles-targets.js +173 -0
  26. package/dist_ts_web/appstate/remoteingress.d.ts +47 -0
  27. package/dist_ts_web/appstate/remoteingress.js +204 -0
  28. package/dist_ts_web/appstate/routes.d.ts +76 -0
  29. package/dist_ts_web/appstate/routes.js +316 -0
  30. package/dist_ts_web/appstate/runtime.d.ts +1 -0
  31. package/dist_ts_web/appstate/runtime.js +276 -0
  32. package/dist_ts_web/appstate/security.d.ts +29 -0
  33. package/dist_ts_web/appstate/security.js +167 -0
  34. package/dist_ts_web/appstate/shared.d.ts +3 -0
  35. package/dist_ts_web/appstate/shared.js +13 -0
  36. package/dist_ts_web/appstate/stats.d.ts +15 -0
  37. package/dist_ts_web/appstate/stats.js +59 -0
  38. package/dist_ts_web/appstate/target-profiles.d.ts +37 -0
  39. package/dist_ts_web/appstate/target-profiles.js +118 -0
  40. package/dist_ts_web/appstate/ui.d.ts +11 -0
  41. package/dist_ts_web/appstate/ui.js +55 -0
  42. package/dist_ts_web/appstate/users.d.ts +27 -0
  43. package/dist_ts_web/appstate/users.js +85 -0
  44. package/dist_ts_web/appstate/vpn.d.ts +44 -0
  45. package/dist_ts_web/appstate/vpn.js +148 -0
  46. package/dist_ts_web/appstate.d.ts +20 -568
  47. package/dist_ts_web/appstate.js +24 -2418
  48. package/package.json +3 -3
  49. package/ts/00_commitinfo_data.ts +1 -1
  50. package/ts/classes.dcrouter.ts +10 -0
  51. package/ts_web/00_commitinfo_data.ts +1 -1
  52. package/ts_web/appstate/acme.ts +93 -0
  53. package/ts_web/appstate/certificates.ts +159 -0
  54. package/ts_web/appstate/config.ts +49 -0
  55. package/ts_web/appstate/domains.ts +429 -0
  56. package/ts_web/appstate/email-domains.ts +155 -0
  57. package/ts_web/appstate/email-ops.ts +57 -0
  58. package/ts_web/appstate/login.ts +128 -0
  59. package/ts_web/appstate/logs.ts +50 -0
  60. package/ts_web/appstate/network.ts +161 -0
  61. package/ts_web/appstate/profiles-targets.ts +240 -0
  62. package/ts_web/appstate/remoteingress.ts +300 -0
  63. package/ts_web/appstate/routes.ts +447 -0
  64. package/ts_web/appstate/runtime.ts +308 -0
  65. package/ts_web/appstate/security.ts +229 -0
  66. package/ts_web/appstate/shared.ts +15 -0
  67. package/ts_web/appstate/stats.ts +79 -0
  68. package/ts_web/appstate/target-profiles.ts +164 -0
  69. package/ts_web/appstate/ui.ts +75 -0
  70. package/ts_web/appstate/users.ts +133 -0
  71. package/ts_web/appstate/vpn.ts +234 -0
  72. package/ts_web/appstate.ts +24 -3403
@@ -1,3403 +1,24 @@
1
- import * as plugins from './plugins.js';
2
- import type * as servezoneInterfaces from '@serve.zone/interfaces';
3
- import * as interfaces from '../ts_interfaces/index.js';
4
-
5
- // Create main app state instance
6
- export const appState = new plugins.domtools.plugins.smartstate.Smartstate();
7
-
8
- // Define state interfaces
9
- export interface ILoginState {
10
- identity: interfaces.data.IIdentity | null;
11
- isLoggedIn: boolean;
12
- }
13
-
14
- export type IAdminBootstrapStatus = interfaces.requests.IReq_GetAdminBootstrapStatus['response'];
15
-
16
- export interface IStatsState {
17
- serverStats: interfaces.data.IServerStats | null;
18
- emailStats: interfaces.data.IEmailStats | null;
19
- dnsStats: interfaces.data.IDnsStats | null;
20
- securityMetrics: interfaces.data.ISecurityMetrics | null;
21
- radiusStats: interfaces.data.IRadiusStats | null;
22
- vpnStats: interfaces.data.IVpnStats | null;
23
- lastUpdated: number;
24
- isLoading: boolean;
25
- error: string | null;
26
- }
27
-
28
- export interface IConfigState {
29
- config: interfaces.requests.IConfigData | null;
30
- isLoading: boolean;
31
- error: string | null;
32
- }
33
-
34
- export interface IUiState {
35
- activeView: string;
36
- activeSubview: string | null;
37
- sidebarCollapsed: boolean;
38
- autoRefresh: boolean;
39
- refreshInterval: number; // milliseconds
40
- theme: 'light' | 'dark';
41
- }
42
-
43
- export interface ILogState {
44
- recentLogs: interfaces.data.ILogEntry[];
45
- isStreaming: boolean;
46
- filters: {
47
- level?: string[];
48
- category?: string[];
49
- };
50
- }
51
-
52
- export interface INetworkState {
53
- connections: interfaces.data.IConnectionInfo[];
54
- connectionsByIP: { [ip: string]: number };
55
- throughputRate: { bytesInPerSecond: number; bytesOutPerSecond: number };
56
- totalBytes: { in: number; out: number };
57
- topIPs: Array<{ ip: string; count: number }>;
58
- topIPsByBandwidth: Array<{ ip: string; count: number; bwIn: number; bwOut: number }>;
59
- topASNs: interfaces.data.IAsnActivity[];
60
- throughputByIP: Array<{ ip: string; in: number; out: number }>;
61
- ipIntelligence: interfaces.data.IIpIntelligenceRecord[];
62
- domainActivity: interfaces.data.IDomainActivity[];
63
- throughputHistory: Array<{ timestamp: number; in: number; out: number }>;
64
- requestsPerSecond: number;
65
- requestsTotal: number;
66
- backends: interfaces.data.IBackendInfo[];
67
- frontendProtocols: interfaces.data.IProtocolDistribution | null;
68
- backendProtocols: interfaces.data.IProtocolDistribution | null;
69
- lastUpdated: number;
70
- isLoading: boolean;
71
- error: string | null;
72
- }
73
-
74
- export interface ISecurityPolicyState {
75
- rules: interfaces.data.ISecurityBlockRule[];
76
- ipIntelligence: interfaces.data.IIpIntelligenceRecord[];
77
- compiledPolicy: interfaces.data.ISecurityCompiledPolicy | null;
78
- auditEvents: interfaces.data.ISecurityPolicyAuditEvent[];
79
- isLoading: boolean;
80
- error: string | null;
81
- lastUpdated: number;
82
- }
83
-
84
- export interface ICertificateState {
85
- certificates: interfaces.requests.ICertificateInfo[];
86
- summary: { total: number; valid: number; expiring: number; expired: number; failed: number; unknown: number };
87
- isLoading: boolean;
88
- error: string | null;
89
- lastUpdated: number;
90
- }
91
-
92
- export interface IEmailOpsState {
93
- emails: interfaces.requests.IEmail[];
94
- isLoading: boolean;
95
- error: string | null;
96
- lastUpdated: number;
97
- }
98
-
99
- // Create state parts with appropriate persistence
100
- export const loginStatePart = await appState.getStatePart<ILoginState>(
101
- 'login',
102
- {
103
- identity: null,
104
- isLoggedIn: false,
105
- },
106
- 'persistent' // Login state persists across browser sessions
107
- );
108
-
109
- export const statsStatePart = await appState.getStatePart<IStatsState>(
110
- 'stats',
111
- {
112
- serverStats: null,
113
- emailStats: null,
114
- dnsStats: null,
115
- securityMetrics: null,
116
- radiusStats: null,
117
- vpnStats: null,
118
- lastUpdated: 0,
119
- isLoading: false,
120
- error: null,
121
- },
122
- 'soft' // Stats are cached but not persisted
123
- );
124
-
125
- export const configStatePart = await appState.getStatePart<IConfigState>(
126
- 'config',
127
- {
128
- config: null,
129
- isLoading: false,
130
- error: null,
131
- }
132
- );
133
-
134
- // Determine initial view from URL path
135
- const getInitialView = (): string => {
136
- const path = typeof window !== 'undefined' ? window.location.pathname : '/';
137
- const validViews = ['overview', 'network', 'email', 'logs', 'access', 'security', 'domains'];
138
- const segments = path.split('/').filter(Boolean);
139
- const view = segments[0];
140
- return validViews.includes(view) ? view : 'overview';
141
- };
142
-
143
- // Determine initial subview (second URL segment) from the path
144
- const getInitialSubview = (): string | null => {
145
- const path = typeof window !== 'undefined' ? window.location.pathname : '/';
146
- const segments = path.split('/').filter(Boolean);
147
- return segments[1] ?? null;
148
- };
149
-
150
- export const uiStatePart = await appState.getStatePart<IUiState>(
151
- 'ui',
152
- {
153
- activeView: getInitialView(),
154
- activeSubview: getInitialSubview(),
155
- sidebarCollapsed: false,
156
- autoRefresh: true,
157
- refreshInterval: 1000, // 1 second
158
- theme: 'light',
159
- },
160
- );
161
-
162
- export const logStatePart = await appState.getStatePart<ILogState>(
163
- 'logs',
164
- {
165
- recentLogs: [],
166
- isStreaming: false,
167
- filters: {},
168
- },
169
- 'soft'
170
- );
171
-
172
- export const networkStatePart = await appState.getStatePart<INetworkState>(
173
- 'network',
174
- {
175
- connections: [],
176
- connectionsByIP: {},
177
- throughputRate: { bytesInPerSecond: 0, bytesOutPerSecond: 0 },
178
- totalBytes: { in: 0, out: 0 },
179
- topIPs: [],
180
- topIPsByBandwidth: [],
181
- topASNs: [],
182
- throughputByIP: [],
183
- ipIntelligence: [],
184
- domainActivity: [],
185
- throughputHistory: [],
186
- requestsPerSecond: 0,
187
- requestsTotal: 0,
188
- backends: [],
189
- frontendProtocols: null,
190
- backendProtocols: null,
191
- lastUpdated: 0,
192
- isLoading: false,
193
- error: null,
194
- },
195
- 'soft'
196
- );
197
-
198
- export const securityPolicyStatePart = await appState.getStatePart<ISecurityPolicyState>(
199
- 'securityPolicy',
200
- {
201
- rules: [],
202
- ipIntelligence: [],
203
- compiledPolicy: null,
204
- auditEvents: [],
205
- isLoading: false,
206
- error: null,
207
- lastUpdated: 0,
208
- },
209
- 'soft',
210
- );
211
-
212
- export const emailOpsStatePart = await appState.getStatePart<IEmailOpsState>(
213
- 'emailOps',
214
- {
215
- emails: [],
216
- isLoading: false,
217
- error: null,
218
- lastUpdated: 0,
219
- },
220
- 'soft'
221
- );
222
-
223
- export const certificateStatePart = await appState.getStatePart<ICertificateState>(
224
- 'certificates',
225
- {
226
- certificates: [],
227
- summary: { total: 0, valid: 0, expiring: 0, expired: 0, failed: 0, unknown: 0 },
228
- isLoading: false,
229
- error: null,
230
- lastUpdated: 0,
231
- },
232
- 'soft'
233
- );
234
-
235
- // ============================================================================
236
- // ACME Config State (DB-backed singleton, managed via Domains > Certificates)
237
- // ============================================================================
238
-
239
- export interface IAcmeConfigState {
240
- config: interfaces.data.IAcmeConfig | null;
241
- isLoading: boolean;
242
- error: string | null;
243
- lastUpdated: number;
244
- }
245
-
246
- export const acmeConfigStatePart = await appState.getStatePart<IAcmeConfigState>(
247
- 'acmeConfig',
248
- {
249
- config: null,
250
- isLoading: false,
251
- error: null,
252
- lastUpdated: 0,
253
- },
254
- 'soft',
255
- );
256
-
257
- // ============================================================================
258
- // Remote Ingress State
259
- // ============================================================================
260
-
261
- export interface IRemoteIngressState {
262
- edges: interfaces.data.IRemoteIngress[];
263
- statuses: interfaces.data.IRemoteIngressStatus[];
264
- hubSettings: interfaces.data.IRemoteIngressHubSettings | null;
265
- selectedEdgeId: string | null;
266
- newEdgeId: string | null;
267
- isLoading: boolean;
268
- error: string | null;
269
- lastUpdated: number;
270
- }
271
-
272
- export const remoteIngressStatePart = await appState.getStatePart<IRemoteIngressState>(
273
- 'remoteIngress',
274
- {
275
- edges: [],
276
- statuses: [],
277
- hubSettings: null,
278
- selectedEdgeId: null,
279
- newEdgeId: null,
280
- isLoading: false,
281
- error: null,
282
- lastUpdated: 0,
283
- },
284
- 'soft'
285
- );
286
-
287
- // ============================================================================
288
- // Route Management State
289
- // ============================================================================
290
-
291
- export interface IRouteManagementState {
292
- mergedRoutes: interfaces.data.IMergedRoute[];
293
- warnings: interfaces.data.IRouteWarning[];
294
- httpRedirects: interfaces.data.IHttpRedirectInfo[];
295
- apiTokens: interfaces.data.IApiTokenInfo[];
296
- gatewayClients: interfaces.data.IGatewayClient[];
297
- isLoading: boolean;
298
- error: string | null;
299
- lastUpdated: number;
300
- }
301
-
302
- export const routeManagementStatePart = await appState.getStatePart<IRouteManagementState>(
303
- 'routeManagement',
304
- {
305
- mergedRoutes: [],
306
- warnings: [],
307
- httpRedirects: [],
308
- apiTokens: [],
309
- gatewayClients: [],
310
- isLoading: false,
311
- error: null,
312
- lastUpdated: 0,
313
- },
314
- 'soft'
315
- );
316
-
317
- // ============================================================================
318
- // Users State (read-only list of OpsServer user accounts)
319
- // ============================================================================
320
-
321
- export interface IUser {
322
- id: string;
323
- username: string;
324
- email?: string;
325
- name?: string;
326
- role: string;
327
- status?: 'active' | 'disabled';
328
- authSources?: Array<'local' | 'idp.global'>;
329
- }
330
-
331
- export interface IUsersState {
332
- users: IUser[];
333
- isLoading: boolean;
334
- error: string | null;
335
- lastUpdated: number;
336
- }
337
-
338
- export const usersStatePart = await appState.getStatePart<IUsersState>(
339
- 'users',
340
- {
341
- users: [],
342
- isLoading: false,
343
- error: null,
344
- lastUpdated: 0,
345
- },
346
- 'soft',
347
- );
348
-
349
- // Actions for state management
350
- interface IActionContext {
351
- identity: interfaces.data.IIdentity | null;
352
- }
353
-
354
- const getActionContext = (): IActionContext => {
355
- const identity = loginStatePart.getState()!.identity;
356
- // Treat expired JWTs as no identity — prevents stale persisted sessions from firing requests
357
- if (identity && identity.expiresAt && identity.expiresAt < Date.now()) {
358
- return { identity: null };
359
- }
360
- return { identity };
361
- };
362
-
363
- // Login Action
364
- export const loginAction = loginStatePart.createAction<{
365
- username: string;
366
- password: string;
367
- authSource?: interfaces.requests.TAdminLoginAuthSource;
368
- }>(async (statePartArg, dataArg): Promise<ILoginState> => {
369
- const typedRequest = new plugins.domtools.plugins.typedrequest.TypedRequest<
370
- interfaces.requests.IReq_AdminLoginWithUsernameAndPassword
371
- >('/typedrequest', 'adminLoginWithUsernameAndPassword');
372
-
373
- try {
374
- const response = await typedRequest.fire({
375
- username: dataArg.username,
376
- password: dataArg.password,
377
- authSource: dataArg.authSource,
378
- });
379
-
380
- if (response.identity) {
381
- return {
382
- identity: response.identity,
383
- isLoggedIn: true,
384
- };
385
- }
386
- return statePartArg.getState()!;
387
- } catch (error: unknown) {
388
- console.error('Login failed:', error);
389
- return statePartArg.getState()!;
390
- }
391
- });
392
-
393
- export async function getAdminBootstrapStatus(): Promise<IAdminBootstrapStatus> {
394
- const request = new plugins.domtools.plugins.typedrequest.TypedRequest<
395
- interfaces.requests.IReq_GetAdminBootstrapStatus
396
- >('/typedrequest', 'getAdminBootstrapStatus');
397
-
398
- return request.fire({});
399
- }
400
-
401
- export async function createInitialAdminUser(optionsArg: {
402
- email: string;
403
- name?: string;
404
- password: string;
405
- enableIdpGlobalAuth?: boolean;
406
- }) {
407
- const context = getActionContext();
408
- if (!context.identity) {
409
- throw new Error('No identity available for admin bootstrap');
410
- }
411
-
412
- const request = new plugins.domtools.plugins.typedrequest.TypedRequest<
413
- interfaces.requests.IReq_CreateInitialAdminUser
414
- >('/typedrequest', 'createInitialAdminUser');
415
-
416
- const response = await request.fire({
417
- identity: context.identity,
418
- email: optionsArg.email,
419
- name: optionsArg.name,
420
- password: optionsArg.password,
421
- enableIdpGlobalAuth: optionsArg.enableIdpGlobalAuth,
422
- });
423
-
424
- if (response.identity) {
425
- loginStatePart.setState({
426
- identity: response.identity,
427
- isLoggedIn: true,
428
- });
429
- }
430
-
431
- return response;
432
- }
433
-
434
- // Logout Action — always clears state, even if identity is expired/missing
435
- export const logoutAction = loginStatePart.createAction(async (statePartArg) => {
436
- const context = getActionContext();
437
-
438
- // Try to notify server, but don't block logout if identity is missing/expired
439
- if (context.identity) {
440
- const typedRequest = new plugins.domtools.plugins.typedrequest.TypedRequest<
441
- interfaces.requests.IReq_AdminLogout
442
- >('/typedrequest', 'adminLogout');
443
- try {
444
- await typedRequest.fire({ identity: context.identity });
445
- } catch (error) {
446
- console.error('Logout error:', error);
447
- }
448
- }
449
-
450
- // Always clear login state
451
- return {
452
- identity: null,
453
- isLoggedIn: false,
454
- };
455
- });
456
-
457
- // Fetch All Stats Action - Using combined endpoint for efficiency
458
- export const fetchAllStatsAction = statsStatePart.createAction(async (statePartArg): Promise<IStatsState> => {
459
- const context = getActionContext();
460
- const currentState = statePartArg.getState()!;
461
- if (!context.identity) return currentState;
462
-
463
- try {
464
- // Use combined metrics endpoint - single request instead of 4
465
- const combinedRequest = new plugins.domtools.plugins.typedrequest.TypedRequest<
466
- interfaces.requests.IReq_GetCombinedMetrics
467
- >('/typedrequest', 'getCombinedMetrics');
468
-
469
- const combinedResponse = await combinedRequest.fire({
470
- identity: context.identity,
471
- sections: {
472
- server: true,
473
- email: true,
474
- dns: true,
475
- security: true,
476
- network: false, // Network is fetched separately for the network view
477
- radius: true,
478
- vpn: true,
479
- },
480
- });
481
-
482
- // Update state with all stats from combined response
483
- return {
484
- serverStats: combinedResponse.metrics.server || currentState.serverStats,
485
- emailStats: combinedResponse.metrics.email || currentState.emailStats,
486
- dnsStats: combinedResponse.metrics.dns || currentState.dnsStats,
487
- securityMetrics: combinedResponse.metrics.security || currentState.securityMetrics,
488
- radiusStats: combinedResponse.metrics.radius || currentState.radiusStats,
489
- vpnStats: combinedResponse.metrics.vpn || currentState.vpnStats,
490
- lastUpdated: Date.now(),
491
- isLoading: false,
492
- error: null,
493
- };
494
- } catch (error: unknown) {
495
- return {
496
- ...currentState,
497
- isLoading: false,
498
- error: (error as Error).message || 'Failed to fetch statistics',
499
- };
500
- }
501
- });
502
-
503
- // Fetch Configuration Action (read-only)
504
- export const fetchConfigurationAction = configStatePart.createAction(async (statePartArg): Promise<IConfigState> => {
505
- const context = getActionContext();
506
- const currentState = statePartArg.getState()!;
507
- if (!context.identity) return currentState;
508
-
509
- try {
510
- const configRequest = new plugins.domtools.plugins.typedrequest.TypedRequest<
511
- interfaces.requests.IReq_GetConfiguration
512
- >('/typedrequest', 'getConfiguration');
513
-
514
- const response = await configRequest.fire({
515
- identity: context.identity,
516
- });
517
-
518
- return {
519
- config: response.config,
520
- isLoading: false,
521
- error: null,
522
- };
523
- } catch (error: unknown) {
524
- return {
525
- ...currentState,
526
- isLoading: false,
527
- error: (error as Error).message || 'Failed to fetch configuration',
528
- };
529
- }
530
- });
531
-
532
- // Fetch Recent Logs Action
533
- export const fetchRecentLogsAction = logStatePart.createAction<{
534
- limit?: number;
535
- level?: 'debug' | 'info' | 'warn' | 'error';
536
- category?: 'smtp' | 'dns' | 'security' | 'system' | 'email';
537
- }>(async (statePartArg, dataArg): Promise<ILogState> => {
538
- const context = getActionContext();
539
- if (!context.identity) return statePartArg.getState()!;
540
-
541
- const logsRequest = new plugins.domtools.plugins.typedrequest.TypedRequest<
542
- interfaces.requests.IReq_GetRecentLogs
543
- >('/typedrequest', 'getRecentLogs');
544
-
545
- const response = await logsRequest.fire({
546
- identity: context.identity,
547
- limit: dataArg.limit || 100,
548
- level: dataArg.level,
549
- category: dataArg.category,
550
- });
551
-
552
- return {
553
- ...statePartArg.getState()!,
554
- recentLogs: response.logs,
555
- };
556
- });
557
-
558
- // Toggle Auto Refresh Action
559
- export const toggleAutoRefreshAction = uiStatePart.createAction(async (statePartArg): Promise<IUiState> => {
560
- const currentState = statePartArg.getState()!;
561
- return {
562
- ...currentState,
563
- autoRefresh: !currentState.autoRefresh,
564
- };
565
- });
566
-
567
- // Set Active View Action
568
- export const setActiveViewAction = uiStatePart.createAction<string>(async (statePartArg, viewName): Promise<IUiState> => {
569
- const currentState = statePartArg.getState()!;
570
-
571
- // If switching to network view, ensure we fetch network data
572
- if (viewName === 'network' && currentState.activeView !== 'network') {
573
- setTimeout(() => {
574
- networkStatePart.dispatchAction(fetchNetworkStatsAction, null);
575
- }, 100);
576
- }
577
-
578
- // If switching to the Domains group, ensure we fetch certificate data
579
- // (Certificates is a subview of Domains).
580
- if (viewName === 'domains' && currentState.activeView !== 'domains') {
581
- setTimeout(() => {
582
- certificateStatePart.dispatchAction(fetchCertificateOverviewAction, null);
583
- }, 100);
584
- }
585
-
586
- return {
587
- ...currentState,
588
- activeView: viewName,
589
- };
590
- });
591
-
592
- const backgroundRefreshesInFlight = new Set<string>();
593
-
594
- function runBackgroundRefresh(key: string, errorMessage: string, task: () => Promise<void>): void {
595
- if (backgroundRefreshesInFlight.has(key)) return;
596
- backgroundRefreshesInFlight.add(key);
597
- void task()
598
- .catch((error) => console.error(errorMessage, error))
599
- .finally(() => backgroundRefreshesInFlight.delete(key));
600
- }
601
-
602
- function refreshNetworkIpIntelligence(identity: interfaces.data.IIdentity, ipAddresses: string[]): void {
603
- const ips = [...new Set(ipAddresses.filter(Boolean))].slice(0, 100);
604
- if (ips.length === 0) return;
605
-
606
- runBackgroundRefresh('networkIpIntelligence', 'IP intelligence refresh failed:', async () => {
607
- const intelligenceRequest = new plugins.domtools.plugins.typedrequest.TypedRequest<
608
- interfaces.requests.IReq_ListIpIntelligence
609
- >('/typedrequest', 'listIpIntelligence');
610
- const intelligenceResponse = await intelligenceRequest.fire({
611
- identity,
612
- ipAddresses: ips,
613
- limit: Math.max(100, ips.length),
614
- });
615
- networkStatePart.setState({
616
- ...networkStatePart.getState()!,
617
- ipIntelligence: intelligenceResponse.records || [],
618
- });
619
- });
620
- }
621
-
622
- function refreshSecurityIpIntelligence(identity: interfaces.data.IIdentity): void {
623
- runBackgroundRefresh('securityIpIntelligence', 'Security IP intelligence refresh failed:', async () => {
624
- const intelligenceRequest = new plugins.domtools.plugins.typedrequest.TypedRequest<
625
- interfaces.requests.IReq_ListIpIntelligence
626
- >('/typedrequest', 'listIpIntelligence');
627
- const intelligenceResponse = await intelligenceRequest.fire({
628
- identity,
629
- limit: 500,
630
- });
631
- securityPolicyStatePart.setState({
632
- ...securityPolicyStatePart.getState()!,
633
- ipIntelligence: intelligenceResponse.records || [],
634
- });
635
- });
636
- }
637
-
638
- // Fetch Network Stats Action
639
- export const fetchNetworkStatsAction = networkStatePart.createAction(async (statePartArg): Promise<INetworkState> => {
640
- const context = getActionContext();
641
- const currentState = statePartArg.getState()!;
642
- if (!context.identity) return currentState;
643
-
644
- try {
645
- // Get network stats for throughput and IP data
646
- const networkStatsRequest = new plugins.domtools.plugins.typedrequest.TypedRequest<
647
- interfaces.requests.IReq_GetNetworkStats
648
- >('/typedrequest', 'getNetworkStats');
649
-
650
- const networkStatsResponse = await networkStatsRequest.fire({
651
- identity: context.identity,
652
- });
653
-
654
- // Use the connections data for the connection list
655
- // and network stats for throughput and IP analytics
656
- const connectionsByIP: { [ip: string]: number } = {};
657
- const throughputByIP = new Map<string, { in: number; out: number }>();
658
- for (const item of networkStatsResponse.throughputByIP || []) {
659
- throughputByIP.set(item.ip, { in: item.in, out: item.out });
660
- }
661
-
662
- // Build connectionsByIP from network stats if available
663
- if (networkStatsResponse.connectionsByIP && Array.isArray(networkStatsResponse.connectionsByIP)) {
664
- networkStatsResponse.connectionsByIP.forEach((item: { ip: string; count: number }) => {
665
- connectionsByIP[item.ip] = item.count;
666
- });
667
- }
668
-
669
- const connections: interfaces.data.IConnectionInfo[] = Object.entries(connectionsByIP).map(([ip, count]) => {
670
- const tp = throughputByIP.get(ip);
671
- return {
672
- id: `ip-${ip}`,
673
- remoteAddress: ip,
674
- localAddress: 'server',
675
- startTime: 0,
676
- protocol: 'https',
677
- state: 'connected',
678
- bytesReceived: tp?.in || 0,
679
- bytesSent: tp?.out || 0,
680
- connectionCount: count,
681
- };
682
- });
683
-
684
- refreshNetworkIpIntelligence(context.identity, [
685
- ...Object.keys(connectionsByIP),
686
- ...(networkStatsResponse.topIPs || []).map((item) => item.ip),
687
- ...(networkStatsResponse.topIPsByBandwidth || []).map((item) => item.ip),
688
- ]);
689
-
690
- return {
691
- connections,
692
- connectionsByIP,
693
- throughputRate: networkStatsResponse.throughputRate || { bytesInPerSecond: 0, bytesOutPerSecond: 0 },
694
- totalBytes: networkStatsResponse.totalDataTransferred
695
- ? { in: networkStatsResponse.totalDataTransferred.bytesIn, out: networkStatsResponse.totalDataTransferred.bytesOut }
696
- : { in: 0, out: 0 },
697
- topIPs: networkStatsResponse.topIPs || [],
698
- topIPsByBandwidth: networkStatsResponse.topIPsByBandwidth || [],
699
- topASNs: networkStatsResponse.topASNs || [],
700
- throughputByIP: networkStatsResponse.throughputByIP || [],
701
- ipIntelligence: currentState.ipIntelligence,
702
- domainActivity: networkStatsResponse.domainActivity || [],
703
- throughputHistory: networkStatsResponse.throughputHistory || [],
704
- requestsPerSecond: networkStatsResponse.requestsPerSecond || 0,
705
- requestsTotal: networkStatsResponse.requestsTotal || 0,
706
- backends: networkStatsResponse.backends || [],
707
- frontendProtocols: networkStatsResponse.frontendProtocols || null,
708
- backendProtocols: networkStatsResponse.backendProtocols || null,
709
- lastUpdated: Date.now(),
710
- isLoading: false,
711
- error: null,
712
- };
713
- } catch (error) {
714
- console.error('Failed to fetch network stats:', error);
715
- return {
716
- ...currentState,
717
- isLoading: false,
718
- error: error instanceof Error ? error.message : 'Failed to fetch network stats',
719
- };
720
- }
721
- });
722
-
723
- // ============================================================================
724
- // Security Policy Actions
725
- // ============================================================================
726
-
727
- export const fetchSecurityPolicyAction = securityPolicyStatePart.createAction(
728
- async (statePartArg): Promise<ISecurityPolicyState> => {
729
- const context = getActionContext();
730
- const currentState = statePartArg.getState()!;
731
- if (!context.identity) return currentState;
732
-
733
- try {
734
- const rulesRequest = new plugins.domtools.plugins.typedrequest.TypedRequest<
735
- interfaces.requests.IReq_ListSecurityBlockRules
736
- >('/typedrequest', 'listSecurityBlockRules');
737
- const compiledPolicyRequest = new plugins.domtools.plugins.typedrequest.TypedRequest<
738
- interfaces.requests.IReq_GetCompiledSecurityPolicy
739
- >('/typedrequest', 'getCompiledSecurityPolicy');
740
- const auditRequest = new plugins.domtools.plugins.typedrequest.TypedRequest<
741
- interfaces.requests.IReq_ListSecurityPolicyAudit
742
- >('/typedrequest', 'listSecurityPolicyAudit');
743
-
744
- const [rulesResponse, compiledPolicyResponse, auditResponse] = await Promise.all([
745
- rulesRequest.fire({ identity: context.identity }),
746
- compiledPolicyRequest.fire({ identity: context.identity }),
747
- auditRequest.fire({ identity: context.identity, limit: 100 }),
748
- ]);
749
-
750
- refreshSecurityIpIntelligence(context.identity);
751
-
752
- return {
753
- rules: rulesResponse.rules || [],
754
- ipIntelligence: currentState.ipIntelligence,
755
- compiledPolicy: compiledPolicyResponse.policy,
756
- auditEvents: auditResponse.events || [],
757
- isLoading: false,
758
- error: null,
759
- lastUpdated: Date.now(),
760
- };
761
- } catch (error: unknown) {
762
- return {
763
- ...currentState,
764
- isLoading: false,
765
- error: error instanceof Error ? error.message : 'Failed to fetch security policy',
766
- };
767
- }
768
- },
769
- );
770
-
771
- export const createSecurityBlockRuleAction = securityPolicyStatePart.createAction<{
772
- type: interfaces.data.TSecurityBlockRuleType;
773
- value: string;
774
- matchMode?: interfaces.data.TSecurityBlockRuleMatchMode;
775
- reason?: string;
776
- enabled?: boolean;
777
- }>(async (statePartArg, dataArg, actionContext): Promise<ISecurityPolicyState> => {
778
- const context = getActionContext();
779
- const currentState = statePartArg.getState()!;
780
- if (!context.identity) return currentState;
781
-
782
- try {
783
- const request = new plugins.domtools.plugins.typedrequest.TypedRequest<
784
- interfaces.requests.IReq_CreateSecurityBlockRule
785
- >('/typedrequest', 'createSecurityBlockRule');
786
-
787
- const response = await request.fire({
788
- identity: context.identity,
789
- type: dataArg.type,
790
- value: dataArg.value,
791
- matchMode: dataArg.matchMode,
792
- reason: dataArg.reason,
793
- enabled: dataArg.enabled,
794
- });
795
-
796
- if (!response.success) {
797
- return { ...currentState, error: response.message || 'Failed to create security block rule' };
798
- }
799
-
800
- return await actionContext!.dispatch(fetchSecurityPolicyAction, null);
801
- } catch (error: unknown) {
802
- return {
803
- ...currentState,
804
- error: error instanceof Error ? error.message : 'Failed to create security block rule',
805
- };
806
- }
807
- });
808
-
809
- export const updateSecurityBlockRuleAction = securityPolicyStatePart.createAction<{
810
- id: string;
811
- value?: string;
812
- matchMode?: interfaces.data.TSecurityBlockRuleMatchMode;
813
- reason?: string;
814
- enabled?: boolean;
815
- }>(async (statePartArg, dataArg, actionContext): Promise<ISecurityPolicyState> => {
816
- const context = getActionContext();
817
- const currentState = statePartArg.getState()!;
818
- if (!context.identity) return currentState;
819
-
820
- try {
821
- const request = new plugins.domtools.plugins.typedrequest.TypedRequest<
822
- interfaces.requests.IReq_UpdateSecurityBlockRule
823
- >('/typedrequest', 'updateSecurityBlockRule');
824
-
825
- const response = await request.fire({
826
- identity: context.identity,
827
- id: dataArg.id,
828
- value: dataArg.value,
829
- matchMode: dataArg.matchMode,
830
- reason: dataArg.reason,
831
- enabled: dataArg.enabled,
832
- });
833
-
834
- if (!response.success) {
835
- return { ...currentState, error: response.message || 'Failed to update security block rule' };
836
- }
837
-
838
- return await actionContext!.dispatch(fetchSecurityPolicyAction, null);
839
- } catch (error: unknown) {
840
- return {
841
- ...currentState,
842
- error: error instanceof Error ? error.message : 'Failed to update security block rule',
843
- };
844
- }
845
- });
846
-
847
- export const deleteSecurityBlockRuleAction = securityPolicyStatePart.createAction<string>(
848
- async (statePartArg, ruleId, actionContext): Promise<ISecurityPolicyState> => {
849
- const context = getActionContext();
850
- const currentState = statePartArg.getState()!;
851
- if (!context.identity) return currentState;
852
-
853
- try {
854
- const request = new plugins.domtools.plugins.typedrequest.TypedRequest<
855
- interfaces.requests.IReq_DeleteSecurityBlockRule
856
- >('/typedrequest', 'deleteSecurityBlockRule');
857
-
858
- const response = await request.fire({ identity: context.identity, id: ruleId });
859
- if (!response.success) {
860
- return { ...currentState, error: response.message || 'Failed to delete security block rule' };
861
- }
862
-
863
- return await actionContext!.dispatch(fetchSecurityPolicyAction, null);
864
- } catch (error: unknown) {
865
- return {
866
- ...currentState,
867
- error: error instanceof Error ? error.message : 'Failed to delete security block rule',
868
- };
869
- }
870
- },
871
- );
872
-
873
- export const refreshIpIntelligenceAction = securityPolicyStatePart.createAction<string>(
874
- async (statePartArg, ipAddress, actionContext): Promise<ISecurityPolicyState> => {
875
- const context = getActionContext();
876
- const currentState = statePartArg.getState()!;
877
- if (!context.identity) return currentState;
878
-
879
- try {
880
- const request = new plugins.domtools.plugins.typedrequest.TypedRequest<
881
- interfaces.requests.IReq_RefreshIpIntelligence
882
- >('/typedrequest', 'refreshIpIntelligence');
883
- const response = await request.fire({ identity: context.identity, ipAddress });
884
- if (!response.success) {
885
- return { ...currentState, error: response.message || 'Failed to refresh IP intelligence' };
886
- }
887
- const refreshedState = await actionContext!.dispatch(fetchSecurityPolicyAction, null);
888
- if (!response.record) return refreshedState;
889
- return {
890
- ...refreshedState,
891
- ipIntelligence: [
892
- response.record,
893
- ...refreshedState.ipIntelligence.filter((record) => record.ipAddress !== response.record!.ipAddress),
894
- ],
895
- };
896
- } catch (error: unknown) {
897
- return {
898
- ...currentState,
899
- error: error instanceof Error ? error.message : 'Failed to refresh IP intelligence',
900
- };
901
- }
902
- },
903
- );
904
-
905
- // ============================================================================
906
- // Email Operations Actions
907
- // ============================================================================
908
-
909
- // Fetch All Emails Action
910
- export const fetchAllEmailsAction = emailOpsStatePart.createAction(async (statePartArg): Promise<IEmailOpsState> => {
911
- const context = getActionContext();
912
- const currentState = statePartArg.getState()!;
913
- if (!context.identity) return currentState;
914
-
915
- try {
916
- const request = new plugins.domtools.plugins.typedrequest.TypedRequest<
917
- interfaces.requests.IReq_GetAllEmails
918
- >('/typedrequest', 'getAllEmails');
919
-
920
- const response = await request.fire({
921
- identity: context.identity,
922
- });
923
-
924
- return {
925
- emails: response.emails,
926
- isLoading: false,
927
- error: null,
928
- lastUpdated: Date.now(),
929
- };
930
- } catch (error) {
931
- return {
932
- ...currentState,
933
- isLoading: false,
934
- error: error instanceof Error ? error.message : 'Failed to fetch emails',
935
- };
936
- }
937
- });
938
-
939
- // ============================================================================
940
- // Certificate Actions
941
- // ============================================================================
942
-
943
- export const fetchCertificateOverviewAction = certificateStatePart.createAction(async (statePartArg): Promise<ICertificateState> => {
944
- const context = getActionContext();
945
- const currentState = statePartArg.getState()!;
946
- if (!context.identity) return currentState;
947
-
948
- try {
949
- const request = new plugins.domtools.plugins.typedrequest.TypedRequest<
950
- interfaces.requests.IReq_GetCertificateOverview
951
- >('/typedrequest', 'getCertificateOverview');
952
-
953
- const response = await request.fire({
954
- identity: context.identity,
955
- });
956
-
957
- return {
958
- certificates: response.certificates,
959
- summary: response.summary,
960
- isLoading: false,
961
- error: null,
962
- lastUpdated: Date.now(),
963
- };
964
- } catch (error) {
965
- return {
966
- ...currentState,
967
- isLoading: false,
968
- error: error instanceof Error ? error.message : 'Failed to fetch certificate overview',
969
- };
970
- }
971
- });
972
-
973
- export const reprovisionCertificateAction = certificateStatePart.createAction<{ domain: string; forceRenew?: boolean }>(
974
- async (statePartArg, dataArg, actionContext): Promise<ICertificateState> => {
975
- const context = getActionContext();
976
- const currentState = statePartArg.getState()!;
977
-
978
- try {
979
- const request = new plugins.domtools.plugins.typedrequest.TypedRequest<
980
- interfaces.requests.IReq_ReprovisionCertificateDomain
981
- >('/typedrequest', 'reprovisionCertificateDomain');
982
-
983
- await request.fire({
984
- identity: context.identity!,
985
- domain: dataArg.domain,
986
- forceRenew: dataArg.forceRenew,
987
- });
988
-
989
- // Re-fetch overview after reprovisioning
990
- return await actionContext!.dispatch(fetchCertificateOverviewAction, null);
991
- } catch (error: unknown) {
992
- return {
993
- ...currentState,
994
- error: error instanceof Error ? error.message : 'Failed to reprovision certificate',
995
- };
996
- }
997
- }
998
- );
999
-
1000
- export const deleteCertificateAction = certificateStatePart.createAction<string>(
1001
- async (statePartArg, domain, actionContext): Promise<ICertificateState> => {
1002
- const context = getActionContext();
1003
- const currentState = statePartArg.getState()!;
1004
-
1005
- try {
1006
- const request = new plugins.domtools.plugins.typedrequest.TypedRequest<
1007
- interfaces.requests.IReq_DeleteCertificate
1008
- >('/typedrequest', 'deleteCertificate');
1009
-
1010
- await request.fire({
1011
- identity: context.identity!,
1012
- domain,
1013
- });
1014
-
1015
- // Re-fetch overview after deletion
1016
- return await actionContext!.dispatch(fetchCertificateOverviewAction, null);
1017
- } catch (error: unknown) {
1018
- return {
1019
- ...currentState,
1020
- error: error instanceof Error ? error.message : 'Failed to delete certificate',
1021
- };
1022
- }
1023
- }
1024
- );
1025
-
1026
- export const importCertificateAction = certificateStatePart.createAction<{
1027
- id: string;
1028
- domainName: string;
1029
- created: number;
1030
- validUntil: number;
1031
- privateKey: string;
1032
- publicKey: string;
1033
- csr: string;
1034
- }>(
1035
- async (statePartArg, cert, actionContext): Promise<ICertificateState> => {
1036
- const context = getActionContext();
1037
- const currentState = statePartArg.getState()!;
1038
-
1039
- try {
1040
- const request = new plugins.domtools.plugins.typedrequest.TypedRequest<
1041
- servezoneInterfaces.requests.gateway.IReq_ImportCertificate
1042
- >('/typedrequest', 'importCertificate');
1043
-
1044
- await request.fire({
1045
- identity: context.identity!,
1046
- cert,
1047
- });
1048
-
1049
- // Re-fetch overview after import
1050
- return await actionContext!.dispatch(fetchCertificateOverviewAction, null);
1051
- } catch (error: unknown) {
1052
- return {
1053
- ...currentState,
1054
- error: error instanceof Error ? error.message : 'Failed to import certificate',
1055
- };
1056
- }
1057
- }
1058
- );
1059
-
1060
- export async function fetchCertificateExport(domain: string) {
1061
- const context = getActionContext();
1062
- const request = new plugins.domtools.plugins.typedrequest.TypedRequest<
1063
- servezoneInterfaces.requests.gateway.IReq_ExportCertificate
1064
- >('/typedrequest', 'exportCertificate');
1065
-
1066
- return request.fire({
1067
- identity: context.identity!,
1068
- domain,
1069
- });
1070
- }
1071
-
1072
- // ============================================================================
1073
- // Remote Ingress Standalone Functions
1074
- // ============================================================================
1075
-
1076
- export async function fetchConnectionToken(edgeId: string) {
1077
- const context = getActionContext();
1078
- const request = new plugins.domtools.plugins.typedrequest.TypedRequest<
1079
- interfaces.requests.IReq_GetRemoteIngressConnectionToken
1080
- >('/typedrequest', 'getRemoteIngressConnectionToken');
1081
- return request.fire({ identity: context.identity!, edgeId });
1082
- }
1083
-
1084
- // ============================================================================
1085
- // Remote Ingress Actions
1086
- // ============================================================================
1087
-
1088
- export const fetchRemoteIngressAction = remoteIngressStatePart.createAction(async (statePartArg): Promise<IRemoteIngressState> => {
1089
- const context = getActionContext();
1090
- const currentState = statePartArg.getState()!;
1091
- if (!context.identity) return currentState;
1092
-
1093
- try {
1094
- const edgesRequest = new plugins.domtools.plugins.typedrequest.TypedRequest<
1095
- interfaces.requests.IReq_GetRemoteIngresses
1096
- >('/typedrequest', 'getRemoteIngresses');
1097
-
1098
- const statusRequest = new plugins.domtools.plugins.typedrequest.TypedRequest<
1099
- interfaces.requests.IReq_GetRemoteIngressStatus
1100
- >('/typedrequest', 'getRemoteIngressStatus');
1101
-
1102
- const hubSettingsRequest = new plugins.domtools.plugins.typedrequest.TypedRequest<
1103
- interfaces.requests.IReq_GetRemoteIngressHubSettings
1104
- >('/typedrequest', 'getRemoteIngressHubSettings');
1105
-
1106
- const [edgesResponse, statusResponse, hubSettingsResponse] = await Promise.all([
1107
- edgesRequest.fire({ identity: context.identity }),
1108
- statusRequest.fire({ identity: context.identity }),
1109
- hubSettingsRequest.fire({ identity: context.identity }),
1110
- ]);
1111
-
1112
- return {
1113
- ...currentState,
1114
- edges: edgesResponse.edges,
1115
- statuses: statusResponse.statuses,
1116
- hubSettings: hubSettingsResponse.settings,
1117
- isLoading: false,
1118
- error: null,
1119
- lastUpdated: Date.now(),
1120
- };
1121
- } catch (error) {
1122
- return {
1123
- ...currentState,
1124
- isLoading: false,
1125
- error: error instanceof Error ? error.message : 'Failed to fetch remote ingress data',
1126
- };
1127
- }
1128
- });
1129
-
1130
- export const createRemoteIngressAction = remoteIngressStatePart.createAction<{
1131
- name: string;
1132
- listenPorts?: number[];
1133
- autoDerivePorts?: boolean;
1134
- performance?: interfaces.data.IRemoteIngressPerformanceConfig;
1135
- tags?: string[];
1136
- }>(async (statePartArg, dataArg, actionContext): Promise<IRemoteIngressState> => {
1137
- const context = getActionContext();
1138
- const currentState = statePartArg.getState()!;
1139
-
1140
- try {
1141
- const request = new plugins.domtools.plugins.typedrequest.TypedRequest<
1142
- interfaces.requests.IReq_CreateRemoteIngress
1143
- >('/typedrequest', 'createRemoteIngress');
1144
-
1145
- const response = await request.fire({
1146
- identity: context.identity!,
1147
- name: dataArg.name,
1148
- listenPorts: dataArg.listenPorts,
1149
- autoDerivePorts: dataArg.autoDerivePorts,
1150
- performance: dataArg.performance,
1151
- tags: dataArg.tags,
1152
- });
1153
-
1154
- if (response.success) {
1155
- // Refresh the list
1156
- await actionContext!.dispatch(fetchRemoteIngressAction, null);
1157
-
1158
- return {
1159
- ...statePartArg.getState()!,
1160
- newEdgeId: response.edge.id,
1161
- };
1162
- }
1163
-
1164
- return currentState;
1165
- } catch (error: unknown) {
1166
- return {
1167
- ...currentState,
1168
- error: error instanceof Error ? error.message : 'Failed to create edge',
1169
- };
1170
- }
1171
- });
1172
-
1173
- export const deleteRemoteIngressAction = remoteIngressStatePart.createAction<string>(
1174
- async (statePartArg, edgeId, actionContext): Promise<IRemoteIngressState> => {
1175
- const context = getActionContext();
1176
- const currentState = statePartArg.getState()!;
1177
-
1178
- try {
1179
- const request = new plugins.domtools.plugins.typedrequest.TypedRequest<
1180
- interfaces.requests.IReq_DeleteRemoteIngress
1181
- >('/typedrequest', 'deleteRemoteIngress');
1182
-
1183
- await request.fire({
1184
- identity: context.identity!,
1185
- id: edgeId,
1186
- });
1187
-
1188
- return await actionContext!.dispatch(fetchRemoteIngressAction, null);
1189
- } catch (error: unknown) {
1190
- return {
1191
- ...currentState,
1192
- error: error instanceof Error ? error.message : 'Failed to delete edge',
1193
- };
1194
- }
1195
- }
1196
- );
1197
-
1198
- export const updateRemoteIngressAction = remoteIngressStatePart.createAction<{
1199
- id: string;
1200
- name?: string;
1201
- listenPorts?: number[];
1202
- autoDerivePorts?: boolean;
1203
- performance?: interfaces.data.IRemoteIngressPerformanceConfig;
1204
- tags?: string[];
1205
- }>(async (statePartArg, dataArg, actionContext): Promise<IRemoteIngressState> => {
1206
- const context = getActionContext();
1207
- const currentState = statePartArg.getState()!;
1208
-
1209
- try {
1210
- const request = new plugins.domtools.plugins.typedrequest.TypedRequest<
1211
- interfaces.requests.IReq_UpdateRemoteIngress
1212
- >('/typedrequest', 'updateRemoteIngress');
1213
-
1214
- await request.fire({
1215
- identity: context.identity!,
1216
- id: dataArg.id,
1217
- name: dataArg.name,
1218
- listenPorts: dataArg.listenPorts,
1219
- autoDerivePorts: dataArg.autoDerivePorts,
1220
- performance: dataArg.performance,
1221
- tags: dataArg.tags,
1222
- });
1223
-
1224
- return await actionContext!.dispatch(fetchRemoteIngressAction, null);
1225
- } catch (error: unknown) {
1226
- return {
1227
- ...currentState,
1228
- error: error instanceof Error ? error.message : 'Failed to update edge',
1229
- };
1230
- }
1231
- });
1232
-
1233
- export const updateRemoteIngressHubSettingsAction = remoteIngressStatePart.createAction<{
1234
- enabled?: boolean;
1235
- tunnelPort?: number;
1236
- hubDomain?: string | null;
1237
- performance?: interfaces.data.IRemoteIngressPerformanceConfig | null;
1238
- }>(async (statePartArg, dataArg, actionContext): Promise<IRemoteIngressState> => {
1239
- const context = getActionContext();
1240
- const currentState = statePartArg.getState()!;
1241
-
1242
- try {
1243
- const request = new plugins.domtools.plugins.typedrequest.TypedRequest<
1244
- interfaces.requests.IReq_UpdateRemoteIngressHubSettings
1245
- >('/typedrequest', 'updateRemoteIngressHubSettings');
1246
-
1247
- const response = await request.fire({
1248
- identity: context.identity!,
1249
- enabled: dataArg.enabled,
1250
- tunnelPort: dataArg.tunnelPort,
1251
- hubDomain: dataArg.hubDomain,
1252
- performance: dataArg.performance,
1253
- });
1254
-
1255
- if (!response.success) {
1256
- return {
1257
- ...currentState,
1258
- error: response.message || 'Failed to update RemoteIngress hub settings',
1259
- };
1260
- }
1261
-
1262
- return await actionContext!.dispatch(fetchRemoteIngressAction, null);
1263
- } catch (error: unknown) {
1264
- return {
1265
- ...currentState,
1266
- error: error instanceof Error ? error.message : 'Failed to update RemoteIngress hub settings',
1267
- };
1268
- }
1269
- });
1270
-
1271
- export const regenerateRemoteIngressSecretAction = remoteIngressStatePart.createAction<string>(
1272
- async (statePartArg, edgeId): Promise<IRemoteIngressState> => {
1273
- const context = getActionContext();
1274
- const currentState = statePartArg.getState()!;
1275
-
1276
- try {
1277
- const request = new plugins.domtools.plugins.typedrequest.TypedRequest<
1278
- interfaces.requests.IReq_RegenerateRemoteIngressSecret
1279
- >('/typedrequest', 'regenerateRemoteIngressSecret');
1280
-
1281
- const response = await request.fire({
1282
- identity: context.identity!,
1283
- id: edgeId,
1284
- });
1285
-
1286
- if (response.success) {
1287
- return {
1288
- ...currentState,
1289
- newEdgeId: edgeId,
1290
- };
1291
- }
1292
-
1293
- return currentState;
1294
- } catch (error) {
1295
- return {
1296
- ...currentState,
1297
- error: error instanceof Error ? error.message : 'Failed to regenerate secret',
1298
- };
1299
- }
1300
- }
1301
- );
1302
-
1303
- export const clearNewEdgeIdAction = remoteIngressStatePart.createAction(
1304
- async (statePartArg): Promise<IRemoteIngressState> => {
1305
- return {
1306
- ...statePartArg.getState()!,
1307
- newEdgeId: null,
1308
- };
1309
- }
1310
- );
1311
-
1312
- export const toggleRemoteIngressAction = remoteIngressStatePart.createAction<{
1313
- id: string;
1314
- enabled: boolean;
1315
- }>(async (statePartArg, dataArg, actionContext): Promise<IRemoteIngressState> => {
1316
- const context = getActionContext();
1317
- const currentState = statePartArg.getState()!;
1318
-
1319
- try {
1320
- const request = new plugins.domtools.plugins.typedrequest.TypedRequest<
1321
- interfaces.requests.IReq_UpdateRemoteIngress
1322
- >('/typedrequest', 'updateRemoteIngress');
1323
-
1324
- await request.fire({
1325
- identity: context.identity!,
1326
- id: dataArg.id,
1327
- enabled: dataArg.enabled,
1328
- });
1329
-
1330
- return await actionContext!.dispatch(fetchRemoteIngressAction, null);
1331
- } catch (error: unknown) {
1332
- return {
1333
- ...currentState,
1334
- error: error instanceof Error ? error.message : 'Failed to toggle edge',
1335
- };
1336
- }
1337
- });
1338
-
1339
- // ============================================================================
1340
- // VPN State
1341
- // ============================================================================
1342
-
1343
- export interface IVpnState {
1344
- clients: interfaces.data.IVpnClient[];
1345
- connectedClients: interfaces.data.IVpnConnectedClient[];
1346
- status: interfaces.data.IVpnServerStatus | null;
1347
- isLoading: boolean;
1348
- error: string | null;
1349
- lastUpdated: number;
1350
- /** WireGuard config shown after create/rotate (only shown once) */
1351
- newClientConfig: string | null;
1352
- }
1353
-
1354
- export const vpnStatePart = await appState.getStatePart<IVpnState>(
1355
- 'vpn',
1356
- {
1357
- clients: [],
1358
- connectedClients: [],
1359
- status: null,
1360
- isLoading: false,
1361
- error: null,
1362
- lastUpdated: 0,
1363
- newClientConfig: null,
1364
- },
1365
- 'soft'
1366
- );
1367
-
1368
- // ============================================================================
1369
- // VPN Actions
1370
- // ============================================================================
1371
-
1372
- export const fetchVpnAction = vpnStatePart.createAction(async (statePartArg): Promise<IVpnState> => {
1373
- const context = getActionContext();
1374
- const currentState = statePartArg.getState()!;
1375
- if (!context.identity) return currentState;
1376
-
1377
- try {
1378
- const clientsRequest = new plugins.domtools.plugins.typedrequest.TypedRequest<
1379
- interfaces.requests.IReq_GetVpnClients
1380
- >('/typedrequest', 'getVpnClients');
1381
-
1382
- const statusRequest = new plugins.domtools.plugins.typedrequest.TypedRequest<
1383
- interfaces.requests.IReq_GetVpnStatus
1384
- >('/typedrequest', 'getVpnStatus');
1385
-
1386
- const connectedRequest = new plugins.domtools.plugins.typedrequest.TypedRequest<
1387
- interfaces.requests.IReq_GetVpnConnectedClients
1388
- >('/typedrequest', 'getVpnConnectedClients');
1389
-
1390
- const [clientsResponse, statusResponse, connectedResponse] = await Promise.all([
1391
- clientsRequest.fire({ identity: context.identity }),
1392
- statusRequest.fire({ identity: context.identity }),
1393
- connectedRequest.fire({ identity: context.identity }),
1394
- ]);
1395
-
1396
- return {
1397
- ...currentState,
1398
- clients: clientsResponse.clients,
1399
- connectedClients: connectedResponse.connectedClients,
1400
- status: statusResponse.status,
1401
- isLoading: false,
1402
- error: null,
1403
- lastUpdated: Date.now(),
1404
- };
1405
- } catch (error) {
1406
- return {
1407
- ...currentState,
1408
- isLoading: false,
1409
- error: error instanceof Error ? error.message : 'Failed to fetch VPN data',
1410
- };
1411
- }
1412
- });
1413
-
1414
- export const createVpnClientAction = vpnStatePart.createAction<{
1415
- clientId: string;
1416
- targetProfileIds?: string[];
1417
- description?: string;
1418
-
1419
- destinationAllowList?: string[];
1420
- destinationBlockList?: string[];
1421
- useHostIp?: boolean;
1422
- useDhcp?: boolean;
1423
- staticIp?: string;
1424
- forceVlan?: boolean;
1425
- vlanId?: number;
1426
- }>(async (statePartArg, dataArg, actionContext): Promise<IVpnState> => {
1427
- const context = getActionContext();
1428
- const currentState = statePartArg.getState()!;
1429
-
1430
- try {
1431
- const request = new plugins.domtools.plugins.typedrequest.TypedRequest<
1432
- interfaces.requests.IReq_CreateVpnClient
1433
- >('/typedrequest', 'createVpnClient');
1434
-
1435
- const response = await request.fire({
1436
- identity: context.identity!,
1437
- clientId: dataArg.clientId,
1438
- targetProfileIds: dataArg.targetProfileIds,
1439
- description: dataArg.description,
1440
-
1441
- destinationAllowList: dataArg.destinationAllowList,
1442
- destinationBlockList: dataArg.destinationBlockList,
1443
- useHostIp: dataArg.useHostIp,
1444
- useDhcp: dataArg.useDhcp,
1445
- staticIp: dataArg.staticIp,
1446
- forceVlan: dataArg.forceVlan,
1447
- vlanId: dataArg.vlanId,
1448
- });
1449
-
1450
- if (!response.success) {
1451
- return { ...currentState, error: response.message || 'Failed to create client' };
1452
- }
1453
-
1454
- const refreshed = await actionContext!.dispatch(fetchVpnAction, null);
1455
- return {
1456
- ...refreshed,
1457
- newClientConfig: response.wireguardConfig || null,
1458
- };
1459
- } catch (error: unknown) {
1460
- return {
1461
- ...currentState,
1462
- error: error instanceof Error ? error.message : 'Failed to create VPN client',
1463
- };
1464
- }
1465
- });
1466
-
1467
- export const deleteVpnClientAction = vpnStatePart.createAction<string>(
1468
- async (statePartArg, clientId, actionContext): Promise<IVpnState> => {
1469
- const context = getActionContext();
1470
- const currentState = statePartArg.getState()!;
1471
-
1472
- try {
1473
- const request = new plugins.domtools.plugins.typedrequest.TypedRequest<
1474
- interfaces.requests.IReq_DeleteVpnClient
1475
- >('/typedrequest', 'deleteVpnClient');
1476
-
1477
- await request.fire({ identity: context.identity!, clientId });
1478
- return await actionContext!.dispatch(fetchVpnAction, null);
1479
- } catch (error: unknown) {
1480
- return {
1481
- ...currentState,
1482
- error: error instanceof Error ? error.message : 'Failed to delete VPN client',
1483
- };
1484
- }
1485
- },
1486
- );
1487
-
1488
- export const toggleVpnClientAction = vpnStatePart.createAction<{
1489
- clientId: string;
1490
- enabled: boolean;
1491
- }>(async (statePartArg, dataArg, actionContext): Promise<IVpnState> => {
1492
- const context = getActionContext();
1493
- const currentState = statePartArg.getState()!;
1494
-
1495
- try {
1496
- const method = dataArg.enabled ? 'enableVpnClient' : 'disableVpnClient';
1497
- type TReq = interfaces.requests.IReq_EnableVpnClient | interfaces.requests.IReq_DisableVpnClient;
1498
- const request = new plugins.domtools.plugins.typedrequest.TypedRequest<TReq>(
1499
- '/typedrequest', method,
1500
- );
1501
-
1502
- await request.fire({ identity: context.identity!, clientId: dataArg.clientId });
1503
- return await actionContext!.dispatch(fetchVpnAction, null);
1504
- } catch (error: unknown) {
1505
- return {
1506
- ...currentState,
1507
- error: error instanceof Error ? error.message : 'Failed to toggle VPN client',
1508
- };
1509
- }
1510
- });
1511
-
1512
- export const updateVpnClientAction = vpnStatePart.createAction<{
1513
- clientId: string;
1514
- description?: string;
1515
- targetProfileIds?: string[];
1516
-
1517
- destinationAllowList?: string[];
1518
- destinationBlockList?: string[];
1519
- useHostIp?: boolean;
1520
- useDhcp?: boolean;
1521
- staticIp?: string;
1522
- forceVlan?: boolean;
1523
- vlanId?: number;
1524
- }>(async (statePartArg, dataArg, actionContext): Promise<IVpnState> => {
1525
- const context = getActionContext();
1526
- const currentState = statePartArg.getState()!;
1527
-
1528
- try {
1529
- const request = new plugins.domtools.plugins.typedrequest.TypedRequest<
1530
- interfaces.requests.IReq_UpdateVpnClient
1531
- >('/typedrequest', 'updateVpnClient');
1532
-
1533
- const response = await request.fire({
1534
- identity: context.identity!,
1535
- clientId: dataArg.clientId,
1536
- description: dataArg.description,
1537
- targetProfileIds: dataArg.targetProfileIds,
1538
-
1539
- destinationAllowList: dataArg.destinationAllowList,
1540
- destinationBlockList: dataArg.destinationBlockList,
1541
- useHostIp: dataArg.useHostIp,
1542
- useDhcp: dataArg.useDhcp,
1543
- staticIp: dataArg.staticIp,
1544
- forceVlan: dataArg.forceVlan,
1545
- vlanId: dataArg.vlanId,
1546
- });
1547
-
1548
- if (!response.success) {
1549
- return { ...currentState, error: response.message || 'Failed to update client' };
1550
- }
1551
-
1552
- return await actionContext!.dispatch(fetchVpnAction, null);
1553
- } catch (error: unknown) {
1554
- return {
1555
- ...currentState,
1556
- error: error instanceof Error ? error.message : 'Failed to update VPN client',
1557
- };
1558
- }
1559
- });
1560
-
1561
- export const clearNewClientConfigAction = vpnStatePart.createAction(
1562
- async (statePartArg): Promise<IVpnState> => {
1563
- return { ...statePartArg.getState()!, newClientConfig: null };
1564
- },
1565
- );
1566
-
1567
- // ============================================================================
1568
- // Target Profiles State
1569
- // ============================================================================
1570
-
1571
- export interface ITargetProfilesState {
1572
- profiles: interfaces.data.ITargetProfile[];
1573
- isLoading: boolean;
1574
- error: string | null;
1575
- lastUpdated: number;
1576
- }
1577
-
1578
- export const targetProfilesStatePart = await appState.getStatePart<ITargetProfilesState>(
1579
- 'targetProfiles',
1580
- {
1581
- profiles: [],
1582
- isLoading: false,
1583
- error: null,
1584
- lastUpdated: 0,
1585
- },
1586
- 'soft'
1587
- );
1588
-
1589
- // ============================================================================
1590
- // Target Profiles Actions
1591
- // ============================================================================
1592
-
1593
- export const fetchTargetProfilesAction = targetProfilesStatePart.createAction(
1594
- async (statePartArg): Promise<ITargetProfilesState> => {
1595
- const context = getActionContext();
1596
- const currentState = statePartArg.getState()!;
1597
- if (!context.identity) return currentState;
1598
-
1599
- try {
1600
- const request = new plugins.domtools.plugins.typedrequest.TypedRequest<
1601
- interfaces.requests.IReq_GetTargetProfiles
1602
- >('/typedrequest', 'getTargetProfiles');
1603
-
1604
- const response = await request.fire({ identity: context.identity });
1605
-
1606
- return {
1607
- profiles: response.profiles,
1608
- isLoading: false,
1609
- error: null,
1610
- lastUpdated: Date.now(),
1611
- };
1612
- } catch (error) {
1613
- return {
1614
- ...currentState,
1615
- isLoading: false,
1616
- error: error instanceof Error ? error.message : 'Failed to fetch target profiles',
1617
- };
1618
- }
1619
- }
1620
- );
1621
-
1622
- export const createTargetProfileAction = targetProfilesStatePart.createAction<{
1623
- name: string;
1624
- description?: string;
1625
- domains?: string[];
1626
- targets?: Array<{ ip: string; port: number }>;
1627
- routeRefs?: string[];
1628
- allowRoutesByClientSourceIp?: boolean;
1629
- }>(async (statePartArg, dataArg, actionContext): Promise<ITargetProfilesState> => {
1630
- const context = getActionContext();
1631
- try {
1632
- const request = new plugins.domtools.plugins.typedrequest.TypedRequest<
1633
- interfaces.requests.IReq_CreateTargetProfile
1634
- >('/typedrequest', 'createTargetProfile');
1635
- const response = await request.fire({
1636
- identity: context.identity!,
1637
- name: dataArg.name,
1638
- description: dataArg.description,
1639
- domains: dataArg.domains,
1640
- targets: dataArg.targets,
1641
- routeRefs: dataArg.routeRefs,
1642
- allowRoutesByClientSourceIp: dataArg.allowRoutesByClientSourceIp,
1643
- });
1644
- if (!response.success) {
1645
- return {
1646
- ...statePartArg.getState()!,
1647
- error: response.message || 'Failed to create target profile',
1648
- };
1649
- }
1650
- return await actionContext!.dispatch(fetchTargetProfilesAction, null);
1651
- } catch (error: unknown) {
1652
- return {
1653
- ...statePartArg.getState()!,
1654
- error: error instanceof Error ? error.message : 'Failed to create target profile',
1655
- };
1656
- }
1657
- });
1658
-
1659
- export const updateTargetProfileAction = targetProfilesStatePart.createAction<{
1660
- id: string;
1661
- name?: string;
1662
- description?: string;
1663
- domains?: string[];
1664
- targets?: Array<{ ip: string; port: number }>;
1665
- routeRefs?: string[];
1666
- allowRoutesByClientSourceIp?: boolean;
1667
- }>(async (statePartArg, dataArg, actionContext): Promise<ITargetProfilesState> => {
1668
- const context = getActionContext();
1669
- try {
1670
- const request = new plugins.domtools.plugins.typedrequest.TypedRequest<
1671
- interfaces.requests.IReq_UpdateTargetProfile
1672
- >('/typedrequest', 'updateTargetProfile');
1673
- const response = await request.fire({
1674
- identity: context.identity!,
1675
- id: dataArg.id,
1676
- name: dataArg.name,
1677
- description: dataArg.description,
1678
- domains: dataArg.domains,
1679
- targets: dataArg.targets,
1680
- routeRefs: dataArg.routeRefs,
1681
- allowRoutesByClientSourceIp: dataArg.allowRoutesByClientSourceIp,
1682
- });
1683
- if (!response.success) {
1684
- return {
1685
- ...statePartArg.getState()!,
1686
- error: response.message || 'Failed to update target profile',
1687
- };
1688
- }
1689
- return await actionContext!.dispatch(fetchTargetProfilesAction, null);
1690
- } catch (error: unknown) {
1691
- return {
1692
- ...statePartArg.getState()!,
1693
- error: error instanceof Error ? error.message : 'Failed to update target profile',
1694
- };
1695
- }
1696
- });
1697
-
1698
- export const deleteTargetProfileAction = targetProfilesStatePart.createAction<{
1699
- id: string;
1700
- force?: boolean;
1701
- }>(async (statePartArg, dataArg, actionContext): Promise<ITargetProfilesState> => {
1702
- const context = getActionContext();
1703
- try {
1704
- const request = new plugins.domtools.plugins.typedrequest.TypedRequest<
1705
- interfaces.requests.IReq_DeleteTargetProfile
1706
- >('/typedrequest', 'deleteTargetProfile');
1707
- const response = await request.fire({
1708
- identity: context.identity!,
1709
- id: dataArg.id,
1710
- force: dataArg.force,
1711
- });
1712
- if (!response.success) {
1713
- return {
1714
- ...statePartArg.getState()!,
1715
- error: response.message || 'Failed to delete target profile',
1716
- };
1717
- }
1718
- return await actionContext!.dispatch(fetchTargetProfilesAction, null);
1719
- } catch (error: unknown) {
1720
- return {
1721
- ...statePartArg.getState()!,
1722
- error: error instanceof Error ? error.message : 'Failed to delete target profile',
1723
- };
1724
- }
1725
- });
1726
-
1727
- // ============================================================================
1728
- // Source Profiles & Network Targets State
1729
- // ============================================================================
1730
-
1731
- export interface IProfilesTargetsState {
1732
- profiles: interfaces.data.ISourceProfile[];
1733
- targets: interfaces.data.INetworkTarget[];
1734
- isLoading: boolean;
1735
- error: string | null;
1736
- lastUpdated: number;
1737
- }
1738
-
1739
- export const profilesTargetsStatePart = await appState.getStatePart<IProfilesTargetsState>(
1740
- 'profilesTargets',
1741
- {
1742
- profiles: [],
1743
- targets: [],
1744
- isLoading: false,
1745
- error: null,
1746
- lastUpdated: 0,
1747
- },
1748
- 'soft'
1749
- );
1750
-
1751
- // ============================================================================
1752
- // Source Profiles & Network Targets Actions
1753
- // ============================================================================
1754
-
1755
- export const fetchProfilesAndTargetsAction = profilesTargetsStatePart.createAction(
1756
- async (statePartArg): Promise<IProfilesTargetsState> => {
1757
- const context = getActionContext();
1758
- const currentState = statePartArg.getState()!;
1759
- if (!context.identity) return currentState;
1760
-
1761
- try {
1762
- const profilesRequest = new plugins.domtools.plugins.typedrequest.TypedRequest<
1763
- interfaces.requests.IReq_GetSourceProfiles
1764
- >('/typedrequest', 'getSourceProfiles');
1765
-
1766
- const targetsRequest = new plugins.domtools.plugins.typedrequest.TypedRequest<
1767
- interfaces.requests.IReq_GetNetworkTargets
1768
- >('/typedrequest', 'getNetworkTargets');
1769
-
1770
- const [profilesResponse, targetsResponse] = await Promise.all([
1771
- profilesRequest.fire({ identity: context.identity }),
1772
- targetsRequest.fire({ identity: context.identity }),
1773
- ]);
1774
-
1775
- return {
1776
- profiles: profilesResponse.profiles,
1777
- targets: targetsResponse.targets,
1778
- isLoading: false,
1779
- error: null,
1780
- lastUpdated: Date.now(),
1781
- };
1782
- } catch (error) {
1783
- return {
1784
- ...currentState,
1785
- isLoading: false,
1786
- error: error instanceof Error ? error.message : 'Failed to fetch profiles/targets',
1787
- };
1788
- }
1789
- }
1790
- );
1791
-
1792
- export const createProfileAction = profilesTargetsStatePart.createAction<{
1793
- name: string;
1794
- description?: string;
1795
- security: any;
1796
- extendsProfiles?: string[];
1797
- }>(async (statePartArg, dataArg, actionContext): Promise<IProfilesTargetsState> => {
1798
- const context = getActionContext();
1799
- try {
1800
- const request = new plugins.domtools.plugins.typedrequest.TypedRequest<
1801
- interfaces.requests.IReq_CreateSourceProfile
1802
- >('/typedrequest', 'createSourceProfile');
1803
- await request.fire({
1804
- identity: context.identity!,
1805
- name: dataArg.name,
1806
- description: dataArg.description,
1807
- security: dataArg.security,
1808
- extendsProfiles: dataArg.extendsProfiles,
1809
- });
1810
- return await actionContext!.dispatch(fetchProfilesAndTargetsAction, null);
1811
- } catch (error: unknown) {
1812
- return {
1813
- ...statePartArg.getState()!,
1814
- error: error instanceof Error ? error.message : 'Failed to create profile',
1815
- };
1816
- }
1817
- });
1818
-
1819
- export const updateProfileAction = profilesTargetsStatePart.createAction<{
1820
- id: string;
1821
- name?: string;
1822
- description?: string;
1823
- security?: any;
1824
- extendsProfiles?: string[];
1825
- }>(async (statePartArg, dataArg, actionContext): Promise<IProfilesTargetsState> => {
1826
- const context = getActionContext();
1827
- try {
1828
- const request = new plugins.domtools.plugins.typedrequest.TypedRequest<
1829
- interfaces.requests.IReq_UpdateSourceProfile
1830
- >('/typedrequest', 'updateSourceProfile');
1831
- await request.fire({
1832
- identity: context.identity!,
1833
- id: dataArg.id,
1834
- name: dataArg.name,
1835
- description: dataArg.description,
1836
- security: dataArg.security,
1837
- extendsProfiles: dataArg.extendsProfiles,
1838
- });
1839
- return await actionContext!.dispatch(fetchProfilesAndTargetsAction, null);
1840
- } catch (error: unknown) {
1841
- return {
1842
- ...statePartArg.getState()!,
1843
- error: error instanceof Error ? error.message : 'Failed to update profile',
1844
- };
1845
- }
1846
- });
1847
-
1848
- export const deleteProfileAction = profilesTargetsStatePart.createAction<{
1849
- id: string;
1850
- force?: boolean;
1851
- }>(async (statePartArg, dataArg, actionContext): Promise<IProfilesTargetsState> => {
1852
- const context = getActionContext();
1853
- try {
1854
- const request = new plugins.domtools.plugins.typedrequest.TypedRequest<
1855
- interfaces.requests.IReq_DeleteSourceProfile
1856
- >('/typedrequest', 'deleteSourceProfile');
1857
- const response = await request.fire({
1858
- identity: context.identity!,
1859
- id: dataArg.id,
1860
- force: dataArg.force,
1861
- });
1862
- if (!response.success) {
1863
- return {
1864
- ...statePartArg.getState()!,
1865
- error: response.message || 'Failed to delete profile',
1866
- };
1867
- }
1868
- return await actionContext!.dispatch(fetchProfilesAndTargetsAction, null);
1869
- } catch (error: unknown) {
1870
- return {
1871
- ...statePartArg.getState()!,
1872
- error: error instanceof Error ? error.message : 'Failed to delete profile',
1873
- };
1874
- }
1875
- });
1876
-
1877
- export const createTargetAction = profilesTargetsStatePart.createAction<{
1878
- name: string;
1879
- description?: string;
1880
- host: string | string[];
1881
- port: number;
1882
- }>(async (statePartArg, dataArg, actionContext): Promise<IProfilesTargetsState> => {
1883
- const context = getActionContext();
1884
- try {
1885
- const request = new plugins.domtools.plugins.typedrequest.TypedRequest<
1886
- interfaces.requests.IReq_CreateNetworkTarget
1887
- >('/typedrequest', 'createNetworkTarget');
1888
- await request.fire({
1889
- identity: context.identity!,
1890
- name: dataArg.name,
1891
- description: dataArg.description,
1892
- host: dataArg.host,
1893
- port: dataArg.port,
1894
- });
1895
- return await actionContext!.dispatch(fetchProfilesAndTargetsAction, null);
1896
- } catch (error: unknown) {
1897
- return {
1898
- ...statePartArg.getState()!,
1899
- error: error instanceof Error ? error.message : 'Failed to create target',
1900
- };
1901
- }
1902
- });
1903
-
1904
- export const updateTargetAction = profilesTargetsStatePart.createAction<{
1905
- id: string;
1906
- name?: string;
1907
- description?: string;
1908
- host?: string | string[];
1909
- port?: number;
1910
- }>(async (statePartArg, dataArg, actionContext): Promise<IProfilesTargetsState> => {
1911
- const context = getActionContext();
1912
- try {
1913
- const request = new plugins.domtools.plugins.typedrequest.TypedRequest<
1914
- interfaces.requests.IReq_UpdateNetworkTarget
1915
- >('/typedrequest', 'updateNetworkTarget');
1916
- await request.fire({
1917
- identity: context.identity!,
1918
- id: dataArg.id,
1919
- name: dataArg.name,
1920
- description: dataArg.description,
1921
- host: dataArg.host,
1922
- port: dataArg.port,
1923
- });
1924
- return await actionContext!.dispatch(fetchProfilesAndTargetsAction, null);
1925
- } catch (error: unknown) {
1926
- return {
1927
- ...statePartArg.getState()!,
1928
- error: error instanceof Error ? error.message : 'Failed to update target',
1929
- };
1930
- }
1931
- });
1932
-
1933
- export const deleteTargetAction = profilesTargetsStatePart.createAction<{
1934
- id: string;
1935
- force?: boolean;
1936
- }>(async (statePartArg, dataArg, actionContext): Promise<IProfilesTargetsState> => {
1937
- const context = getActionContext();
1938
- try {
1939
- const request = new plugins.domtools.plugins.typedrequest.TypedRequest<
1940
- interfaces.requests.IReq_DeleteNetworkTarget
1941
- >('/typedrequest', 'deleteNetworkTarget');
1942
- const response = await request.fire({
1943
- identity: context.identity!,
1944
- id: dataArg.id,
1945
- force: dataArg.force,
1946
- });
1947
- if (!response.success) {
1948
- return {
1949
- ...statePartArg.getState()!,
1950
- error: response.message || 'Failed to delete target',
1951
- };
1952
- }
1953
- return await actionContext!.dispatch(fetchProfilesAndTargetsAction, null);
1954
- } catch (error: unknown) {
1955
- return {
1956
- ...statePartArg.getState()!,
1957
- error: error instanceof Error ? error.message : 'Failed to delete target',
1958
- };
1959
- }
1960
- });
1961
-
1962
- // ============================================================================
1963
- // Domains State (DNS providers + domains + records)
1964
- // ============================================================================
1965
-
1966
- export interface IDomainsState {
1967
- providers: interfaces.data.IDnsProviderPublic[];
1968
- domains: interfaces.data.IDomain[];
1969
- records: interfaces.data.IDnsRecord[];
1970
- /** id of the currently-selected domain in the DNS records subview. */
1971
- selectedDomainId: string | null;
1972
- isLoading: boolean;
1973
- error: string | null;
1974
- lastUpdated: number;
1975
- }
1976
-
1977
- export const domainsStatePart = await appState.getStatePart<IDomainsState>(
1978
- 'domains',
1979
- {
1980
- providers: [],
1981
- domains: [],
1982
- records: [],
1983
- selectedDomainId: null,
1984
- isLoading: false,
1985
- error: null,
1986
- lastUpdated: 0,
1987
- },
1988
- 'soft',
1989
- );
1990
-
1991
- export const fetchDomainsAndProvidersAction = domainsStatePart.createAction(
1992
- async (statePartArg): Promise<IDomainsState> => {
1993
- const context = getActionContext();
1994
- const currentState = statePartArg.getState()!;
1995
- if (!context.identity) return currentState;
1996
-
1997
- try {
1998
- const providersRequest = new plugins.domtools.plugins.typedrequest.TypedRequest<
1999
- interfaces.requests.IReq_GetDnsProviders
2000
- >('/typedrequest', 'getDnsProviders');
2001
- const domainsRequest = new plugins.domtools.plugins.typedrequest.TypedRequest<
2002
- interfaces.requests.IReq_GetDomains
2003
- >('/typedrequest', 'getDomains');
2004
-
2005
- const [providersResponse, domainsResponse] = await Promise.all([
2006
- providersRequest.fire({ identity: context.identity }),
2007
- domainsRequest.fire({ identity: context.identity }),
2008
- ]);
2009
-
2010
- return {
2011
- ...currentState,
2012
- providers: providersResponse.providers,
2013
- domains: domainsResponse.domains,
2014
- isLoading: false,
2015
- error: null,
2016
- lastUpdated: Date.now(),
2017
- };
2018
- } catch (error: unknown) {
2019
- return {
2020
- ...currentState,
2021
- isLoading: false,
2022
- error: error instanceof Error ? error.message : 'Failed to fetch domains/providers',
2023
- };
2024
- }
2025
- },
2026
- );
2027
-
2028
- export const fetchDnsRecordsForDomainAction = domainsStatePart.createAction<{ domainId: string }>(
2029
- async (statePartArg, dataArg): Promise<IDomainsState> => {
2030
- const context = getActionContext();
2031
- const currentState = statePartArg.getState()!;
2032
- if (!context.identity) return currentState;
2033
-
2034
- try {
2035
- const request = new plugins.domtools.plugins.typedrequest.TypedRequest<
2036
- interfaces.requests.IReq_GetDnsRecords
2037
- >('/typedrequest', 'getDnsRecords');
2038
- const response = await request.fire({
2039
- identity: context.identity,
2040
- domainId: dataArg.domainId,
2041
- });
2042
- return {
2043
- ...currentState,
2044
- records: response.records,
2045
- selectedDomainId: dataArg.domainId,
2046
- error: null,
2047
- };
2048
- } catch (error: unknown) {
2049
- return {
2050
- ...currentState,
2051
- error: error instanceof Error ? error.message : 'Failed to fetch DNS records',
2052
- };
2053
- }
2054
- },
2055
- );
2056
-
2057
- export const createDnsProviderAction = domainsStatePart.createAction<{
2058
- name: string;
2059
- type: interfaces.data.TDnsProviderType;
2060
- credentials: interfaces.data.TDnsProviderCredentials;
2061
- }>(async (statePartArg, dataArg, actionContext): Promise<IDomainsState> => {
2062
- const context = getActionContext();
2063
- try {
2064
- const request = new plugins.domtools.plugins.typedrequest.TypedRequest<
2065
- interfaces.requests.IReq_CreateDnsProvider
2066
- >('/typedrequest', 'createDnsProvider');
2067
- const response = await request.fire({
2068
- identity: context.identity!,
2069
- name: dataArg.name,
2070
- type: dataArg.type,
2071
- credentials: dataArg.credentials,
2072
- });
2073
- if (!response.success) {
2074
- return {
2075
- ...statePartArg.getState()!,
2076
- error: response.message || 'Failed to create provider',
2077
- };
2078
- }
2079
- return await actionContext!.dispatch(fetchDomainsAndProvidersAction, null);
2080
- } catch (error: unknown) {
2081
- return {
2082
- ...statePartArg.getState()!,
2083
- error: error instanceof Error ? error.message : 'Failed to create provider',
2084
- };
2085
- }
2086
- });
2087
-
2088
- export const updateDnsProviderAction = domainsStatePart.createAction<{
2089
- id: string;
2090
- name?: string;
2091
- credentials?: interfaces.data.TDnsProviderCredentials;
2092
- }>(async (statePartArg, dataArg, actionContext): Promise<IDomainsState> => {
2093
- const context = getActionContext();
2094
- try {
2095
- const request = new plugins.domtools.plugins.typedrequest.TypedRequest<
2096
- interfaces.requests.IReq_UpdateDnsProvider
2097
- >('/typedrequest', 'updateDnsProvider');
2098
- const response = await request.fire({
2099
- identity: context.identity!,
2100
- id: dataArg.id,
2101
- name: dataArg.name,
2102
- credentials: dataArg.credentials,
2103
- });
2104
- if (!response.success) {
2105
- return {
2106
- ...statePartArg.getState()!,
2107
- error: response.message || 'Failed to update provider',
2108
- };
2109
- }
2110
- return await actionContext!.dispatch(fetchDomainsAndProvidersAction, null);
2111
- } catch (error: unknown) {
2112
- return {
2113
- ...statePartArg.getState()!,
2114
- error: error instanceof Error ? error.message : 'Failed to update provider',
2115
- };
2116
- }
2117
- });
2118
-
2119
- export const deleteDnsProviderAction = domainsStatePart.createAction<{ id: string; force?: boolean }>(
2120
- async (statePartArg, dataArg, actionContext): Promise<IDomainsState> => {
2121
- const context = getActionContext();
2122
- try {
2123
- const request = new plugins.domtools.plugins.typedrequest.TypedRequest<
2124
- interfaces.requests.IReq_DeleteDnsProvider
2125
- >('/typedrequest', 'deleteDnsProvider');
2126
- const response = await request.fire({
2127
- identity: context.identity!,
2128
- id: dataArg.id,
2129
- force: dataArg.force,
2130
- });
2131
- if (!response.success) {
2132
- return {
2133
- ...statePartArg.getState()!,
2134
- error: response.message || 'Failed to delete provider',
2135
- };
2136
- }
2137
- return await actionContext!.dispatch(fetchDomainsAndProvidersAction, null);
2138
- } catch (error: unknown) {
2139
- return {
2140
- ...statePartArg.getState()!,
2141
- error: error instanceof Error ? error.message : 'Failed to delete provider',
2142
- };
2143
- }
2144
- },
2145
- );
2146
-
2147
- export const testDnsProviderAction = domainsStatePart.createAction<{ id: string }>(
2148
- async (statePartArg, dataArg, actionContext): Promise<IDomainsState> => {
2149
- const context = getActionContext();
2150
- try {
2151
- const request = new plugins.domtools.plugins.typedrequest.TypedRequest<
2152
- interfaces.requests.IReq_TestDnsProvider
2153
- >('/typedrequest', 'testDnsProvider');
2154
- await request.fire({ identity: context.identity!, id: dataArg.id });
2155
- return await actionContext!.dispatch(fetchDomainsAndProvidersAction, null);
2156
- } catch (error: unknown) {
2157
- return {
2158
- ...statePartArg.getState()!,
2159
- error: error instanceof Error ? error.message : 'Failed to test provider',
2160
- };
2161
- }
2162
- },
2163
- );
2164
-
2165
- /** One-shot fetch for the import-domain modal. Does NOT modify state. */
2166
- export async function fetchProviderDomains(
2167
- providerId: string,
2168
- ): Promise<{ success: boolean; domains?: interfaces.data.IProviderDomainListing[]; message?: string }> {
2169
- const context = getActionContext();
2170
- if (!context.identity) return { success: false, message: 'Not authenticated' };
2171
- const request = new plugins.domtools.plugins.typedrequest.TypedRequest<
2172
- interfaces.requests.IReq_ListProviderDomains
2173
- >('/typedrequest', 'listProviderDomains');
2174
- return await request.fire({ identity: context.identity, providerId });
2175
- }
2176
-
2177
- export const createDcrouterDomainAction = domainsStatePart.createAction<{
2178
- name: string;
2179
- description?: string;
2180
- }>(async (statePartArg, dataArg, actionContext): Promise<IDomainsState> => {
2181
- const context = getActionContext();
2182
- try {
2183
- const request = new plugins.domtools.plugins.typedrequest.TypedRequest<
2184
- interfaces.requests.IReq_CreateDomain
2185
- >('/typedrequest', 'createDomain');
2186
- const response = await request.fire({
2187
- identity: context.identity!,
2188
- name: dataArg.name,
2189
- description: dataArg.description,
2190
- });
2191
- if (!response.success) {
2192
- return { ...statePartArg.getState()!, error: response.message || 'Failed to create domain' };
2193
- }
2194
- return await actionContext!.dispatch(fetchDomainsAndProvidersAction, null);
2195
- } catch (error: unknown) {
2196
- return {
2197
- ...statePartArg.getState()!,
2198
- error: error instanceof Error ? error.message : 'Failed to create domain',
2199
- };
2200
- }
2201
- });
2202
-
2203
- export const importDomainsFromProviderAction = domainsStatePart.createAction<{
2204
- providerId: string;
2205
- domainNames: string[];
2206
- }>(async (statePartArg, dataArg, actionContext): Promise<IDomainsState> => {
2207
- const context = getActionContext();
2208
- try {
2209
- const request = new plugins.domtools.plugins.typedrequest.TypedRequest<
2210
- interfaces.requests.IReq_ImportDomain
2211
- >('/typedrequest', 'importDomain');
2212
- const response = await request.fire({
2213
- identity: context.identity!,
2214
- providerId: dataArg.providerId,
2215
- domainNames: dataArg.domainNames,
2216
- });
2217
- if (!response.success) {
2218
- return { ...statePartArg.getState()!, error: response.message || 'Failed to import domains' };
2219
- }
2220
- return await actionContext!.dispatch(fetchDomainsAndProvidersAction, null);
2221
- } catch (error: unknown) {
2222
- return {
2223
- ...statePartArg.getState()!,
2224
- error: error instanceof Error ? error.message : 'Failed to import domains',
2225
- };
2226
- }
2227
- });
2228
-
2229
- export const deleteDomainAction = domainsStatePart.createAction<{ id: string }>(
2230
- async (statePartArg, dataArg, actionContext): Promise<IDomainsState> => {
2231
- const context = getActionContext();
2232
- try {
2233
- const request = new plugins.domtools.plugins.typedrequest.TypedRequest<
2234
- interfaces.requests.IReq_DeleteDomain
2235
- >('/typedrequest', 'deleteDomain');
2236
- const response = await request.fire({ identity: context.identity!, id: dataArg.id });
2237
- if (!response.success) {
2238
- return { ...statePartArg.getState()!, error: response.message || 'Failed to delete domain' };
2239
- }
2240
- return await actionContext!.dispatch(fetchDomainsAndProvidersAction, null);
2241
- } catch (error: unknown) {
2242
- return {
2243
- ...statePartArg.getState()!,
2244
- error: error instanceof Error ? error.message : 'Failed to delete domain',
2245
- };
2246
- }
2247
- },
2248
- );
2249
-
2250
- export const syncDomainAction = domainsStatePart.createAction<{ id: string }>(
2251
- async (statePartArg, dataArg, actionContext): Promise<IDomainsState> => {
2252
- const context = getActionContext();
2253
- try {
2254
- const request = new plugins.domtools.plugins.typedrequest.TypedRequest<
2255
- interfaces.requests.IReq_SyncDomain
2256
- >('/typedrequest', 'syncDomain');
2257
- const response = await request.fire({ identity: context.identity!, id: dataArg.id });
2258
- if (!response.success) {
2259
- return { ...statePartArg.getState()!, error: response.message || 'Failed to sync domain' };
2260
- }
2261
- return await actionContext!.dispatch(fetchDomainsAndProvidersAction, null);
2262
- } catch (error: unknown) {
2263
- return {
2264
- ...statePartArg.getState()!,
2265
- error: error instanceof Error ? error.message : 'Failed to sync domain',
2266
- };
2267
- }
2268
- },
2269
- );
2270
-
2271
- export const migrateDomainAction = domainsStatePart.createAction<{
2272
- id: string;
2273
- targetSource: interfaces.data.TDomainSource;
2274
- targetProviderId?: string;
2275
- deleteExistingProviderRecords?: boolean;
2276
- }>(
2277
- async (statePartArg, dataArg, actionContext): Promise<IDomainsState> => {
2278
- const context = getActionContext();
2279
- try {
2280
- const request = new plugins.domtools.plugins.typedrequest.TypedRequest<
2281
- interfaces.requests.IReq_MigrateDomain
2282
- >('/typedrequest', 'migrateDomain');
2283
- const response = await request.fire({ identity: context.identity!, ...dataArg });
2284
- if (!response.success) {
2285
- return { ...statePartArg.getState()!, error: response.message || 'Migration failed' };
2286
- }
2287
- return await actionContext!.dispatch(fetchDomainsAndProvidersAction, null);
2288
- } catch (error: unknown) {
2289
- return {
2290
- ...statePartArg.getState()!,
2291
- error: error instanceof Error ? error.message : 'Migration failed',
2292
- };
2293
- }
2294
- },
2295
- );
2296
-
2297
- export const createDnsRecordAction = domainsStatePart.createAction<{
2298
- domainId: string;
2299
- name: string;
2300
- type: interfaces.data.TDnsRecordType;
2301
- value: string;
2302
- ttl?: number;
2303
- proxied?: boolean;
2304
- }>(async (statePartArg, dataArg, actionContext): Promise<IDomainsState> => {
2305
- const context = getActionContext();
2306
- try {
2307
- const request = new plugins.domtools.plugins.typedrequest.TypedRequest<
2308
- interfaces.requests.IReq_CreateDnsRecord
2309
- >('/typedrequest', 'createDnsRecord');
2310
- const response = await request.fire({
2311
- identity: context.identity!,
2312
- domainId: dataArg.domainId,
2313
- name: dataArg.name,
2314
- type: dataArg.type,
2315
- value: dataArg.value,
2316
- ttl: dataArg.ttl,
2317
- proxied: dataArg.proxied,
2318
- });
2319
- if (!response.success) {
2320
- return { ...statePartArg.getState()!, error: response.message || 'Failed to create record' };
2321
- }
2322
- return await actionContext!.dispatch(fetchDnsRecordsForDomainAction, { domainId: dataArg.domainId });
2323
- } catch (error: unknown) {
2324
- return {
2325
- ...statePartArg.getState()!,
2326
- error: error instanceof Error ? error.message : 'Failed to create record',
2327
- };
2328
- }
2329
- });
2330
-
2331
- export const updateDnsRecordAction = domainsStatePart.createAction<{
2332
- id: string;
2333
- domainId: string;
2334
- name?: string;
2335
- value?: string;
2336
- ttl?: number;
2337
- proxied?: boolean;
2338
- }>(async (statePartArg, dataArg, actionContext): Promise<IDomainsState> => {
2339
- const context = getActionContext();
2340
- try {
2341
- const request = new plugins.domtools.plugins.typedrequest.TypedRequest<
2342
- interfaces.requests.IReq_UpdateDnsRecord
2343
- >('/typedrequest', 'updateDnsRecord');
2344
- const response = await request.fire({
2345
- identity: context.identity!,
2346
- id: dataArg.id,
2347
- name: dataArg.name,
2348
- value: dataArg.value,
2349
- ttl: dataArg.ttl,
2350
- proxied: dataArg.proxied,
2351
- });
2352
- if (!response.success) {
2353
- return { ...statePartArg.getState()!, error: response.message || 'Failed to update record' };
2354
- }
2355
- return await actionContext!.dispatch(fetchDnsRecordsForDomainAction, { domainId: dataArg.domainId });
2356
- } catch (error: unknown) {
2357
- return {
2358
- ...statePartArg.getState()!,
2359
- error: error instanceof Error ? error.message : 'Failed to update record',
2360
- };
2361
- }
2362
- });
2363
-
2364
- export const deleteDnsRecordAction = domainsStatePart.createAction<{ id: string; domainId: string }>(
2365
- async (statePartArg, dataArg, actionContext): Promise<IDomainsState> => {
2366
- const context = getActionContext();
2367
- try {
2368
- const request = new plugins.domtools.plugins.typedrequest.TypedRequest<
2369
- interfaces.requests.IReq_DeleteDnsRecord
2370
- >('/typedrequest', 'deleteDnsRecord');
2371
- const response = await request.fire({ identity: context.identity!, id: dataArg.id });
2372
- if (!response.success) {
2373
- return { ...statePartArg.getState()!, error: response.message || 'Failed to delete record' };
2374
- }
2375
- return await actionContext!.dispatch(fetchDnsRecordsForDomainAction, { domainId: dataArg.domainId });
2376
- } catch (error: unknown) {
2377
- return {
2378
- ...statePartArg.getState()!,
2379
- error: error instanceof Error ? error.message : 'Failed to delete record',
2380
- };
2381
- }
2382
- },
2383
- );
2384
-
2385
- // ============================================================================
2386
- // ACME Config Actions
2387
- // ============================================================================
2388
-
2389
- export const fetchAcmeConfigAction = acmeConfigStatePart.createAction(
2390
- async (statePartArg): Promise<IAcmeConfigState> => {
2391
- const context = getActionContext();
2392
- const currentState = statePartArg.getState()!;
2393
- if (!context.identity) return currentState;
2394
-
2395
- try {
2396
- const request = new plugins.domtools.plugins.typedrequest.TypedRequest<
2397
- interfaces.requests.IReq_GetAcmeConfig
2398
- >('/typedrequest', 'getAcmeConfig');
2399
- const response = await request.fire({ identity: context.identity });
2400
- return {
2401
- config: response.config,
2402
- isLoading: false,
2403
- error: null,
2404
- lastUpdated: Date.now(),
2405
- };
2406
- } catch (error: unknown) {
2407
- return {
2408
- ...currentState,
2409
- isLoading: false,
2410
- error: error instanceof Error ? error.message : 'Failed to fetch ACME config',
2411
- };
2412
- }
2413
- },
2414
- );
2415
-
2416
- export const updateAcmeConfigAction = acmeConfigStatePart.createAction<{
2417
- accountEmail?: string;
2418
- enabled?: boolean;
2419
- useProduction?: boolean;
2420
- autoRenew?: boolean;
2421
- renewThresholdDays?: number;
2422
- }>(async (statePartArg, dataArg, actionContext): Promise<IAcmeConfigState> => {
2423
- const context = getActionContext();
2424
- try {
2425
- const request = new plugins.domtools.plugins.typedrequest.TypedRequest<
2426
- interfaces.requests.IReq_UpdateAcmeConfig
2427
- >('/typedrequest', 'updateAcmeConfig');
2428
- const response = await request.fire({
2429
- identity: context.identity!,
2430
- accountEmail: dataArg.accountEmail,
2431
- enabled: dataArg.enabled,
2432
- useProduction: dataArg.useProduction,
2433
- autoRenew: dataArg.autoRenew,
2434
- renewThresholdDays: dataArg.renewThresholdDays,
2435
- });
2436
- if (!response.success) {
2437
- return {
2438
- ...statePartArg.getState()!,
2439
- error: response.message || 'Failed to update ACME config',
2440
- };
2441
- }
2442
- return await actionContext!.dispatch(fetchAcmeConfigAction, null);
2443
- } catch (error: unknown) {
2444
- return {
2445
- ...statePartArg.getState()!,
2446
- error: error instanceof Error ? error.message : 'Failed to update ACME config',
2447
- };
2448
- }
2449
- });
2450
-
2451
- // ============================================================================
2452
- // Route Management Actions
2453
- // ============================================================================
2454
-
2455
- export const fetchMergedRoutesAction = routeManagementStatePart.createAction(async (statePartArg): Promise<IRouteManagementState> => {
2456
- const context = getActionContext();
2457
- const currentState = statePartArg.getState()!;
2458
- if (!context.identity) return currentState;
2459
-
2460
- try {
2461
- const request = new plugins.domtools.plugins.typedrequest.TypedRequest<
2462
- interfaces.requests.IReq_GetMergedRoutes
2463
- >('/typedrequest', 'getMergedRoutes');
2464
-
2465
- const response = await request.fire({
2466
- identity: context.identity,
2467
- });
2468
-
2469
- return {
2470
- ...currentState,
2471
- mergedRoutes: response.routes,
2472
- warnings: response.warnings,
2473
- isLoading: false,
2474
- error: null,
2475
- lastUpdated: Date.now(),
2476
- };
2477
- } catch (error) {
2478
- return {
2479
- ...currentState,
2480
- isLoading: false,
2481
- error: error instanceof Error ? error.message : 'Failed to fetch routes',
2482
- };
2483
- }
2484
- });
2485
-
2486
- export const fetchHttpRedirectsAction = routeManagementStatePart.createAction(async (statePartArg): Promise<IRouteManagementState> => {
2487
- const context = getActionContext();
2488
- const currentState = statePartArg.getState()!;
2489
- if (!context.identity) return currentState;
2490
-
2491
- try {
2492
- const request = new plugins.domtools.plugins.typedrequest.TypedRequest<
2493
- interfaces.requests.IReq_GetHttpRedirects
2494
- >('/typedrequest', 'getHttpRedirects');
2495
-
2496
- const response = await request.fire({
2497
- identity: context.identity,
2498
- });
2499
-
2500
- return {
2501
- ...currentState,
2502
- httpRedirects: response.redirects,
2503
- isLoading: false,
2504
- error: null,
2505
- lastUpdated: Date.now(),
2506
- };
2507
- } catch (error) {
2508
- return {
2509
- ...currentState,
2510
- isLoading: false,
2511
- error: error instanceof Error ? error.message : 'Failed to fetch HTTP redirects',
2512
- };
2513
- }
2514
- });
2515
-
2516
- export const createRouteAction = routeManagementStatePart.createAction<{
2517
- route: any;
2518
- enabled?: boolean;
2519
- metadata?: any;
2520
- }>(async (statePartArg, dataArg, actionContext): Promise<IRouteManagementState> => {
2521
- const context = getActionContext();
2522
- const currentState = statePartArg.getState()!;
2523
-
2524
- try {
2525
- const request = new plugins.domtools.plugins.typedrequest.TypedRequest<
2526
- interfaces.requests.IReq_CreateRoute
2527
- >('/typedrequest', 'createRoute');
2528
-
2529
- await request.fire({
2530
- identity: context.identity!,
2531
- route: dataArg.route,
2532
- enabled: dataArg.enabled,
2533
- metadata: dataArg.metadata,
2534
- });
2535
-
2536
- return await actionContext!.dispatch(fetchMergedRoutesAction, null);
2537
- } catch (error: unknown) {
2538
- return {
2539
- ...currentState,
2540
- error: error instanceof Error ? error.message : 'Failed to create route',
2541
- };
2542
- }
2543
- });
2544
-
2545
- export const updateRouteAction = routeManagementStatePart.createAction<{
2546
- id: string;
2547
- route?: any;
2548
- enabled?: boolean;
2549
- metadata?: any;
2550
- }>(async (statePartArg, dataArg, actionContext): Promise<IRouteManagementState> => {
2551
- const context = getActionContext();
2552
- const currentState = statePartArg.getState()!;
2553
-
2554
- try {
2555
- const request = new plugins.domtools.plugins.typedrequest.TypedRequest<
2556
- interfaces.requests.IReq_UpdateRoute
2557
- >('/typedrequest', 'updateRoute');
2558
-
2559
- const response = await request.fire({
2560
- identity: context.identity!,
2561
- id: dataArg.id,
2562
- route: dataArg.route,
2563
- enabled: dataArg.enabled,
2564
- metadata: dataArg.metadata,
2565
- });
2566
-
2567
- if (!response.success) {
2568
- throw new Error(response.message || 'Failed to update route');
2569
- }
2570
-
2571
- return await actionContext!.dispatch(fetchMergedRoutesAction, null);
2572
- } catch (error: unknown) {
2573
- return {
2574
- ...currentState,
2575
- error: error instanceof Error ? error.message : 'Failed to update route',
2576
- };
2577
- }
2578
- });
2579
-
2580
- export const deleteRouteAction = routeManagementStatePart.createAction<string>(
2581
- async (statePartArg, routeId, actionContext): Promise<IRouteManagementState> => {
2582
- const context = getActionContext();
2583
- const currentState = statePartArg.getState()!;
2584
-
2585
- try {
2586
- const request = new plugins.domtools.plugins.typedrequest.TypedRequest<
2587
- interfaces.requests.IReq_DeleteRoute
2588
- >('/typedrequest', 'deleteRoute');
2589
-
2590
- const response = await request.fire({
2591
- identity: context.identity!,
2592
- id: routeId,
2593
- });
2594
-
2595
- if (!response.success) {
2596
- throw new Error(response.message || 'Failed to delete route');
2597
- }
2598
-
2599
- return await actionContext!.dispatch(fetchMergedRoutesAction, null);
2600
- } catch (error: unknown) {
2601
- return {
2602
- ...currentState,
2603
- error: error instanceof Error ? error.message : 'Failed to delete route',
2604
- };
2605
- }
2606
- }
2607
- );
2608
-
2609
- export const toggleRouteAction = routeManagementStatePart.createAction<{
2610
- id: string;
2611
- enabled: boolean;
2612
- }>(async (statePartArg, dataArg, actionContext): Promise<IRouteManagementState> => {
2613
- const context = getActionContext();
2614
- const currentState = statePartArg.getState()!;
2615
-
2616
- try {
2617
- const request = new plugins.domtools.plugins.typedrequest.TypedRequest<
2618
- interfaces.requests.IReq_ToggleRoute
2619
- >('/typedrequest', 'toggleRoute');
2620
-
2621
- const response = await request.fire({
2622
- identity: context.identity!,
2623
- id: dataArg.id,
2624
- enabled: dataArg.enabled,
2625
- });
2626
-
2627
- if (!response.success) {
2628
- throw new Error(response.message || 'Failed to toggle route');
2629
- }
2630
-
2631
- return await actionContext!.dispatch(fetchMergedRoutesAction, null);
2632
- } catch (error: unknown) {
2633
- return {
2634
- ...currentState,
2635
- error: error instanceof Error ? error.message : 'Failed to toggle route',
2636
- };
2637
- }
2638
- });
2639
-
2640
- // ============================================================================
2641
- // API Token Actions
2642
- // ============================================================================
2643
-
2644
- export const fetchApiTokensAction = routeManagementStatePart.createAction(async (statePartArg): Promise<IRouteManagementState> => {
2645
- const context = getActionContext();
2646
- const currentState = statePartArg.getState()!;
2647
- if (!context.identity) return currentState;
2648
-
2649
- try {
2650
- const request = new plugins.domtools.plugins.typedrequest.TypedRequest<
2651
- interfaces.requests.IReq_ListApiTokens
2652
- >('/typedrequest', 'listApiTokens');
2653
-
2654
- const response = await request.fire({
2655
- identity: context.identity,
2656
- });
2657
-
2658
- return {
2659
- ...currentState,
2660
- apiTokens: response.tokens,
2661
- };
2662
- } catch (error) {
2663
- return {
2664
- ...currentState,
2665
- error: error instanceof Error ? error.message : 'Failed to fetch tokens',
2666
- };
2667
- }
2668
- });
2669
-
2670
- export const fetchGatewayClientsAction = routeManagementStatePart.createAction(async (statePartArg): Promise<IRouteManagementState> => {
2671
- const context = getActionContext();
2672
- const currentState = statePartArg.getState()!;
2673
- if (!context.identity) return currentState;
2674
-
2675
- try {
2676
- const request = new plugins.domtools.plugins.typedrequest.TypedRequest<
2677
- interfaces.requests.IReq_ListGatewayClients
2678
- >('/typedrequest', 'listGatewayClients');
2679
- const response = await request.fire({ identity: context.identity });
2680
- return {
2681
- ...currentState,
2682
- gatewayClients: response.gatewayClients,
2683
- error: null,
2684
- lastUpdated: Date.now(),
2685
- };
2686
- } catch (error) {
2687
- return {
2688
- ...currentState,
2689
- error: error instanceof Error ? error.message : 'Failed to fetch gateway clients',
2690
- };
2691
- }
2692
- });
2693
-
2694
- export async function createGatewayClient(data: {
2695
- id?: string;
2696
- type: interfaces.data.IGatewayClient['type'];
2697
- name: string;
2698
- description?: string;
2699
- hostnamePatterns?: string[];
2700
- allowedRouteTargets?: interfaces.data.IGatewayClient['allowedRouteTargets'];
2701
- }) {
2702
- const context = getActionContext();
2703
- const request = new plugins.domtools.plugins.typedrequest.TypedRequest<
2704
- interfaces.requests.IReq_CreateGatewayClient
2705
- >('/typedrequest', 'createGatewayClient');
2706
- return request.fire({
2707
- identity: context.identity!,
2708
- capabilities: {
2709
- readDomains: true,
2710
- readDnsRecords: true,
2711
- syncRoutes: true,
2712
- syncDnsRecords: false,
2713
- requestCertificates: false,
2714
- },
2715
- ...data,
2716
- });
2717
- }
2718
-
2719
- export const updateGatewayClientAction = routeManagementStatePart.createAction<{
2720
- id: string;
2721
- name?: string;
2722
- description?: string;
2723
- hostnamePatterns?: string[];
2724
- allowedRouteTargets?: interfaces.data.IGatewayClient['allowedRouteTargets'];
2725
- enabled?: boolean;
2726
- }>(async (statePartArg, dataArg, actionContext): Promise<IRouteManagementState> => {
2727
- const context = getActionContext();
2728
- const currentState = statePartArg.getState()!;
2729
- try {
2730
- const request = new plugins.domtools.plugins.typedrequest.TypedRequest<
2731
- interfaces.requests.IReq_UpdateGatewayClient
2732
- >('/typedrequest', 'updateGatewayClient');
2733
- await request.fire({ identity: context.identity!, ...dataArg });
2734
- return await actionContext!.dispatch(fetchGatewayClientsAction, null);
2735
- } catch (error) {
2736
- return {
2737
- ...currentState,
2738
- error: error instanceof Error ? error.message : 'Failed to update gateway client',
2739
- };
2740
- }
2741
- });
2742
-
2743
- export const deleteGatewayClientAction = routeManagementStatePart.createAction<string>(
2744
- async (statePartArg, gatewayClientId, actionContext): Promise<IRouteManagementState> => {
2745
- const context = getActionContext();
2746
- const currentState = statePartArg.getState()!;
2747
- try {
2748
- const request = new plugins.domtools.plugins.typedrequest.TypedRequest<
2749
- interfaces.requests.IReq_DeleteGatewayClient
2750
- >('/typedrequest', 'deleteGatewayClient');
2751
- await request.fire({ identity: context.identity!, id: gatewayClientId });
2752
- return await actionContext!.dispatch(fetchGatewayClientsAction, null);
2753
- } catch (error) {
2754
- return {
2755
- ...currentState,
2756
- error: error instanceof Error ? error.message : 'Failed to delete gateway client',
2757
- };
2758
- }
2759
- },
2760
- );
2761
-
2762
- export async function createGatewayClientToken(
2763
- gatewayClientId: string,
2764
- name?: string,
2765
- expiresInDays?: number | null,
2766
- ) {
2767
- const context = getActionContext();
2768
- const request = new plugins.domtools.plugins.typedrequest.TypedRequest<
2769
- interfaces.requests.IReq_CreateGatewayClientToken
2770
- >('/typedrequest', 'createGatewayClientToken');
2771
- return request.fire({
2772
- identity: context.identity!,
2773
- gatewayClientId,
2774
- name,
2775
- expiresInDays,
2776
- });
2777
- }
2778
-
2779
- // Users
2780
- export const fetchUsersAction = usersStatePart.createAction(async (statePartArg): Promise<IUsersState> => {
2781
- const context = getActionContext();
2782
- const currentState = statePartArg.getState()!;
2783
- if (!context.identity) return currentState;
2784
-
2785
- try {
2786
- const request = new plugins.domtools.plugins.typedrequest.TypedRequest<
2787
- interfaces.requests.IReq_ListUsers
2788
- >('/typedrequest', 'listUsers');
2789
-
2790
- const response = await request.fire({
2791
- identity: context.identity,
2792
- });
2793
-
2794
- return {
2795
- ...currentState,
2796
- users: response.users,
2797
- error: null,
2798
- lastUpdated: Date.now(),
2799
- };
2800
- } catch (error) {
2801
- return {
2802
- ...currentState,
2803
- error: error instanceof Error ? error.message : 'Failed to fetch users',
2804
- };
2805
- }
2806
- });
2807
-
2808
- export const createUserAction = usersStatePart.createAction<{
2809
- email: string;
2810
- name?: string;
2811
- role: interfaces.requests.TUserManagementRole;
2812
- password: string;
2813
- enableIdpGlobalAuth?: boolean;
2814
- }>(async (statePartArg, dataArg, actionContext): Promise<IUsersState> => {
2815
- const context = getActionContext();
2816
- const currentState = statePartArg.getState()!;
2817
- if (!context.identity) return currentState;
2818
-
2819
- try {
2820
- const request = new plugins.domtools.plugins.typedrequest.TypedRequest<
2821
- interfaces.requests.IReq_CreateUser
2822
- >('/typedrequest', 'createUser');
2823
-
2824
- const response = await request.fire({
2825
- identity: context.identity,
2826
- email: dataArg.email,
2827
- name: dataArg.name,
2828
- role: dataArg.role,
2829
- password: dataArg.password,
2830
- enableIdpGlobalAuth: dataArg.enableIdpGlobalAuth,
2831
- });
2832
-
2833
- if (!response.success) {
2834
- throw new Error(response.message || 'Failed to create user');
2835
- }
2836
-
2837
- return await actionContext!.dispatch(fetchUsersAction, null);
2838
- } catch (error) {
2839
- return {
2840
- ...currentState,
2841
- error: error instanceof Error ? error.message : 'Failed to create user',
2842
- };
2843
- }
2844
- });
2845
-
2846
- export const deleteUserAction = usersStatePart.createAction<string>(
2847
- async (statePartArg, userIdArg, actionContext): Promise<IUsersState> => {
2848
- const context = getActionContext();
2849
- const currentState = statePartArg.getState()!;
2850
- if (!context.identity) return currentState;
2851
-
2852
- try {
2853
- const request = new plugins.domtools.plugins.typedrequest.TypedRequest<
2854
- interfaces.requests.IReq_DeleteUser
2855
- >('/typedrequest', 'deleteUser');
2856
-
2857
- const response = await request.fire({
2858
- identity: context.identity,
2859
- id: userIdArg,
2860
- });
2861
-
2862
- if (!response.success) {
2863
- throw new Error(response.message || 'Failed to delete user');
2864
- }
2865
-
2866
- return await actionContext!.dispatch(fetchUsersAction, null);
2867
- } catch (error) {
2868
- return {
2869
- ...currentState,
2870
- error: error instanceof Error ? error.message : 'Failed to delete user',
2871
- };
2872
- }
2873
- },
2874
- );
2875
-
2876
- export async function createApiToken(
2877
- name: string,
2878
- scopes: interfaces.data.TApiTokenScope[],
2879
- expiresInDays?: number | null,
2880
- policy?: any,
2881
- ) {
2882
- const context = getActionContext();
2883
- const request = new plugins.domtools.plugins.typedrequest.TypedRequest<
2884
- interfaces.requests.IReq_CreateApiToken
2885
- >('/typedrequest', 'createApiToken');
2886
-
2887
- return request.fire({
2888
- identity: context.identity!,
2889
- name,
2890
- scopes,
2891
- policy,
2892
- expiresInDays,
2893
- });
2894
- }
2895
-
2896
- export async function rollApiToken(id: string) {
2897
- const context = getActionContext();
2898
- const request = new plugins.domtools.plugins.typedrequest.TypedRequest<
2899
- interfaces.requests.IReq_RollApiToken
2900
- >('/typedrequest', 'rollApiToken');
2901
-
2902
- return request.fire({
2903
- identity: context.identity!,
2904
- id,
2905
- });
2906
- }
2907
-
2908
- export const revokeApiTokenAction = routeManagementStatePart.createAction<string>(
2909
- async (statePartArg, tokenId, actionContext): Promise<IRouteManagementState> => {
2910
- const context = getActionContext();
2911
- const currentState = statePartArg.getState()!;
2912
-
2913
- try {
2914
- const request = new plugins.domtools.plugins.typedrequest.TypedRequest<
2915
- interfaces.requests.IReq_RevokeApiToken
2916
- >('/typedrequest', 'revokeApiToken');
2917
-
2918
- await request.fire({
2919
- identity: context.identity!,
2920
- id: tokenId,
2921
- });
2922
-
2923
- return await actionContext!.dispatch(fetchApiTokensAction, null);
2924
- } catch (error: unknown) {
2925
- return {
2926
- ...currentState,
2927
- error: error instanceof Error ? error.message : 'Failed to revoke token',
2928
- };
2929
- }
2930
- }
2931
- );
2932
-
2933
- export const toggleApiTokenAction = routeManagementStatePart.createAction<{
2934
- id: string;
2935
- enabled: boolean;
2936
- }>(async (statePartArg, dataArg, actionContext): Promise<IRouteManagementState> => {
2937
- const context = getActionContext();
2938
- const currentState = statePartArg.getState()!;
2939
-
2940
- try {
2941
- const request = new plugins.domtools.plugins.typedrequest.TypedRequest<
2942
- interfaces.requests.IReq_ToggleApiToken
2943
- >('/typedrequest', 'toggleApiToken');
2944
-
2945
- await request.fire({
2946
- identity: context.identity!,
2947
- id: dataArg.id,
2948
- enabled: dataArg.enabled,
2949
- });
2950
-
2951
- return await actionContext!.dispatch(fetchApiTokensAction, null);
2952
- } catch (error: unknown) {
2953
- return {
2954
- ...currentState,
2955
- error: error instanceof Error ? error.message : 'Failed to toggle token',
2956
- };
2957
- }
2958
- });
2959
-
2960
- // ============================================================================
2961
- // Email Domains State
2962
- // ============================================================================
2963
-
2964
- export interface IEmailDomainsState {
2965
- domains: interfaces.data.IEmailDomain[];
2966
- settings: interfaces.data.IEmailServerSettings | null;
2967
- isLoading: boolean;
2968
- lastUpdated: number;
2969
- }
2970
-
2971
- export const emailDomainsStatePart = await appState.getStatePart<IEmailDomainsState>(
2972
- 'emailDomains',
2973
- {
2974
- domains: [],
2975
- settings: null,
2976
- isLoading: false,
2977
- lastUpdated: 0,
2978
- },
2979
- 'soft',
2980
- );
2981
-
2982
- export const fetchEmailDomainsAction = emailDomainsStatePart.createAction(
2983
- async (statePartArg): Promise<IEmailDomainsState> => {
2984
- const context = getActionContext();
2985
- const currentState = statePartArg.getState()!;
2986
- if (!context.identity) return currentState;
2987
-
2988
- try {
2989
- const request = new plugins.domtools.plugins.typedrequest.TypedRequest<
2990
- interfaces.requests.IReq_GetEmailDomains
2991
- >('/typedrequest', 'getEmailDomains');
2992
- const settingsRequest = new plugins.domtools.plugins.typedrequest.TypedRequest<
2993
- interfaces.requests.IReq_GetEmailServerSettings
2994
- >('/typedrequest', 'getEmailServerSettings');
2995
- const response = await request.fire({ identity: context.identity });
2996
- const settingsResponse = await settingsRequest.fire({ identity: context.identity });
2997
- return {
2998
- ...currentState,
2999
- domains: response.domains,
3000
- settings: settingsResponse.settings,
3001
- isLoading: false,
3002
- lastUpdated: Date.now(),
3003
- };
3004
- } catch {
3005
- return { ...currentState, isLoading: false };
3006
- }
3007
- },
3008
- );
3009
-
3010
- export const createEmailDomainAction = emailDomainsStatePart.createAction<{
3011
- linkedDomainId: string;
3012
- subdomain?: string;
3013
- dkimSelector?: string;
3014
- dkimKeySize?: number;
3015
- rotateKeys?: boolean;
3016
- rotationIntervalDays?: number;
3017
- }>(async (statePartArg, args, actionContext) => {
3018
- const context = getActionContext();
3019
- const currentState = statePartArg.getState()!;
3020
- try {
3021
- const request = new plugins.domtools.plugins.typedrequest.TypedRequest<
3022
- interfaces.requests.IReq_CreateEmailDomain
3023
- >('/typedrequest', 'createEmailDomain');
3024
- await request.fire({ identity: context.identity!, ...args });
3025
- return await actionContext!.dispatch(fetchEmailDomainsAction, null);
3026
- } catch {
3027
- return currentState;
3028
- }
3029
- });
3030
-
3031
- export const updateEmailServerSettingsAction = emailDomainsStatePart.createAction<
3032
- interfaces.data.TEmailServerSettingsUpdate
3033
- >(async (statePartArg, settings, actionContext) => {
3034
- const context = getActionContext();
3035
- const currentState = statePartArg.getState()!;
3036
- try {
3037
- const request = new plugins.domtools.plugins.typedrequest.TypedRequest<
3038
- interfaces.requests.IReq_UpdateEmailServerSettings
3039
- >('/typedrequest', 'updateEmailServerSettings');
3040
- const response = await request.fire({ identity: context.identity!, settings });
3041
- if (!response.success) {
3042
- return currentState;
3043
- }
3044
- return await actionContext!.dispatch(fetchEmailDomainsAction, null);
3045
- } catch {
3046
- return currentState;
3047
- }
3048
- });
3049
-
3050
- export const deleteEmailDomainAction = emailDomainsStatePart.createAction<string>(
3051
- async (statePartArg, id, actionContext) => {
3052
- const context = getActionContext();
3053
- const currentState = statePartArg.getState()!;
3054
- try {
3055
- const request = new plugins.domtools.plugins.typedrequest.TypedRequest<
3056
- interfaces.requests.IReq_DeleteEmailDomain
3057
- >('/typedrequest', 'deleteEmailDomain');
3058
- await request.fire({ identity: context.identity!, id });
3059
- return await actionContext!.dispatch(fetchEmailDomainsAction, null);
3060
- } catch {
3061
- return currentState;
3062
- }
3063
- },
3064
- );
3065
-
3066
- export const validateEmailDomainAction = emailDomainsStatePart.createAction<string>(
3067
- async (statePartArg, id, actionContext) => {
3068
- const context = getActionContext();
3069
- const currentState = statePartArg.getState()!;
3070
- try {
3071
- const request = new plugins.domtools.plugins.typedrequest.TypedRequest<
3072
- interfaces.requests.IReq_ValidateEmailDomain
3073
- >('/typedrequest', 'validateEmailDomain');
3074
- await request.fire({ identity: context.identity!, id });
3075
- return await actionContext!.dispatch(fetchEmailDomainsAction, null);
3076
- } catch {
3077
- return currentState;
3078
- }
3079
- },
3080
- );
3081
-
3082
- export const provisionEmailDomainDnsAction = emailDomainsStatePart.createAction<string>(
3083
- async (statePartArg, id, actionContext) => {
3084
- const context = getActionContext();
3085
- const currentState = statePartArg.getState()!;
3086
- try {
3087
- const request = new plugins.domtools.plugins.typedrequest.TypedRequest<
3088
- interfaces.requests.IReq_ProvisionEmailDomainDns
3089
- >('/typedrequest', 'provisionEmailDomainDns');
3090
- await request.fire({ identity: context.identity!, id });
3091
- return await actionContext!.dispatch(fetchEmailDomainsAction, null);
3092
- } catch {
3093
- return currentState;
3094
- }
3095
- },
3096
- );
3097
-
3098
- // ============================================================================
3099
- // Email Domain Standalone Functions
3100
- // ============================================================================
3101
-
3102
- export async function fetchEmailDomainDnsRecords(id: string) {
3103
- const context = getActionContext();
3104
- const request = new plugins.domtools.plugins.typedrequest.TypedRequest<
3105
- interfaces.requests.IReq_GetEmailDomainDnsRecords
3106
- >('/typedrequest', 'getEmailDomainDnsRecords');
3107
- return request.fire({ identity: context.identity!, id });
3108
- }
3109
-
3110
- // ============================================================================
3111
- // TypedSocket Client for Real-time Log Streaming
3112
- // ============================================================================
3113
-
3114
- let socketClient: plugins.typedsocket.TypedSocket | null = null;
3115
- const socketRouter = new plugins.domtools.plugins.typedrequest.TypedRouter();
3116
-
3117
- // Batched log entry handler — buffers incoming entries and flushes once per animation frame
3118
- let logEntryBuffer: interfaces.data.ILogEntry[] = [];
3119
- let logFlushScheduled = false;
3120
-
3121
- function flushLogEntries() {
3122
- logFlushScheduled = false;
3123
- if (logEntryBuffer.length === 0) return;
3124
- const current = logStatePart.getState()!;
3125
- const updated = [...current.recentLogs, ...logEntryBuffer];
3126
- logEntryBuffer = [];
3127
- // Cap at 2000 entries
3128
- if (updated.length > 2000) {
3129
- updated.splice(0, updated.length - 2000);
3130
- }
3131
- logStatePart.setState({ ...current, recentLogs: updated } as ILogState);
3132
- }
3133
-
3134
- // Register handler for pushed log entries from the server
3135
- socketRouter.addTypedHandler(
3136
- new plugins.domtools.plugins.typedrequest.TypedHandler<interfaces.requests.IReq_PushLogEntry>(
3137
- 'pushLogEntry',
3138
- async (dataArg) => {
3139
- logEntryBuffer.push(dataArg.entry);
3140
- if (!logFlushScheduled) {
3141
- logFlushScheduled = true;
3142
- requestAnimationFrame(flushLogEntries);
3143
- }
3144
- return {};
3145
- }
3146
- )
3147
- );
3148
-
3149
- async function connectSocket() {
3150
- if (socketClient) return;
3151
- try {
3152
- socketClient = await plugins.typedsocket.TypedSocket.createClient(
3153
- socketRouter,
3154
- plugins.typedsocket.TypedSocket.useWindowLocationOriginUrl(),
3155
- );
3156
- await socketClient.setTag('role', 'ops_dashboard');
3157
- } catch (err) {
3158
- console.error('TypedSocket connection failed:', err);
3159
- socketClient = null;
3160
- }
3161
- }
3162
-
3163
- async function disconnectSocket() {
3164
- if (socketClient) {
3165
- try {
3166
- await socketClient.stop();
3167
- } catch {
3168
- // ignore disconnect errors
3169
- }
3170
- socketClient = null;
3171
- }
3172
- }
3173
-
3174
- // In-flight guard to prevent concurrent refresh requests
3175
- let isRefreshing = false;
3176
-
3177
- // Combined refresh action for efficient polling
3178
- async function dispatchCombinedRefreshAction() {
3179
- if (isRefreshing) return;
3180
- isRefreshing = true;
3181
- try {
3182
- await dispatchCombinedRefreshActionInner();
3183
- } finally {
3184
- isRefreshing = false;
3185
- }
3186
- }
3187
-
3188
- async function dispatchCombinedRefreshActionInner() {
3189
- const context = getActionContext();
3190
- if (!context.identity) return;
3191
- const currentView = uiStatePart.getState()!.activeView;
3192
- const currentSubview = uiStatePart.getState()!.activeSubview;
3193
-
3194
- try {
3195
- // Always fetch basic stats for dashboard widgets
3196
- const combinedRequest = new plugins.domtools.plugins.typedrequest.TypedRequest<
3197
- interfaces.requests.IReq_GetCombinedMetrics
3198
- >('/typedrequest', 'getCombinedMetrics');
3199
-
3200
- const combinedResponse = await combinedRequest.fire({
3201
- identity: context.identity,
3202
- sections: {
3203
- server: true,
3204
- email: true,
3205
- dns: true,
3206
- security: true,
3207
- network: currentView === 'network' && currentSubview === 'activity',
3208
- radius: true,
3209
- vpn: true,
3210
- },
3211
- });
3212
-
3213
- // Update all stats from combined response
3214
- const currentStatsState = statsStatePart.getState()!;
3215
- statsStatePart.setState({
3216
- ...currentStatsState,
3217
- serverStats: combinedResponse.metrics.server || currentStatsState.serverStats,
3218
- emailStats: combinedResponse.metrics.email || currentStatsState.emailStats,
3219
- dnsStats: combinedResponse.metrics.dns || currentStatsState.dnsStats,
3220
- securityMetrics: combinedResponse.metrics.security || currentStatsState.securityMetrics,
3221
- radiusStats: combinedResponse.metrics.radius || currentStatsState.radiusStats,
3222
- vpnStats: combinedResponse.metrics.vpn || currentStatsState.vpnStats,
3223
- lastUpdated: Date.now(),
3224
- isLoading: false,
3225
- error: null,
3226
- });
3227
-
3228
- // Update network stats if included
3229
- if (combinedResponse.metrics.network && currentView === 'network') {
3230
- const network = combinedResponse.metrics.network;
3231
- const connectionsByIP: { [ip: string]: number } = {};
3232
-
3233
- // Build connectionsByIP from connectionDetails (now populated with real per-IP data)
3234
- network.connectionDetails.forEach(conn => {
3235
- connectionsByIP[conn.remoteAddress] = (connectionsByIP[conn.remoteAddress] || 0) + (conn.connectionCount || 1);
3236
- });
3237
-
3238
- // Build connections from connectionDetails (real per-IP aggregates)
3239
- const connections: interfaces.data.IConnectionInfo[] = network.connectionDetails.map((conn, i) => ({
3240
- id: `ip-${conn.remoteAddress}`,
3241
- remoteAddress: conn.remoteAddress,
3242
- localAddress: 'server',
3243
- startTime: conn.startTime,
3244
- protocol: conn.protocol as any,
3245
- state: conn.state as any,
3246
- bytesReceived: conn.bytesIn,
3247
- bytesSent: conn.bytesOut,
3248
- connectionCount: conn.connectionCount,
3249
- }));
3250
-
3251
- networkStatePart.setState({
3252
- ...networkStatePart.getState()!,
3253
- connections,
3254
- connectionsByIP,
3255
- throughputRate: {
3256
- bytesInPerSecond: network.totalBandwidth.in,
3257
- bytesOutPerSecond: network.totalBandwidth.out,
3258
- },
3259
- totalBytes: network.totalBytes || { in: 0, out: 0 },
3260
- topIPs: network.topEndpoints.map(e => ({ ip: e.endpoint, count: e.connections })),
3261
- topIPsByBandwidth: (network.topEndpointsByBandwidth || []).map(e => ({
3262
- ip: e.endpoint,
3263
- count: e.connections,
3264
- bwIn: e.bandwidth?.in || 0,
3265
- bwOut: e.bandwidth?.out || 0,
3266
- })),
3267
- topASNs: network.topASNs || [],
3268
- throughputByIP: network.topEndpoints.map(e => ({ ip: e.endpoint, in: e.bandwidth?.in || 0, out: e.bandwidth?.out || 0 })),
3269
- domainActivity: network.domainActivity || [],
3270
- throughputHistory: network.throughputHistory || [],
3271
- requestsPerSecond: network.requestsPerSecond || 0,
3272
- requestsTotal: network.requestsTotal || 0,
3273
- backends: network.backends || [],
3274
- frontendProtocols: network.frontendProtocols || null,
3275
- backendProtocols: network.backendProtocols || null,
3276
- lastUpdated: Date.now(),
3277
- isLoading: false,
3278
- error: null,
3279
- });
3280
-
3281
- refreshNetworkIpIntelligence(context.identity, [
3282
- ...network.connectionDetails.map((conn) => conn.remoteAddress),
3283
- ...network.topEndpoints.map((endpoint) => endpoint.endpoint),
3284
- ...(network.topEndpointsByBandwidth || []).map((endpoint) => endpoint.endpoint),
3285
- ]);
3286
- }
3287
-
3288
- if (currentView === 'security') {
3289
- runBackgroundRefresh('securityPolicy', 'Security policy refresh failed:', async () => {
3290
- await securityPolicyStatePart.dispatchAction(fetchSecurityPolicyAction, null);
3291
- });
3292
- }
3293
-
3294
- // Refresh certificate data if on Domains > Certificates subview
3295
- if (currentView === 'domains' && currentSubview === 'certificates') {
3296
- runBackgroundRefresh('certificates', 'Certificate refresh failed:', async () => {
3297
- await certificateStatePart.dispatchAction(fetchCertificateOverviewAction, null);
3298
- });
3299
- }
3300
-
3301
- // Refresh remote ingress data if on the Network → Remote Ingress subview
3302
- if (currentView === 'network' && currentSubview === 'remoteingress') {
3303
- runBackgroundRefresh('remoteIngress', 'Remote ingress refresh failed:', async () => {
3304
- await remoteIngressStatePart.dispatchAction(fetchRemoteIngressAction, null);
3305
- });
3306
- }
3307
-
3308
- // Refresh VPN data if on the Network → VPN subview
3309
- if (currentView === 'network' && currentSubview === 'vpn') {
3310
- runBackgroundRefresh('vpn', 'VPN refresh failed:', async () => {
3311
- await vpnStatePart.dispatchAction(fetchVpnAction, null);
3312
- });
3313
- }
3314
- } catch (error) {
3315
- console.error('Combined refresh failed:', error);
3316
- // If the error looks like an auth failure (invalid JWT), force re-login
3317
- const errMsg = String(error);
3318
- if (errMsg.includes('invalid') || errMsg.includes('unauthorized') || errMsg.includes('401')) {
3319
- await loginStatePart.dispatchAction(logoutAction, null);
3320
- window.location.reload();
3321
- }
3322
- }
3323
- }
3324
-
3325
- // Create a proper action for the combined refresh so we can use createScheduledAction
3326
- const combinedRefreshAction = statsStatePart.createAction<void>(async (statePartArg) => {
3327
- await dispatchCombinedRefreshAction();
3328
- // Return current state — dispatchCombinedRefreshAction already updates all state parts directly
3329
- return statePartArg.getState()!;
3330
- });
3331
-
3332
- // Scheduled refresh process with autoPause: 'visibility' — automatically pauses when tab is hidden
3333
- let refreshProcess: ReturnType<typeof statsStatePart.createScheduledAction> | null = null;
3334
-
3335
- const startAutoRefresh = () => {
3336
- const uiState = uiStatePart.getState()!;
3337
- const loginState = loginStatePart.getState()!;
3338
-
3339
- if (uiState.autoRefresh && loginState.isLoggedIn) {
3340
- // Dispose old process if interval changed or not running
3341
- if (refreshProcess) {
3342
- refreshProcess.dispose();
3343
- refreshProcess = null;
3344
- }
3345
- refreshProcess = statsStatePart.createScheduledAction({
3346
- action: combinedRefreshAction,
3347
- payload: undefined,
3348
- intervalMs: uiState.refreshInterval,
3349
- autoPause: 'visibility',
3350
- });
3351
- } else {
3352
- if (refreshProcess) {
3353
- refreshProcess.dispose();
3354
- refreshProcess = null;
3355
- }
3356
- }
3357
- };
3358
-
3359
- // Watch for relevant changes
3360
- let previousAutoRefresh = uiStatePart.getState()!.autoRefresh;
3361
- let previousRefreshInterval = uiStatePart.getState()!.refreshInterval;
3362
- let previousIsLoggedIn = loginStatePart.getState()!.isLoggedIn;
3363
-
3364
- uiStatePart.select((s) => ({ autoRefresh: s.autoRefresh, refreshInterval: s.refreshInterval }))
3365
- .subscribe((state) => {
3366
- if (state.autoRefresh !== previousAutoRefresh ||
3367
- state.refreshInterval !== previousRefreshInterval) {
3368
- previousAutoRefresh = state.autoRefresh;
3369
- previousRefreshInterval = state.refreshInterval;
3370
- startAutoRefresh();
3371
- }
3372
- });
3373
-
3374
- loginStatePart.select((s) => s.isLoggedIn).subscribe((isLoggedIn) => {
3375
- if (isLoggedIn !== previousIsLoggedIn) {
3376
- previousIsLoggedIn = isLoggedIn;
3377
- startAutoRefresh();
3378
-
3379
- // Connect/disconnect TypedSocket based on login state
3380
- if (isLoggedIn) {
3381
- connectSocket();
3382
- } else {
3383
- disconnectSocket();
3384
- }
3385
- }
3386
- });
3387
-
3388
- // Pause/resume WebSocket when tab visibility changes
3389
- document.addEventListener('visibilitychange', () => {
3390
- if (document.hidden) {
3391
- disconnectSocket();
3392
- } else if (loginStatePart.getState()!.isLoggedIn) {
3393
- connectSocket();
3394
- }
3395
- });
3396
-
3397
- // Initial start
3398
- startAutoRefresh();
3399
-
3400
- // Connect TypedSocket if already logged in (e.g., persistent session)
3401
- if (loginStatePart.getState()!.isLoggedIn) {
3402
- connectSocket();
3403
- }
1
+ // State management for the dcrouter Ops UI, split into per-domain modules.
2
+ // This barrel preserves the historical import path for all elements.
3
+ export * from './appstate/shared.js';
4
+ export * from './appstate/login.js';
5
+ export * from './appstate/stats.js';
6
+ export * from './appstate/config.js';
7
+ export * from './appstate/logs.js';
8
+ export * from './appstate/network.js';
9
+ export * from './appstate/security.js';
10
+ export * from './appstate/ui.js';
11
+ export * from './appstate/email-ops.js';
12
+ export * from './appstate/certificates.js';
13
+ export * from './appstate/acme.js';
14
+ export * from './appstate/remoteingress.js';
15
+ export * from './appstate/vpn.js';
16
+ export * from './appstate/target-profiles.js';
17
+ export * from './appstate/profiles-targets.js';
18
+ export * from './appstate/domains.js';
19
+ export * from './appstate/routes.js';
20
+ export * from './appstate/users.js';
21
+ export * from './appstate/email-domains.js';
22
+
23
+ // Side-effect module: auto-refresh scheduling and TypedSocket log streaming.
24
+ import './appstate/runtime.js';