@serve.zone/dcrouter 12.9.3 → 12.10.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.
@@ -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 = {
@@ -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>
@@ -435,6 +480,14 @@ export class OpsViewRoutes extends DeesElement {
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,6 +529,25 @@ 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
552
  const profileRefValue = formData.securityProfileRef as any;
481
553
  const profileKey = typeof profileRefValue === 'string' ? profileRefValue : profileRefValue?.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>
@@ -536,6 +614,14 @@ export class OpsViewRoutes extends DeesElement {
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,6 +663,23 @@ 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
685
  const profileRefValue = formData.securityProfileRef as any;
@@ -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() {