@serve.zone/dcrouter 13.15.0 → 13.16.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.
Files changed (46) hide show
  1. package/dist_serve/bundle.js +510 -517
  2. package/dist_ts/00_commitinfo_data.js +1 -1
  3. package/dist_ts/classes.dcrouter.d.ts +3 -6
  4. package/dist_ts/classes.dcrouter.js +39 -53
  5. package/dist_ts/config/classes.reference-resolver.d.ts +8 -8
  6. package/dist_ts/config/classes.reference-resolver.js +6 -6
  7. package/dist_ts/config/classes.route-config-manager.d.ts +13 -13
  8. package/dist_ts/config/classes.route-config-manager.js +117 -144
  9. package/dist_ts/config/classes.target-profile-manager.d.ts +2 -2
  10. package/dist_ts/config/classes.target-profile-manager.js +7 -18
  11. package/dist_ts/db/documents/{classes.stored-route.doc.d.ts → classes.route.doc.d.ts} +6 -3
  12. package/dist_ts/db/documents/{classes.stored-route.doc.js → classes.route.doc.js} +21 -9
  13. package/dist_ts/db/documents/index.d.ts +1 -2
  14. package/dist_ts/db/documents/index.js +2 -3
  15. package/dist_ts/monitoring/classes.metricsmanager.js +86 -8
  16. package/dist_ts/opsserver/handlers/network-target.handler.js +3 -3
  17. package/dist_ts/opsserver/handlers/route-management.handler.js +3 -23
  18. package/dist_ts/opsserver/handlers/source-profile.handler.js +3 -3
  19. package/dist_ts_apiclient/classes.route.d.ts +2 -5
  20. package/dist_ts_apiclient/classes.route.js +13 -42
  21. package/dist_ts_interfaces/data/route-management.d.ts +8 -17
  22. package/dist_ts_interfaces/requests/route-management.d.ts +6 -37
  23. package/dist_ts_migrations/index.js +23 -1
  24. package/dist_ts_web/00_commitinfo_data.js +1 -1
  25. package/dist_ts_web/appstate.d.ts +0 -5
  26. package/dist_ts_web/appstate.js +1 -38
  27. package/dist_ts_web/elements/network/ops-view-routes.js +60 -110
  28. package/package.json +1 -1
  29. package/ts/00_commitinfo_data.ts +1 -1
  30. package/ts/classes.dcrouter.ts +45 -55
  31. package/ts/config/classes.reference-resolver.ts +13 -13
  32. package/ts/config/classes.route-config-manager.ts +128 -146
  33. package/ts/config/classes.target-profile-manager.ts +7 -20
  34. package/ts/db/documents/{classes.stored-route.doc.ts → classes.route.doc.ts} +16 -5
  35. package/ts/db/documents/index.ts +1 -2
  36. package/ts/monitoring/classes.metricsmanager.ts +80 -7
  37. package/ts/opsserver/handlers/network-target.handler.ts +2 -2
  38. package/ts/opsserver/handlers/route-management.handler.ts +2 -34
  39. package/ts/opsserver/handlers/source-profile.handler.ts +2 -2
  40. package/ts_apiclient/classes.route.ts +12 -49
  41. package/ts_web/00_commitinfo_data.ts +1 -1
  42. package/ts_web/appstate.ts +0 -52
  43. package/ts_web/elements/network/ops-view-routes.ts +65 -123
  44. package/dist_ts/db/documents/classes.route-override.doc.d.ts +0 -10
  45. package/dist_ts/db/documents/classes.route-override.doc.js +0 -91
  46. package/ts/db/documents/classes.route-override.doc.ts +0 -32
