@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.
Files changed (86) hide show
  1. package/dist_serve/bundle.js +1030 -923
  2. package/dist_ts/00_commitinfo_data.js +1 -1
  3. package/dist_ts/classes.dcrouter.d.ts +5 -4
  4. package/dist_ts/classes.dcrouter.js +25 -49
  5. package/dist_ts/config/classes.reference-resolver.d.ts +7 -7
  6. package/dist_ts/config/classes.reference-resolver.js +27 -27
  7. package/dist_ts/config/classes.route-config-manager.d.ts +2 -2
  8. package/dist_ts/config/classes.route-config-manager.js +24 -16
  9. package/dist_ts/config/classes.target-profile-manager.d.ts +63 -0
  10. package/dist_ts/config/classes.target-profile-manager.js +295 -0
  11. package/dist_ts/config/index.d.ts +1 -0
  12. package/dist_ts/config/index.js +2 -1
  13. package/dist_ts/db/documents/{classes.security-profile.doc.d.ts → classes.source-profile.doc.d.ts} +4 -4
  14. package/dist_ts/db/documents/{classes.security-profile.doc.js → classes.source-profile.doc.js} +9 -9
  15. package/dist_ts/db/documents/classes.target-profile.doc.d.ts +17 -0
  16. package/dist_ts/db/documents/classes.target-profile.doc.js +124 -0
  17. package/dist_ts/db/documents/classes.vpn-client.doc.d.ts +1 -1
  18. package/dist_ts/db/documents/classes.vpn-client.doc.js +8 -8
  19. package/dist_ts/db/documents/index.d.ts +2 -1
  20. package/dist_ts/db/documents/index.js +3 -2
  21. package/dist_ts/opsserver/classes.opsserver.d.ts +2 -1
  22. package/dist_ts/opsserver/classes.opsserver.js +5 -3
  23. package/dist_ts/opsserver/handlers/index.d.ts +2 -1
  24. package/dist_ts/opsserver/handlers/index.js +3 -2
  25. package/dist_ts/opsserver/handlers/{security-profile.handler.d.ts → source-profile.handler.d.ts} +1 -1
  26. package/dist_ts/opsserver/handlers/{security-profile.handler.js → source-profile.handler.js} +20 -20
  27. package/dist_ts/opsserver/handlers/target-profile.handler.d.ts +10 -0
  28. package/dist_ts/opsserver/handlers/target-profile.handler.js +115 -0
  29. package/dist_ts/opsserver/handlers/vpn.handler.js +5 -5
  30. package/dist_ts/vpn/classes.vpn-manager.d.ts +6 -10
  31. package/dist_ts/vpn/classes.vpn-manager.js +11 -34
  32. package/dist_ts_interfaces/data/index.d.ts +1 -0
  33. package/dist_ts_interfaces/data/index.js +2 -1
  34. package/dist_ts_interfaces/data/remoteingress.d.ts +4 -15
  35. package/dist_ts_interfaces/data/route-management.d.ts +9 -6
  36. package/dist_ts_interfaces/data/target-profile.d.ts +28 -0
  37. package/dist_ts_interfaces/data/target-profile.js +2 -0
  38. package/dist_ts_interfaces/data/vpn.d.ts +2 -1
  39. package/dist_ts_interfaces/requests/index.d.ts +2 -1
  40. package/dist_ts_interfaces/requests/index.js +3 -2
  41. package/dist_ts_interfaces/requests/{security-profiles.d.ts → source-profiles.d.ts} +21 -21
  42. package/dist_ts_interfaces/requests/source-profiles.js +2 -0
  43. package/dist_ts_interfaces/requests/target-profiles.d.ts +103 -0
  44. package/dist_ts_interfaces/requests/target-profiles.js +2 -0
  45. package/dist_ts_interfaces/requests/vpn.d.ts +2 -2
  46. package/dist_ts_web/00_commitinfo_data.js +1 -1
  47. package/dist_ts_web/appstate.d.ts +36 -3
  48. package/dist_ts_web/appstate.js +127 -10
  49. package/dist_ts_web/elements/index.d.ts +2 -1
  50. package/dist_ts_web/elements/index.js +3 -2
  51. package/dist_ts_web/elements/ops-dashboard.js +10 -4
  52. package/dist_ts_web/elements/ops-view-routes.js +120 -10
  53. package/dist_ts_web/elements/{ops-view-securityprofiles.d.ts → ops-view-sourceprofiles.d.ts} +2 -2
  54. package/dist_ts_web/elements/{ops-view-securityprofiles.js → ops-view-sourceprofiles.js} +12 -12
  55. package/dist_ts_web/elements/ops-view-targetprofiles.d.ts +19 -0
  56. package/dist_ts_web/elements/ops-view-targetprofiles.js +412 -0
  57. package/dist_ts_web/elements/ops-view-vpn.js +13 -13
  58. package/dist_ts_web/router.d.ts +1 -1
  59. package/dist_ts_web/router.js +2 -2
  60. package/package.json +3 -3
  61. package/ts/00_commitinfo_data.ts +1 -1
  62. package/ts/classes.dcrouter.ts +33 -50
  63. package/ts/config/classes.reference-resolver.ts +34 -34
  64. package/ts/config/classes.route-config-manager.ts +21 -13
  65. package/ts/config/classes.target-profile-manager.ts +348 -0
  66. package/ts/config/index.ts +2 -1
  67. package/ts/db/documents/{classes.security-profile.doc.ts → classes.source-profile.doc.ts} +7 -7
  68. package/ts/db/documents/classes.target-profile.doc.ts +52 -0
  69. package/ts/db/documents/classes.vpn-client.doc.ts +1 -1
  70. package/ts/db/documents/index.ts +2 -1
  71. package/ts/opsserver/classes.opsserver.ts +4 -2
  72. package/ts/opsserver/handlers/index.ts +2 -1
  73. package/ts/opsserver/handlers/{security-profile.handler.ts → source-profile.handler.ts} +25 -25
  74. package/ts/opsserver/handlers/target-profile.handler.ts +155 -0
  75. package/ts/opsserver/handlers/vpn.handler.ts +4 -4
  76. package/ts/vpn/classes.vpn-manager.ts +14 -38
  77. package/ts_web/00_commitinfo_data.ts +1 -1
  78. package/ts_web/appstate.ts +180 -17
  79. package/ts_web/elements/index.ts +2 -1
  80. package/ts_web/elements/ops-dashboard.ts +9 -3
  81. package/ts_web/elements/ops-view-routes.ts +118 -9
  82. package/ts_web/elements/{ops-view-securityprofiles.ts → ops-view-sourceprofiles.ts} +13 -13
  83. package/ts_web/elements/ops-view-targetprofiles.ts +379 -0
  84. package/ts_web/elements/ops-view-vpn.ts +12 -12
  85. package/ts_web/router.ts +1 -1
  86. package/dist_ts_interfaces/requests/security-profiles.js +0 -2
