@serve.zone/dcrouter 12.9.4 → 13.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist_serve/bundle.js +1030 -923
- package/dist_ts/00_commitinfo_data.js +1 -1
- package/dist_ts/classes.dcrouter.d.ts +5 -4
- package/dist_ts/classes.dcrouter.js +25 -49
- package/dist_ts/config/classes.reference-resolver.d.ts +7 -7
- package/dist_ts/config/classes.reference-resolver.js +27 -27
- package/dist_ts/config/classes.route-config-manager.d.ts +2 -2
- package/dist_ts/config/classes.route-config-manager.js +24 -16
- package/dist_ts/config/classes.target-profile-manager.d.ts +63 -0
- package/dist_ts/config/classes.target-profile-manager.js +295 -0
- package/dist_ts/config/index.d.ts +1 -0
- package/dist_ts/config/index.js +2 -1
- package/dist_ts/db/documents/{classes.security-profile.doc.d.ts → classes.source-profile.doc.d.ts} +4 -4
- package/dist_ts/db/documents/{classes.security-profile.doc.js → classes.source-profile.doc.js} +9 -9
- package/dist_ts/db/documents/classes.target-profile.doc.d.ts +17 -0
- package/dist_ts/db/documents/classes.target-profile.doc.js +124 -0
- package/dist_ts/db/documents/classes.vpn-client.doc.d.ts +1 -1
- package/dist_ts/db/documents/classes.vpn-client.doc.js +8 -8
- package/dist_ts/db/documents/index.d.ts +2 -1
- package/dist_ts/db/documents/index.js +3 -2
- package/dist_ts/opsserver/classes.opsserver.d.ts +2 -1
- package/dist_ts/opsserver/classes.opsserver.js +5 -3
- package/dist_ts/opsserver/handlers/index.d.ts +2 -1
- package/dist_ts/opsserver/handlers/index.js +3 -2
- package/dist_ts/opsserver/handlers/{security-profile.handler.d.ts → source-profile.handler.d.ts} +1 -1
- package/dist_ts/opsserver/handlers/{security-profile.handler.js → source-profile.handler.js} +20 -20
- package/dist_ts/opsserver/handlers/target-profile.handler.d.ts +10 -0
- package/dist_ts/opsserver/handlers/target-profile.handler.js +115 -0
- package/dist_ts/opsserver/handlers/vpn.handler.js +5 -5
- package/dist_ts/vpn/classes.vpn-manager.d.ts +6 -10
- package/dist_ts/vpn/classes.vpn-manager.js +11 -34
- package/dist_ts_interfaces/data/index.d.ts +1 -0
- package/dist_ts_interfaces/data/index.js +2 -1
- package/dist_ts_interfaces/data/remoteingress.d.ts +4 -15
- package/dist_ts_interfaces/data/route-management.d.ts +9 -6
- package/dist_ts_interfaces/data/target-profile.d.ts +28 -0
- package/dist_ts_interfaces/data/target-profile.js +2 -0
- package/dist_ts_interfaces/data/vpn.d.ts +2 -1
- package/dist_ts_interfaces/requests/index.d.ts +2 -1
- package/dist_ts_interfaces/requests/index.js +3 -2
- package/dist_ts_interfaces/requests/{security-profiles.d.ts → source-profiles.d.ts} +21 -21
- package/dist_ts_interfaces/requests/source-profiles.js +2 -0
- package/dist_ts_interfaces/requests/target-profiles.d.ts +103 -0
- package/dist_ts_interfaces/requests/target-profiles.js +2 -0
- package/dist_ts_interfaces/requests/vpn.d.ts +2 -2
- package/dist_ts_web/00_commitinfo_data.js +1 -1
- package/dist_ts_web/appstate.d.ts +36 -3
- package/dist_ts_web/appstate.js +127 -10
- package/dist_ts_web/elements/index.d.ts +2 -1
- package/dist_ts_web/elements/index.js +3 -2
- package/dist_ts_web/elements/ops-dashboard.js +10 -4
- package/dist_ts_web/elements/ops-view-routes.js +120 -10
- package/dist_ts_web/elements/{ops-view-securityprofiles.d.ts → ops-view-sourceprofiles.d.ts} +2 -2
- package/dist_ts_web/elements/{ops-view-securityprofiles.js → ops-view-sourceprofiles.js} +12 -12
- package/dist_ts_web/elements/ops-view-targetprofiles.d.ts +19 -0
- package/dist_ts_web/elements/ops-view-targetprofiles.js +412 -0
- package/dist_ts_web/elements/ops-view-vpn.js +13 -13
- package/dist_ts_web/router.d.ts +1 -1
- package/dist_ts_web/router.js +2 -2
- package/package.json +3 -3
- package/ts/00_commitinfo_data.ts +1 -1
- package/ts/classes.dcrouter.ts +33 -50
- package/ts/config/classes.reference-resolver.ts +34 -34
- package/ts/config/classes.route-config-manager.ts +21 -13
- package/ts/config/classes.target-profile-manager.ts +348 -0
- package/ts/config/index.ts +2 -1
- package/ts/db/documents/{classes.security-profile.doc.ts → classes.source-profile.doc.ts} +7 -7
- package/ts/db/documents/classes.target-profile.doc.ts +52 -0
- package/ts/db/documents/classes.vpn-client.doc.ts +1 -1
- package/ts/db/documents/index.ts +2 -1
- package/ts/opsserver/classes.opsserver.ts +4 -2
- package/ts/opsserver/handlers/index.ts +2 -1
- package/ts/opsserver/handlers/{security-profile.handler.ts → source-profile.handler.ts} +25 -25
- package/ts/opsserver/handlers/target-profile.handler.ts +155 -0
- package/ts/opsserver/handlers/vpn.handler.ts +4 -4
- package/ts/vpn/classes.vpn-manager.ts +14 -38
- package/ts_web/00_commitinfo_data.ts +1 -1
- package/ts_web/appstate.ts +180 -17
- package/ts_web/elements/index.ts +2 -1
- package/ts_web/elements/ops-dashboard.ts +9 -3
- package/ts_web/elements/ops-view-routes.ts +118 -9
- package/ts_web/elements/{ops-view-securityprofiles.ts → ops-view-sourceprofiles.ts} +13 -13
- package/ts_web/elements/ops-view-targetprofiles.ts +379 -0
- package/ts_web/elements/ops-view-vpn.ts +12 -12
- package/ts_web/router.ts +1 -1
- package/dist_ts_interfaces/requests/security-profiles.js +0 -2
package/ts_web/appstate.ts
CHANGED
|
@@ -116,7 +116,7 @@ export const configStatePart = await appState.getStatePart<IConfigState>(
|
|
|
116
116
|
// Determine initial view from URL path
|
|
117
117
|
const getInitialView = (): string => {
|
|
118
118
|
const path = typeof window !== 'undefined' ? window.location.pathname : '/';
|
|
119
|
-
const validViews = ['overview', 'network', 'emails', 'logs', 'routes', 'apitokens', 'configuration', 'security', 'certificates', 'remoteingress', '
|
|
119
|
+
const validViews = ['overview', 'network', 'emails', 'logs', 'routes', 'apitokens', 'configuration', 'security', 'certificates', 'remoteingress', 'sourceprofiles', 'networktargets', 'targetprofiles'];
|
|
120
120
|
const segments = path.split('/').filter(Boolean);
|
|
121
121
|
const view = segments[0];
|
|
122
122
|
return validViews.includes(view) ? view : 'overview';
|
|
@@ -459,12 +459,19 @@ export const setActiveViewAction = uiStatePart.createAction<string>(async (state
|
|
|
459
459
|
}
|
|
460
460
|
|
|
461
461
|
// If switching to security profiles or network targets views, fetch profiles/targets data
|
|
462
|
-
if ((viewName === '
|
|
462
|
+
if ((viewName === 'sourceprofiles' || viewName === 'networktargets') && currentState.activeView !== viewName) {
|
|
463
463
|
setTimeout(() => {
|
|
464
464
|
profilesTargetsStatePart.dispatchAction(fetchProfilesAndTargetsAction, null);
|
|
465
465
|
}, 100);
|
|
466
466
|
}
|
|
467
467
|
|
|
468
|
+
// If switching to target profiles view, fetch target profiles data
|
|
469
|
+
if (viewName === 'targetprofiles' && currentState.activeView !== viewName) {
|
|
470
|
+
setTimeout(() => {
|
|
471
|
+
targetProfilesStatePart.dispatchAction(fetchTargetProfilesAction, null);
|
|
472
|
+
}, 100);
|
|
473
|
+
}
|
|
474
|
+
|
|
468
475
|
return {
|
|
469
476
|
...currentState,
|
|
470
477
|
activeView: viewName,
|
|
@@ -1006,7 +1013,7 @@ export const fetchVpnAction = vpnStatePart.createAction(async (statePartArg): Pr
|
|
|
1006
1013
|
|
|
1007
1014
|
export const createVpnClientAction = vpnStatePart.createAction<{
|
|
1008
1015
|
clientId: string;
|
|
1009
|
-
|
|
1016
|
+
targetProfileIds?: string[];
|
|
1010
1017
|
description?: string;
|
|
1011
1018
|
forceDestinationSmartproxy?: boolean;
|
|
1012
1019
|
destinationAllowList?: string[];
|
|
@@ -1028,7 +1035,7 @@ export const createVpnClientAction = vpnStatePart.createAction<{
|
|
|
1028
1035
|
const response = await request.fire({
|
|
1029
1036
|
identity: context.identity!,
|
|
1030
1037
|
clientId: dataArg.clientId,
|
|
1031
|
-
|
|
1038
|
+
targetProfileIds: dataArg.targetProfileIds,
|
|
1032
1039
|
description: dataArg.description,
|
|
1033
1040
|
forceDestinationSmartproxy: dataArg.forceDestinationSmartproxy,
|
|
1034
1041
|
destinationAllowList: dataArg.destinationAllowList,
|
|
@@ -1105,7 +1112,7 @@ export const toggleVpnClientAction = vpnStatePart.createAction<{
|
|
|
1105
1112
|
export const updateVpnClientAction = vpnStatePart.createAction<{
|
|
1106
1113
|
clientId: string;
|
|
1107
1114
|
description?: string;
|
|
1108
|
-
|
|
1115
|
+
targetProfileIds?: string[];
|
|
1109
1116
|
forceDestinationSmartproxy?: boolean;
|
|
1110
1117
|
destinationAllowList?: string[];
|
|
1111
1118
|
destinationBlockList?: string[];
|
|
@@ -1127,7 +1134,7 @@ export const updateVpnClientAction = vpnStatePart.createAction<{
|
|
|
1127
1134
|
identity: context.identity!,
|
|
1128
1135
|
clientId: dataArg.clientId,
|
|
1129
1136
|
description: dataArg.description,
|
|
1130
|
-
|
|
1137
|
+
targetProfileIds: dataArg.targetProfileIds,
|
|
1131
1138
|
forceDestinationSmartproxy: dataArg.forceDestinationSmartproxy,
|
|
1132
1139
|
destinationAllowList: dataArg.destinationAllowList,
|
|
1133
1140
|
destinationBlockList: dataArg.destinationBlockList,
|
|
@@ -1158,11 +1165,167 @@ export const clearNewClientConfigAction = vpnStatePart.createAction(
|
|
|
1158
1165
|
);
|
|
1159
1166
|
|
|
1160
1167
|
// ============================================================================
|
|
1161
|
-
//
|
|
1168
|
+
// Target Profiles State
|
|
1169
|
+
// ============================================================================
|
|
1170
|
+
|
|
1171
|
+
export interface ITargetProfilesState {
|
|
1172
|
+
profiles: interfaces.data.ITargetProfile[];
|
|
1173
|
+
isLoading: boolean;
|
|
1174
|
+
error: string | null;
|
|
1175
|
+
lastUpdated: number;
|
|
1176
|
+
}
|
|
1177
|
+
|
|
1178
|
+
export const targetProfilesStatePart = await appState.getStatePart<ITargetProfilesState>(
|
|
1179
|
+
'targetProfiles',
|
|
1180
|
+
{
|
|
1181
|
+
profiles: [],
|
|
1182
|
+
isLoading: false,
|
|
1183
|
+
error: null,
|
|
1184
|
+
lastUpdated: 0,
|
|
1185
|
+
},
|
|
1186
|
+
'soft'
|
|
1187
|
+
);
|
|
1188
|
+
|
|
1189
|
+
// ============================================================================
|
|
1190
|
+
// Target Profiles Actions
|
|
1191
|
+
// ============================================================================
|
|
1192
|
+
|
|
1193
|
+
export const fetchTargetProfilesAction = targetProfilesStatePart.createAction(
|
|
1194
|
+
async (statePartArg): Promise<ITargetProfilesState> => {
|
|
1195
|
+
const context = getActionContext();
|
|
1196
|
+
const currentState = statePartArg.getState()!;
|
|
1197
|
+
if (!context.identity) return currentState;
|
|
1198
|
+
|
|
1199
|
+
try {
|
|
1200
|
+
const request = new plugins.domtools.plugins.typedrequest.TypedRequest<
|
|
1201
|
+
interfaces.requests.IReq_GetTargetProfiles
|
|
1202
|
+
>('/typedrequest', 'getTargetProfiles');
|
|
1203
|
+
|
|
1204
|
+
const response = await request.fire({ identity: context.identity });
|
|
1205
|
+
|
|
1206
|
+
return {
|
|
1207
|
+
profiles: response.profiles,
|
|
1208
|
+
isLoading: false,
|
|
1209
|
+
error: null,
|
|
1210
|
+
lastUpdated: Date.now(),
|
|
1211
|
+
};
|
|
1212
|
+
} catch (error) {
|
|
1213
|
+
return {
|
|
1214
|
+
...currentState,
|
|
1215
|
+
isLoading: false,
|
|
1216
|
+
error: error instanceof Error ? error.message : 'Failed to fetch target profiles',
|
|
1217
|
+
};
|
|
1218
|
+
}
|
|
1219
|
+
}
|
|
1220
|
+
);
|
|
1221
|
+
|
|
1222
|
+
export const createTargetProfileAction = targetProfilesStatePart.createAction<{
|
|
1223
|
+
name: string;
|
|
1224
|
+
description?: string;
|
|
1225
|
+
domains?: string[];
|
|
1226
|
+
targets?: Array<{ host: string; port: number }>;
|
|
1227
|
+
routeRefs?: string[];
|
|
1228
|
+
}>(async (statePartArg, dataArg, actionContext): Promise<ITargetProfilesState> => {
|
|
1229
|
+
const context = getActionContext();
|
|
1230
|
+
try {
|
|
1231
|
+
const request = new plugins.domtools.plugins.typedrequest.TypedRequest<
|
|
1232
|
+
interfaces.requests.IReq_CreateTargetProfile
|
|
1233
|
+
>('/typedrequest', 'createTargetProfile');
|
|
1234
|
+
const response = await request.fire({
|
|
1235
|
+
identity: context.identity!,
|
|
1236
|
+
name: dataArg.name,
|
|
1237
|
+
description: dataArg.description,
|
|
1238
|
+
domains: dataArg.domains,
|
|
1239
|
+
targets: dataArg.targets,
|
|
1240
|
+
routeRefs: dataArg.routeRefs,
|
|
1241
|
+
});
|
|
1242
|
+
if (!response.success) {
|
|
1243
|
+
return {
|
|
1244
|
+
...statePartArg.getState()!,
|
|
1245
|
+
error: response.message || 'Failed to create target profile',
|
|
1246
|
+
};
|
|
1247
|
+
}
|
|
1248
|
+
return await actionContext!.dispatch(fetchTargetProfilesAction, null);
|
|
1249
|
+
} catch (error: unknown) {
|
|
1250
|
+
return {
|
|
1251
|
+
...statePartArg.getState()!,
|
|
1252
|
+
error: error instanceof Error ? error.message : 'Failed to create target profile',
|
|
1253
|
+
};
|
|
1254
|
+
}
|
|
1255
|
+
});
|
|
1256
|
+
|
|
1257
|
+
export const updateTargetProfileAction = targetProfilesStatePart.createAction<{
|
|
1258
|
+
id: string;
|
|
1259
|
+
name?: string;
|
|
1260
|
+
description?: string;
|
|
1261
|
+
domains?: string[];
|
|
1262
|
+
targets?: Array<{ host: string; port: number }>;
|
|
1263
|
+
routeRefs?: string[];
|
|
1264
|
+
}>(async (statePartArg, dataArg, actionContext): Promise<ITargetProfilesState> => {
|
|
1265
|
+
const context = getActionContext();
|
|
1266
|
+
try {
|
|
1267
|
+
const request = new plugins.domtools.plugins.typedrequest.TypedRequest<
|
|
1268
|
+
interfaces.requests.IReq_UpdateTargetProfile
|
|
1269
|
+
>('/typedrequest', 'updateTargetProfile');
|
|
1270
|
+
const response = await request.fire({
|
|
1271
|
+
identity: context.identity!,
|
|
1272
|
+
id: dataArg.id,
|
|
1273
|
+
name: dataArg.name,
|
|
1274
|
+
description: dataArg.description,
|
|
1275
|
+
domains: dataArg.domains,
|
|
1276
|
+
targets: dataArg.targets,
|
|
1277
|
+
routeRefs: dataArg.routeRefs,
|
|
1278
|
+
});
|
|
1279
|
+
if (!response.success) {
|
|
1280
|
+
return {
|
|
1281
|
+
...statePartArg.getState()!,
|
|
1282
|
+
error: response.message || 'Failed to update target profile',
|
|
1283
|
+
};
|
|
1284
|
+
}
|
|
1285
|
+
return await actionContext!.dispatch(fetchTargetProfilesAction, null);
|
|
1286
|
+
} catch (error: unknown) {
|
|
1287
|
+
return {
|
|
1288
|
+
...statePartArg.getState()!,
|
|
1289
|
+
error: error instanceof Error ? error.message : 'Failed to update target profile',
|
|
1290
|
+
};
|
|
1291
|
+
}
|
|
1292
|
+
});
|
|
1293
|
+
|
|
1294
|
+
export const deleteTargetProfileAction = targetProfilesStatePart.createAction<{
|
|
1295
|
+
id: string;
|
|
1296
|
+
force?: boolean;
|
|
1297
|
+
}>(async (statePartArg, dataArg, actionContext): Promise<ITargetProfilesState> => {
|
|
1298
|
+
const context = getActionContext();
|
|
1299
|
+
try {
|
|
1300
|
+
const request = new plugins.domtools.plugins.typedrequest.TypedRequest<
|
|
1301
|
+
interfaces.requests.IReq_DeleteTargetProfile
|
|
1302
|
+
>('/typedrequest', 'deleteTargetProfile');
|
|
1303
|
+
const response = await request.fire({
|
|
1304
|
+
identity: context.identity!,
|
|
1305
|
+
id: dataArg.id,
|
|
1306
|
+
force: dataArg.force,
|
|
1307
|
+
});
|
|
1308
|
+
if (!response.success) {
|
|
1309
|
+
return {
|
|
1310
|
+
...statePartArg.getState()!,
|
|
1311
|
+
error: response.message || 'Failed to delete target profile',
|
|
1312
|
+
};
|
|
1313
|
+
}
|
|
1314
|
+
return await actionContext!.dispatch(fetchTargetProfilesAction, null);
|
|
1315
|
+
} catch (error: unknown) {
|
|
1316
|
+
return {
|
|
1317
|
+
...statePartArg.getState()!,
|
|
1318
|
+
error: error instanceof Error ? error.message : 'Failed to delete target profile',
|
|
1319
|
+
};
|
|
1320
|
+
}
|
|
1321
|
+
});
|
|
1322
|
+
|
|
1323
|
+
// ============================================================================
|
|
1324
|
+
// Source Profiles & Network Targets State
|
|
1162
1325
|
// ============================================================================
|
|
1163
1326
|
|
|
1164
1327
|
export interface IProfilesTargetsState {
|
|
1165
|
-
profiles: interfaces.data.
|
|
1328
|
+
profiles: interfaces.data.ISourceProfile[];
|
|
1166
1329
|
targets: interfaces.data.INetworkTarget[];
|
|
1167
1330
|
isLoading: boolean;
|
|
1168
1331
|
error: string | null;
|
|
@@ -1182,7 +1345,7 @@ export const profilesTargetsStatePart = await appState.getStatePart<IProfilesTar
|
|
|
1182
1345
|
);
|
|
1183
1346
|
|
|
1184
1347
|
// ============================================================================
|
|
1185
|
-
//
|
|
1348
|
+
// Source Profiles & Network Targets Actions
|
|
1186
1349
|
// ============================================================================
|
|
1187
1350
|
|
|
1188
1351
|
export const fetchProfilesAndTargetsAction = profilesTargetsStatePart.createAction(
|
|
@@ -1193,8 +1356,8 @@ export const fetchProfilesAndTargetsAction = profilesTargetsStatePart.createActi
|
|
|
1193
1356
|
|
|
1194
1357
|
try {
|
|
1195
1358
|
const profilesRequest = new plugins.domtools.plugins.typedrequest.TypedRequest<
|
|
1196
|
-
interfaces.requests.
|
|
1197
|
-
>('/typedrequest', '
|
|
1359
|
+
interfaces.requests.IReq_GetSourceProfiles
|
|
1360
|
+
>('/typedrequest', 'getSourceProfiles');
|
|
1198
1361
|
|
|
1199
1362
|
const targetsRequest = new plugins.domtools.plugins.typedrequest.TypedRequest<
|
|
1200
1363
|
interfaces.requests.IReq_GetNetworkTargets
|
|
@@ -1231,8 +1394,8 @@ export const createProfileAction = profilesTargetsStatePart.createAction<{
|
|
|
1231
1394
|
const context = getActionContext();
|
|
1232
1395
|
try {
|
|
1233
1396
|
const request = new plugins.domtools.plugins.typedrequest.TypedRequest<
|
|
1234
|
-
interfaces.requests.
|
|
1235
|
-
>('/typedrequest', '
|
|
1397
|
+
interfaces.requests.IReq_CreateSourceProfile
|
|
1398
|
+
>('/typedrequest', 'createSourceProfile');
|
|
1236
1399
|
await request.fire({
|
|
1237
1400
|
identity: context.identity!,
|
|
1238
1401
|
name: dataArg.name,
|
|
@@ -1259,8 +1422,8 @@ export const updateProfileAction = profilesTargetsStatePart.createAction<{
|
|
|
1259
1422
|
const context = getActionContext();
|
|
1260
1423
|
try {
|
|
1261
1424
|
const request = new plugins.domtools.plugins.typedrequest.TypedRequest<
|
|
1262
|
-
interfaces.requests.
|
|
1263
|
-
>('/typedrequest', '
|
|
1425
|
+
interfaces.requests.IReq_UpdateSourceProfile
|
|
1426
|
+
>('/typedrequest', 'updateSourceProfile');
|
|
1264
1427
|
await request.fire({
|
|
1265
1428
|
identity: context.identity!,
|
|
1266
1429
|
id: dataArg.id,
|
|
@@ -1285,8 +1448,8 @@ export const deleteProfileAction = profilesTargetsStatePart.createAction<{
|
|
|
1285
1448
|
const context = getActionContext();
|
|
1286
1449
|
try {
|
|
1287
1450
|
const request = new plugins.domtools.plugins.typedrequest.TypedRequest<
|
|
1288
|
-
interfaces.requests.
|
|
1289
|
-
>('/typedrequest', '
|
|
1451
|
+
interfaces.requests.IReq_DeleteSourceProfile
|
|
1452
|
+
>('/typedrequest', 'deleteSourceProfile');
|
|
1290
1453
|
const response = await request.fire({
|
|
1291
1454
|
identity: context.identity!,
|
|
1292
1455
|
id: dataArg.id,
|
package/ts_web/elements/index.ts
CHANGED
|
@@ -10,6 +10,7 @@ export * from './ops-view-security.js';
|
|
|
10
10
|
export * from './ops-view-certificates.js';
|
|
11
11
|
export * from './ops-view-remoteingress.js';
|
|
12
12
|
export * from './ops-view-vpn.js';
|
|
13
|
-
export * from './ops-view-
|
|
13
|
+
export * from './ops-view-sourceprofiles.js';
|
|
14
14
|
export * from './ops-view-networktargets.js';
|
|
15
|
+
export * from './ops-view-targetprofiles.js';
|
|
15
16
|
export * from './shared/index.js';
|
|
@@ -24,8 +24,9 @@ import { OpsViewSecurity } from './ops-view-security.js';
|
|
|
24
24
|
import { OpsViewCertificates } from './ops-view-certificates.js';
|
|
25
25
|
import { OpsViewRemoteIngress } from './ops-view-remoteingress.js';
|
|
26
26
|
import { OpsViewVpn } from './ops-view-vpn.js';
|
|
27
|
-
import {
|
|
27
|
+
import { OpsViewSourceProfiles } from './ops-view-sourceprofiles.js';
|
|
28
28
|
import { OpsViewNetworkTargets } from './ops-view-networktargets.js';
|
|
29
|
+
import { OpsViewTargetProfiles } from './ops-view-targetprofiles.js';
|
|
29
30
|
|
|
30
31
|
@customElement('ops-dashboard')
|
|
31
32
|
export class OpsDashboard extends DeesElement {
|
|
@@ -81,15 +82,20 @@ export class OpsDashboard extends DeesElement {
|
|
|
81
82
|
element: OpsViewRoutes,
|
|
82
83
|
},
|
|
83
84
|
{
|
|
84
|
-
name: '
|
|
85
|
+
name: 'SourceProfiles',
|
|
85
86
|
iconName: 'lucide:shieldCheck',
|
|
86
|
-
element:
|
|
87
|
+
element: OpsViewSourceProfiles,
|
|
87
88
|
},
|
|
88
89
|
{
|
|
89
90
|
name: 'NetworkTargets',
|
|
90
91
|
iconName: 'lucide:server',
|
|
91
92
|
element: OpsViewNetworkTargets,
|
|
92
93
|
},
|
|
94
|
+
{
|
|
95
|
+
name: 'TargetProfiles',
|
|
96
|
+
iconName: 'lucide:target',
|
|
97
|
+
element: OpsViewTargetProfiles,
|
|
98
|
+
},
|
|
93
99
|
{
|
|
94
100
|
name: 'ApiTokens',
|
|
95
101
|
iconName: 'lucide:key',
|
|
@@ -13,6 +13,40 @@ import {
|
|
|
13
13
|
type TemplateResult,
|
|
14
14
|
} from '@design.estate/dees-element';
|
|
15
15
|
|
|
16
|
+
// TLS dropdown options shared by create and edit dialogs
|
|
17
|
+
const tlsModeOptions = [
|
|
18
|
+
{ key: 'none', option: '(none — no TLS)' },
|
|
19
|
+
{ key: 'passthrough', option: 'Passthrough' },
|
|
20
|
+
{ key: 'terminate', option: 'Terminate' },
|
|
21
|
+
{ key: 'terminate-and-reencrypt', option: 'Terminate & Re-encrypt' },
|
|
22
|
+
];
|
|
23
|
+
const tlsCertOptions = [
|
|
24
|
+
{ key: 'auto', option: 'Auto (ACME/Let\'s Encrypt)' },
|
|
25
|
+
{ key: 'custom', option: 'Custom certificate' },
|
|
26
|
+
];
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Toggle TLS form field visibility based on selected TLS mode and certificate type.
|
|
30
|
+
*/
|
|
31
|
+
function setupTlsVisibility(formEl: any) {
|
|
32
|
+
const updateVisibility = async () => {
|
|
33
|
+
const data = await formEl.collectFormData();
|
|
34
|
+
const contentEl = formEl.closest('.content') || formEl.parentElement;
|
|
35
|
+
if (!contentEl) return;
|
|
36
|
+
const tlsModeValue = data.tlsMode;
|
|
37
|
+
const modeKey = typeof tlsModeValue === 'string' ? tlsModeValue : tlsModeValue?.key;
|
|
38
|
+
const needsCert = modeKey === 'terminate' || modeKey === 'terminate-and-reencrypt';
|
|
39
|
+
const certGroup = contentEl.querySelector('.tlsCertificateGroup') as HTMLElement;
|
|
40
|
+
if (certGroup) certGroup.style.display = needsCert ? 'flex' : 'none';
|
|
41
|
+
const tlsCertValue = data.tlsCertificate;
|
|
42
|
+
const certKey = typeof tlsCertValue === 'string' ? tlsCertValue : tlsCertValue?.key;
|
|
43
|
+
const customGroup = contentEl.querySelector('.tlsCustomCertGroup') as HTMLElement;
|
|
44
|
+
if (customGroup) customGroup.style.display = (needsCert && certKey === 'custom') ? 'flex' : 'none';
|
|
45
|
+
};
|
|
46
|
+
formEl.changeSubject.subscribe(() => updateVisibility());
|
|
47
|
+
updateVisibility();
|
|
48
|
+
}
|
|
49
|
+
|
|
16
50
|
@customElement('ops-view-routes')
|
|
17
51
|
export class OpsViewRoutes extends DeesElement {
|
|
18
52
|
@state() accessor routeState: appstate.IRouteManagementState = {
|
|
@@ -303,7 +337,7 @@ export class OpsViewRoutes extends DeesElement {
|
|
|
303
337
|
<p>Source: <strong style="color: #0af;">programmatic</strong></p>
|
|
304
338
|
<p>Status: <strong>${merged.enabled ? 'Enabled' : 'Disabled'}</strong></p>
|
|
305
339
|
<p>ID: <code style="color: #888;">${merged.storedRouteId}</code></p>
|
|
306
|
-
${meta?.
|
|
340
|
+
${meta?.sourceProfileName ? html`<p>Source Profile: <strong style="color: #a78bfa;">${meta.sourceProfileName}</strong></p>` : ''}
|
|
307
341
|
${meta?.networkTargetName ? html`<p>Network Target: <strong style="color: #a78bfa;">${meta.networkTargetName}</strong></p>` : ''}
|
|
308
342
|
</div>
|
|
309
343
|
`,
|
|
@@ -423,7 +457,18 @@ export class OpsViewRoutes extends DeesElement {
|
|
|
423
457
|
: '';
|
|
424
458
|
const currentTargetPort = firstTarget?.port != null ? String(firstTarget.port) : '';
|
|
425
459
|
|
|
426
|
-
|
|
460
|
+
// Compute current TLS state for pre-population
|
|
461
|
+
const currentTls = (route.action as any).tls;
|
|
462
|
+
const currentTlsMode = currentTls?.mode || 'none';
|
|
463
|
+
const currentTlsCert = currentTls
|
|
464
|
+
? (currentTls.certificate === 'auto' || !currentTls.certificate ? 'auto' : 'custom')
|
|
465
|
+
: 'auto';
|
|
466
|
+
const currentCustomKey = (typeof currentTls?.certificate === 'object') ? currentTls.certificate.key : '';
|
|
467
|
+
const currentCustomCert = (typeof currentTls?.certificate === 'object') ? currentTls.certificate.cert : '';
|
|
468
|
+
const needsCert = currentTlsMode === 'terminate' || currentTlsMode === 'terminate-and-reencrypt';
|
|
469
|
+
const isCustom = currentTlsCert === 'custom';
|
|
470
|
+
|
|
471
|
+
const editModal = await DeesModal.createAndShow({
|
|
427
472
|
heading: `Edit Route: ${route.name}`,
|
|
428
473
|
content: html`
|
|
429
474
|
<dees-form>
|
|
@@ -431,10 +476,18 @@ export class OpsViewRoutes extends DeesElement {
|
|
|
431
476
|
<dees-input-text .key=${'ports'} .label=${'Ports (comma-separated)'} .value=${currentPorts} .required=${true}></dees-input-text>
|
|
432
477
|
<dees-input-list .key=${'domains'} .label=${'Domains'} .placeholder=${'Add domain...'} .value=${currentDomains}></dees-input-list>
|
|
433
478
|
<dees-input-text .key=${'priority'} .label=${'Priority (higher = matched first)'} .value=${route.priority != null ? String(route.priority) : ''}></dees-input-text>
|
|
434
|
-
<dees-input-dropdown .key=${'
|
|
479
|
+
<dees-input-dropdown .key=${'sourceProfileRef'} .label=${'Source Profile'} .options=${profileOptions} .selectedOption=${profileOptions.find((o) => o.key === (merged.metadata?.sourceProfileRef || '')) || null}></dees-input-dropdown>
|
|
435
480
|
<dees-input-dropdown .key=${'networkTargetRef'} .label=${'Network Target'} .options=${targetOptions} .selectedOption=${targetOptions.find((o) => o.key === (merged.metadata?.networkTargetRef || '')) || null}></dees-input-dropdown>
|
|
436
481
|
<dees-input-text .key=${'targetHost'} .label=${'Target Host (if no target selected)'} .value=${currentTargetHost}></dees-input-text>
|
|
437
482
|
<dees-input-text .key=${'targetPort'} .label=${'Target Port (if no target selected)'} .value=${currentTargetPort}></dees-input-text>
|
|
483
|
+
<dees-input-dropdown .key=${'tlsMode'} .label=${'TLS Mode'} .options=${tlsModeOptions} .selectedOption=${tlsModeOptions.find((o) => o.key === currentTlsMode) || tlsModeOptions[0]}></dees-input-dropdown>
|
|
484
|
+
<div class="tlsCertificateGroup" style="display: ${needsCert ? 'flex' : 'none'}; flex-direction: column; gap: 16px;">
|
|
485
|
+
<dees-input-dropdown .key=${'tlsCertificate'} .label=${'Certificate'} .options=${tlsCertOptions} .selectedOption=${tlsCertOptions.find((o) => o.key === currentTlsCert) || tlsCertOptions[0]}></dees-input-dropdown>
|
|
486
|
+
<div class="tlsCustomCertGroup" style="display: ${needsCert && isCustom ? 'flex' : 'none'}; flex-direction: column; gap: 16px;">
|
|
487
|
+
<dees-input-text .key=${'tlsCertKey'} .label=${'Private Key (PEM)'} .value=${currentCustomKey}></dees-input-text>
|
|
488
|
+
<dees-input-text .key=${'tlsCertCert'} .label=${'Certificate (PEM)'} .value=${currentCustomCert}></dees-input-text>
|
|
489
|
+
</div>
|
|
490
|
+
</div>
|
|
438
491
|
</dees-form>
|
|
439
492
|
`,
|
|
440
493
|
menuOptions: [
|
|
@@ -476,11 +529,30 @@ export class OpsViewRoutes extends DeesElement {
|
|
|
476
529
|
...(priority != null && !isNaN(priority) ? { priority } : {}),
|
|
477
530
|
};
|
|
478
531
|
|
|
532
|
+
// Build TLS config from form
|
|
533
|
+
const tlsModeValue = formData.tlsMode as any;
|
|
534
|
+
const tlsModeKey = typeof tlsModeValue === 'string' ? tlsModeValue : tlsModeValue?.key;
|
|
535
|
+
if (tlsModeKey && tlsModeKey !== 'none') {
|
|
536
|
+
const tls: any = { mode: tlsModeKey };
|
|
537
|
+
if (tlsModeKey !== 'passthrough') {
|
|
538
|
+
const tlsCertValue = formData.tlsCertificate as any;
|
|
539
|
+
const tlsCertKey = typeof tlsCertValue === 'string' ? tlsCertValue : tlsCertValue?.key;
|
|
540
|
+
if (tlsCertKey === 'custom' && formData.tlsCertKey && formData.tlsCertCert) {
|
|
541
|
+
tls.certificate = { key: formData.tlsCertKey, cert: formData.tlsCertCert };
|
|
542
|
+
} else {
|
|
543
|
+
tls.certificate = 'auto';
|
|
544
|
+
}
|
|
545
|
+
}
|
|
546
|
+
updatedRoute.action.tls = tls;
|
|
547
|
+
} else {
|
|
548
|
+
updatedRoute.action.tls = null; // explicit removal
|
|
549
|
+
}
|
|
550
|
+
|
|
479
551
|
const metadata: any = {};
|
|
480
|
-
const profileRefValue = formData.
|
|
552
|
+
const profileRefValue = formData.sourceProfileRef as any;
|
|
481
553
|
const profileKey = typeof profileRefValue === 'string' ? profileRefValue : profileRefValue?.key;
|
|
482
554
|
if (profileKey) {
|
|
483
|
-
metadata.
|
|
555
|
+
metadata.sourceProfileRef = profileKey;
|
|
484
556
|
}
|
|
485
557
|
const targetRefValue = formData.networkTargetRef as any;
|
|
486
558
|
const targetKey = typeof targetRefValue === 'string' ? targetRefValue : targetRefValue?.key;
|
|
@@ -501,6 +573,12 @@ export class OpsViewRoutes extends DeesElement {
|
|
|
501
573
|
},
|
|
502
574
|
],
|
|
503
575
|
});
|
|
576
|
+
// Setup conditional TLS field visibility after modal renders
|
|
577
|
+
const editForm = editModal?.shadowRoot?.querySelector('.content')?.querySelector('dees-form') as any;
|
|
578
|
+
if (editForm) {
|
|
579
|
+
await editForm.updateComplete;
|
|
580
|
+
setupTlsVisibility(editForm);
|
|
581
|
+
}
|
|
504
582
|
}
|
|
505
583
|
|
|
506
584
|
private async showCreateRouteDialog() {
|
|
@@ -524,7 +602,7 @@ export class OpsViewRoutes extends DeesElement {
|
|
|
524
602
|
})),
|
|
525
603
|
];
|
|
526
604
|
|
|
527
|
-
await DeesModal.createAndShow({
|
|
605
|
+
const createModal = await DeesModal.createAndShow({
|
|
528
606
|
heading: 'Add Programmatic Route',
|
|
529
607
|
content: html`
|
|
530
608
|
<dees-form>
|
|
@@ -532,10 +610,18 @@ export class OpsViewRoutes extends DeesElement {
|
|
|
532
610
|
<dees-input-text .key=${'ports'} .label=${'Ports (comma-separated)'} .required=${true}></dees-input-text>
|
|
533
611
|
<dees-input-list .key=${'domains'} .label=${'Domains'} .placeholder=${'Add domain...'}></dees-input-list>
|
|
534
612
|
<dees-input-text .key=${'priority'} .label=${'Priority (higher = matched first)'}></dees-input-text>
|
|
535
|
-
<dees-input-dropdown .key=${'
|
|
613
|
+
<dees-input-dropdown .key=${'sourceProfileRef'} .label=${'Source Profile'} .options=${profileOptions}></dees-input-dropdown>
|
|
536
614
|
<dees-input-dropdown .key=${'networkTargetRef'} .label=${'Network Target'} .options=${targetOptions}></dees-input-dropdown>
|
|
537
615
|
<dees-input-text .key=${'targetHost'} .label=${'Target Host (if no target selected)'} .value=${'localhost'}></dees-input-text>
|
|
538
616
|
<dees-input-text .key=${'targetPort'} .label=${'Target Port (if no target selected)'}></dees-input-text>
|
|
617
|
+
<dees-input-dropdown .key=${'tlsMode'} .label=${'TLS Mode'} .options=${tlsModeOptions} .selectedOption=${tlsModeOptions[0]}></dees-input-dropdown>
|
|
618
|
+
<div class="tlsCertificateGroup" style="display: none; flex-direction: column; gap: 16px;">
|
|
619
|
+
<dees-input-dropdown .key=${'tlsCertificate'} .label=${'Certificate'} .options=${tlsCertOptions} .selectedOption=${tlsCertOptions[0]}></dees-input-dropdown>
|
|
620
|
+
<div class="tlsCustomCertGroup" style="display: none; flex-direction: column; gap: 16px;">
|
|
621
|
+
<dees-input-text .key=${'tlsCertKey'} .label=${'Private Key (PEM)'}></dees-input-text>
|
|
622
|
+
<dees-input-text .key=${'tlsCertCert'} .label=${'Certificate (PEM)'}></dees-input-text>
|
|
623
|
+
</div>
|
|
624
|
+
</div>
|
|
539
625
|
</dees-form>
|
|
540
626
|
`,
|
|
541
627
|
menuOptions: [
|
|
@@ -577,12 +663,29 @@ export class OpsViewRoutes extends DeesElement {
|
|
|
577
663
|
...(priority != null && !isNaN(priority) ? { priority } : {}),
|
|
578
664
|
};
|
|
579
665
|
|
|
666
|
+
// Build TLS config from form
|
|
667
|
+
const tlsModeValue = formData.tlsMode as any;
|
|
668
|
+
const tlsModeKey = typeof tlsModeValue === 'string' ? tlsModeValue : tlsModeValue?.key;
|
|
669
|
+
if (tlsModeKey && tlsModeKey !== 'none') {
|
|
670
|
+
const tls: any = { mode: tlsModeKey };
|
|
671
|
+
if (tlsModeKey !== 'passthrough') {
|
|
672
|
+
const tlsCertValue = formData.tlsCertificate as any;
|
|
673
|
+
const tlsCertKey = typeof tlsCertValue === 'string' ? tlsCertValue : tlsCertValue?.key;
|
|
674
|
+
if (tlsCertKey === 'custom' && formData.tlsCertKey && formData.tlsCertCert) {
|
|
675
|
+
tls.certificate = { key: formData.tlsCertKey, cert: formData.tlsCertCert };
|
|
676
|
+
} else {
|
|
677
|
+
tls.certificate = 'auto';
|
|
678
|
+
}
|
|
679
|
+
}
|
|
680
|
+
route.action.tls = tls;
|
|
681
|
+
}
|
|
682
|
+
|
|
580
683
|
// Build metadata if profile/target selected
|
|
581
684
|
const metadata: any = {};
|
|
582
|
-
const profileRefValue = formData.
|
|
685
|
+
const profileRefValue = formData.sourceProfileRef as any;
|
|
583
686
|
const profileKey = typeof profileRefValue === 'string' ? profileRefValue : profileRefValue?.key;
|
|
584
687
|
if (profileKey) {
|
|
585
|
-
metadata.
|
|
688
|
+
metadata.sourceProfileRef = profileKey;
|
|
586
689
|
}
|
|
587
690
|
const targetRefValue = formData.networkTargetRef as any;
|
|
588
691
|
const targetKey = typeof targetRefValue === 'string' ? targetRefValue : targetRefValue?.key;
|
|
@@ -602,6 +705,12 @@ export class OpsViewRoutes extends DeesElement {
|
|
|
602
705
|
},
|
|
603
706
|
],
|
|
604
707
|
});
|
|
708
|
+
// Setup conditional TLS field visibility after modal renders
|
|
709
|
+
const createForm = createModal?.shadowRoot?.querySelector('.content')?.querySelector('dees-form') as any;
|
|
710
|
+
if (createForm) {
|
|
711
|
+
await createForm.updateComplete;
|
|
712
|
+
setupTlsVisibility(createForm);
|
|
713
|
+
}
|
|
605
714
|
}
|
|
606
715
|
|
|
607
716
|
private refreshData() {
|
|
@@ -14,12 +14,12 @@ import { type IStatsTile } from '@design.estate/dees-catalog';
|
|
|
14
14
|
|
|
15
15
|
declare global {
|
|
16
16
|
interface HTMLElementTagNameMap {
|
|
17
|
-
'ops-view-
|
|
17
|
+
'ops-view-sourceprofiles': OpsViewSourceProfiles;
|
|
18
18
|
}
|
|
19
19
|
}
|
|
20
20
|
|
|
21
|
-
@customElement('ops-view-
|
|
22
|
-
export class
|
|
21
|
+
@customElement('ops-view-sourceprofiles')
|
|
22
|
+
export class OpsViewSourceProfiles extends DeesElement {
|
|
23
23
|
@state()
|
|
24
24
|
accessor profilesState: appstate.IProfilesTargetsState = appstate.profilesTargetsStatePart.getState()!;
|
|
25
25
|
|
|
@@ -58,20 +58,20 @@ export class OpsViewSecurityProfiles extends DeesElement {
|
|
|
58
58
|
type: 'number',
|
|
59
59
|
value: profiles.length,
|
|
60
60
|
icon: 'lucide:shieldCheck',
|
|
61
|
-
description: 'Reusable
|
|
61
|
+
description: 'Reusable source profiles',
|
|
62
62
|
color: '#3b82f6',
|
|
63
63
|
},
|
|
64
64
|
];
|
|
65
65
|
|
|
66
66
|
return html`
|
|
67
|
-
<ops-sectionheading>
|
|
67
|
+
<ops-sectionheading>Source Profiles</ops-sectionheading>
|
|
68
68
|
<div class="profilesContainer">
|
|
69
69
|
<dees-statsgrid .tiles=${statsTiles}></dees-statsgrid>
|
|
70
70
|
<dees-table
|
|
71
|
-
.heading1=${'
|
|
72
|
-
.heading2=${'Reusable
|
|
71
|
+
.heading1=${'Source Profiles'}
|
|
72
|
+
.heading2=${'Reusable source configurations for routes'}
|
|
73
73
|
.data=${profiles}
|
|
74
|
-
.displayFunction=${(profile: interfaces.data.
|
|
74
|
+
.displayFunction=${(profile: interfaces.data.ISourceProfile) => ({
|
|
75
75
|
Name: profile.name,
|
|
76
76
|
Description: profile.description || '-',
|
|
77
77
|
'IP Allow List': (profile.security?.ipAllowList || []).join(', ') || '-',
|
|
@@ -107,7 +107,7 @@ export class OpsViewSecurityProfiles extends DeesElement {
|
|
|
107
107
|
iconName: 'lucide:pencil',
|
|
108
108
|
type: ['inRow', 'contextmenu'] as any,
|
|
109
109
|
actionFunc: async (actionData: any) => {
|
|
110
|
-
const profile = actionData.item as interfaces.data.
|
|
110
|
+
const profile = actionData.item as interfaces.data.ISourceProfile;
|
|
111
111
|
await this.showEditProfileDialog(profile);
|
|
112
112
|
},
|
|
113
113
|
},
|
|
@@ -116,7 +116,7 @@ export class OpsViewSecurityProfiles extends DeesElement {
|
|
|
116
116
|
iconName: 'lucide:trash2',
|
|
117
117
|
type: ['inRow', 'contextmenu'] as any,
|
|
118
118
|
actionFunc: async (actionData: any) => {
|
|
119
|
-
const profile = actionData.item as interfaces.data.
|
|
119
|
+
const profile = actionData.item as interfaces.data.ISourceProfile;
|
|
120
120
|
await this.deleteProfile(profile);
|
|
121
121
|
},
|
|
122
122
|
},
|
|
@@ -129,7 +129,7 @@ export class OpsViewSecurityProfiles extends DeesElement {
|
|
|
129
129
|
private async showCreateProfileDialog() {
|
|
130
130
|
const { DeesModal } = await import('@design.estate/dees-catalog');
|
|
131
131
|
DeesModal.createAndShow({
|
|
132
|
-
heading: 'Create
|
|
132
|
+
heading: 'Create Source Profile',
|
|
133
133
|
content: html`
|
|
134
134
|
<dees-form>
|
|
135
135
|
<dees-input-text .key=${'name'} .label=${'Name'} .required=${true}></dees-input-text>
|
|
@@ -167,7 +167,7 @@ export class OpsViewSecurityProfiles extends DeesElement {
|
|
|
167
167
|
});
|
|
168
168
|
}
|
|
169
169
|
|
|
170
|
-
private async showEditProfileDialog(profile: interfaces.data.
|
|
170
|
+
private async showEditProfileDialog(profile: interfaces.data.ISourceProfile) {
|
|
171
171
|
const { DeesModal } = await import('@design.estate/dees-catalog');
|
|
172
172
|
DeesModal.createAndShow({
|
|
173
173
|
heading: `Edit Profile: ${profile.name}`,
|
|
@@ -209,7 +209,7 @@ export class OpsViewSecurityProfiles extends DeesElement {
|
|
|
209
209
|
});
|
|
210
210
|
}
|
|
211
211
|
|
|
212
|
-
private async deleteProfile(profile: interfaces.data.
|
|
212
|
+
private async deleteProfile(profile: interfaces.data.ISourceProfile) {
|
|
213
213
|
await appstate.profilesTargetsStatePart.dispatchAction(appstate.deleteProfileAction, {
|
|
214
214
|
id: profile.id,
|
|
215
215
|
force: false,
|