@serve.zone/dcrouter 13.18.0 → 13.19.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 (35) hide show
  1. package/dist_serve/bundle.js +6 -5
  2. package/dist_ts/00_commitinfo_data.js +1 -1
  3. package/dist_ts/classes.dcrouter.d.ts +2 -0
  4. package/dist_ts/classes.dcrouter.js +50 -39
  5. package/dist_ts/config/classes.route-config-manager.d.ts +13 -5
  6. package/dist_ts/config/classes.route-config-manager.js +76 -36
  7. package/dist_ts/db/documents/classes.route.doc.d.ts +2 -0
  8. package/dist_ts/db/documents/classes.route.doc.js +11 -2
  9. package/dist_ts/email/classes.email-domain.manager.js +9 -28
  10. package/dist_ts/email/email-dns-records.d.ts +14 -0
  11. package/dist_ts/email/email-dns-records.js +34 -0
  12. package/dist_ts/email/index.d.ts +1 -0
  13. package/dist_ts/email/index.js +2 -1
  14. package/dist_ts/opsserver/handlers/route-management.handler.js +5 -7
  15. package/dist_ts_interfaces/data/route-management.d.ts +2 -0
  16. package/dist_ts_migrations/index.js +25 -1
  17. package/dist_ts_web/00_commitinfo_data.js +1 -1
  18. package/dist_ts_web/appstate.js +13 -4
  19. package/dist_ts_web/elements/network/ops-view-routes.d.ts +2 -0
  20. package/dist_ts_web/elements/network/ops-view-routes.js +44 -21
  21. package/package.json +2 -3
  22. package/readme.md +190 -1543
  23. package/ts/00_commitinfo_data.ts +1 -1
  24. package/ts/classes.dcrouter.ts +61 -47
  25. package/ts/config/classes.route-config-manager.ts +97 -42
  26. package/ts/db/documents/classes.route.doc.ts +7 -0
  27. package/ts/email/classes.email-domain.manager.ts +8 -28
  28. package/ts/email/email-dns-records.ts +53 -0
  29. package/ts/email/index.ts +1 -0
  30. package/ts/opsserver/handlers/route-management.handler.ts +4 -6
  31. package/ts_apiclient/readme.md +69 -195
  32. package/ts_web/00_commitinfo_data.ts +1 -1
  33. package/ts_web/appstate.ts +16 -4
  34. package/ts_web/elements/network/ops-view-routes.ts +47 -29
  35. package/ts_web/readme.md +41 -242
@@ -1,8 +1,8 @@
1
1
  # @serve.zone/dcrouter-apiclient
2
2
 
3
- A typed, object-oriented API client for DcRouter with a fluent builder pattern. 🔧
3
+ Typed, object-oriented API client for operating a running dcrouter instance. 🔧
4
4
 
5
- Programmatically manage your DcRouter instance routes, certificates, API tokens, remote ingress edges, RADIUS, email operations, and more all with full TypeScript type safety and an intuitive OO interface.
5
+ Use this package when you want a clean TypeScript client instead of manually firing TypedRequest calls. It wraps the OpsServer API in resource managers and resource classes such as routes, certificates, tokens, edges, emails, stats, logs, config, and RADIUS.
6
6
 
7
7
  ## Issue Reporting and Security
8
8
 