@@ -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', 'securityprofiles', 'networktargets'];
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 === 'securityprofiles' || viewName === 'networktargets') && currentState.activeView !== 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
- serverDefinedClientTags?: string[];
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
- serverDefinedClientTags: dataArg.serverDefinedClientTags,
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
- serverDefinedClientTags?: string[];
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
- serverDefinedClientTags: dataArg.serverDefinedClientTags,
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
- // Security Profiles & Network Targets State
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.ISecurityProfile[];
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
- // Security Profiles & Network Targets Actions
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.IReq_GetSecurityProfiles
1197
- >('/typedrequest', 'getSecurityProfiles');
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.IReq_CreateSecurityProfile
1235
- >('/typedrequest', 'createSecurityProfile');
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.IReq_UpdateSecurityProfile
1263
- >('/typedrequest', 'updateSecurityProfile');
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.IReq_DeleteSecurityProfile
1289
- >('/typedrequest', 'deleteSecurityProfile');
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,
@@ -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-securityprofiles.js';
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 { OpsViewSecurityProfiles } from './ops-view-securityprofiles.js';
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: 'SecurityProfiles',
85
+ name: 'SourceProfiles',
85
86
  iconName: 'lucide:shieldCheck',
86
- element: OpsViewSecurityProfiles,
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?.securityProfileName ? html`<p>Security Profile: <strong style="color: #a78bfa;">${meta.securityProfileName}</strong></p>` : ''}
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
- await DeesModal.createAndShow({
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=${'securityProfileRef'} .label=${'Security Profile'} .options=${profileOptions} .selectedOption=${profileOptions.find((o) => o.key === (merged.metadata?.securityProfileRef || '')) || null}></dees-input-dropdown>
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.securityProfileRef as any;
552
+ const profileRefValue = formData.sourceProfileRef as any;
481
553
  const profileKey = typeof profileRefValue === 'string' ? profileRefValue : profileRefValue?.key;
482
554
  if (profileKey) {
483
- metadata.securityProfileRef = profileKey;
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=${'securityProfileRef'} .label=${'Security Profile'} .options=${profileOptions}></dees-input-dropdown>
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.securityProfileRef as any;
685
+ const profileRefValue = formData.sourceProfileRef as any;
583
686
  const profileKey = typeof profileRefValue === 'string' ? profileRefValue : profileRefValue?.key;
584
687
  if (profileKey) {
585
- metadata.securityProfileRef = profileKey;
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-securityprofiles': OpsViewSecurityProfiles;
17
+ 'ops-view-sourceprofiles': OpsViewSourceProfiles;
18
18
  }
19
19
  }
20
20
 
21
- @customElement('ops-view-securityprofiles')
22
- export class OpsViewSecurityProfiles extends DeesElement {
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 security profiles',
61
+ description: 'Reusable source profiles',
62
62
  color: '#3b82f6',
63
63
  },
64
64
  ];
65
65
 
66
66
  return html`
67
- <ops-sectionheading>Security Profiles</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=${'Security Profiles'}
72
- .heading2=${'Reusable security configurations for routes'}
71
+ .heading1=${'Source Profiles'}
72
+ .heading2=${'Reusable source configurations for routes'}
73
73
  .data=${profiles}
74
- .displayFunction=${(profile: interfaces.data.ISecurityProfile) => ({
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.ISecurityProfile;
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.ISecurityProfile;
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 Security Profile',
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.ISecurityProfile) {
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.ISecurityProfile) {
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,