@serve.zone/dcrouter 15.0.1 → 15.0.3

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 (117) 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/acme/classes.smartacme-lifecycle.d.ts +25 -0
  5. package/dist_ts/acme/classes.smartacme-lifecycle.js +144 -0
  6. package/dist_ts/acme/index.d.ts +1 -0
  7. package/dist_ts/acme/index.js +2 -1
  8. package/dist_ts/classes.dcrouter.d.ts +21 -139
  9. package/dist_ts/classes.dcrouter.js +71 -1585
  10. package/dist_ts/dns/classes.dns-server-runtime.d.ts +37 -0
  11. package/dist_ts/dns/classes.dns-server-runtime.js +449 -0
  12. package/dist_ts/dns/index.d.ts +1 -0
  13. package/dist_ts/dns/index.js +2 -1
  14. package/dist_ts/email/classes.accepted-email-spool.d.ts +55 -0
  15. package/dist_ts/email/classes.accepted-email-spool.js +345 -0
  16. package/dist_ts/email/classes.email-route-builder.d.ts +28 -0
  17. package/dist_ts/email/classes.email-route-builder.js +260 -0
  18. package/dist_ts/email/index.d.ts +2 -0
  19. package/dist_ts/email/index.js +3 -1
  20. package/dist_ts/opsserver/handlers/gatewayclient.handler.js +10 -8
  21. package/dist_ts/remoteingress/classes.hub-lifecycle.d.ts +27 -0
  22. package/dist_ts/remoteingress/classes.hub-lifecycle.js +241 -0
  23. package/dist_ts/remoteingress/classes.remoteingress-manager.d.ts +1 -2
  24. package/dist_ts/remoteingress/index.d.ts +1 -0
  25. package/dist_ts/remoteingress/index.js +2 -1
  26. package/dist_ts/security/classes.route-policy-augmenter.d.ts +22 -0
  27. package/dist_ts/security/classes.route-policy-augmenter.js +120 -0
  28. package/dist_ts/security/index.d.ts +1 -0
  29. package/dist_ts/security/index.js +2 -1
  30. package/dist_ts/vpn/classes.vpn-access-resolver.d.ts +34 -0
  31. package/dist_ts/vpn/classes.vpn-access-resolver.js +101 -0
  32. package/dist_ts/vpn/index.d.ts +1 -0
  33. package/dist_ts/vpn/index.js +2 -1
  34. package/dist_ts_migrations/index.js +92 -9
  35. package/dist_ts_web/00_commitinfo_data.js +1 -1
  36. package/dist_ts_web/appstate/acme.d.ts +17 -0
  37. package/dist_ts_web/appstate/acme.js +64 -0
  38. package/dist_ts_web/appstate/certificates.d.ts +37 -0
  39. package/dist_ts_web/appstate/certificates.js +107 -0
  40. package/dist_ts_web/appstate/config.d.ts +9 -0
  41. package/dist_ts_web/appstate/config.js +35 -0
  42. package/dist_ts_web/appstate/domains.d.ts +80 -0
  43. package/dist_ts_web/appstate/domains.js +324 -0
  44. package/dist_ts_web/appstate/email-domains.d.ts +25 -0
  45. package/dist_ts_web/appstate/email-domains.js +104 -0
  46. package/dist_ts_web/appstate/email-ops.d.ts +10 -0
  47. package/dist_ts_web/appstate/email-ops.js +40 -0
  48. package/dist_ts_web/appstate/login.d.ts +30 -0
  49. package/dist_ts_web/appstate/login.js +83 -0
  50. package/dist_ts_web/appstate/logs.d.ts +16 -0
  51. package/dist_ts_web/appstate/logs.js +27 -0
  52. package/dist_ts_web/appstate/network.d.ts +50 -0
  53. package/dist_ts_web/appstate/network.js +122 -0
  54. package/dist_ts_web/appstate/profiles-targets.d.ts +45 -0
  55. package/dist_ts_web/appstate/profiles-targets.js +173 -0
  56. package/dist_ts_web/appstate/remoteingress.d.ts +47 -0
  57. package/dist_ts_web/appstate/remoteingress.js +204 -0
  58. package/dist_ts_web/appstate/routes.d.ts +76 -0
  59. package/dist_ts_web/appstate/routes.js +316 -0
  60. package/dist_ts_web/appstate/runtime.d.ts +1 -0
  61. package/dist_ts_web/appstate/runtime.js +276 -0
  62. package/dist_ts_web/appstate/security.d.ts +29 -0
  63. package/dist_ts_web/appstate/security.js +167 -0
  64. package/dist_ts_web/appstate/shared.d.ts +3 -0
  65. package/dist_ts_web/appstate/shared.js +13 -0
  66. package/dist_ts_web/appstate/stats.d.ts +15 -0
  67. package/dist_ts_web/appstate/stats.js +59 -0
  68. package/dist_ts_web/appstate/target-profiles.d.ts +37 -0
  69. package/dist_ts_web/appstate/target-profiles.js +118 -0
  70. package/dist_ts_web/appstate/ui.d.ts +11 -0
  71. package/dist_ts_web/appstate/ui.js +55 -0
  72. package/dist_ts_web/appstate/users.d.ts +27 -0
  73. package/dist_ts_web/appstate/users.js +85 -0
  74. package/dist_ts_web/appstate/vpn.d.ts +44 -0
  75. package/dist_ts_web/appstate/vpn.js +148 -0
  76. package/dist_ts_web/appstate.d.ts +20 -568
  77. package/dist_ts_web/appstate.js +24 -2418
  78. package/package.json +1 -1
  79. package/ts/00_commitinfo_data.ts +1 -1
  80. package/ts/acme/classes.smartacme-lifecycle.ts +155 -0
  81. package/ts/acme/index.ts +1 -0
  82. package/ts/classes.dcrouter.ts +118 -1919
  83. package/ts/dns/classes.dns-server-runtime.ts +525 -0
  84. package/ts/dns/index.ts +1 -0
  85. package/ts/email/classes.accepted-email-spool.ts +434 -0
  86. package/ts/email/classes.email-route-builder.ts +312 -0
  87. package/ts/email/index.ts +2 -0
  88. package/ts/opsserver/handlers/gatewayclient.handler.ts +9 -7
  89. package/ts/remoteingress/classes.hub-lifecycle.ts +278 -0
  90. package/ts/remoteingress/classes.remoteingress-manager.ts +1 -1
  91. package/ts/remoteingress/index.ts +1 -0
  92. package/ts/security/classes.route-policy-augmenter.ts +140 -0
  93. package/ts/security/index.ts +1 -0
  94. package/ts/vpn/classes.vpn-access-resolver.ts +126 -0
  95. package/ts/vpn/index.ts +1 -0
  96. package/ts_web/00_commitinfo_data.ts +1 -1
  97. package/ts_web/appstate/acme.ts +93 -0
  98. package/ts_web/appstate/certificates.ts +159 -0
  99. package/ts_web/appstate/config.ts +49 -0
  100. package/ts_web/appstate/domains.ts +429 -0
  101. package/ts_web/appstate/email-domains.ts +155 -0
  102. package/ts_web/appstate/email-ops.ts +57 -0
  103. package/ts_web/appstate/login.ts +128 -0
  104. package/ts_web/appstate/logs.ts +50 -0
  105. package/ts_web/appstate/network.ts +161 -0
  106. package/ts_web/appstate/profiles-targets.ts +240 -0
  107. package/ts_web/appstate/remoteingress.ts +300 -0
  108. package/ts_web/appstate/routes.ts +447 -0
  109. package/ts_web/appstate/runtime.ts +308 -0
  110. package/ts_web/appstate/security.ts +229 -0
  111. package/ts_web/appstate/shared.ts +15 -0
  112. package/ts_web/appstate/stats.ts +79 -0
  113. package/ts_web/appstate/target-profiles.ts +164 -0
  114. package/ts_web/appstate/ui.ts +75 -0
  115. package/ts_web/appstate/users.ts +133 -0
  116. package/ts_web/appstate/vpn.ts +234 -0
  117. package/ts_web/appstate.ts +24 -3403
