@serve.zone/dcrouter 13.9.0 → 13.9.2

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 (28) hide show
  1. package/dist_serve/bundle.js +2575 -2229
  2. package/dist_ts/00_commitinfo_data.js +1 -1
  3. package/dist_ts_interfaces/data/dns-provider.js +2 -2
  4. package/dist_ts_web/00_commitinfo_data.js +1 -1
  5. package/dist_ts_web/elements/access/ops-view-apitokens.js +2 -2
  6. package/dist_ts_web/elements/domains/dns-provider-form.js +4 -19
  7. package/dist_ts_web/elements/domains/ops-view-certificates.d.ts +1 -1
  8. package/dist_ts_web/elements/domains/ops-view-certificates.js +35 -44
  9. package/dist_ts_web/elements/domains/ops-view-dns.js +8 -7
  10. package/dist_ts_web/elements/domains/ops-view-domains.js +5 -4
  11. package/dist_ts_web/elements/network/ops-view-network-activity.js +25 -31
  12. package/dist_ts_web/elements/network/ops-view-remoteingress.js +7 -5
  13. package/dist_ts_web/elements/network/ops-view-routes.js +13 -13
  14. package/dist_ts_web/elements/network/ops-view-targetprofiles.js +3 -3
  15. package/dist_ts_web/elements/network/ops-view-vpn.js +7 -5
  16. package/package.json +4 -4
  17. package/ts/00_commitinfo_data.ts +1 -1
  18. package/ts_web/00_commitinfo_data.ts +1 -1
  19. package/ts_web/elements/access/ops-view-apitokens.ts +1 -1
  20. package/ts_web/elements/domains/dns-provider-form.ts +3 -18
  21. package/ts_web/elements/domains/ops-view-certificates.ts +34 -43
  22. package/ts_web/elements/domains/ops-view-dns.ts +7 -6
  23. package/ts_web/elements/domains/ops-view-domains.ts +4 -3
  24. package/ts_web/elements/network/ops-view-network-activity.ts +24 -31
  25. package/ts_web/elements/network/ops-view-remoteingress.ts +6 -4
  26. package/ts_web/elements/network/ops-view-routes.ts +12 -12
  27. package/ts_web/elements/network/ops-view-targetprofiles.ts +2 -2
  28. package/ts_web/elements/network/ops-view-vpn.ts +6 -4
