@serve.zone/dcrouter 12.1.0 → 12.2.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 (72) hide show
  1. package/dist_serve/bundle.js +750 -688
  2. package/dist_ts/00_commitinfo_data.js +1 -1
  3. package/dist_ts/classes.dcrouter.d.ts +6 -1
  4. package/dist_ts/classes.dcrouter.js +11 -3
  5. package/dist_ts/config/classes.db-seeder.d.ts +25 -0
  6. package/dist_ts/config/classes.db-seeder.js +69 -0
  7. package/dist_ts/config/classes.reference-resolver.d.ts +80 -0
  8. package/dist_ts/config/classes.reference-resolver.js +482 -0
  9. package/dist_ts/config/classes.route-config-manager.d.ts +13 -3
  10. package/dist_ts/config/classes.route-config-manager.js +53 -3
  11. package/dist_ts/config/index.d.ts +2 -0
  12. package/dist_ts/config/index.js +3 -1
  13. package/dist_ts/db/documents/classes.network-target.doc.d.ts +15 -0
  14. package/dist_ts/db/documents/classes.network-target.doc.js +118 -0
  15. package/dist_ts/db/documents/classes.security-profile.doc.d.ts +16 -0
  16. package/dist_ts/db/documents/classes.security-profile.doc.js +118 -0
  17. package/dist_ts/db/documents/classes.stored-route.doc.d.ts +2 -0
  18. package/dist_ts/db/documents/classes.stored-route.doc.js +8 -2
  19. package/dist_ts/db/documents/index.d.ts +2 -0
  20. package/dist_ts/db/documents/index.js +3 -1
  21. package/dist_ts/opsserver/classes.opsserver.d.ts +2 -0
  22. package/dist_ts/opsserver/classes.opsserver.js +5 -1
  23. package/dist_ts/opsserver/handlers/index.d.ts +2 -0
  24. package/dist_ts/opsserver/handlers/index.js +3 -1
  25. package/dist_ts/opsserver/handlers/network-target.handler.d.ts +10 -0
  26. package/dist_ts/opsserver/handlers/network-target.handler.js +117 -0
  27. package/dist_ts/opsserver/handlers/route-management.handler.js +3 -2
  28. package/dist_ts/opsserver/handlers/security-profile.handler.d.ts +10 -0
  29. package/dist_ts/opsserver/handlers/security-profile.handler.js +119 -0
  30. package/dist_ts_interfaces/data/route-management.d.ts +48 -1
  31. package/dist_ts_interfaces/requests/index.d.ts +2 -0
  32. package/dist_ts_interfaces/requests/index.js +3 -1
  33. package/dist_ts_interfaces/requests/network-targets.d.ts +102 -0
  34. package/dist_ts_interfaces/requests/network-targets.js +2 -0
  35. package/dist_ts_interfaces/requests/route-management.d.ts +3 -1
  36. package/dist_ts_interfaces/requests/security-profiles.d.ts +102 -0
  37. package/dist_ts_interfaces/requests/security-profiles.js +2 -0
  38. package/dist_ts_web/00_commitinfo_data.js +1 -1
  39. package/dist_ts_web/appstate.d.ts +43 -0
  40. package/dist_ts_web/appstate.js +176 -2
  41. package/dist_ts_web/elements/index.d.ts +2 -0
  42. package/dist_ts_web/elements/index.js +3 -1
  43. package/dist_ts_web/elements/ops-dashboard.js +13 -1
  44. package/dist_ts_web/elements/ops-view-networktargets.d.ts +17 -0
  45. package/dist_ts_web/elements/ops-view-networktargets.js +246 -0
  46. package/dist_ts_web/elements/ops-view-securityprofiles.d.ts +17 -0
  47. package/dist_ts_web/elements/ops-view-securityprofiles.js +275 -0
  48. package/dist_ts_web/router.d.ts +1 -1
  49. package/dist_ts_web/router.js +2 -2
  50. package/package.json +1 -1
  51. package/ts/00_commitinfo_data.ts +1 -1
  52. package/ts/classes.dcrouter.ts +19 -1
  53. package/ts/config/classes.db-seeder.ts +95 -0
  54. package/ts/config/classes.reference-resolver.ts +576 -0
  55. package/ts/config/classes.route-config-manager.ts +64 -1
  56. package/ts/config/index.ts +3 -1
  57. package/ts/db/documents/classes.network-target.doc.ts +48 -0
  58. package/ts/db/documents/classes.security-profile.doc.ts +49 -0
  59. package/ts/db/documents/classes.stored-route.doc.ts +4 -0
  60. package/ts/db/documents/index.ts +2 -0
  61. package/ts/opsserver/classes.opsserver.ts +4 -0
  62. package/ts/opsserver/handlers/index.ts +3 -1
  63. package/ts/opsserver/handlers/network-target.handler.ts +167 -0
  64. package/ts/opsserver/handlers/route-management.handler.ts +2 -1
  65. package/ts/opsserver/handlers/security-profile.handler.ts +169 -0
  66. package/ts_web/00_commitinfo_data.ts +1 -1
  67. package/ts_web/appstate.ts +243 -1
  68. package/ts_web/elements/index.ts +2 -0
  69. package/ts_web/elements/ops-dashboard.ts +12 -0
  70. package/ts_web/elements/ops-view-networktargets.ts +214 -0
  71. package/ts_web/elements/ops-view-securityprofiles.ts +242 -0
  72. package/ts_web/router.ts +1 -1
