@serve.zone/dcrouter 13.40.3 → 13.41.1

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 (34) hide show
  1. package/deno.json +1 -1
  2. package/dist_serve/bundle.js +590 -546
  3. package/dist_ts/00_commitinfo_data.js +1 -1
  4. package/dist_ts/classes.dcrouter.d.ts +26 -0
  5. package/dist_ts/classes.dcrouter.js +296 -80
  6. package/dist_ts/db/documents/classes.remote-ingress-hub-settings.doc.d.ts +10 -0
  7. package/dist_ts/db/documents/classes.remote-ingress-hub-settings.doc.js +88 -0
  8. package/dist_ts/db/documents/index.d.ts +1 -0
  9. package/dist_ts/db/documents/index.js +2 -1
  10. package/dist_ts/opsserver/handlers/config.handler.js +2 -2
  11. package/dist_ts/opsserver/handlers/remoteingress.handler.js +71 -55
  12. package/dist_ts/remoteingress/classes.remoteingress-manager.d.ts +12 -2
  13. package/dist_ts/remoteingress/classes.remoteingress-manager.js +136 -3
  14. package/dist_ts/remoteingress/classes.tunnel-manager.d.ts +2 -0
  15. package/dist_ts/remoteingress/classes.tunnel-manager.js +45 -13
  16. package/dist_ts_interfaces/data/remoteingress.d.ts +5 -0
  17. package/dist_ts_interfaces/requests/remoteingress.d.ts +30 -1
  18. package/dist_ts_web/00_commitinfo_data.js +1 -1
  19. package/dist_ts_web/appstate.d.ts +4 -0
  20. package/dist_ts_web/appstate.js +30 -2
  21. package/dist_ts_web/elements/network/ops-view-remoteingress.d.ts +4 -0
  22. package/dist_ts_web/elements/network/ops-view-remoteingress.js +148 -1
  23. package/package.json +1 -1
  24. package/ts/00_commitinfo_data.ts +1 -1
  25. package/ts/classes.dcrouter.ts +329 -84
  26. package/ts/db/documents/classes.remote-ingress-hub-settings.doc.ts +29 -0
  27. package/ts/db/documents/index.ts +1 -0
  28. package/ts/opsserver/handlers/config.handler.ts +1 -1
  29. package/ts/opsserver/handlers/remoteingress.handler.ts +97 -74
  30. package/ts/remoteingress/classes.remoteingress-manager.ts +172 -3
  31. package/ts/remoteingress/classes.tunnel-manager.ts +41 -14
  32. package/ts_web/00_commitinfo_data.ts +1 -1
  33. package/ts_web/appstate.ts +41 -1
  34. package/ts_web/elements/network/ops-view-remoteingress.ts +164 -0
@@ -12,6 +12,17 @@ import * as interfaces from '../../../dist_ts_interfaces/index.js';
12
12
  import { viewHostCss } from '../shared/css.js';
13
13
  import { type IStatsTile } from '@design.estate/dees-catalog';
14
14
 