@@ -0,0 +1,164 @@
1
+ import * as plugins from '../plugins.js';
2
+ import * as interfaces from '../../ts_interfaces/index.js';
3
+ import { appState } from './shared.js';
4
+ import { getActionContext } from './login.js';
5
+
6
+ // Target Profiles State
7
+ // ============================================================================
8
+
9
+ export interface ITargetProfilesState {
10
+ profiles: interfaces.data.ITargetProfile[];
11
+ isLoading: boolean;
12
+ error: string | null;
13
+ lastUpdated: number;
14
+ }
15
+
16
+ export const targetProfilesStatePart = await appState.getStatePart<ITargetProfilesState>(
17
+ 'targetProfiles',
18
+ {
19
+ profiles: [],
20
+ isLoading: false,
21
+ error: null,
22
+ lastUpdated: 0,
23
+ },
24
+ 'soft'
25
+ );
26
+
27
+ // ============================================================================
28
+ // Target Profiles Actions
29
+ // ============================================================================
30
+
31
+ export const fetchTargetProfilesAction = targetProfilesStatePart.createAction(
32
+ async (statePartArg): Promise<ITargetProfilesState> => {
33
+ const context = getActionContext();
34
+ const currentState = statePartArg.getState()!;
35
+ if (!context.identity) return currentState;
36
+
37
+ try {
38
+ const request = new plugins.domtools.plugins.typedrequest.TypedRequest<
39
+ interfaces.requests.IReq_GetTargetProfiles
40
+ >('/typedrequest', 'getTargetProfiles');
41
+
42
+ const response = await request.fire({ identity: context.identity });
43
+
44
+ return {
45
+ profiles: response.profiles,
46
+ isLoading: false,
47
+ error: null,
48
+ lastUpdated: Date.now(),
49
+ };
50
+ } catch (error) {
51
+ return {
52
+ ...currentState,
53
+ isLoading: false,
54
+ error: error instanceof Error ? error.message : 'Failed to fetch target profiles',
55
+ };
56
+ }
57
+ }
58
+ );
59
+
60
+ export const createTargetProfileAction = targetProfilesStatePart.createAction<{
61
+ name: string;
62
+ description?: string;
63
+ domains?: string[];
64
+ targets?: Array<{ ip: string; port: number }>;
65
+ routeRefs?: string[];
66
+ allowRoutesByClientSourceIp?: boolean;
67
+ }>(async (statePartArg, dataArg, actionContext): Promise<ITargetProfilesState> => {
68
+ const context = getActionContext();
69
+ try {
70
+ const request = new plugins.domtools.plugins.typedrequest.TypedRequest<
71
+ interfaces.requests.IReq_CreateTargetProfile
72
+ >('/typedrequest', 'createTargetProfile');
73
+ const response = await request.fire({
74
+ identity: context.identity!,
75
+ name: dataArg.name,
76
+ description: dataArg.description,
77
+ domains: dataArg.domains,
78
+ targets: dataArg.targets,
79
+ routeRefs: dataArg.routeRefs,
80
+ allowRoutesByClientSourceIp: dataArg.allowRoutesByClientSourceIp,
81
+ });
82
+ if (!response.success) {
83
+ return {
84
+ ...statePartArg.getState()!,
85
+ error: response.message || 'Failed to create target profile',
86
+ };
87
+ }
88
+ return await actionContext!.dispatch(fetchTargetProfilesAction, null);
89
+ } catch (error: unknown) {
90
+ return {
91
+ ...statePartArg.getState()!,
92
+ error: error instanceof Error ? error.message : 'Failed to create target profile',
93
+ };
94
+ }
95
+ });
96
+
97
+ export const updateTargetProfileAction = targetProfilesStatePart.createAction<{
98
+ id: string;
99
+ name?: string;
100
+ description?: string;
101
+ domains?: string[];
102
+ targets?: Array<{ ip: string; port: number }>;
103
+ routeRefs?: string[];
104
+ allowRoutesByClientSourceIp?: boolean;
105
+ }>(async (statePartArg, dataArg, actionContext): Promise<ITargetProfilesState> => {
106
+ const context = getActionContext();
107
+ try {
108
+ const request = new plugins.domtools.plugins.typedrequest.TypedRequest<
109
+ interfaces.requests.IReq_UpdateTargetProfile
110
+ >('/typedrequest', 'updateTargetProfile');
111
+ const response = await request.fire({
112
+ identity: context.identity!,
113
+ id: dataArg.id,
114
+ name: dataArg.name,
115
+ description: dataArg.description,
116
+ domains: dataArg.domains,
117
+ targets: dataArg.targets,
118
+ routeRefs: dataArg.routeRefs,
119
+ allowRoutesByClientSourceIp: dataArg.allowRoutesByClientSourceIp,
120
+ });
121
+ if (!response.success) {
122
+ return {
123
+ ...statePartArg.getState()!,
124
+ error: response.message || 'Failed to update target profile',
125
+ };
126
+ }
127
+ return await actionContext!.dispatch(fetchTargetProfilesAction, null);
128
+ } catch (error: unknown) {
129
+ return {
130
+ ...statePartArg.getState()!,
131
+ error: error instanceof Error ? error.message : 'Failed to update target profile',
132
+ };
133
+ }
134
+ });
135
+
136
+ export const deleteTargetProfileAction = targetProfilesStatePart.createAction<{
137
+ id: string;
138
+ force?: boolean;
139
+ }>(async (statePartArg, dataArg, actionContext): Promise<ITargetProfilesState> => {
140
+ const context = getActionContext();
141
+ try {
142
+ const request = new plugins.domtools.plugins.typedrequest.TypedRequest<
143
+ interfaces.requests.IReq_DeleteTargetProfile
144
+ >('/typedrequest', 'deleteTargetProfile');
145
+ const response = await request.fire({
146
+ identity: context.identity!,
147
+ id: dataArg.id,
148
+ force: dataArg.force,
149
+ });
150
+ if (!response.success) {
151
+ return {
152
+ ...statePartArg.getState()!,
153
+ error: response.message || 'Failed to delete target profile',
154
+ };
155
+ }
156
+ return await actionContext!.dispatch(fetchTargetProfilesAction, null);
157
+ } catch (error: unknown) {
158
+ return {
159
+ ...statePartArg.getState()!,
160
+ error: error instanceof Error ? error.message : 'Failed to delete target profile',
161
+ };
162
+ }
163
+ });
164
+
@@ -0,0 +1,75 @@
1
+ import { appState } from './shared.js';
2
+ import { networkStatePart, fetchNetworkStatsAction } from './network.js';
3
+ import { certificateStatePart, fetchCertificateOverviewAction } from './certificates.js';
4
+
5
+ export interface IUiState {
6
+ activeView: string;
7
+ activeSubview: string | null;
8
+ sidebarCollapsed: boolean;
9
+ autoRefresh: boolean;
10
+ refreshInterval: number; // milliseconds
11
+ theme: 'light' | 'dark';
12
+ }
13
+
14
+ // Determine initial view from URL path
15
+ const getInitialView = (): string => {
16
+ const path = typeof window !== 'undefined' ? window.location.pathname : '/';
17
+ const validViews = ['overview', 'network', 'email', 'logs', 'access', 'security', 'domains'];
18
+ const segments = path.split('/').filter(Boolean);
19
+ const view = segments[0];
20
+ return validViews.includes(view) ? view : 'overview';
21
+ };
22
+
23
+ // Determine initial subview (second URL segment) from the path
24
+ const getInitialSubview = (): string | null => {
25
+ const path = typeof window !== 'undefined' ? window.location.pathname : '/';
26
+ const segments = path.split('/').filter(Boolean);
27
+ return segments[1] ?? null;
28
+ };
29
+
30
+ export const uiStatePart = await appState.getStatePart<IUiState>(
31
+ 'ui',
32
+ {
33
+ activeView: getInitialView(),
34
+ activeSubview: getInitialSubview(),
35
+ sidebarCollapsed: false,
36
+ autoRefresh: true,
37
+ refreshInterval: 1000, // 1 second
38
+ theme: 'light',
39
+ },
40
+ );
41
+
42
+ // Toggle Auto Refresh Action
43
+ export const toggleAutoRefreshAction = uiStatePart.createAction(async (statePartArg): Promise<IUiState> => {
44
+ const currentState = statePartArg.getState()!;
45
+ return {
46
+ ...currentState,
47
+ autoRefresh: !currentState.autoRefresh,
48
+ };
49
+ });
50
+
51
+ // Set Active View Action
52
+ export const setActiveViewAction = uiStatePart.createAction<string>(async (statePartArg, viewName): Promise<IUiState> => {
53
+ const currentState = statePartArg.getState()!;
54
+
55
+ // If switching to network view, ensure we fetch network data
56
+ if (viewName === 'network' && currentState.activeView !== 'network') {
57
+ setTimeout(() => {
58
+ networkStatePart.dispatchAction(fetchNetworkStatsAction, null);
59
+ }, 100);
60
+ }
61
+
62
+ // If switching to the Domains group, ensure we fetch certificate data
63
+ // (Certificates is a subview of Domains).
64
+ if (viewName === 'domains' && currentState.activeView !== 'domains') {
65
+ setTimeout(() => {
66
+ certificateStatePart.dispatchAction(fetchCertificateOverviewAction, null);
67
+ }, 100);
68
+ }
69
+
70
+ return {
71
+ ...currentState,
72
+ activeView: viewName,
73
+ };
74
+ });
75
+
@@ -0,0 +1,133 @@
1
+ import * as plugins from '../plugins.js';
2
+ import * as interfaces from '../../ts_interfaces/index.js';
3
+ import { appState } from './shared.js';
4
+ import { getActionContext } from './login.js';
5
+
6
+ // ============================================================================
7
+ // Users State (read-only list of OpsServer user accounts)
8
+ // ============================================================================
9
+
10
+ export interface IUser {
11
+ id: string;
12
+ username: string;
13
+ email?: string;
14
+ name?: string;
15
+ role: string;
16
+ status?: 'active' | 'disabled';
17
+ authSources?: Array<'local' | 'idp.global'>;
18
+ }
19
+
20
+ export interface IUsersState {
21
+ users: IUser[];
22
+ isLoading: boolean;
23
+ error: string | null;
24
+ lastUpdated: number;
25
+ }
26
+
27
+ export const usersStatePart = await appState.getStatePart<IUsersState>(
28
+ 'users',
29
+ {
30
+ users: [],
31
+ isLoading: false,
32
+ error: null,
33
+ lastUpdated: 0,
34
+ },
35
+ 'soft',
36
+ );
37
+
38
+ export const fetchUsersAction = usersStatePart.createAction(async (statePartArg): Promise<IUsersState> => {
39
+ const context = getActionContext();
40
+ const currentState = statePartArg.getState()!;
41
+ if (!context.identity) return currentState;
42
+
43
+ try {
44
+ const request = new plugins.domtools.plugins.typedrequest.TypedRequest<
45
+ interfaces.requests.IReq_ListUsers
46
+ >('/typedrequest', 'listUsers');
47
+
48
+ const response = await request.fire({
49
+ identity: context.identity,
50
+ });
51
+
52
+ return {
53
+ ...currentState,
54
+ users: response.users,
55
+ error: null,
56
+ lastUpdated: Date.now(),
57
+ };
58
+ } catch (error) {
59
+ return {
60
+ ...currentState,
61
+ error: error instanceof Error ? error.message : 'Failed to fetch users',
62
+ };
63
+ }
64
+ });
65
+
66
+ export const createUserAction = usersStatePart.createAction<{
67
+ email: string;
68
+ name?: string;
69
+ role: interfaces.requests.TUserManagementRole;
70
+ password: string;
71
+ enableIdpGlobalAuth?: boolean;
72
+ }>(async (statePartArg, dataArg, actionContext): Promise<IUsersState> => {
73
+ const context = getActionContext();
74
+ const currentState = statePartArg.getState()!;
75
+ if (!context.identity) return currentState;
76
+
77
+ try {
78
+ const request = new plugins.domtools.plugins.typedrequest.TypedRequest<
79
+ interfaces.requests.IReq_CreateUser
80
+ >('/typedrequest', 'createUser');
81
+
82
+ const response = await request.fire({
83
+ identity: context.identity,
84
+ email: dataArg.email,
85
+ name: dataArg.name,
86
+ role: dataArg.role,
87
+ password: dataArg.password,
88
+ enableIdpGlobalAuth: dataArg.enableIdpGlobalAuth,
89
+ });
90
+
91
+ if (!response.success) {
92
+ throw new Error(response.message || 'Failed to create user');
93
+ }
94
+
95
+ return await actionContext!.dispatch(fetchUsersAction, null);
96
+ } catch (error) {
97
+ return {
98
+ ...currentState,
99
+ error: error instanceof Error ? error.message : 'Failed to create user',
100
+ };
101
+ }
102
+ });
103
+
104
+ export const deleteUserAction = usersStatePart.createAction<string>(
105
+ async (statePartArg, userIdArg, actionContext): Promise<IUsersState> => {
106
+ const context = getActionContext();
107
+ const currentState = statePartArg.getState()!;
108
+ if (!context.identity) return currentState;
109
+
110
+ try {
111
+ const request = new plugins.domtools.plugins.typedrequest.TypedRequest<
112
+ interfaces.requests.IReq_DeleteUser
113
+ >('/typedrequest', 'deleteUser');
114
+
115
+ const response = await request.fire({
116
+ identity: context.identity,
117
+ id: userIdArg,
118
+ });
119
+
120
+ if (!response.success) {
121
+ throw new Error(response.message || 'Failed to delete user');
122
+ }
123
+
124
+ return await actionContext!.dispatch(fetchUsersAction, null);
125
+ } catch (error) {
126
+ return {
127
+ ...currentState,
128
+ error: error instanceof Error ? error.message : 'Failed to delete user',
129
+ };
130
+ }
131
+ },
132
+ );
133
+
@@ -0,0 +1,234 @@
1
+ import * as plugins from '../plugins.js';
2
+ import * as interfaces from '../../ts_interfaces/index.js';
3
+ import { appState } from './shared.js';
4
+ import { getActionContext } from './login.js';
5
+
6
+ // ============================================================================
7
+ // VPN State
8
+ // ============================================================================
9
+
10
+ export interface IVpnState {
11
+ clients: interfaces.data.IVpnClient[];
12
+ connectedClients: interfaces.data.IVpnConnectedClient[];
13
+ status: interfaces.data.IVpnServerStatus | null;
14
+ isLoading: boolean;
15
+ error: string | null;
16
+ lastUpdated: number;
17
+ /** WireGuard config shown after create/rotate (only shown once) */
18
+ newClientConfig: string | null;
19
+ }
20
+
21
+ export const vpnStatePart = await appState.getStatePart<IVpnState>(
22
+ 'vpn',
23
+ {
24
+ clients: [],
25
+ connectedClients: [],
26
+ status: null,
27
+ isLoading: false,
28
+ error: null,
29
+ lastUpdated: 0,
30
+ newClientConfig: null,
31
+ },
32
+ 'soft'
33
+ );
34
+
35
+ // ============================================================================
36
+ // VPN Actions
37
+ // ============================================================================
38
+
39
+ export const fetchVpnAction = vpnStatePart.createAction(async (statePartArg): Promise<IVpnState> => {
40
+ const context = getActionContext();
41
+ const currentState = statePartArg.getState()!;
42
+ if (!context.identity) return currentState;
43
+
44
+ try {
45
+ const clientsRequest = new plugins.domtools.plugins.typedrequest.TypedRequest<
46
+ interfaces.requests.IReq_GetVpnClients
47
+ >('/typedrequest', 'getVpnClients');
48
+
49
+ const statusRequest = new plugins.domtools.plugins.typedrequest.TypedRequest<
50
+ interfaces.requests.IReq_GetVpnStatus
51
+ >('/typedrequest', 'getVpnStatus');
52
+
53
+ const connectedRequest = new plugins.domtools.plugins.typedrequest.TypedRequest<
54
+ interfaces.requests.IReq_GetVpnConnectedClients
55
+ >('/typedrequest', 'getVpnConnectedClients');
56
+
57
+ const [clientsResponse, statusResponse, connectedResponse] = await Promise.all([
58
+ clientsRequest.fire({ identity: context.identity }),
59
+ statusRequest.fire({ identity: context.identity }),
60
+ connectedRequest.fire({ identity: context.identity }),
61
+ ]);
62
+
63
+ return {
64
+ ...currentState,
65
+ clients: clientsResponse.clients,
66
+ connectedClients: connectedResponse.connectedClients,
67
+ status: statusResponse.status,
68
+ isLoading: false,
69
+ error: null,
70
+ lastUpdated: Date.now(),
71
+ };
72
+ } catch (error) {
73
+ return {
74
+ ...currentState,
75
+ isLoading: false,
76
+ error: error instanceof Error ? error.message : 'Failed to fetch VPN data',
77
+ };
78
+ }
79
+ });
80
+
81
+ export const createVpnClientAction = vpnStatePart.createAction<{
82
+ clientId: string;
83
+ targetProfileIds?: string[];
84
+ description?: string;
85
+
86
+ destinationAllowList?: string[];
87
+ destinationBlockList?: string[];
88
+ useHostIp?: boolean;
89
+ useDhcp?: boolean;
90
+ staticIp?: string;
91
+ forceVlan?: boolean;
92
+ vlanId?: number;
93
+ }>(async (statePartArg, dataArg, actionContext): Promise<IVpnState> => {
94
+ const context = getActionContext();
95
+ const currentState = statePartArg.getState()!;
96
+
97
+ try {
98
+ const request = new plugins.domtools.plugins.typedrequest.TypedRequest<
99
+ interfaces.requests.IReq_CreateVpnClient
100
+ >('/typedrequest', 'createVpnClient');
101
+
102
+ const response = await request.fire({
103
+ identity: context.identity!,
104
+ clientId: dataArg.clientId,
105
+ targetProfileIds: dataArg.targetProfileIds,
106
+ description: dataArg.description,
107
+
108
+ destinationAllowList: dataArg.destinationAllowList,
109
+ destinationBlockList: dataArg.destinationBlockList,
110
+ useHostIp: dataArg.useHostIp,
111
+ useDhcp: dataArg.useDhcp,
112
+ staticIp: dataArg.staticIp,
113
+ forceVlan: dataArg.forceVlan,
114
+ vlanId: dataArg.vlanId,
115
+ });
116
+
117
+ if (!response.success) {
118
+ return { ...currentState, error: response.message || 'Failed to create client' };
119
+ }
120
+
121
+ const refreshed = await actionContext!.dispatch(fetchVpnAction, null);
122
+ return {
123
+ ...refreshed,
124
+ newClientConfig: response.wireguardConfig || null,
125
+ };
126
+ } catch (error: unknown) {
127
+ return {
128
+ ...currentState,
129
+ error: error instanceof Error ? error.message : 'Failed to create VPN client',
130
+ };
131
+ }
132
+ });
133
+
134
+ export const deleteVpnClientAction = vpnStatePart.createAction<string>(
135
+ async (statePartArg, clientId, actionContext): Promise<IVpnState> => {
136
+ const context = getActionContext();
137
+ const currentState = statePartArg.getState()!;
138
+
139
+ try {
140
+ const request = new plugins.domtools.plugins.typedrequest.TypedRequest<
141
+ interfaces.requests.IReq_DeleteVpnClient
142
+ >('/typedrequest', 'deleteVpnClient');
143
+
144
+ await request.fire({ identity: context.identity!, clientId });
145
+ return await actionContext!.dispatch(fetchVpnAction, null);
146
+ } catch (error: unknown) {
147
+ return {
148
+ ...currentState,
149
+ error: error instanceof Error ? error.message : 'Failed to delete VPN client',
150
+ };
151
+ }
152
+ },
153
+ );
154
+
155
+ export const toggleVpnClientAction = vpnStatePart.createAction<{
156
+ clientId: string;
157
+ enabled: boolean;
158
+ }>(async (statePartArg, dataArg, actionContext): Promise<IVpnState> => {
159
+ const context = getActionContext();
160
+ const currentState = statePartArg.getState()!;
161
+
162
+ try {
163
+ const method = dataArg.enabled ? 'enableVpnClient' : 'disableVpnClient';
164
+ type TReq = interfaces.requests.IReq_EnableVpnClient | interfaces.requests.IReq_DisableVpnClient;
165
+ const request = new plugins.domtools.plugins.typedrequest.TypedRequest<TReq>(
166
+ '/typedrequest', method,
167
+ );
168
+
169
+ await request.fire({ identity: context.identity!, clientId: dataArg.clientId });
170
+ return await actionContext!.dispatch(fetchVpnAction, null);
171
+ } catch (error: unknown) {
172
+ return {
173
+ ...currentState,
174
+ error: error instanceof Error ? error.message : 'Failed to toggle VPN client',
175
+ };
176
+ }
177
+ });
178
+
179
+ export const updateVpnClientAction = vpnStatePart.createAction<{
180
+ clientId: string;
181
+ description?: string;
182
+ targetProfileIds?: string[];
183
+
184
+ destinationAllowList?: string[];
185
+ destinationBlockList?: string[];
186
+ useHostIp?: boolean;
187
+ useDhcp?: boolean;
188
+ staticIp?: string;
189
+ forceVlan?: boolean;
190
+ vlanId?: number;
191
+ }>(async (statePartArg, dataArg, actionContext): Promise<IVpnState> => {
192
+ const context = getActionContext();
193
+ const currentState = statePartArg.getState()!;
194
+
195
+ try {
196
+ const request = new plugins.domtools.plugins.typedrequest.TypedRequest<
197
+ interfaces.requests.IReq_UpdateVpnClient
198
+ >('/typedrequest', 'updateVpnClient');
199
+
200
+ const response = await request.fire({
201
+ identity: context.identity!,
202
+ clientId: dataArg.clientId,
203
+ description: dataArg.description,
204
+ targetProfileIds: dataArg.targetProfileIds,
205
+
206
+ destinationAllowList: dataArg.destinationAllowList,
207
+ destinationBlockList: dataArg.destinationBlockList,
208
+ useHostIp: dataArg.useHostIp,
209
+ useDhcp: dataArg.useDhcp,
210
+ staticIp: dataArg.staticIp,
211
+ forceVlan: dataArg.forceVlan,
212
+ vlanId: dataArg.vlanId,
213
+ });
214
+
215
+ if (!response.success) {
216
+ return { ...currentState, error: response.message || 'Failed to update client' };
217
+ }
218
+
219
+ return await actionContext!.dispatch(fetchVpnAction, null);
220
+ } catch (error: unknown) {
221
+ return {
222
+ ...currentState,
223
+ error: error instanceof Error ? error.message : 'Failed to update VPN client',
224
+ };
225
+ }
226
+ });
227
+
228
+ export const clearNewClientConfigAction = vpnStatePart.createAction(
229
+ async (statePartArg): Promise<IVpnState> => {
230
+ return { ...statePartArg.getState()!, newClientConfig: null };
231
+ },
232
+ );
233
+
234
+ // ============================================================================