@serve.zone/dcrouter 12.3.0 → 12.5.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.
@@ -204,7 +204,10 @@ export class OpsViewRoutes extends DeesElement {
204
204
  ? html`
205
205
  <sz-route-list-view
206
206
  .routes=${szRoutes}
207
+ .showActionsFilter=${(route: any) => route.tags?.includes('programmatic') ?? false}
207
208
  @route-click=${(e: CustomEvent) => this.handleRouteClick(e)}
209
+ @route-edit=${(e: CustomEvent) => this.handleRouteEdit(e)}
210
+ @route-delete=${(e: CustomEvent) => this.handleRouteDelete(e)}
208
211
  ></sz-route-list-view>
209
212
  `
210
213
  : html`
@@ -337,6 +340,165 @@ export class OpsViewRoutes extends DeesElement {
337
340
  }
338
341
  }
339
342
 
343
+ private async handleRouteEdit(e: CustomEvent) {
344
+ const clickedRoute = e.detail;
345
+ if (!clickedRoute) return;
346
+
347
+ const merged = this.routeState.mergedRoutes.find(
348
+ (mr) => mr.route.name === clickedRoute.name,
349
+ );
350
+ if (!merged || !merged.storedRouteId) return;
351
+
352
+ this.showEditRouteDialog(merged);
353
+ }
354
+
355
+ private async handleRouteDelete(e: CustomEvent) {
356
+ const clickedRoute = e.detail;
357
+ if (!clickedRoute) return;
358
+
359
+ const merged = this.routeState.mergedRoutes.find(
360
+ (mr) => mr.route.name === clickedRoute.name,
361
+ );
362
+ if (!merged || !merged.storedRouteId) return;
363
+
364
+ const { DeesModal } = await import('@design.estate/dees-catalog');
365
+ await DeesModal.createAndShow({
366
+ heading: `Delete Route: ${merged.route.name}`,
367
+ content: html`
368
+ <div style="color: #ccc; padding: 8px 0;">
369
+ <p>Are you sure you want to delete this route? This action cannot be undone.</p>
370
+ </div>
371
+ `,
372
+ menuOptions: [
373
+ {
374
+ name: 'Cancel',
375
+ iconName: 'lucide:x',
376
+ action: async (modalArg: any) => await modalArg.destroy(),
377
+ },
378
+ {
379
+ name: 'Delete',
380
+ iconName: 'lucide:trash-2',
381
+ action: async (modalArg: any) => {
382
+ await appstate.routeManagementStatePart.dispatchAction(
383
+ appstate.deleteRouteAction,
384
+ merged.storedRouteId!,
385
+ );
386
+ await modalArg.destroy();
387
+ },
388
+ },
389
+ ],
390
+ });
391
+ }
392
+
393
+ private async showEditRouteDialog(merged: interfaces.data.IMergedRoute) {
394
+ const { DeesModal } = await import('@design.estate/dees-catalog');
395
+ const profiles = this.profilesTargetsState.profiles;
396
+ const targets = this.profilesTargetsState.targets;
397
+
398
+ const profileOptions = [
399
+ { key: '', option: '(none — inline security)' },
400
+ ...profiles.map((p) => ({
401
+ key: p.id,
402
+ option: `${p.name}${p.description ? ' — ' + p.description : ''}`,
403
+ })),
404
+ ];
405
+ const targetOptions = [
406
+ { key: '', option: '(none — inline target)' },
407
+ ...targets.map((t) => ({
408
+ key: t.id,
409
+ option: `${t.name} (${Array.isArray(t.host) ? t.host.join(',') : t.host}:${t.port})`,
410
+ })),
411
+ ];
412
+
413
+ const route = merged.route;
414
+ const currentPorts = Array.isArray(route.match.ports)
415
+ ? route.match.ports.map((p: any) => typeof p === 'number' ? String(p) : `${p.from}-${p.to}`).join(', ')
416
+ : String(route.match.ports);
417
+ const currentDomains: string[] = route.match.domains
418
+ ? (Array.isArray(route.match.domains) ? route.match.domains : [route.match.domains])
419
+ : [];
420
+ const firstTarget = route.action.targets?.[0];
421
+ const currentTargetHost = firstTarget
422
+ ? (Array.isArray(firstTarget.host) ? firstTarget.host[0] : firstTarget.host)
423
+ : '';
424
+ const currentTargetPort = firstTarget?.port != null ? String(firstTarget.port) : '';
425
+
426
+ await DeesModal.createAndShow({
427
+ heading: `Edit Route: ${route.name}`,
428
+ content: html`
429
+ <dees-form>
430
+ <dees-input-text .key=${'name'} .label=${'Route Name'} .value=${route.name || ''} .required=${true}></dees-input-text>
431
+ <dees-input-text .key=${'ports'} .label=${'Ports (comma-separated)'} .value=${currentPorts} .required=${true}></dees-input-text>
432
+ <dees-input-list .key=${'domains'} .label=${'Domains'} .placeholder=${'Add domain...'} .value=${currentDomains}></dees-input-list>
433
+ <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} .selectedKey=${merged.metadata?.securityProfileRef || ''}></dees-input-dropdown>
435
+ <dees-input-dropdown .key=${'networkTargetRef'} .label=${'Network Target'} .options=${targetOptions} .selectedKey=${merged.metadata?.networkTargetRef || ''}></dees-input-dropdown>
436
+ <dees-input-text .key=${'targetHost'} .label=${'Target Host (if no target selected)'} .value=${currentTargetHost}></dees-input-text>
437
+ <dees-input-text .key=${'targetPort'} .label=${'Target Port (if no target selected)'} .value=${currentTargetPort}></dees-input-text>
438
+ </dees-form>
439
+ `,
440
+ menuOptions: [
441
+ {
442
+ name: 'Cancel',
443
+ iconName: 'lucide:x',
444
+ action: async (modalArg: any) => await modalArg.destroy(),
445
+ },
446
+ {
447
+ name: 'Save',
448
+ iconName: 'lucide:check',
449
+ action: async (modalArg: any) => {
450
+ const form = modalArg.shadowRoot?.querySelector('.content')?.querySelector('dees-form');
451
+ if (!form) return;
452
+ const formData = await form.collectFormData();
453
+ if (!formData.name || !formData.ports) return;
454
+
455
+ const ports = formData.ports.split(',').map((p: string) => parseInt(p.trim(), 10)).filter((p: number) => !isNaN(p));
456
+ const domains: string[] = Array.isArray(formData.domains)
457
+ ? formData.domains.filter(Boolean)
458
+ : [];
459
+ const priority = formData.priority ? parseInt(formData.priority, 10) : undefined;
460
+
461
+ const updatedRoute: any = {
462
+ name: formData.name,
463
+ match: {
464
+ ports,
465
+ ...(domains.length > 0 ? { domains } : {}),
466
+ },
467
+ action: {
468
+ type: 'forward',
469
+ targets: [
470
+ {
471
+ host: formData.targetHost || 'localhost',
472
+ port: parseInt(formData.targetPort, 10) || 443,
473
+ },
474
+ ],
475
+ },
476
+ ...(priority != null && !isNaN(priority) ? { priority } : {}),
477
+ };
478
+
479
+ const metadata: any = {};
480
+ if (formData.securityProfileRef) {
481
+ metadata.securityProfileRef = formData.securityProfileRef;
482
+ }
483
+ if (formData.networkTargetRef) {
484
+ metadata.networkTargetRef = formData.networkTargetRef;
485
+ }
486
+
487
+ await appstate.routeManagementStatePart.dispatchAction(
488
+ appstate.updateRouteAction,
489
+ {
490
+ id: merged.storedRouteId!,
491
+ route: updatedRoute,
492
+ metadata: Object.keys(metadata).length > 0 ? metadata : undefined,
493
+ },
494
+ );
495
+ await modalArg.destroy();
496
+ },
497
+ },
498
+ ],
499
+ });
500
+ }
501
+
340
502
  private async showCreateRouteDialog() {
341
503
  const { DeesModal } = await import('@design.estate/dees-catalog');
342
504
  const profiles = this.profilesTargetsState.profiles;
@@ -364,7 +526,8 @@ export class OpsViewRoutes extends DeesElement {
364
526
  <dees-form>
365
527
  <dees-input-text .key=${'name'} .label=${'Route Name'} .required=${true}></dees-input-text>
366
528
  <dees-input-text .key=${'ports'} .label=${'Ports (comma-separated)'} .required=${true}></dees-input-text>
367
- <dees-input-text .key=${'domains'} .label=${'Domains (comma-separated, optional)'}></dees-input-text>
529
+ <dees-input-list .key=${'domains'} .label=${'Domains'} .placeholder=${'Add domain...'}></dees-input-list>
530
+ <dees-input-text .key=${'priority'} .label=${'Priority (higher = matched first)'}></dees-input-text>
368
531
  <dees-input-dropdown .key=${'securityProfileRef'} .label=${'Security Profile'} .options=${profileOptions} .selectedKey=${''}></dees-input-dropdown>
369
532
  <dees-input-dropdown .key=${'networkTargetRef'} .label=${'Network Target'} .options=${targetOptions} .selectedKey=${''}></dees-input-dropdown>
370
533
  <dees-input-text .key=${'targetHost'} .label=${'Target Host (if no target selected)'} .value=${'localhost'}></dees-input-text>
@@ -387,15 +550,16 @@ export class OpsViewRoutes extends DeesElement {
387
550
  if (!formData.name || !formData.ports) return;
388
551
 
389
552
  const ports = formData.ports.split(',').map((p: string) => parseInt(p.trim(), 10)).filter((p: number) => !isNaN(p));
390
- const domains = formData.domains
391
- ? formData.domains.split(',').map((d: string) => d.trim()).filter(Boolean)
392
- : undefined;
553
+ const domains: string[] = Array.isArray(formData.domains)
554
+ ? formData.domains.filter(Boolean)
555
+ : [];
556
+ const priority = formData.priority ? parseInt(formData.priority, 10) : undefined;
393
557
 
394
558
  const route: any = {
395
559
  name: formData.name,
396
560
  match: {
397
561
  ports,
398
- ...(domains && domains.length > 0 ? { domains } : {}),
562
+ ...(domains.length > 0 ? { domains } : {}),
399
563
  },
400
564
  action: {
401
565
  type: 'forward',
@@ -406,6 +570,7 @@ export class OpsViewRoutes extends DeesElement {
406
570
  },
407
571
  ],
408
572
  },
573
+ ...(priority != null && !isNaN(priority) ? { priority } : {}),
409
574
  };
410
575
 
411
576
  // Build metadata if profile/target selected