@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.
- package/deno.json +1 -1
- package/dist_serve/bundle.js +768 -768
- package/dist_ts/00_commitinfo_data.js +1 -1
- package/dist_ts/acme/classes.smartacme-lifecycle.d.ts +25 -0
- package/dist_ts/acme/classes.smartacme-lifecycle.js +144 -0
- package/dist_ts/acme/index.d.ts +1 -0
- package/dist_ts/acme/index.js +2 -1
- package/dist_ts/classes.dcrouter.d.ts +21 -139
- package/dist_ts/classes.dcrouter.js +71 -1585
- package/dist_ts/dns/classes.dns-server-runtime.d.ts +37 -0
- package/dist_ts/dns/classes.dns-server-runtime.js +449 -0
- package/dist_ts/dns/index.d.ts +1 -0
- package/dist_ts/dns/index.js +2 -1
- package/dist_ts/email/classes.accepted-email-spool.d.ts +55 -0
- package/dist_ts/email/classes.accepted-email-spool.js +345 -0
- package/dist_ts/email/classes.email-route-builder.d.ts +28 -0
- package/dist_ts/email/classes.email-route-builder.js +260 -0
- package/dist_ts/email/index.d.ts +2 -0
- package/dist_ts/email/index.js +3 -1
- package/dist_ts/opsserver/handlers/gatewayclient.handler.js +10 -8
- package/dist_ts/remoteingress/classes.hub-lifecycle.d.ts +27 -0
- package/dist_ts/remoteingress/classes.hub-lifecycle.js +241 -0
- package/dist_ts/remoteingress/classes.remoteingress-manager.d.ts +1 -2
- package/dist_ts/remoteingress/index.d.ts +1 -0
- package/dist_ts/remoteingress/index.js +2 -1
- package/dist_ts/security/classes.route-policy-augmenter.d.ts +22 -0
- package/dist_ts/security/classes.route-policy-augmenter.js +120 -0
- package/dist_ts/security/index.d.ts +1 -0
- package/dist_ts/security/index.js +2 -1
- package/dist_ts/vpn/classes.vpn-access-resolver.d.ts +34 -0
- package/dist_ts/vpn/classes.vpn-access-resolver.js +101 -0
- package/dist_ts/vpn/index.d.ts +1 -0
- package/dist_ts/vpn/index.js +2 -1
- package/dist_ts_migrations/index.js +92 -9
- package/dist_ts_web/00_commitinfo_data.js +1 -1
- package/dist_ts_web/appstate/acme.d.ts +17 -0
- package/dist_ts_web/appstate/acme.js +64 -0
- package/dist_ts_web/appstate/certificates.d.ts +37 -0
- package/dist_ts_web/appstate/certificates.js +107 -0
- package/dist_ts_web/appstate/config.d.ts +9 -0
- package/dist_ts_web/appstate/config.js +35 -0
- package/dist_ts_web/appstate/domains.d.ts +80 -0
- package/dist_ts_web/appstate/domains.js +324 -0
- package/dist_ts_web/appstate/email-domains.d.ts +25 -0
- package/dist_ts_web/appstate/email-domains.js +104 -0
- package/dist_ts_web/appstate/email-ops.d.ts +10 -0
- package/dist_ts_web/appstate/email-ops.js +40 -0
- package/dist_ts_web/appstate/login.d.ts +30 -0
- package/dist_ts_web/appstate/login.js +83 -0
- package/dist_ts_web/appstate/logs.d.ts +16 -0
- package/dist_ts_web/appstate/logs.js +27 -0
- package/dist_ts_web/appstate/network.d.ts +50 -0
- package/dist_ts_web/appstate/network.js +122 -0
- package/dist_ts_web/appstate/profiles-targets.d.ts +45 -0
- package/dist_ts_web/appstate/profiles-targets.js +173 -0
- package/dist_ts_web/appstate/remoteingress.d.ts +47 -0
- package/dist_ts_web/appstate/remoteingress.js +204 -0
- package/dist_ts_web/appstate/routes.d.ts +76 -0
- package/dist_ts_web/appstate/routes.js +316 -0
- package/dist_ts_web/appstate/runtime.d.ts +1 -0
- package/dist_ts_web/appstate/runtime.js +276 -0
- package/dist_ts_web/appstate/security.d.ts +29 -0
- package/dist_ts_web/appstate/security.js +167 -0
- package/dist_ts_web/appstate/shared.d.ts +3 -0
- package/dist_ts_web/appstate/shared.js +13 -0
- package/dist_ts_web/appstate/stats.d.ts +15 -0
- package/dist_ts_web/appstate/stats.js +59 -0
- package/dist_ts_web/appstate/target-profiles.d.ts +37 -0
- package/dist_ts_web/appstate/target-profiles.js +118 -0
- package/dist_ts_web/appstate/ui.d.ts +11 -0
- package/dist_ts_web/appstate/ui.js +55 -0
- package/dist_ts_web/appstate/users.d.ts +27 -0
- package/dist_ts_web/appstate/users.js +85 -0
- package/dist_ts_web/appstate/vpn.d.ts +44 -0
- package/dist_ts_web/appstate/vpn.js +148 -0
- package/dist_ts_web/appstate.d.ts +20 -568
- package/dist_ts_web/appstate.js +24 -2418
- package/package.json +1 -1
- package/ts/00_commitinfo_data.ts +1 -1
- package/ts/acme/classes.smartacme-lifecycle.ts +155 -0
- package/ts/acme/index.ts +1 -0
- package/ts/classes.dcrouter.ts +118 -1919
- package/ts/dns/classes.dns-server-runtime.ts +525 -0
- package/ts/dns/index.ts +1 -0
- package/ts/email/classes.accepted-email-spool.ts +434 -0
- package/ts/email/classes.email-route-builder.ts +312 -0
- package/ts/email/index.ts +2 -0
- package/ts/opsserver/handlers/gatewayclient.handler.ts +9 -7
- package/ts/remoteingress/classes.hub-lifecycle.ts +278 -0
- package/ts/remoteingress/classes.remoteingress-manager.ts +1 -1
- package/ts/remoteingress/index.ts +1 -0
- package/ts/security/classes.route-policy-augmenter.ts +140 -0
- package/ts/security/index.ts +1 -0
- package/ts/vpn/classes.vpn-access-resolver.ts +126 -0
- package/ts/vpn/index.ts +1 -0
- package/ts_web/00_commitinfo_data.ts +1 -1
- package/ts_web/appstate/acme.ts +93 -0
- package/ts_web/appstate/certificates.ts +159 -0
- package/ts_web/appstate/config.ts +49 -0
- package/ts_web/appstate/domains.ts +429 -0
- package/ts_web/appstate/email-domains.ts +155 -0
- package/ts_web/appstate/email-ops.ts +57 -0
- package/ts_web/appstate/login.ts +128 -0
- package/ts_web/appstate/logs.ts +50 -0
- package/ts_web/appstate/network.ts +161 -0
- package/ts_web/appstate/profiles-targets.ts +240 -0
- package/ts_web/appstate/remoteingress.ts +300 -0
- package/ts_web/appstate/routes.ts +447 -0
- package/ts_web/appstate/runtime.ts +308 -0
- package/ts_web/appstate/security.ts +229 -0
- package/ts_web/appstate/shared.ts +15 -0
- package/ts_web/appstate/stats.ts +79 -0
- package/ts_web/appstate/target-profiles.ts +164 -0
- package/ts_web/appstate/ui.ts +75 -0
- package/ts_web/appstate/users.ts +133 -0
- package/ts_web/appstate/vpn.ts +234 -0
- 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
|
+
// ============================================================================
|