@serve.zone/dcrouter 13.8.0 → 13.9.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 (43) hide show
  1. package/dist_serve/bundle.js +1632 -1522
  2. package/dist_ts/00_commitinfo_data.js +1 -1
  3. package/dist_ts/classes.dcrouter.js +3 -2
  4. package/dist_ts/dns/manager.dns.d.ts +17 -15
  5. package/dist_ts/dns/manager.dns.js +33 -27
  6. package/dist_ts/dns/providers/factory.js +10 -1
  7. package/dist_ts/opsserver/handlers/dns-provider.handler.js +42 -5
  8. package/dist_ts/opsserver/handlers/domain.handler.js +3 -3
  9. package/dist_ts_interfaces/data/dns-provider.d.ts +28 -4
  10. package/dist_ts_interfaces/data/dns-provider.js +15 -1
  11. package/dist_ts_interfaces/data/dns-record.d.ts +9 -7
  12. package/dist_ts_interfaces/data/domain.d.ts +8 -7
  13. package/dist_ts_interfaces/requests/dns-records.d.ts +1 -1
  14. package/dist_ts_interfaces/requests/domains.d.ts +3 -3
  15. package/dist_ts_migrations/index.js +17 -1
  16. package/dist_ts_web/00_commitinfo_data.js +1 -1
  17. package/dist_ts_web/appstate.d.ts +1 -1
  18. package/dist_ts_web/appstate.js +2 -2
  19. package/dist_ts_web/elements/domains/dns-provider-form.d.ts +4 -2
  20. package/dist_ts_web/elements/domains/dns-provider-form.js +11 -5
  21. package/dist_ts_web/elements/domains/ops-view-dns.js +3 -3
  22. package/dist_ts_web/elements/domains/ops-view-domains.d.ts +1 -1
  23. package/dist_ts_web/elements/domains/ops-view-domains.js +10 -10
  24. package/dist_ts_web/elements/domains/ops-view-providers.js +19 -5
  25. package/dist_ts_web/elements/network/ops-view-network-activity.js +25 -31
  26. package/dist_ts_web/elements/network/ops-view-remoteingress.js +3 -1
  27. package/dist_ts_web/elements/network/ops-view-vpn.js +3 -1
  28. package/package.json +3 -3
  29. package/ts/00_commitinfo_data.ts +1 -1
  30. package/ts/classes.dcrouter.ts +2 -1
  31. package/ts/dns/manager.dns.ts +38 -27
  32. package/ts/dns/providers/factory.ts +11 -0
  33. package/ts/opsserver/handlers/dns-provider.handler.ts +41 -3
  34. package/ts/opsserver/handlers/domain.handler.ts +2 -2
  35. package/ts_web/00_commitinfo_data.ts +1 -1
  36. package/ts_web/appstate.ts +1 -1
  37. package/ts_web/elements/domains/dns-provider-form.ts +12 -4
  38. package/ts_web/elements/domains/ops-view-dns.ts +2 -2
  39. package/ts_web/elements/domains/ops-view-domains.ts +9 -9
  40. package/ts_web/elements/domains/ops-view-providers.ts +18 -4
  41. package/ts_web/elements/network/ops-view-network-activity.ts +24 -31
  42. package/ts_web/elements/network/ops-view-remoteingress.ts +2 -0
  43. package/ts_web/elements/network/ops-view-vpn.ts +2 -0
@@ -1793,7 +1793,7 @@ export async function fetchProviderDomains(
1793
1793
  return await request.fire({ identity: context.identity, providerId });
1794
1794
  }
1795
1795
 