@@ -724,8 +724,8 @@ export class MetricsManager {
724
724
  const connectionsByRoute = proxyMetrics.connections.byRoute();
725
725
  const throughputByRoute = proxyMetrics.throughput.byRoute();
726
726
 
727
- // Map route name → primary domain using dcrouter's route configs
728
- const routeToDomain = new Map<string, string>();
727
+ // Map route name → ALL its domains (not just the first one)
728
+ const routeDomains = new Map<string, string[]>();
729
729
  if (this.dcRouter.smartProxy) {
730
730
  for (const route of this.dcRouter.smartProxy.routeManager.getRoutes()) {
731
731
  if (!route.name || !route.match.domains) continue;
@@ -733,29 +733,101 @@ export class MetricsManager {
733
733
  ? route.match.domains
734
734
  : [route.match.domains];
735
735
  if (domains.length > 0) {
736
- routeToDomain.set(route.name, domains[0]);
736
+ routeDomains.set(route.name, domains);
737
737
  }
738
738
  }
739
739
  }
740
740
 
741
- // Aggregate metrics by domain
741
+ // Use protocol cache to discover actual active domains (resolves wildcards)
742
+ const activeDomains = new Set<string>();
743
+ const domainToBackend = new Map<string, string>(); // domain → host:port
744
+ for (const entry of protocolCache) {
745
+ if (entry.domain) {
746
+ activeDomains.add(entry.domain);
747
+ domainToBackend.set(entry.domain, `${entry.host}:${entry.port}`);
748
+ }
749
+ }
750
+
751
+ // Build reverse map: domain → route name(s) that handle it
752
+ // For concrete domains: direct lookup from route config
753
+ // For wildcard patterns: match active domains from protocol cache
754
+ const domainToRoutes = new Map<string, string[]>();
755
+ for (const [routeName, domains] of routeDomains) {
756
+ for (const pattern of domains) {
757
+ if (pattern.includes('*')) {
758
+ // Wildcard pattern — match against active domains from protocol cache
759
+ const regex = new RegExp('^' + pattern.replace(/\./g, '\\.').replace(/\*/g, '[^.]+') + '$');
760
+ for (const activeDomain of activeDomains) {
761
+ if (regex.test(activeDomain)) {
762
+ const existing = domainToRoutes.get(activeDomain);
763
+ if (existing) { existing.push(routeName); }
764
+ else { domainToRoutes.set(activeDomain, [routeName]); }
765
+ }
766
+ }
767
+ } else {
768
+ // Concrete domain
769
+ const existing = domainToRoutes.get(pattern);
770
+ if (existing) { existing.push(routeName); }
771
+ else { domainToRoutes.set(pattern, [routeName]); }
772
+ }
773
+ }
774
+ }
775
+
776
+ // Aggregate metrics per domain
777
+ // For each domain, sum metrics from all routes that serve it,
778
+ // divided by the number of domains each route serves
742
779
  const domainAgg = new Map<string, {
743
780
  activeConnections: number;
744
781
  bytesInPerSec: number;
745
782
  bytesOutPerSec: number;
746
783
  routeCount: number;
747
784
  }>();
785
+
786
+ // Track which routes are accounted for
787
+ const accountedRoutes = new Set<string>();
788
+
789
+ for (const [domain, routeNames] of domainToRoutes) {
790
+ let totalConns = 0;
791
+ let totalIn = 0;
792
+ let totalOut = 0;
793
+
794
+ for (const routeName of routeNames) {
795
+ accountedRoutes.add(routeName);
796
+ const conns = connectionsByRoute.get(routeName) || 0;
797
+ const tp = throughputByRoute.get(routeName) || { in: 0, out: 0 };
798
+ // Count how many resolved domains share this route
799
+ let domainsInRoute = 0;
800
+ for (const [, routes] of domainToRoutes) {
801
+ if (routes.includes(routeName)) domainsInRoute++;
802
+ }
803
+ const share = Math.max(domainsInRoute, 1);
804
+ totalConns += conns / share;
805
+ totalIn += tp.in / share;
806
+ totalOut += tp.out / share;
807
+ }
808
+
809
+ domainAgg.set(domain, {
810
+ activeConnections: Math.round(totalConns),
811
+ bytesInPerSec: totalIn,
812
+ bytesOutPerSec: totalOut,
813
+ routeCount: routeNames.length,
814
+ });
815
+ }
816
+
817
+ // Include routes with no domain config (fallback: use route name)
748
818
  for (const [routeName, activeConns] of connectionsByRoute) {
749
- const domain = routeToDomain.get(routeName) || routeName;
819
+ if (accountedRoutes.has(routeName)) continue;
820
+ if (routeDomains.has(routeName)) continue; // has domains but no traffic matched
750
821
  const tp = throughputByRoute.get(routeName) || { in: 0, out: 0 };
751
- const existing = domainAgg.get(domain);
822
+ if (activeConns === 0 && tp.in === 0 && tp.out === 0) continue;
823
+ const existing = domainAgg.get(routeName);
752
824
  if (existing) {
753
825
  existing.activeConnections += activeConns;
754
826
  existing.bytesInPerSec += tp.in;
755
827
  existing.bytesOutPerSec += tp.out;
756
828
  existing.routeCount++;
757
829
  } else {
758
- domainAgg.set(domain, {
830
+ domainAgg.set(routeName, {
759
831
  activeConnections: activeConns,
760
832
  bytesInPerSec: tp.in,
761
833
  bytesOutPerSec: tp.out,
@@ -763,6 +835,7 @@ export class MetricsManager {
763
835
  });
764
836
  }
765
837
  }
838
+
766
839
  const domainActivity = Array.from(domainAgg.entries())
767
840
  .map(([domain, data]) => ({
768
841
  domain,
@@ -135,7 +135,7 @@ export class NetworkTargetHandler {
135
135
  const result = await resolver.deleteTarget(
136
136
  dataArg.id,
137
137
  dataArg.force ?? false,
138
- manager.getStoredRoutes(),
138
+ manager.getRoutes(),
139
139
  );
140
140
 
141
141
  if (result.success && dataArg.force) {
@@ -158,7 +158,7 @@ export class NetworkTargetHandler {
158
158
  if (!resolver || !manager) {
159
159
  return { routes: [] };
160
160
  }
161
- const usage = resolver.getTargetUsageForId(dataArg.id, manager.getStoredRoutes());
161
+ const usage = resolver.getTargetUsageForId(dataArg.id, manager.getRoutes());
162
162
  return { routes: usage.map((u) => ({ id: u.id, name: u.routeName })) };
163
163
  },
164
164
  ),
@@ -72,7 +72,7 @@ export class RouteManagementHandler {
72
72
  return { success: false, message: 'Route management not initialized' };
73
73
  }
74
74
  const id = await manager.createRoute(dataArg.route, userId, dataArg.enabled ?? true, dataArg.metadata);
75
- return { success: true, storedRouteId: id };
75
+ return { success: true, routeId: id };
76
76
  },
77
77
  ),
78
78
  );
@@ -113,39 +113,7 @@ export class RouteManagementHandler {
113
113
  ),
114
114
  );
115
115
 
116
- // Set override on a hardcoded route
117
- this.typedrouter.addTypedHandler(
118
- new plugins.typedrequest.TypedHandler<interfaces.requests.IReq_SetRouteOverride>(
119
- 'setRouteOverride',
120
- async (dataArg) => {
121
- const userId = await this.requireAuth(dataArg, 'routes:write');
122
- const manager = this.opsServerRef.dcRouterRef.routeConfigManager;
123
- if (!manager) {
124
- return { success: false, message: 'Route management not initialized' };
125
- }
126
- await manager.setOverride(dataArg.routeName, dataArg.enabled, userId);
127
- return { success: true };
128
- },
129
- ),
130
- );
131
-
132
- // Remove override from a hardcoded route
133
- this.typedrouter.addTypedHandler(
134
- new plugins.typedrequest.TypedHandler<interfaces.requests.IReq_RemoveRouteOverride>(
135
- 'removeRouteOverride',
136
- async (dataArg) => {
137
- await this.requireAuth(dataArg, 'routes:write');
138
- const manager = this.opsServerRef.dcRouterRef.routeConfigManager;
139
- if (!manager) {
140
- return { success: false, message: 'Route management not initialized' };
141
- }
142
- const ok = await manager.removeOverride(dataArg.routeName);
143
- return { success: ok, message: ok ? undefined : 'Override not found' };
144
- },
145
- ),
146
- );
147
-
148
- // Toggle programmatic route
116
+ // Toggle route
149
117
  this.typedrouter.addTypedHandler(
150
118
  new plugins.typedrequest.TypedHandler<interfaces.requests.IReq_ToggleRoute>(
151
119
  'toggleRoute',
@@ -136,7 +136,7 @@ export class SourceProfileHandler {
136
136
  const result = await resolver.deleteProfile(
137
137
  dataArg.id,
138
138
  dataArg.force ?? false,
139
- manager.getStoredRoutes(),
139
+ manager.getRoutes(),
140
140
  );
141
141
 
142
142
  // If force-deleted with affected routes, re-apply
@@ -160,7 +160,7 @@ export class SourceProfileHandler {
160
160
  if (!resolver || !manager) {
161
161
  return { routes: [] };
162
162
  }
163
- const usage = resolver.getProfileUsageForId(dataArg.id, manager.getStoredRoutes());
163
+ const usage = resolver.getProfileUsageForId(dataArg.id, manager.getRoutes());
164
164
  return { routes: usage.map((u) => ({ id: u.id, name: u.routeName })) };
165
165
  },
166
166
  ),
@@ -7,10 +7,9 @@ export class Route {
7
7
 
8
8
  // Data from IMergedRoute
9
9
  public routeConfig: IRouteConfig;
10
- public source: 'hardcoded' | 'programmatic';
10
+ public id: string;
11
11
  public enabled: boolean;
12
- public overridden: boolean;
13
- public storedRouteId?: string;
12
+ public origin: 'config' | 'email' | 'dns' | 'api';
14
13
  public createdAt?: number;
15
14
  public updatedAt?: number;
16
15
 
@@ -22,21 +21,17 @@ export class Route {
22
21
  constructor(clientRef: DcRouterApiClient, data: interfaces.data.IMergedRoute) {
23
22
  this.clientRef = clientRef;
24
23
  this.routeConfig = data.route;
25
- this.source = data.source;
24
+ this.id = data.id;
26
25
  this.enabled = data.enabled;
27
- this.overridden = data.overridden;
28
- this.storedRouteId = data.storedRouteId;
26
+ this.origin = data.origin;
29
27
  this.createdAt = data.createdAt;
30
28
  this.updatedAt = data.updatedAt;
31
29
  }
32
30
 
33
31
  public async update(changes: Partial<IRouteConfig>): Promise<void> {
34
- if (!this.storedRouteId) {
35
- throw new Error('Cannot update a hardcoded route. Use setOverride() instead.');
36
- }
37
32
  const response = await this.clientRef.request<interfaces.requests.IReq_UpdateRoute>(
38
33
  'updateRoute',
39
- this.clientRef.buildRequestPayload({ id: this.storedRouteId, route: changes }) as any,
34
+ this.clientRef.buildRequestPayload({ id: this.id, route: changes }) as any,
40
35
  );
41
36
  if (!response.success) {
42
37
  throw new Error(response.message || 'Failed to update route');
@@ -44,12 +39,9 @@ export class Route {
44
39
  }
45
40
 
46
41
  public async delete(): Promise<void> {
47
- if (!this.storedRouteId) {
48
- throw new Error('Cannot delete a hardcoded route. Use setOverride() instead.');
49
- }
50
42
  const response = await this.clientRef.request<interfaces.requests.IReq_DeleteRoute>(
51
43
  'deleteRoute',
52
- this.clientRef.buildRequestPayload({ id: this.storedRouteId }) as any,
44
+ this.clientRef.buildRequestPayload({ id: this.id }) as any,
53
45
  );
54
46
  if (!response.success) {
55
47
  throw new Error(response.message || 'Failed to delete route');
@@ -57,41 +49,15 @@ export class Route {
57
49
  }
58
50
 
59
51
  public async toggle(enabled: boolean): Promise<void> {
60
- if (!this.storedRouteId) {
61
- throw new Error('Cannot toggle a hardcoded route. Use setOverride() instead.');
62
- }
63
52
  const response = await this.clientRef.request<interfaces.requests.IReq_ToggleRoute>(
64
53
  'toggleRoute',
65
- this.clientRef.buildRequestPayload({ id: this.storedRouteId, enabled }) as any,
54
+ this.clientRef.buildRequestPayload({ id: this.id, enabled }) as any,
66
55
  );
67
56
  if (!response.success) {
68
57
  throw new Error(response.message || 'Failed to toggle route');
69
58
  }
70
59
  this.enabled = enabled;
71
60
  }
72
-
73
- public async setOverride(enabled: boolean): Promise<void> {
74
- const response = await this.clientRef.request<interfaces.requests.IReq_SetRouteOverride>(
75
- 'setRouteOverride',
76
- this.clientRef.buildRequestPayload({ routeName: this.name, enabled }) as any,
77
- );
78
- if (!response.success) {
79
- throw new Error(response.message || 'Failed to set route override');
80
- }
81
- this.overridden = true;
82
- this.enabled = enabled;
83
- }
84
-
85
- public async removeOverride(): Promise<void> {
86
- const response = await this.clientRef.request<interfaces.requests.IReq_RemoveRouteOverride>(
87
- 'removeRouteOverride',
88
- this.clientRef.buildRequestPayload({ routeName: this.name }) as any,
89
- );
90
- if (!response.success) {
91
- throw new Error(response.message || 'Failed to remove route override');
92
- }
93
- this.overridden = false;
94
- }
95
61
  }
96
62
 
97
63
  export class RouteBuilder {
@@ -144,9 +110,8 @@ export class RouteBuilder {
144
110
  }
145
111
 
146
112
  // Return a Route instance by re-fetching the list
147
- // The created route is programmatic, so we find it by storedRouteId
148
113
  const { routes } = await new RouteManager(this.clientRef).list();
149
- const created = routes.find((r) => r.storedRouteId === response.storedRouteId);
114
+ const created = routes.find((r) => r.id === response.routeId);
150
115
  if (created) {
151
116
  return created;
152
117
  }
@@ -154,10 +119,9 @@ export class RouteBuilder {
154
119
  // Fallback: construct from known data
155
120
  return new Route(this.clientRef, {
156
121
  route: this.routeConfig as IRouteConfig,
157
- source: 'programmatic',
122
+ id: response.routeId || '',
158
123
  enabled: this.isEnabled,
159
- overridden: false,
160
- storedRouteId: response.storedRouteId,
124
+ origin: 'api',
161
125
  });
162
126
  }
163
127
  }
@@ -190,10 +154,9 @@ export class RouteManager {
190
154
  }
191
155
  return new Route(this.clientRef, {
192
156
  route: routeConfig,
193
- source: 'programmatic',
157
+ id: response.routeId || '',
194
158
  enabled: enabled ?? true,
195
- overridden: false,
196
- storedRouteId: response.storedRouteId,
159
+ origin: 'api',
197
160
  });
198
161
  }
199
162
 
@@ -3,6 +3,6 @@
3
3
  */
4
4
  export const commitinfo = {
5
5
  name: '@serve.zone/dcrouter',
6
- version: '13.15.0',
6
+ version: '13.16.0',
7
7
  description: 'A multifaceted routing service handling mail and SMS delivery functions.'
8
8
  }
@@ -2219,58 +2219,6 @@ export const toggleRouteAction = routeManagementStatePart.createAction<{
2219
2219
  }
2220
2220
  });
2221
2221
 
2222
- export const setRouteOverrideAction = routeManagementStatePart.createAction<{
2223
- routeName: string;
2224
- enabled: boolean;
2225
- }>(async (statePartArg, dataArg, actionContext): Promise<IRouteManagementState> => {
2226
- const context = getActionContext();
2227
- const currentState = statePartArg.getState()!;
2228
-
2229
- try {
2230
- const request = new plugins.domtools.plugins.typedrequest.TypedRequest<
2231
- interfaces.requests.IReq_SetRouteOverride
2232
- >('/typedrequest', 'setRouteOverride');
2233
-
2234
- await request.fire({
2235
- identity: context.identity!,
2236
- routeName: dataArg.routeName,
2237
- enabled: dataArg.enabled,
2238
- });
2239
-
2240
- return await actionContext!.dispatch(fetchMergedRoutesAction, null);
2241
- } catch (error: unknown) {
2242
- return {
2243
- ...currentState,
2244
- error: error instanceof Error ? error.message : 'Failed to set override',
2245
- };
2246
- }
2247
- });
2248
-
2249
- export const removeRouteOverrideAction = routeManagementStatePart.createAction<string>(
2250
- async (statePartArg, routeName, actionContext): Promise<IRouteManagementState> => {
2251
- const context = getActionContext();
2252
- const currentState = statePartArg.getState()!;
2253
-
2254
- try {
2255
- const request = new plugins.domtools.plugins.typedrequest.TypedRequest<
2256
- interfaces.requests.IReq_RemoveRouteOverride
2257
- >('/typedrequest', 'removeRouteOverride');
2258
-
2259
- await request.fire({
2260
- identity: context.identity!,
2261
- routeName,
2262
- });
2263
-
2264
- return await actionContext!.dispatch(fetchMergedRoutesAction, null);
2265
- } catch (error: unknown) {
2266
- return {
2267
- ...currentState,
2268
- error: error instanceof Error ? error.message : 'Failed to remove override',
2269
- };
2270
- }
2271
- }
2272
- );
2273
-
2274
2222
  // ============================================================================
2275
2223
  // API Token Actions
2276
2224
  // ============================================================================