@@ -14,7 +14,7 @@ For reporting bugs, issues, or security vulnerabilities, please visit [community
14
14
  pnpm add @serve.zone/dcrouter-apiclient
15
15
  ```
16
16
 
17
- Or import directly from the main package:
17
+ Or import through the main package:
18
18
 
19
19
  ```typescript
20
20
  import { DcRouterApiClient } from '@serve.zone/dcrouter/apiclient';
@@ -23,239 +23,113 @@ import { DcRouterApiClient } from '@serve.zone/dcrouter/apiclient';
23
23
  ## Quick Start
24
24
 
25
25
  ```typescript
26
- import { DcRouterApiClient } from '@serve.zone/dcrouter/apiclient';
26
+ import { DcRouterApiClient } from '@serve.zone/dcrouter-apiclient';
27
27
 
28
- const client = new DcRouterApiClient({ baseUrl: 'https://dcrouter.example.com' });
28
+ const client = new DcRouterApiClient({
29
+ baseUrl: 'https://dcrouter.example.com',
30
+ });
29
31
 
30
- // Authenticate
31
32
  await client.login('admin', 'password');
32
33
 
33
- // List routes
34
- const { routes, warnings } = await client.routes.list();
35
- console.log(`${routes.length} routes, ${warnings.length} warnings`);
34
+ const { routes } = await client.routes.list();
35
+ console.log(routes.map((route) => `${route.origin}:${route.name}`));
36
36
 
37
- // Check health
38
- const { health } = await client.stats.getHealth();
39
- console.log(`Healthy: ${health.healthy}`);
37
+ await client.routes.build()
38
+ .setName('api-gateway')
39
+ .setMatch({ ports: 443, domains: ['api.example.com'] })
40
+ .setAction({ type: 'forward', targets: [{ host: '127.0.0.1', port: 8080 }] })
41
+ .save();
40
42
  ```
41
43
 
42
- ## Usage
44
+ ## Authentication Modes
43
45
 
44
- ### 🔐 Authentication
46
+ | Mode | How it works |
47
+ | --- | --- |
48
+ | Admin login | Call `login(username, password)` and the client stores the returned identity for later requests |
49
+ | API token | Pass `apiToken` into the constructor for token-based automation |
45
50
 
46
51
  ```typescript
47
- // Login with credentials — identity is stored and auto-injected into all subsequent requests
48
- const identity = await client.login('admin', 'password');
49
-
50
- // Verify current session
51
- const { valid } = await client.verifyIdentity();
52
-
53
- // Logout
54
- await client.logout();
55
-
56
- // Or use an API token for programmatic access (route management only)
57
52
  const client = new DcRouterApiClient({
58
53
  baseUrl: 'https://dcrouter.example.com',
59
54
  apiToken: 'dcr_your_token_here',
60
55
  });
61
56
  ```
62
57
 
63
- ### 🌐 Routes — OO Resources + Builder
58
+ ## Main Managers
64
59
 
65
- Routes are returned as `Route` instances with methods for update, delete, toggle, and overrides:
60
+ | Manager | Purpose |
61
+ | --- | --- |
62
+ | `client.routes` | List routes and create API-managed routes |
63
+ | `client.certificates` | Inspect and operate on certificate records |
64
+ | `client.apiTokens` | Create, list, toggle, roll, revoke API tokens |
65
+ | `client.remoteIngress` | Manage registered remote ingress edges |
66
+ | `client.stats` | Read operational metrics and health data |
67
+ | `client.config` | Read current configuration view |
68
+ | `client.logs` | Read recent logs or stream them |
69
+ | `client.emails` | List emails and trigger resend flows |
70
+ | `client.radius` | Operate on RADIUS clients, VLANs, sessions, and accounting |
66
71
 
67
- ```typescript
68
- // List all routes (hardcoded + programmatic)
69
- const { routes, warnings } = await client.routes.list();
72
+ ## Route Behavior
70
73
 
71
- // Inspect a route
72
- const route = routes[0];
73
- console.log(route.name, route.source, route.enabled);
74
+ Routes are returned as `Route` instances with:
74
75
 
75
- // Modify a programmatic route
76
- await route.update({ name: 'renamed-route' });
77
- await route.toggle(false);
78
- await route.delete();
76
+ - `id`
77
+ - `name`
78
+ - `enabled`
79
+ - `origin`
79
80
 
80
- // Override a hardcoded route (disable it)
81
- const hardcodedRoute = routes.find(r => r.source === 'hardcoded');
82
- await hardcodedRoute.setOverride(false);
83
- await hardcodedRoute.removeOverride();
84
- ```
81
+ Important behavior:
85
82
 
86
- **Builder pattern** for creating new routes:
83
+ - API routes can be created, updated, deleted, and toggled.
84
+ - System routes can be listed and toggled, but not edited or deleted.
85
+ - A system route is any route whose `origin !== 'api'`.
87
86
 
88
87
  ```typescript
89
- const newRoute = await client.routes.build()
90
- .setName('api-gateway')
91
- .setMatch({ ports: 443, domains: ['api.example.com'] })
92
- .setAction({ type: 'forward', targets: [{ host: 'backend', port: 8080 }] })
93
- .setTls({ mode: 'terminate', certificate: 'auto' })
94
- .setEnabled(true)
95
- .save();
88
+ const { routes } = await client.routes.list();
96
89
 
97
- // Or use quick creation
98
- const route = await client.routes.create(routeConfig);
90
+ for (const route of routes) {
91
+ if (route.origin !== 'api') {
92
+ await route.toggle(false);
93
+ }
94
+ }
99
95
  ```
100
96
 
101
- ### 🔑 API Tokens
97
+ ## Builder Example
102
98
 
103
99
  ```typescript
104
- // List existing tokens
105
- const tokens = await client.apiTokens.list();
106
-
107
- // Create with builder
108
- const token = await client.apiTokens.build()
109
- .setName('ci-pipeline')
110
- .setScopes(['routes:read', 'routes:write'])
111
- .addScope('config:read')
112
- .setExpiresInDays(90)
100
+ const route = await client.routes.build()
101
+ .setName('internal-app')
102
+ .setMatch({
103
+ ports: 80,
104
+ domains: ['internal.example.com'],
105
+ })
106
+ .setAction({
107
+ type: 'forward',
108
+ targets: [{ host: '127.0.0.1', port: 3000 }],
109
+ })
110
+ .setEnabled(true)
113
111
  .save();
114
112
 
115
- console.log(token.tokenValue); // Only available at creation time!
116
-
117
- // Manage tokens
118
- await token.toggle(false); // Disable
119
- const newValue = await token.roll(); // Regenerate secret
120
- await token.revoke(); // Delete
113
+ await route.toggle(false);
121
114
  ```
122
115
 
123
- ### 🔐 Certificates
116
+ ## Example: Certificates and Stats
124
117
 
125
118
  ```typescript
126
119
  const { certificates, summary } = await client.certificates.list();
127
- console.log(`${summary.valid} valid, ${summary.expiring} expiring, ${summary.failed} failed`);
128
-
129
- // Operate on individual certificates
130
- const cert = certificates[0];
131
- await cert.reprovision();
132
- const exported = await cert.export();
133
- await cert.delete();
134
-
135
- // Import a certificate
136
- await client.certificates.import({
137
- id: 'cert-id',
138
- domainName: 'example.com',
139
- created: Date.now(),
140
- validUntil: Date.now() + 90 * 24 * 3600 * 1000,
141
- privateKey: '...',
142
- publicKey: '...',
143
- csr: '...',
144
- });
145
- ```
120
+ console.log(summary.valid, summary.failed);
146
121
 
147
- ### 🌍 Remote Ingress
148
-
149
- ```typescript
150
- // List edges and their statuses
151
- const edges = await client.remoteIngress.list();
152
- const statuses = await client.remoteIngress.getStatuses();
153
-
154
- // Create with builder
155
- const edge = await client.remoteIngress.build()
156
- .setName('edge-nyc-01')
157
- .setListenPorts([80, 443])
158
- .setAutoDerivePorts(true)
159
- .setTags(['us-east'])
160
- .save();
161
-
162
- // Manage an edge
163
- await edge.update({ name: 'edge-nyc-02' });
164
- const newSecret = await edge.regenerateSecret();
165
- const token = await edge.getConnectionToken();
166
- await edge.delete();
167
- ```
168
-
169
- ### 📊 Statistics (Read-Only)
170
-
171
- ```typescript
172
- const serverStats = await client.stats.getServer({ timeRange: '24h', includeHistory: true });
173
- const emailStats = await client.stats.getEmail({ domain: 'example.com' });
174
- const dnsStats = await client.stats.getDns();
175
- const security = await client.stats.getSecurity({ includeDetails: true });
176
- const connections = await client.stats.getConnections({ protocol: 'https' });
177
- const queues = await client.stats.getQueues();
178
- const health = await client.stats.getHealth(true);
179
- const network = await client.stats.getNetwork();
180
- const combined = await client.stats.getCombined({ server: true, email: true });
181
- ```
182
-
183
- ### ⚙️ Configuration & Logs
184
-
185
- ```typescript
186
- // Read-only configuration
187
- const config = await client.config.get();
188
- const emailSection = await client.config.get('email');
189
-
190
- // Logs
191
- const { logs, total, hasMore } = await client.logs.getRecent({
192
- level: 'error',
193
- category: 'smtp',
194
- limit: 50,
195
- });
196
- ```
197
-
198
- ### 📧 Email Operations
199
-
200
- ```typescript
201
- const emails = await client.emails.list();
202
- const email = emails[0];
203
- const detail = await email.getDetail();
204
- await email.resend();
205
-
206
- // Or use the manager directly
207
- const detail2 = await client.emails.getDetail('email-id');
208
- await client.emails.resend('email-id');
209
- ```
210
-
211
- ### 📡 RADIUS
212
-
213
- ```typescript
214
- // Client management
215
- const clients = await client.radius.clients.list();
216
- await client.radius.clients.set({
217
- name: 'switch-1',
218
- ipRange: '192.168.1.0/24',
219
- secret: 'shared-secret',
220
- enabled: true,
221
- });
222
- await client.radius.clients.remove('switch-1');
223
-
224
- // VLAN management
225
- const { mappings, config: vlanConfig } = await client.radius.vlans.list();
226
- await client.radius.vlans.set({ mac: 'aa:bb:cc:dd:ee:ff', vlan: 10, enabled: true });
227
- const result = await client.radius.vlans.testAssignment('aa:bb:cc:dd:ee:ff');
228
- await client.radius.vlans.updateConfig({ defaultVlan: 200 });
229
-
230
- // Sessions
231
- const { sessions } = await client.radius.sessions.list({ vlanId: 10 });
232
- await client.radius.sessions.disconnect('session-id', 'Admin disconnect');
233
-
234
- // Statistics & Accounting
235
- const stats = await client.radius.getStatistics();
236
- const summary = await client.radius.getAccountingSummary(startTime, endTime);
122
+ const health = await client.stats.getHealth();
123
+ const recentLogs = await client.logs.getRecent({ level: 'error', limit: 20 });
237
124
  ```
238
125
 
239
- ## API Surface
240
-
241
- | Manager | Methods |
242
- |---------|---------|
243
- | `client.login()` / `logout()` / `verifyIdentity()` | Authentication |
244
- | `client.routes` | `list()`, `create()`, `build()` → Route: `update()`, `delete()`, `toggle()`, `setOverride()`, `removeOverride()` |
245
- | `client.certificates` | `list()`, `import()` → Certificate: `reprovision()`, `delete()`, `export()` |
246
- | `client.apiTokens` | `list()`, `create()`, `build()` → ApiToken: `revoke()`, `roll()`, `toggle()` |
247
- | `client.remoteIngress` | `list()`, `getStatuses()`, `create()`, `build()` → RemoteIngress: `update()`, `delete()`, `regenerateSecret()`, `getConnectionToken()` |
248
- | `client.stats` | `getServer()`, `getEmail()`, `getDns()`, `getRateLimits()`, `getSecurity()`, `getConnections()`, `getQueues()`, `getHealth()`, `getNetwork()`, `getCombined()` |
249
- | `client.config` | `get(section?)` |
250
- | `client.logs` | `getRecent()`, `getStream()` |
251
- | `client.emails` | `list()`, `getDetail()`, `resend()` → Email: `getDetail()`, `resend()` |
252
- | `client.radius` | `.clients.list/set/remove()`, `.vlans.list/set/remove/updateConfig/testAssignment()`, `.sessions.list/disconnect()`, `getStatistics()`, `getAccountingSummary()` |
253
-
254
- ## Architecture
126
+ ## What This Package Does Not Do
255
127
 
256
- The client uses HTTP-based [TypedRequest](https://code.foss.global/api.global/typedrequest) for transport. All requests are sent as POST to `{baseUrl}/typedrequest`. Authentication (JWT identity and/or API token) is automatically injected into every request payload via `buildRequestPayload()`.
128
+ - It does not start dcrouter.
129
+ - It does not embed the dashboard.
130
+ - It does not replace the request interfaces package if you only need raw types.
257
131
 
258
- Resource classes (`Route`, `Certificate`, `ApiToken`, `RemoteIngress`, `Email`) hold a reference to the client and provide instance methods that fire the appropriate TypedRequest operations. Builder classes (`RouteBuilder`, `ApiTokenBuilder`, `RemoteIngressBuilder`) use fluent chaining and a terminal `.save()` method.
132
+ Use `@serve.zone/dcrouter` to run the server, `@serve.zone/dcrouter-web` for the dashboard bundle/components, and `@serve.zone/dcrouter-interfaces` for raw API contracts.
259
133
 
260
134
  ## License and Legal Information
261
135
 
@@ -3,6 +3,6 @@
3
3
  */
4
4
  export const commitinfo = {
5
5
  name: '@serve.zone/dcrouter',
6
- version: '13.18.0',
6
+ version: '13.19.0',
7
7
  description: 'A multifaceted routing service handling mail and SMS delivery functions.'
8
8
  }
@@ -2150,7 +2150,7 @@ export const updateRouteAction = routeManagementStatePart.createAction<{
2150
2150
  interfaces.requests.IReq_UpdateRoute
2151
2151
  >('/typedrequest', 'updateRoute');
2152
2152
 
2153
- await request.fire({
2153
+ const response = await request.fire({
2154
2154
  identity: context.identity!,
2155
2155
  id: dataArg.id,
2156
2156
  route: dataArg.route,
@@ -2158,6 +2158,10 @@ export const updateRouteAction = routeManagementStatePart.createAction<{
2158
2158
  metadata: dataArg.metadata,
2159
2159
  });
2160
2160
 
2161
+ if (!response.success) {
2162
+ throw new Error(response.message || 'Failed to update route');
2163
+ }
2164
+
2161
2165
  return await actionContext!.dispatch(fetchMergedRoutesAction, null);
2162
2166
  } catch (error: unknown) {
2163
2167
  return {
@@ -2177,11 +2181,15 @@ export const deleteRouteAction = routeManagementStatePart.createAction<string>(
2177
2181
  interfaces.requests.IReq_DeleteRoute
2178
2182
  >('/typedrequest', 'deleteRoute');
2179
2183
 
2180
- await request.fire({
2184
+ const response = await request.fire({
2181
2185
  identity: context.identity!,
2182
2186
  id: routeId,
2183
2187
  });
2184
2188
 
2189
+ if (!response.success) {
2190
+ throw new Error(response.message || 'Failed to delete route');
2191
+ }
2192
+
2185
2193
  return await actionContext!.dispatch(fetchMergedRoutesAction, null);
2186
2194
  } catch (error: unknown) {
2187
2195
  return {
@@ -2204,12 +2212,16 @@ export const toggleRouteAction = routeManagementStatePart.createAction<{
2204
2212
  interfaces.requests.IReq_ToggleRoute
2205
2213
  >('/typedrequest', 'toggleRoute');
2206
2214
 
2207
- await request.fire({
2215
+ const response = await request.fire({
2208
2216
  identity: context.identity!,
2209
2217
  id: dataArg.id,
2210
2218
  enabled: dataArg.enabled,
2211
2219
  });
2212
2220
 
2221
+ if (!response.success) {
2222
+ throw new Error(response.message || 'Failed to toggle route');
2223
+ }
2224
+
2213
2225
  return await actionContext!.dispatch(fetchMergedRoutesAction, null);
2214
2226
  } catch (error: unknown) {
2215
2227
  return {
@@ -2765,4 +2777,4 @@ startAutoRefresh();
2765
2777
  // Connect TypedSocket if already logged in (e.g., persistent session)
2766
2778
  if (loginStatePart.getState()!.isLoggedIn) {
2767
2779
  connectSocket();
2768
- }
2780
+ }
@@ -272,15 +272,13 @@ export class OpsViewRoutes extends DeesElement {
272
272
  const clickedRoute = e.detail;
273
273
  if (!clickedRoute) return;
274
274
 
275
- // Find the corresponding merged route
276
- const merged = this.routeState.mergedRoutes.find(
277
- (mr) => mr.route.name === clickedRoute.name,
278
- );
275
+ const merged = this.findMergedRoute(clickedRoute);
279
276
  if (!merged) return;
280
277
 
281
278
  const { DeesModal } = await import('@design.estate/dees-catalog');
282
279
 
283
280
  const meta = merged.metadata;
281
+ const isSystemManaged = this.isSystemManagedRoute(merged);
284
282
  await DeesModal.createAndShow({
285
283
  heading: `Route: ${merged.route.name}`,
286
284
  content: html`
@@ -288,6 +286,7 @@ export class OpsViewRoutes extends DeesElement {
288
286
  <p>Origin: <strong style="color: #0af;">${merged.origin}</strong></p>
289
287
  <p>Status: <strong>${merged.enabled ? 'Enabled' : 'Disabled'}</strong></p>
290
288
  <p>ID: <code style="color: #888;">${merged.id}</code></p>
289
+ ${isSystemManaged ? html`<p>This route is system-managed. Change its source config to modify it directly.</p>` : ''}
291
290
  ${meta?.sourceProfileName ? html`<p>Source Profile: <strong style="color: #a78bfa;">${meta.sourceProfileName}</strong></p>` : ''}
292
291
  ${meta?.networkTargetName ? html`<p>Network Target: <strong style="color: #a78bfa;">${meta.networkTargetName}</strong></p>` : ''}
293
292
  </div>
@@ -304,25 +303,29 @@ export class OpsViewRoutes extends DeesElement {
304
303
  await modalArg.destroy();
305
304
  },
306
305
  },
307
- {
308
- name: 'Edit',
309
- iconName: 'lucide:pencil',
310
- action: async (modalArg: any) => {
311
- await modalArg.destroy();
312
- this.showEditRouteDialog(merged);
313
- },
314
- },
315
- {
316
- name: 'Delete',
317
- iconName: 'lucide:trash-2',
318
- action: async (modalArg: any) => {
319
- await appstate.routeManagementStatePart.dispatchAction(
320
- appstate.deleteRouteAction,
321
- merged.id,
322
- );
323
- await modalArg.destroy();
324
- },
325
- },
306
+ ...(!isSystemManaged
307
+ ? [
308
+ {
309
+ name: 'Edit',
310
+ iconName: 'lucide:pencil',
311
+ action: async (modalArg: any) => {
312
+ await modalArg.destroy();
313
+ this.showEditRouteDialog(merged);
314
+ },
315
+ },
316
+ {
317
+ name: 'Delete',
318
+ iconName: 'lucide:trash-2',
319
+ action: async (modalArg: any) => {
320
+ await appstate.routeManagementStatePart.dispatchAction(
321
+ appstate.deleteRouteAction,
322
+ merged.id,
323
+ );
324
+ await modalArg.destroy();
325
+ },
326
+ },
327
+ ]
328
+ : []),
326
329
  {
327
330
  name: 'Close',
328
331
  iconName: 'lucide:x',
@@ -336,10 +339,9 @@ export class OpsViewRoutes extends DeesElement {
336
339
  const clickedRoute = e.detail;
337
340
  if (!clickedRoute) return;
338
341
 
339
- const merged = this.routeState.mergedRoutes.find(
340
- (mr) => mr.route.name === clickedRoute.name,
341
- );
342
+ const merged = this.findMergedRoute(clickedRoute);
342
343
  if (!merged) return;
344
+ if (this.isSystemManagedRoute(merged)) return;
343
345
 
344
346
  this.showEditRouteDialog(merged);
345
347
  }
@@ -348,10 +350,9 @@ export class OpsViewRoutes extends DeesElement {
348
350
  const clickedRoute = e.detail;
349
351
  if (!clickedRoute) return;
350
352
 
351
- const merged = this.routeState.mergedRoutes.find(
352
- (mr) => mr.route.name === clickedRoute.name,
353
- );
353
+ const merged = this.findMergedRoute(clickedRoute);
354
354
  if (!merged) return;
355
+ if (this.isSystemManagedRoute(merged)) return;
355
356
 
356
357
  const { DeesModal } = await import('@design.estate/dees-catalog');
357
358
  await DeesModal.createAndShow({
@@ -675,6 +676,23 @@ export class OpsViewRoutes extends DeesElement {
675
676
  appstate.routeManagementStatePart.dispatchAction(appstate.fetchMergedRoutesAction, null);
676
677
  }
677
678
 
679
+ private findMergedRoute(clickedRoute: { id?: string; name?: string }): interfaces.data.IMergedRoute | undefined {
680
+ if (clickedRoute.id) {
681
+ const routeById = this.routeState.mergedRoutes.find((mr) => mr.id === clickedRoute.id);
682
+ if (routeById) return routeById;
683
+ }
684
+
685
+ if (clickedRoute.name) {
686
+ return this.routeState.mergedRoutes.find((mr) => mr.route.name === clickedRoute.name);
687
+ }
688
+
689
+ return undefined;
690
+ }
691
+
692
+ private isSystemManagedRoute(merged: interfaces.data.IMergedRoute): boolean {
693
+ return merged.origin !== 'api';
694
+ }
695
+
678
696
  async firstUpdated() {
679
697
  await appstate.routeManagementStatePart.dispatchAction(appstate.fetchMergedRoutesAction, null);
680
698