@@ -181,8 +181,8 @@ export class OpsViewDomains extends DeesElement {
181
181
  heading: 'Add DcRouter Domain',
182
182
  content: html`
183
183
  <dees-form>
184
- <dees-input-text .key=${'name'} .label=${'FQDN (e.g. example.com)'} .required=${true}></dees-input-text>
185
- <dees-input-text .key=${'description'} .label=${'Description (optional)'}></dees-input-text>
184
+ <dees-input-text .key=${'name'} .label=${'FQDN'} .description=${'e.g. example.com'} .required=${true}></dees-input-text>
185
+ <dees-input-text .key=${'description'} .label=${'Description'}></dees-input-text>
186
186
  </dees-form>
187
187
  <p style="margin-top: 12px; font-size: 12px; opacity: 0.7;">
188
188
  dcrouter will become the authoritative DNS server for this domain. You'll need to
@@ -235,7 +235,8 @@ export class OpsViewDomains extends DeesElement {
235
235
  ></dees-input-dropdown>
236
236
  <dees-input-text
237
237
  .key=${'domainNames'}
238
- .label=${'Comma-separated FQDNs to import (e.g. example.com, foo.com)'}
238
+ .label=${'Domain Names'}
239
+ .description=${'Comma-separated FQDNs, e.g. example.com, foo.com'}
239
240
  .required=${true}
240
241
  ></dees-input-text>
241
242
  </dees-form>
@@ -323,6 +323,8 @@ export class OpsViewNetworkActivity extends DeesElement {
323
323
  <!-- Requests Table -->
324
324
  <dees-table
325
325
  .data=${this.networkRequests}
326
+ .rowKey=${'id'}
327
+ .highlightUpdates=${'flash'}
326
328
  .displayFunction=${(req: INetworkRequest) => ({
327
329
  Time: new Date(req.timestamp).toLocaleTimeString(),
328
330
  Protocol: html`<span class="protocolBadge ${req.protocol}">${req.protocol.toUpperCase()}</span>`,
@@ -595,6 +597,8 @@ export class OpsViewNetworkActivity extends DeesElement {
595
597
  return html`
596
598
  <dees-table
597
599
  .data=${this.networkState.topIPs}
600
+ .rowKey=${'ip'}
601
+ .highlightUpdates=${'flash'}
598
602
  .displayFunction=${(ipData: { ip: string; count: number }) => {
599
603
  const bw = bandwidthByIP.get(ipData.ip);
600
604
  return {
@@ -624,6 +628,8 @@ export class OpsViewNetworkActivity extends DeesElement {
624
628
  return html`
625
629
  <dees-table
626
630
  .data=${backends}
631
+ .rowKey=${'backend'}
632
+ .highlightUpdates=${'flash'}
627
633
  .displayFunction=${(item: interfaces.data.IBackendInfo) => {
628
634
  const totalErrors = item.connectErrors + item.handshakeErrors + item.requestErrors;
629
635
  const protocolClass = item.protocol.toLowerCase().replace(/[^a-z0-9]/g, '');
@@ -724,37 +730,24 @@ export class OpsViewNetworkActivity extends DeesElement {
724
730
  this.requestsPerSecHistory.shift();
725
731
  }
726
732
 
727
- // Only update if connections changed significantly
728
- const newConnectionCount = this.networkState.connections.length;
729
- const oldConnectionCount = this.networkRequests.length;
730
-
731
- // Check if we need to update the network requests array
732
- const shouldUpdate = newConnectionCount !== oldConnectionCount ||
733
- newConnectionCount === 0 ||
734
- (newConnectionCount > 0 && this.networkRequests.length === 0);
735
-
736
- if (shouldUpdate) {
737
- // Convert connection data to network requests format
738
- if (newConnectionCount > 0) {
739
- this.networkRequests = this.networkState.connections.map((conn, index) => ({
740
- id: conn.id,
741
- timestamp: conn.startTime,
742
- method: 'GET', // Default method for proxy connections
743
- url: '/',
744
- hostname: conn.remoteAddress,
745
- port: conn.protocol === 'https' ? 443 : 80,
746
- protocol: conn.protocol === 'https' || conn.protocol === 'http' ? conn.protocol : 'tcp',
747
- statusCode: conn.state === 'connected' ? 200 : undefined,
748
- duration: Date.now() - conn.startTime,
749
- bytesIn: conn.bytesReceived,
750
- bytesOut: conn.bytesSent,
751
- remoteIp: conn.remoteAddress,
752
- route: 'proxy',
753
- }));
754
- } else {
755
- this.networkRequests = [];
756
- }
757
- }
733
+ // Reassign unconditionally so dees-table's flash diff can compare per-cell
734
+ // values against the previous snapshot. Row identity is preserved via
735
+ // rowKey='id', so DOM nodes are reused across ticks.
736
+ this.networkRequests = this.networkState.connections.map((conn) => ({
737
+ id: conn.id,
738
+ timestamp: conn.startTime,
739
+ method: 'GET', // Default method for proxy connections
740
+ url: '/',
741
+ hostname: conn.remoteAddress,
742
+ port: conn.protocol === 'https' ? 443 : 80,
743
+ protocol: conn.protocol === 'https' || conn.protocol === 'http' ? conn.protocol : 'tcp',
744
+ statusCode: conn.state === 'connected' ? 200 : undefined,
745
+ duration: Date.now() - conn.startTime,
746
+ bytesIn: conn.bytesReceived,
747
+ bytesOut: conn.bytesSent,
748
+ remoteIp: conn.remoteAddress,
749
+ route: 'proxy',
750
+ }));
758
751
 
759
752
  // Load server-side throughput history into chart (once)
760
753
  if (!this.historyLoaded && this.networkState.throughputHistory && this.networkState.throughputHistory.length > 0) {
@@ -220,6 +220,8 @@ export class OpsViewRemoteIngress extends DeesElement {
220
220
  .heading1=${'Edge Nodes'}
221
221
  .heading2=${'Manage remote ingress edge registrations'}
222
222
  .data=${this.riState.edges}
223
+ .rowKey=${'id'}
224
+ .highlightUpdates=${'flash'}
223
225
  .showColumnFilters=${true}
224
226
  .displayFunction=${(edge: interfaces.data.IRemoteIngress) => ({
225
227
  name: edge.name,
@@ -241,9 +243,9 @@ export class OpsViewRemoteIngress extends DeesElement {
241
243
  content: html`
242
244
  <dees-form>
243
245
  <dees-input-text .key=${'name'} .label=${'Name'} .required=${true}></dees-input-text>
244
- <dees-input-text .key=${'listenPorts'} .label=${'Additional Manual Ports (comma-separated, optional)'}></dees-input-text>
246
+ <dees-input-text .key=${'listenPorts'} .label=${'Manual Ports'} .description=${'Comma-separated port numbers, optional'}></dees-input-text>
245
247
  <dees-input-checkbox .key=${'autoDerivePorts'} .label=${'Auto-derive ports from routes'} .value=${true}></dees-input-checkbox>
246
- <dees-input-text .key=${'tags'} .label=${'Tags (comma-separated, optional)'}></dees-input-text>
248
+ <dees-input-text .key=${'tags'} .label=${'Tags'} .description=${'Comma-separated, optional'}></dees-input-text>
247
249
  </dees-form>
248
250
  `,
249
251
  menuOptions: [
@@ -318,9 +320,9 @@ export class OpsViewRemoteIngress extends DeesElement {
318
320
  content: html`
319
321
  <dees-form>
320
322
  <dees-input-text .key=${'name'} .label=${'Name'} .value=${edge.name}></dees-input-text>
321
- <dees-input-text .key=${'listenPorts'} .label=${'Manual Ports (comma-separated)'} .value=${(edge.listenPorts || []).join(', ')}></dees-input-text>
323
+ <dees-input-text .key=${'listenPorts'} .label=${'Manual Ports'} .description=${'Comma-separated port numbers'} .value=${(edge.listenPorts || []).join(', ')}></dees-input-text>
322
324
  <dees-input-checkbox .key=${'autoDerivePorts'} .label=${'Auto-derive ports from routes'} .value=${edge.autoDerivePorts !== false}></dees-input-checkbox>
323
- <dees-input-text .key=${'tags'} .label=${'Tags (comma-separated)'} .value=${(edge.tags || []).join(', ')}></dees-input-text>
325
+ <dees-input-text .key=${'tags'} .label=${'Tags'} .description=${'Comma-separated'} .value=${(edge.tags || []).join(', ')}></dees-input-text>
324
326
  </dees-form>
325
327
  `,
326
328
  menuOptions: [
@@ -473,19 +473,19 @@ export class OpsViewRoutes extends DeesElement {
473
473
  content: html`
474
474
  <dees-form>
475
475
  <dees-input-text .key=${'name'} .label=${'Route Name'} .value=${route.name || ''} .required=${true}></dees-input-text>
476
- <dees-input-text .key=${'ports'} .label=${'Ports (comma-separated)'} .value=${currentPorts} .required=${true}></dees-input-text>
476
+ <dees-input-text .key=${'ports'} .label=${'Ports'} .description=${'Comma-separated, e.g. 80, 443'} .value=${currentPorts} .required=${true}></dees-input-text>
477
477
  <dees-input-list .key=${'domains'} .label=${'Domains'} .placeholder=${'Add domain...'} .value=${currentDomains}></dees-input-list>
478
- <dees-input-text .key=${'priority'} .label=${'Priority (higher = matched first)'} .value=${route.priority != null ? String(route.priority) : ''}></dees-input-text>
478
+ <dees-input-text .key=${'priority'} .label=${'Priority'} .description=${'Higher values are matched first'} .value=${route.priority != null ? String(route.priority) : ''}></dees-input-text>
479
479
  <dees-input-dropdown .key=${'sourceProfileRef'} .label=${'Source Profile'} .options=${profileOptions} .selectedOption=${profileOptions.find((o) => o.key === (merged.metadata?.sourceProfileRef || '')) || null}></dees-input-dropdown>
480
480
  <dees-input-dropdown .key=${'networkTargetRef'} .label=${'Network Target'} .options=${targetOptions} .selectedOption=${targetOptions.find((o) => o.key === (merged.metadata?.networkTargetRef || '')) || null}></dees-input-dropdown>
481
- <dees-input-text .key=${'targetHost'} .label=${'Target Host (if no target selected)'} .value=${currentTargetHost}></dees-input-text>
482
- <dees-input-text .key=${'targetPort'} .label=${'Target Port (if no target selected)'} .value=${currentTargetPort}></dees-input-text>
481
+ <dees-input-text .key=${'targetHost'} .label=${'Target Host'} .description=${'Used when no network target is selected'} .value=${currentTargetHost}></dees-input-text>
482
+ <dees-input-text .key=${'targetPort'} .label=${'Target Port'} .description=${'Used when no network target is selected'} .value=${currentTargetPort}></dees-input-text>
483
483
  <dees-input-dropdown .key=${'tlsMode'} .label=${'TLS Mode'} .options=${tlsModeOptions} .selectedOption=${tlsModeOptions.find((o) => o.key === currentTlsMode) || tlsModeOptions[0]}></dees-input-dropdown>
484
484
  <div class="tlsCertificateGroup" style="display: ${needsCert ? 'flex' : 'none'}; flex-direction: column; gap: 16px;">
485
485
  <dees-input-dropdown .key=${'tlsCertificate'} .label=${'Certificate'} .options=${tlsCertOptions} .selectedOption=${tlsCertOptions.find((o) => o.key === currentTlsCert) || tlsCertOptions[0]}></dees-input-dropdown>
486
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>
487
+ <dees-input-text .key=${'tlsCertKey'} .label=${'Private Key'} .description=${'PEM-encoded private key'} .value=${currentCustomKey}></dees-input-text>
488
+ <dees-input-text .key=${'tlsCertCert'} .label=${'Certificate'} .description=${'PEM-encoded certificate'} .value=${currentCustomCert}></dees-input-text>
489
489
  </div>
490
490
  </div>
491
491
  </dees-form>
@@ -607,19 +607,19 @@ export class OpsViewRoutes extends DeesElement {
607
607
  content: html`
608
608
  <dees-form>
609
609
  <dees-input-text .key=${'name'} .label=${'Route Name'} .required=${true}></dees-input-text>
610
- <dees-input-text .key=${'ports'} .label=${'Ports (comma-separated)'} .required=${true}></dees-input-text>
610
+ <dees-input-text .key=${'ports'} .label=${'Ports'} .description=${'Comma-separated, e.g. 80, 443'} .required=${true}></dees-input-text>
611
611
  <dees-input-list .key=${'domains'} .label=${'Domains'} .placeholder=${'Add domain...'}></dees-input-list>
612
- <dees-input-text .key=${'priority'} .label=${'Priority (higher = matched first)'}></dees-input-text>
612
+ <dees-input-text .key=${'priority'} .label=${'Priority'} .description=${'Higher values are matched first'}></dees-input-text>
613
613
  <dees-input-dropdown .key=${'sourceProfileRef'} .label=${'Source Profile'} .options=${profileOptions}></dees-input-dropdown>
614
614
  <dees-input-dropdown .key=${'networkTargetRef'} .label=${'Network Target'} .options=${targetOptions}></dees-input-dropdown>
615
- <dees-input-text .key=${'targetHost'} .label=${'Target Host (if no target selected)'} .value=${'localhost'}></dees-input-text>
616
- <dees-input-text .key=${'targetPort'} .label=${'Target Port (if no target selected)'}></dees-input-text>
615
+ <dees-input-text .key=${'targetHost'} .label=${'Target Host'} .description=${'Used when no network target is selected'} .value=${'localhost'}></dees-input-text>
616
+ <dees-input-text .key=${'targetPort'} .label=${'Target Port'} .description=${'Used when no network target is selected'}></dees-input-text>
617
617
  <dees-input-dropdown .key=${'tlsMode'} .label=${'TLS Mode'} .options=${tlsModeOptions} .selectedOption=${tlsModeOptions[0]}></dees-input-dropdown>
618
618
  <div class="tlsCertificateGroup" style="display: none; flex-direction: column; gap: 16px;">
619
619
  <dees-input-dropdown .key=${'tlsCertificate'} .label=${'Certificate'} .options=${tlsCertOptions} .selectedOption=${tlsCertOptions[0]}></dees-input-dropdown>
620
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>
621
+ <dees-input-text .key=${'tlsCertKey'} .label=${'Private Key'} .description=${'PEM-encoded private key'}></dees-input-text>
622
+ <dees-input-text .key=${'tlsCertCert'} .label=${'Certificate'} .description=${'PEM-encoded certificate'}></dees-input-text>
623
623
  </div>
624
624
  </div>
625
625
  </dees-form>
@@ -176,7 +176,7 @@ export class OpsViewTargetProfiles extends DeesElement {
176
176
  <dees-input-text .key=${'name'} .label=${'Name'} .required=${true}></dees-input-text>
177
177
  <dees-input-text .key=${'description'} .label=${'Description'}></dees-input-text>
178
178
  <dees-input-list .key=${'domains'} .label=${'Domains'} .placeholder=${'e.g. *.example.com'} .allowFreeform=${true}></dees-input-list>
179
- <dees-input-list .key=${'targets'} .label=${'Targets (ip:port)'} .placeholder=${'e.g. 10.0.0.1:443'} .allowFreeform=${true}></dees-input-list>
179
+ <dees-input-list .key=${'targets'} .label=${'Targets'} .description=${'Format: ip:port, e.g. 10.0.0.1:443'} .placeholder=${'e.g. 10.0.0.1:443'} .allowFreeform=${true}></dees-input-list>
180
180
  <dees-input-list .key=${'routeRefs'} .label=${'Route Refs'} .placeholder=${'Type to search routes...'} .candidates=${routeCandidates} .allowFreeform=${true}></dees-input-list>
181
181
  </dees-form>
182
182
  `,
@@ -235,7 +235,7 @@ export class OpsViewTargetProfiles extends DeesElement {
235
235
  <dees-input-text .key=${'name'} .label=${'Name'} .value=${profile.name}></dees-input-text>
236
236
  <dees-input-text .key=${'description'} .label=${'Description'} .value=${profile.description || ''}></dees-input-text>
237
237
  <dees-input-list .key=${'domains'} .label=${'Domains'} .placeholder=${'e.g. *.example.com'} .allowFreeform=${true} .value=${currentDomains}></dees-input-list>
238
- <dees-input-list .key=${'targets'} .label=${'Targets (ip:port)'} .placeholder=${'e.g. 10.0.0.1:443'} .allowFreeform=${true} .value=${currentTargets}></dees-input-list>
238
+ <dees-input-list .key=${'targets'} .label=${'Targets'} .description=${'Format: ip:port, e.g. 10.0.0.1:443'} .placeholder=${'e.g. 10.0.0.1:443'} .allowFreeform=${true} .value=${currentTargets}></dees-input-list>
239
239
  <dees-input-list .key=${'routeRefs'} .label=${'Route Refs'} .placeholder=${'Type to search routes...'} .candidates=${routeCandidates} .allowFreeform=${true} .value=${currentRouteRefs}></dees-input-list>
240
240
  </dees-form>
241
241
  `,
@@ -305,6 +305,8 @@ export class OpsViewVpn extends DeesElement {
305
305
  .heading1=${'VPN Clients'}
306
306
  .heading2=${'Manage WireGuard and SmartVPN client registrations'}
307
307
  .data=${clients}
308
+ .rowKey=${'clientId'}
309
+ .highlightUpdates=${'flash'}
308
310
  .showColumnFilters=${true}
309
311
  .displayFunction=${(client: interfaces.data.IVpnClient) => {
310
312
  const conn = this.getConnectedInfo(client);
@@ -369,8 +371,8 @@ export class OpsViewVpn extends DeesElement {
369
371
  </div>
370
372
  <dees-input-checkbox .key=${'allowAdditionalAcls'} .label=${'Allow additional ACLs'} .value=${false}></dees-input-checkbox>
371
373
  <div class="aclGroup" style="display: none; flex-direction: column; gap: 16px;">
372
- <dees-input-text .key=${'destinationAllowList'} .label=${'Destination Allow List (comma-separated IPs/CIDRs)'}></dees-input-text>
373
- <dees-input-text .key=${'destinationBlockList'} .label=${'Destination Block List (comma-separated IPs/CIDRs)'}></dees-input-text>
374
+ <dees-input-text .key=${'destinationAllowList'} .label=${'Destination Allow List'} .description=${'Comma-separated IPs or CIDRs'}></dees-input-text>
375
+ <dees-input-text .key=${'destinationBlockList'} .label=${'Destination Block List'} .description=${'Comma-separated IPs or CIDRs'}></dees-input-text>
374
376
  </div>
375
377
  </dees-form>
376
378
  `,
@@ -679,8 +681,8 @@ export class OpsViewVpn extends DeesElement {
679
681
  </div>
680
682
  <dees-input-checkbox .key=${'allowAdditionalAcls'} .label=${'Allow additional ACLs'} .value=${currentAllowAcls}></dees-input-checkbox>
681
683
  <div class="aclGroup" style="display: ${currentAllowAcls ? 'flex' : 'none'}; flex-direction: column; gap: 16px;">
682
- <dees-input-text .key=${'destinationAllowList'} .label=${'Destination Allow List (comma-separated IPs/CIDRs)'} .value=${currentAllowList}></dees-input-text>
683
- <dees-input-text .key=${'destinationBlockList'} .label=${'Destination Block List (comma-separated IPs/CIDRs)'} .value=${currentBlockList}></dees-input-text>
684
+ <dees-input-text .key=${'destinationAllowList'} .label=${'Destination Allow List'} .description=${'Comma-separated IPs or CIDRs'} .value=${currentAllowList}></dees-input-text>
685
+ <dees-input-text .key=${'destinationBlockList'} .label=${'Destination Block List'} .description=${'Comma-separated IPs or CIDRs'} .value=${currentBlockList}></dees-input-text>
684
686
  </div>
685
687
  </dees-form>
686
688
  `,