1796
- export const createManualDomainAction = domainsStatePart.createAction<{
1796
+ export const createDcrouterDomainAction = domainsStatePart.createAction<{
1797
1797
  name: string;
1798
1798
  description?: string;
1799
1799
  }>(async (statePartArg, dataArg, actionContext): Promise<IDomainsState> => {
@@ -44,12 +44,15 @@ export class DnsProviderForm extends DeesElement {
44
44
  accessor providerName: string = '';
45
45
 
46
46
  /**
47
- * Currently selected provider type. Initialized to the first descriptor;
48
- * caller can override before mounting (e.g. for edit dialogs).
47
+ * Currently selected provider type. Initialized to the first user-creatable
48
+ * descriptor; caller can override before mounting (e.g. for edit dialogs).
49
+ * The built-in 'dcrouter' pseudo-provider is excluded from the picker —
50
+ * operators cannot create another one.
49
51
  */
50
52
  @state()
51
53
  accessor selectedType: interfaces.data.TDnsProviderType =
52
- interfaces.data.dnsProviderTypeDescriptors[0]?.type ?? 'cloudflare';
54
+ interfaces.data.dnsProviderTypeDescriptors.find((d) => d.type !== 'dcrouter')?.type ??
55
+ 'cloudflare';
53
56
 
54
57
  /** When true, hide the type picker — used in edit dialogs. */
55
58
  @property({ type: Boolean })
@@ -102,7 +105,12 @@ export class DnsProviderForm extends DeesElement {
102
105
  ];
103
106
 
104
107
  public render(): TemplateResult {
105
- const descriptors = interfaces.data.dnsProviderTypeDescriptors;
108
+ // Exclude the built-in 'dcrouter' pseudo-provider from the type picker —
109
+ // operators cannot create another one, it's surfaced at read time by the
110
+ // backend handler instead.
111
+ const descriptors = interfaces.data.dnsProviderTypeDescriptors.filter(
112
+ (d) => d.type !== 'dcrouter',
113
+ );
106
114
  const descriptor = interfaces.data.getDnsProviderTypeDescriptor(this.selectedType);
107
115
 
108
116
  return html`
@@ -80,7 +80,7 @@ export class OpsViewDns extends DeesElement {
80
80
  font-weight: 500;
81
81
  }
82
82
 
83
- .sourceBadge.manual {
83
+ .sourceBadge.local {
84
84
  background: ${cssManager.bdTheme('#e0e7ff', '#1e1b4b')};
85
85
  color: ${cssManager.bdTheme('#3730a3', '#a5b4fc')};
86
86
  }
@@ -184,7 +184,7 @@ export class OpsViewDns extends DeesElement {
184
184
  private domainHint(domainId: string): string {
185
185
  const domain = this.domainsState.domains.find((d) => d.id === domainId);
186
186
  if (!domain) return '';
187
- if (domain.source === 'manual') {
187
+ if (domain.source === 'dcrouter') {
188
188
  return 'Records are served by dcrouter (authoritative).';
189
189
  }
190
190
  return 'Records are stored at the provider — changes here are pushed via the provider API.';
@@ -55,7 +55,7 @@ export class OpsViewDomains extends DeesElement {
55
55
  font-weight: 500;
56
56
  }
57
57
 
58
- .sourceBadge.manual {
58
+ .sourceBadge.dcrouter {
59
59
  background: ${cssManager.bdTheme('#e0e7ff', '#1e1b4b')};
60
60
  color: ${cssManager.bdTheme('#3730a3', '#a5b4fc')};
61
61
  }
@@ -76,7 +76,7 @@ export class OpsViewDomains extends DeesElement {
76
76
  <div class="domainsContainer">
77
77
  <dees-table
78
78
  .heading1=${'Domains'}
79
- .heading2=${'Domains under management — manual (authoritative) or imported from a provider'}
79
+ .heading2=${'Domains under management — served by dcrouter (authoritative) or imported from a provider'}
80
80
  .data=${domains}
81
81
  .showColumnFilters=${true}
82
82
  .displayFunction=${(d: interfaces.data.IDomain) => ({
@@ -90,11 +90,11 @@ export class OpsViewDomains extends DeesElement {
90
90
  })}
91
91
  .dataActions=${[
92
92
  {
93
- name: 'Add Manual Domain',
93
+ name: 'Add DcRouter Domain',
94
94
  iconName: 'lucide:plus',
95
95
  type: ['header' as const],
96
96
  actionFunc: async () => {
97
- await this.showCreateManualDialog();
97
+ await this.showCreateDcrouterDialog();
98
98
  },
99
99
  },
100
100
  {
@@ -168,17 +168,17 @@ export class OpsViewDomains extends DeesElement {
168
168
  d: interfaces.data.IDomain,
169
169
  providersById: Map<string, interfaces.data.IDnsProviderPublic>,
170
170
  ): TemplateResult {
171
- if (d.source === 'manual') {
172
- return html`<span class="sourceBadge manual">Manual</span>`;
171
+ if (d.source === 'dcrouter') {
172
+ return html`<span class="sourceBadge dcrouter">DcRouter</span>`;
173
173
  }
174
174
  const provider = d.providerId ? providersById.get(d.providerId) : undefined;
175
175
  return html`<span class="sourceBadge provider">${provider?.name || 'Provider'}</span>`;
176
176
  }
177
177
 
178
- private async showCreateManualDialog() {
178
+ private async showCreateDcrouterDialog() {
179
179
  const { DeesModal } = await import('@design.estate/dees-catalog');
180
180
  DeesModal.createAndShow({
181
- heading: 'Add Manual Domain',
181
+ heading: 'Add DcRouter Domain',
182
182
  content: html`
183
183
  <dees-form>
184
184
  <dees-input-text .key=${'name'} .label=${'FQDN (e.g. example.com)'} .required=${true}></dees-input-text>
@@ -199,7 +199,7 @@ export class OpsViewDomains extends DeesElement {
199
199
  ?.querySelector('dees-form');
200
200
  if (!form) return;
201
201
  const data = await form.collectFormData();
202
- await appstate.domainsStatePart.dispatchAction(appstate.createManualDomainAction, {
202
+ await appstate.domainsStatePart.dispatchAction(appstate.createDcrouterDomainAction, {
203
203
  name: String(data.name),
204
204
  description: data.description ? String(data.description) : undefined,
205
205
  });
@@ -71,6 +71,11 @@ export class OpsViewProviders extends DeesElement {
71
71
  background: ${cssManager.bdTheme('#f3f4f6', '#1f2937')};
72
72
  color: ${cssManager.bdTheme('#4b5563', '#9ca3af')};
73
73
  }
74
+
75
+ .statusBadge.builtin {
76
+ background: ${cssManager.bdTheme('#e0e7ff', '#1e1b4b')};
77
+ color: ${cssManager.bdTheme('#3730a3', '#a5b4fc')};
78
+ }
74
79
  `,
75
80
  ];
76
81
 
@@ -82,15 +87,21 @@ export class OpsViewProviders extends DeesElement {
82
87
  <div class="providersContainer">
83
88
  <dees-table
84
89
  .heading1=${'Providers'}
85
- .heading2=${'External DNS provider accounts'}
90
+ .heading2=${'Built-in dcrouter + external DNS provider accounts'}
86
91
  .data=${providers}
87
92
  .showColumnFilters=${true}
88
93
  .displayFunction=${(p: interfaces.data.IDnsProviderPublic) => ({
89
94
  Name: p.name,
90
95
  Type: this.providerTypeLabel(p.type),
91
- Status: this.renderStatusBadge(p.status),
92
- 'Last Tested': p.lastTestedAt ? new Date(p.lastTestedAt).toLocaleString() : 'never',
93
- Error: p.lastError || '-',
96
+ Status: p.builtIn
97
+ ? html`<span class="statusBadge builtin">built-in</span>`
98
+ : this.renderStatusBadge(p.status),
99
+ 'Last Tested': p.builtIn
100
+ ? '—'
101
+ : p.lastTestedAt
102
+ ? new Date(p.lastTestedAt).toLocaleString()
103
+ : 'never',
104
+ Error: p.builtIn ? '—' : p.lastError || '-',
94
105
  })}
95
106
  .dataActions=${[
96
107
  {
@@ -116,6 +127,7 @@ export class OpsViewProviders extends DeesElement {
116
127
  name: 'Test Connection',
117
128
  iconName: 'lucide:plug',
118
129
  type: ['inRow', 'contextmenu'] as any,
130
+ actionRelevancyCheckFunc: (p: interfaces.data.IDnsProviderPublic) => !p.builtIn,
119
131
  actionFunc: async (actionData: any) => {
120
132
  const provider = actionData.item as interfaces.data.IDnsProviderPublic;
121
133
  await this.testProvider(provider);
@@ -125,6 +137,7 @@ export class OpsViewProviders extends DeesElement {
125
137
  name: 'Edit',
126
138
  iconName: 'lucide:pencil',
127
139
  type: ['inRow', 'contextmenu'] as any,
140
+ actionRelevancyCheckFunc: (p: interfaces.data.IDnsProviderPublic) => !p.builtIn,
128
141
  actionFunc: async (actionData: any) => {
129
142
  const provider = actionData.item as interfaces.data.IDnsProviderPublic;
130
143
  await this.showEditDialog(provider);
@@ -134,6 +147,7 @@ export class OpsViewProviders extends DeesElement {
134
147
  name: 'Delete',
135
148
  iconName: 'lucide:trash2',
136
149
  type: ['inRow', 'contextmenu'] as any,
150
+ actionRelevancyCheckFunc: (p: interfaces.data.IDnsProviderPublic) => !p.builtIn,
137
151
  actionFunc: async (actionData: any) => {
138
152
  const provider = actionData.item as interfaces.data.IDnsProviderPublic;
139
153
  await this.deleteProvider(provider);
@@ -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,
@@ -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);