@@ -110,7 +110,7 @@ export const configStatePart = await appState.getStatePart<IConfigState>(
110
110
  // Determine initial view from URL path
111
111
  const getInitialView = (): string => {
112
112
  const path = typeof window !== 'undefined' ? window.location.pathname : '/';
113
- const validViews = ['overview', 'network', 'emails', 'logs', 'routes', 'apitokens', 'configuration', 'security', 'certificates', 'remoteingress'];
113
+ const validViews = ['overview', 'network', 'emails', 'logs', 'routes', 'apitokens', 'configuration', 'security', 'certificates', 'remoteingress', 'securityprofiles', 'networktargets'];
114
114
  const segments = path.split('/').filter(Boolean);
115
115
  const view = segments[0];
116
116
  return validViews.includes(view) ? view : 'overview';
@@ -444,6 +444,13 @@ export const setActiveViewAction = uiStatePart.createAction<string>(async (state
444
444
  }, 100);
445
445
  }
446
446
 
447
+ // If switching to security profiles or network targets views, fetch profiles/targets data
448
+ if ((viewName === 'securityprofiles' || viewName === 'networktargets') && currentState.activeView !== viewName) {
449
+ setTimeout(() => {
450
+ profilesTargetsStatePart.dispatchAction(fetchProfilesAndTargetsAction, null);
451
+ }, 100);
452
+ }
453
+
447
454
  return {
448
455
  ...currentState,
449
456
  activeView: viewName,
@@ -1133,6 +1140,241 @@ export const clearNewClientConfigAction = vpnStatePart.createAction(
1133
1140
  },
1134
1141
  );
1135
1142
 
1143
+ // ============================================================================
1144
+ // Security Profiles & Network Targets State
1145
+ // ============================================================================
1146
+
1147
+ export interface IProfilesTargetsState {
1148
+ profiles: interfaces.data.ISecurityProfile[];
1149
+ targets: interfaces.data.INetworkTarget[];
1150
+ isLoading: boolean;
1151
+ error: string | null;
1152
+ lastUpdated: number;
1153
+ }
1154
+
1155
+ export const profilesTargetsStatePart = await appState.getStatePart<IProfilesTargetsState>(
1156
+ 'profilesTargets',
1157
+ {
1158
+ profiles: [],
1159
+ targets: [],
1160
+ isLoading: false,
1161
+ error: null,
1162
+ lastUpdated: 0,
1163
+ },
1164
+ 'soft'
1165
+ );
1166
+
1167
+ // ============================================================================
1168
+ // Security Profiles & Network Targets Actions
1169
+ // ============================================================================
1170
+
1171
+ export const fetchProfilesAndTargetsAction = profilesTargetsStatePart.createAction(
1172
+ async (statePartArg): Promise<IProfilesTargetsState> => {
1173
+ const context = getActionContext();
1174
+ const currentState = statePartArg.getState()!;
1175
+ if (!context.identity) return currentState;
1176
+
1177
+ try {
1178
+ const profilesRequest = new plugins.domtools.plugins.typedrequest.TypedRequest<
1179
+ interfaces.requests.IReq_GetSecurityProfiles
1180
+ >('/typedrequest', 'getSecurityProfiles');
1181
+
1182
+ const targetsRequest = new plugins.domtools.plugins.typedrequest.TypedRequest<
1183
+ interfaces.requests.IReq_GetNetworkTargets
1184
+ >('/typedrequest', 'getNetworkTargets');
1185
+
1186
+ const [profilesResponse, targetsResponse] = await Promise.all([
1187
+ profilesRequest.fire({ identity: context.identity }),
1188
+ targetsRequest.fire({ identity: context.identity }),
1189
+ ]);
1190
+
1191
+ return {
1192
+ profiles: profilesResponse.profiles,
1193
+ targets: targetsResponse.targets,
1194
+ isLoading: false,
1195
+ error: null,
1196
+ lastUpdated: Date.now(),
1197
+ };
1198
+ } catch (error) {
1199
+ return {
1200
+ ...currentState,
1201
+ isLoading: false,
1202
+ error: error instanceof Error ? error.message : 'Failed to fetch profiles/targets',
1203
+ };
1204
+ }
1205
+ }
1206
+ );
1207
+
1208
+ export const createProfileAction = profilesTargetsStatePart.createAction<{
1209
+ name: string;
1210
+ description?: string;
1211
+ security: any;
1212
+ extendsProfiles?: string[];
1213
+ }>(async (statePartArg, dataArg, actionContext): Promise<IProfilesTargetsState> => {
1214
+ const context = getActionContext();
1215
+ try {
1216
+ const request = new plugins.domtools.plugins.typedrequest.TypedRequest<
1217
+ interfaces.requests.IReq_CreateSecurityProfile
1218
+ >('/typedrequest', 'createSecurityProfile');
1219
+ await request.fire({
1220
+ identity: context.identity!,
1221
+ name: dataArg.name,
1222
+ description: dataArg.description,
1223
+ security: dataArg.security,
1224
+ extendsProfiles: dataArg.extendsProfiles,
1225
+ });
1226
+ return await actionContext!.dispatch(fetchProfilesAndTargetsAction, null);
1227
+ } catch (error: unknown) {
1228
+ return {
1229
+ ...statePartArg.getState()!,
1230
+ error: error instanceof Error ? error.message : 'Failed to create profile',
1231
+ };
1232
+ }
1233
+ });
1234
+
1235
+ export const updateProfileAction = profilesTargetsStatePart.createAction<{
1236
+ id: string;
1237
+ name?: string;
1238
+ description?: string;
1239
+ security?: any;
1240
+ extendsProfiles?: string[];
1241
+ }>(async (statePartArg, dataArg, actionContext): Promise<IProfilesTargetsState> => {
1242
+ const context = getActionContext();
1243
+ try {
1244
+ const request = new plugins.domtools.plugins.typedrequest.TypedRequest<
1245
+ interfaces.requests.IReq_UpdateSecurityProfile
1246
+ >('/typedrequest', 'updateSecurityProfile');
1247
+ await request.fire({
1248
+ identity: context.identity!,
1249
+ id: dataArg.id,
1250
+ name: dataArg.name,
1251
+ description: dataArg.description,
1252
+ security: dataArg.security,
1253
+ extendsProfiles: dataArg.extendsProfiles,
1254
+ });
1255
+ return await actionContext!.dispatch(fetchProfilesAndTargetsAction, null);
1256
+ } catch (error: unknown) {
1257
+ return {
1258
+ ...statePartArg.getState()!,
1259
+ error: error instanceof Error ? error.message : 'Failed to update profile',
1260
+ };
1261
+ }
1262
+ });
1263
+
1264
+ export const deleteProfileAction = profilesTargetsStatePart.createAction<{
1265
+ id: string;
1266
+ force?: boolean;
1267
+ }>(async (statePartArg, dataArg, actionContext): Promise<IProfilesTargetsState> => {
1268
+ const context = getActionContext();
1269
+ try {
1270
+ const request = new plugins.domtools.plugins.typedrequest.TypedRequest<
1271
+ interfaces.requests.IReq_DeleteSecurityProfile
1272
+ >('/typedrequest', 'deleteSecurityProfile');
1273
+ const response = await request.fire({
1274
+ identity: context.identity!,
1275
+ id: dataArg.id,
1276
+ force: dataArg.force,
1277
+ });
1278
+ if (!response.success) {
1279
+ return {
1280
+ ...statePartArg.getState()!,
1281
+ error: response.message || 'Failed to delete profile',
1282
+ };
1283
+ }
1284
+ return await actionContext!.dispatch(fetchProfilesAndTargetsAction, null);
1285
+ } catch (error: unknown) {
1286
+ return {
1287
+ ...statePartArg.getState()!,
1288
+ error: error instanceof Error ? error.message : 'Failed to delete profile',
1289
+ };
1290
+ }
1291
+ });
1292
+
1293
+ export const createTargetAction = profilesTargetsStatePart.createAction<{
1294
+ name: string;
1295
+ description?: string;
1296
+ host: string | string[];
1297
+ port: number;
1298
+ }>(async (statePartArg, dataArg, actionContext): Promise<IProfilesTargetsState> => {
1299
+ const context = getActionContext();
1300
+ try {
1301
+ const request = new plugins.domtools.plugins.typedrequest.TypedRequest<
1302
+ interfaces.requests.IReq_CreateNetworkTarget
1303
+ >('/typedrequest', 'createNetworkTarget');
1304
+ await request.fire({
1305
+ identity: context.identity!,
1306
+ name: dataArg.name,
1307
+ description: dataArg.description,
1308
+ host: dataArg.host,
1309
+ port: dataArg.port,
1310
+ });
1311
+ return await actionContext!.dispatch(fetchProfilesAndTargetsAction, null);
1312
+ } catch (error: unknown) {
1313
+ return {
1314
+ ...statePartArg.getState()!,
1315
+ error: error instanceof Error ? error.message : 'Failed to create target',
1316
+ };
1317
+ }
1318
+ });
1319
+
1320
+ export const updateTargetAction = profilesTargetsStatePart.createAction<{
1321
+ id: string;
1322
+ name?: string;
1323
+ description?: string;
1324
+ host?: string | string[];
1325
+ port?: number;
1326
+ }>(async (statePartArg, dataArg, actionContext): Promise<IProfilesTargetsState> => {
1327
+ const context = getActionContext();
1328
+ try {
1329
+ const request = new plugins.domtools.plugins.typedrequest.TypedRequest<
1330
+ interfaces.requests.IReq_UpdateNetworkTarget
1331
+ >('/typedrequest', 'updateNetworkTarget');
1332
+ await request.fire({
1333
+ identity: context.identity!,
1334
+ id: dataArg.id,
1335
+ name: dataArg.name,
1336
+ description: dataArg.description,
1337
+ host: dataArg.host,
1338
+ port: dataArg.port,
1339
+ });
1340
+ return await actionContext!.dispatch(fetchProfilesAndTargetsAction, null);
1341
+ } catch (error: unknown) {
1342
+ return {
1343
+ ...statePartArg.getState()!,
1344
+ error: error instanceof Error ? error.message : 'Failed to update target',
1345
+ };
1346
+ }
1347
+ });
1348
+
1349
+ export const deleteTargetAction = profilesTargetsStatePart.createAction<{
1350
+ id: string;
1351
+ force?: boolean;
1352
+ }>(async (statePartArg, dataArg, actionContext): Promise<IProfilesTargetsState> => {
1353
+ const context = getActionContext();
1354
+ try {
1355
+ const request = new plugins.domtools.plugins.typedrequest.TypedRequest<
1356
+ interfaces.requests.IReq_DeleteNetworkTarget
1357
+ >('/typedrequest', 'deleteNetworkTarget');
1358
+ const response = await request.fire({
1359
+ identity: context.identity!,
1360
+ id: dataArg.id,
1361
+ force: dataArg.force,
1362
+ });
1363
+ if (!response.success) {
1364
+ return {
1365
+ ...statePartArg.getState()!,
1366
+ error: response.message || 'Failed to delete target',
1367
+ };
1368
+ }
1369
+ return await actionContext!.dispatch(fetchProfilesAndTargetsAction, null);
1370
+ } catch (error: unknown) {
1371
+ return {
1372
+ ...statePartArg.getState()!,
1373
+ error: error instanceof Error ? error.message : 'Failed to delete target',
1374
+ };
1375
+ }
1376
+ });
1377
+
1136
1378
  // ============================================================================
1137
1379
  // Route Management Actions
1138
1380
  // ============================================================================
@@ -10,4 +10,6 @@ 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';
14
+ export * from './ops-view-networktargets.js';
13
15
  export * from './shared/index.js';
@@ -25,6 +25,8 @@ import { OpsViewSecurity } from './ops-view-security.js';
25
25
  import { OpsViewCertificates } from './ops-view-certificates.js';
26
26
  import { OpsViewRemoteIngress } from './ops-view-remoteingress.js';
27
27
  import { OpsViewVpn } from './ops-view-vpn.js';
28
+ import { OpsViewSecurityProfiles } from './ops-view-securityprofiles.js';
29
+ import { OpsViewNetworkTargets } from './ops-view-networktargets.js';
28
30
 
29
31
  @customElement('ops-dashboard')
30
32
  export class OpsDashboard extends DeesElement {
@@ -73,6 +75,16 @@ export class OpsDashboard extends DeesElement {
73
75
  iconName: 'lucide:route',
74
76
  element: OpsViewRoutes,
75
77
  },
78
+ {
79
+ name: 'SecurityProfiles',
80
+ iconName: 'lucide:shieldCheck',
81
+ element: OpsViewSecurityProfiles,
82
+ },
83
+ {
84
+ name: 'NetworkTargets',
85
+ iconName: 'lucide:server',
86
+ element: OpsViewNetworkTargets,
87
+ },
76
88
  {
77
89
  name: 'ApiTokens',
78
90
  iconName: 'lucide:key',
@@ -0,0 +1,214 @@
1
+ import {
2
+ DeesElement,
3
+ html,
4
+ customElement,
5
+ type TemplateResult,
6
+ css,
7
+ state,
8
+ cssManager,
9
+ } from '@design.estate/dees-element';
10
+ import * as appstate from '../appstate.js';
11
+ import * as interfaces from '../../dist_ts_interfaces/index.js';
12
+ import { viewHostCss } from './shared/css.js';
13
+ import { type IStatsTile } from '@design.estate/dees-catalog';
14
+
15
+ declare global {
16
+ interface HTMLElementTagNameMap {
17
+ 'ops-view-networktargets': OpsViewNetworkTargets;
18
+ }
19
+ }
20
+
21
+ @customElement('ops-view-networktargets')
22
+ export class OpsViewNetworkTargets extends DeesElement {
23
+ @state()
24
+ accessor profilesState: appstate.IProfilesTargetsState = appstate.profilesTargetsStatePart.getState()!;
25
+
26
+ constructor() {
27
+ super();
28
+ const sub = appstate.profilesTargetsStatePart.select().subscribe((newState) => {
29
+ this.profilesState = newState;
30
+ });
31
+ this.rxSubscriptions.push(sub);
32
+ }
33
+
34
+ async connectedCallback() {
35
+ await super.connectedCallback();
36
+ await appstate.profilesTargetsStatePart.dispatchAction(appstate.fetchProfilesAndTargetsAction, null);
37
+ }
38
+
39
+ public static styles = [
40
+ cssManager.defaultStyles,
41
+ viewHostCss,
42
+ css`
43
+ .targetsContainer {
44
+ display: flex;
45
+ flex-direction: column;
46
+ gap: 24px;
47
+ }
48
+ `,
49
+ ];
50
+
51
+ public render(): TemplateResult {
52
+ const targets = this.profilesState.targets;
53
+
54
+ const statsTiles: IStatsTile[] = [
55
+ {
56
+ id: 'totalTargets',
57
+ title: 'Total Targets',
58
+ type: 'number',
59
+ value: targets.length,
60
+ icon: 'lucide:server',
61
+ description: 'Reusable network targets',
62
+ color: '#8b5cf6',
63
+ },
64
+ ];
65
+
66
+ return html`
67
+ <div class="targetsContainer">
68
+ <dees-statsgrid .tiles=${statsTiles}></dees-statsgrid>
69
+ <dees-table
70
+ .heading=${'Network Targets'}
71
+ .data=${targets}
72
+ .displayFunction=${(target: interfaces.data.INetworkTarget) => ({
73
+ Name: target.name,
74
+ Host: Array.isArray(target.host) ? target.host.join(', ') : target.host,
75
+ Port: target.port,
76
+ Description: target.description || '-',
77
+ })}
78
+ .dataActions=${[
79
+ {
80
+ name: 'Create Target',
81
+ iconName: 'lucide:plus',
82
+ type: ['header' as const],
83
+ action: async (_: any, table: any) => {
84
+ await this.showCreateTargetDialog(table);
85
+ },
86
+ },
87
+ {
88
+ name: 'Refresh',
89
+ iconName: 'lucide:rotateCw',
90
+ type: ['header' as const],
91
+ action: async () => {
92
+ await appstate.profilesTargetsStatePart.dispatchAction(appstate.fetchProfilesAndTargetsAction, null);
93
+ },
94
+ },
95
+ {
96
+ name: 'Edit',
97
+ iconName: 'lucide:pencil',
98
+ type: ['contextmenu' as const],
99
+ action: async (target: interfaces.data.INetworkTarget, table: any) => {
100
+ await this.showEditTargetDialog(target, table);
101
+ },
102
+ },
103
+ {
104
+ name: 'Delete',
105
+ iconName: 'lucide:trash2',
106
+ type: ['contextmenu' as const],
107
+ action: async (target: interfaces.data.INetworkTarget) => {
108
+ await this.deleteTarget(target);
109
+ },
110
+ },
111
+ ]}
112
+ ></dees-table>
113
+ </div>
114
+ `;
115
+ }
116
+
117
+ private async showCreateTargetDialog(table: any) {
118
+ const { DeesModal } = await import('@design.estate/dees-catalog');
119
+ DeesModal.createAndShow({
120
+ heading: 'Create Network Target',
121
+ content: html`
122
+ <dees-form>
123
+ <dees-input-text .key=${'name'} .label=${'Name'} .required=${true}></dees-input-text>
124
+ <dees-input-text .key=${'host'} .label=${'Host'} .required=${true}></dees-input-text>
125
+ <dees-input-text .key=${'port'} .label=${'Port'} .required=${true} .value=${'443'}></dees-input-text>
126
+ <dees-input-text .key=${'description'} .label=${'Description'}></dees-input-text>
127
+ </dees-form>
128
+ `,
129
+ menuOptions: [
130
+ {
131
+ name: 'Create',
132
+ action: async (modalArg: any) => {
133
+ const form = modalArg.shadowRoot!.querySelector('dees-form');
134
+ const data = await form.collectFormData();
135
+
136
+ await appstate.profilesTargetsStatePart.dispatchAction(appstate.createTargetAction, {
137
+ name: String(data.name),
138
+ description: data.description ? String(data.description) : undefined,
139
+ host: String(data.host),
140
+ port: parseInt(String(data.port)) || 443,
141
+ });
142
+ modalArg.destroy();
143
+ },
144
+ },
145
+ { name: 'Cancel', action: async (modalArg: any) => modalArg.destroy() },
146
+ ],
147
+ });
148
+ }
149
+
150
+ private async showEditTargetDialog(target: interfaces.data.INetworkTarget, table: any) {
151
+ const hostStr = Array.isArray(target.host) ? target.host.join(', ') : target.host;
152
+
153
+ const { DeesModal } = await import('@design.estate/dees-catalog');
154
+ DeesModal.createAndShow({
155
+ heading: `Edit Target: ${target.name}`,
156
+ content: html`
157
+ <dees-form>
158
+ <dees-input-text .key=${'name'} .label=${'Name'} .value=${target.name}></dees-input-text>
159
+ <dees-input-text .key=${'host'} .label=${'Host'} .value=${hostStr}></dees-input-text>
160
+ <dees-input-text .key=${'port'} .label=${'Port'} .value=${String(target.port)}></dees-input-text>
161
+ <dees-input-text .key=${'description'} .label=${'Description'} .value=${target.description || ''}></dees-input-text>
162
+ </dees-form>
163
+ `,
164
+ menuOptions: [
165
+ {
166
+ name: 'Save',
167
+ action: async (modalArg: any) => {
168
+ const form = modalArg.shadowRoot!.querySelector('dees-form');
169
+ const data = await form.collectFormData();
170
+
171
+ await appstate.profilesTargetsStatePart.dispatchAction(appstate.updateTargetAction, {
172
+ id: target.id,
173
+ name: String(data.name),
174
+ description: data.description ? String(data.description) : undefined,
175
+ host: String(data.host),
176
+ port: parseInt(String(data.port)) || 443,
177
+ });
178
+ modalArg.destroy();
179
+ },
180
+ },
181
+ { name: 'Cancel', action: async (modalArg: any) => modalArg.destroy() },
182
+ ],
183
+ });
184
+ }
185
+
186
+ private async deleteTarget(target: interfaces.data.INetworkTarget) {
187
+ await appstate.profilesTargetsStatePart.dispatchAction(appstate.deleteTargetAction, {
188
+ id: target.id,
189
+ force: false,
190
+ });
191
+
192
+ const currentState = appstate.profilesTargetsStatePart.getState()!;
193
+ if (currentState.error?.includes('in use')) {
194
+ const { DeesModal } = await import('@design.estate/dees-catalog');
195
+ DeesModal.createAndShow({
196
+ heading: 'Target In Use',
197
+ content: html`<p>${currentState.error} Force delete?</p>`,
198
+ menuOptions: [
199
+ {
200
+ name: 'Force Delete',
201
+ action: async (modalArg: any) => {
202
+ await appstate.profilesTargetsStatePart.dispatchAction(appstate.deleteTargetAction, {
203
+ id: target.id,
204
+ force: true,
205
+ });
206
+ modalArg.destroy();
207
+ },
208
+ },
209
+ { name: 'Cancel', action: async (modalArg: any) => modalArg.destroy() },
210
+ ],
211
+ });
212
+ }
213
+ }
214
+ }