15
+ const performanceProfileOptions = [
16
+ { key: '', option: 'Default' },
17
+ { key: 'balanced', option: 'Balanced' },
18
+ { key: 'throughput', option: 'Throughput' },
19
+ { key: 'highConcurrency', option: 'High concurrency' },
20
+ ];
21
+
22
+ function getDropdownKey(value: any): string {
23
+ return typeof value === 'string' ? value : value?.key || '';
24
+ }
25
+
15
26
  declare global {
16
27
  interface HTMLElementTagNameMap {
17
28
  'ops-view-remoteingress': OpsViewRemoteIngress;
@@ -137,6 +148,13 @@ export class OpsViewRemoteIngress extends DeesElement {
137
148
  .metricMuted {
138
149
  color: var(--text-muted, #6b7280);
139
150
  }
151
+
152
+ .settingsNote {
153
+ margin: 12px 0 0;
154
+ font-size: 12px;
155
+ line-height: 1.5;
156
+ color: ${cssManager.bdTheme('#6b7280', '#9ca3af')};
157
+ }
140
158
  `,
141
159
  ];
142
160
 
@@ -308,6 +326,14 @@ export class OpsViewRemoteIngress extends DeesElement {
308
326
  });
309
327
  },
310
328
  },
329
+ {
330
+ name: 'Hub Settings',
331
+ iconName: 'lucide:slidersHorizontal',
332
+ type: ['header' as const],
333
+ actionFunc: async () => {
334
+ await this.showHubSettingsDialog();
335
+ },
336
+ },
311
337
  {
312
338
  name: 'Enable',
313
339
  iconName: 'lucide:play',
@@ -591,4 +617,142 @@ export class OpsViewRemoteIngress extends DeesElement {
591
617
 
592
618
  return base ? {} : undefined;
593
619
  }
620
+
621
+ private async showHubSettingsDialog(): Promise<void> {
622
+ const { DeesModal, DeesToast } = await import('@design.estate/dees-catalog');
623
+ const performance = this.riState.hubSettings?.performance || {};
624
+ const selectedProfile = performanceProfileOptions.find((option) => option.key === (performance.profile || '')) || performanceProfileOptions[0];
625
+ const updatedAt = this.riState.hubSettings?.updatedAt
626
+ ? new Date(this.riState.hubSettings.updatedAt).toLocaleString()
627
+ : 'not persisted yet';
628
+
629
+ await DeesModal.createAndShow({
630
+ heading: 'RemoteIngress Hub Settings',
631
+ content: html`
632
+ <dees-form>
633
+ <dees-input-dropdown
634
+ .key=${'profile'}
635
+ .label=${'Performance Profile'}
636
+ .options=${performanceProfileOptions}
637
+ .selectedOption=${selectedProfile}
638
+ ></dees-input-dropdown>
639
+ <dees-input-text
640
+ .key=${'maxStreamsPerEdge'}
641
+ .label=${'Max Connections / Edge'}
642
+ .description=${'Maximum concurrent client streams per edge. Leave empty for RemoteIngress defaults.'}
643
+ .value=${performance.maxStreamsPerEdge?.toString() || ''}
644
+ ></dees-input-text>
645
+ <dees-input-text
646
+ .key=${'clientWriteTimeoutMs'}
647
+ .label=${'Client Write Timeout'}
648
+ .description=${'Milliseconds before idle client writes are timed out. Leave empty for default.'}
649
+ .value=${performance.clientWriteTimeoutMs?.toString() || ''}
650
+ ></dees-input-text>
651
+ <dees-input-text
652
+ .key=${'firstDataConnectTimeoutMs'}
653
+ .label=${'First Data Timeout'}
654
+ .description=${'Milliseconds to wait for initial client data before connecting upstream. Leave empty for default.'}
655
+ .value=${performance.firstDataConnectTimeoutMs?.toString() || ''}
656
+ ></dees-input-text>
657
+ <dees-input-text
658
+ .key=${'serverFirstPorts'}
659
+ .label=${'Server-first Ports'}
660
+ .description=${'Comma-separated ports such as 21, 22, 25, 110, 143, 587. Do not include 443.'}
661
+ .value=${(performance.serverFirstPorts || []).join(', ')}
662
+ ></dees-input-text>
663
+ </dees-form>
664
+ <p class="settingsNote">
665
+ Saving restarts the RemoteIngress hub so connected edges reconnect and pick up the new defaults.
666
+ Last updated: ${updatedAt} by ${this.riState.hubSettings?.updatedBy || 'default'}.
667
+ </p>
668
+ `,
669
+ menuOptions: [
670
+ {
671
+ name: 'Cancel',
672
+ iconName: 'lucide:x',
673
+ action: async (modalArg: any) => await modalArg.destroy(),
674
+ },
675
+ {
676
+ name: 'Save',
677
+ iconName: 'lucide:check',
678
+ action: async (modalArg: any) => {
679
+ const form = modalArg.shadowRoot?.querySelector('.content')?.querySelector('dees-form');
680
+ if (!form) return;
681
+ const formData = await form.collectFormData();
682
+ let performanceSettings: interfaces.data.IRemoteIngressPerformanceConfig | undefined;
683
+ try {
684
+ performanceSettings = this.collectHubPerformanceSettings(formData);
685
+ } catch (err: unknown) {
686
+ DeesToast.show({ message: (err as Error).message, type: 'error', duration: 4000 });
687
+ return;
688
+ }
689
+
690
+ const nextState = await appstate.remoteIngressStatePart.dispatchAction(
691
+ appstate.updateRemoteIngressHubSettingsAction,
692
+ { performance: performanceSettings },
693
+ );
694
+ if (nextState.error) {
695
+ DeesToast.show({ message: nextState.error, type: 'error', duration: 4000 });
696
+ return;
697
+ }
698
+ await modalArg.destroy();
699
+ DeesToast.show({ message: 'RemoteIngress hub settings saved', type: 'success', duration: 3000 });
700
+ },
701
+ },
702
+ ],
703
+ });
704
+ }
705
+
706
+ private collectHubPerformanceSettings(formData: Record<string, any>): interfaces.data.IRemoteIngressPerformanceConfig | undefined {
707
+ const next: interfaces.data.IRemoteIngressPerformanceConfig = {};
708
+ const profile = getDropdownKey(formData.profile) as interfaces.data.TRemoteIngressPerformanceProfile | '';
709
+ if (profile) {
710
+ next.profile = profile;
711
+ }
712
+
713
+ this.assignPositiveIntegerSetting(next, 'maxStreamsPerEdge', formData.maxStreamsPerEdge, 'Max Connections / Edge');
714
+ this.assignPositiveIntegerSetting(next, 'clientWriteTimeoutMs', formData.clientWriteTimeoutMs, 'Client Write Timeout');
715
+ this.assignPositiveIntegerSetting(next, 'firstDataConnectTimeoutMs', formData.firstDataConnectTimeoutMs, 'First Data Timeout');
716
+
717
+ const serverFirstPorts = this.parsePortList(formData.serverFirstPorts, 'Server-first Ports');
718
+ if (serverFirstPorts.length > 0) {
719
+ if (serverFirstPorts.includes(443)) {
720
+ throw new Error('Port 443 is client-first TLS and must not be listed as server-first');
721
+ }
722
+ next.serverFirstPorts = serverFirstPorts;
723
+ }
724
+
725
+ return Object.keys(next).length > 0 ? next : undefined;
726
+ }
727
+
728
+ private assignPositiveIntegerSetting(
729
+ target: interfaces.data.IRemoteIngressPerformanceConfig,
730
+ key: 'maxStreamsPerEdge' | 'clientWriteTimeoutMs' | 'firstDataConnectTimeoutMs',
731
+ value: any,
732
+ label: string,
733
+ ): void {
734
+ const text = `${value || ''}`.trim();
735
+ if (!text) {
736
+ return;
737
+ }
738
+ const parsed = Number.parseInt(text, 10);
739
+ if (!Number.isInteger(parsed) || parsed < 1) {
740
+ throw new Error(`${label} must be a positive integer`);
741
+ }
742
+ target[key] = parsed;
743
+ }
744
+
745
+ private parsePortList(value: any, label: string): number[] {
746
+ const text = `${value || ''}`.trim();
747
+ if (!text) {
748
+ return [];
749
+ }
750
+ const ports = text.split(',').map((part) => Number.parseInt(part.trim(), 10));
751
+ for (const port of ports) {
752
+ if (!Number.isInteger(port) || port < 1 || port > 65535) {
753
+ throw new Error(`${label} must contain valid port numbers`);
754
+ }
755
+ }
756
+ return [...new Set(ports)].sort((a, b) => a - b);
757
+ }
594
758
  }