@serve.zone/dcrouter 16.2.1 → 16.3.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 (38) hide show
  1. package/deno.json +1 -1
  2. package/dist_serve/bundle.js +1024 -960
  3. package/dist_ts/00_commitinfo_data.js +1 -1
  4. package/dist_ts/config/helpers.http-redirects.d.ts +1 -1
  5. package/dist_ts/config/helpers.http-redirects.js +4 -3
  6. package/dist_ts_interfaces/data/route-management.d.ts +2 -0
  7. package/dist_ts_interfaces/data/route-management.js +6 -1
  8. package/dist_ts_web/00_commitinfo_data.js +1 -1
  9. package/dist_ts_web/elements/access/ops-view-apitokens.js +3 -3
  10. package/dist_ts_web/elements/access/ops-view-gatewayclients.js +3 -3
  11. package/dist_ts_web/elements/network/ops-view-network-activity.js +6 -6
  12. package/dist_ts_web/elements/network/ops-view-redirects.d.ts +2 -0
  13. package/dist_ts_web/elements/network/ops-view-redirects.js +14 -3
  14. package/dist_ts_web/elements/network/ops-view-remoteingress.js +3 -3
  15. package/dist_ts_web/elements/network/ops-view-routes.d.ts +6 -7
  16. package/dist_ts_web/elements/network/ops-view-routes.js +178 -132
  17. package/dist_ts_web/elements/network/ops-view-special-forwards.d.ts +2 -0
  18. package/dist_ts_web/elements/network/ops-view-special-forwards.js +38 -10
  19. package/dist_ts_web/elements/network/ops-view-vpn.js +3 -3
  20. package/dist_ts_web/elements/network/route-view-filters.d.ts +6 -0
  21. package/dist_ts_web/elements/network/route-view-filters.js +29 -0
  22. package/dist_ts_web/elements/security/ops-view-security-blocked.js +6 -6
  23. package/package.json +2 -2
  24. package/readme.md +1 -1
  25. package/ts/00_commitinfo_data.ts +1 -1
  26. package/ts/config/helpers.http-redirects.ts +6 -3
  27. package/ts_web/00_commitinfo_data.ts +1 -1
  28. package/ts_web/elements/access/ops-view-apitokens.ts +2 -2
  29. package/ts_web/elements/access/ops-view-gatewayclients.ts +2 -2
  30. package/ts_web/elements/network/ops-view-network-activity.ts +5 -5
  31. package/ts_web/elements/network/ops-view-redirects.ts +9 -1
  32. package/ts_web/elements/network/ops-view-remoteingress.ts +2 -2
  33. package/ts_web/elements/network/ops-view-routes.ts +197 -138
  34. package/ts_web/elements/network/ops-view-special-forwards.ts +35 -7
  35. package/ts_web/elements/network/ops-view-vpn.ts +2 -2
  36. package/ts_web/elements/network/route-view-filters.ts +43 -0
  37. package/ts_web/elements/security/ops-view-security-blocked.ts +5 -5
  38. package/ts_web/readme.md +4 -4
@@ -3,6 +3,6 @@
3
3
  */
4
4
  export const commitinfo = {
5
5
  name: '@serve.zone/dcrouter',
6
- version: '16.2.1',
6
+ version: '16.3.0',
7
7
  description: 'A multifaceted routing service handling mail and SMS delivery functions.'
8
8
  }
@@ -133,7 +133,7 @@ export class OpsViewApiTokens extends DeesElement {
133
133
  name: 'Enable',
134
134
  iconName: 'lucide:play',
135
135
  type: ['inRow', 'contextmenu'] as any,
136
- actionRelevancyCheckFunc: (actionData: any) => !actionData.item.enabled,
136
+ actionRelevancyCheckFunc: (token: any) => !token.enabled,
137
137
  actionFunc: async (actionData: any) => {
138
138
  const token = actionData.item as interfaces.data.IApiTokenInfo;
139
139
  await appstate.routeManagementStatePart.dispatchAction(
@@ -146,7 +146,7 @@ export class OpsViewApiTokens extends DeesElement {
146
146
  name: 'Disable',
147
147
  iconName: 'lucide:pause',
148
148
  type: ['inRow', 'contextmenu'] as any,
149
- actionRelevancyCheckFunc: (actionData: any) => actionData.item.enabled,
149
+ actionRelevancyCheckFunc: (token: any) => token.enabled,
150
150
  actionFunc: async (actionData: any) => {
151
151
  const token = actionData.item as interfaces.data.IApiTokenInfo;
152
152
  await appstate.routeManagementStatePart.dispatchAction(
@@ -97,7 +97,7 @@ export class OpsViewGatewayClients extends DeesElement {
97
97
  name: 'Enable',
98
98
  iconName: 'lucide:play',
99
99
  type: ['inRow', 'contextmenu'] as any,
100
- actionRelevancyCheckFunc: (actionData: any) => !actionData.item.enabled,
100
+ actionRelevancyCheckFunc: (client: any) => !client.enabled,
101
101
  actionFunc: async (actionData: any) => {
102
102
  await appstate.routeManagementStatePart.dispatchAction(appstate.updateGatewayClientAction, {
103
103
  id: actionData.item.id,
@@ -109,7 +109,7 @@ export class OpsViewGatewayClients extends DeesElement {
109
109
  name: 'Disable',
110
110
  iconName: 'lucide:pause',
111
111
  type: ['inRow', 'contextmenu'] as any,
112
- actionRelevancyCheckFunc: (actionData: any) => actionData.item.enabled,
112
+ actionRelevancyCheckFunc: (client: any) => client.enabled,
113
113
  actionFunc: async (actionData: any) => {
114
114
  await appstate.routeManagementStatePart.dispatchAction(appstate.updateGatewayClientAction, {
115
115
  id: actionData.item.id,
@@ -415,7 +415,7 @@ export class OpsViewNetworkActivity extends DeesElement {
415
415
  name: 'Block Network Range',
416
416
  iconName: 'lucide:network',
417
417
  type: ['contextmenu'] as any,
418
- actionRelevancyCheckFunc: (actionData: any) => Boolean(this.getIpIntelligence(actionData.item.ip)?.networkRange),
418
+ actionRelevancyCheckFunc: (entry: any) => Boolean(this.getIpIntelligence(entry.ip)?.networkRange),
419
419
  actionFunc: async (actionData: any) => {
420
420
  const record = this.getIpIntelligence(actionData.item.ip);
421
421
  await this.createBlockRuleDialog('cidr', record!.networkRange!, 'Blocked network range from Network Activity');
@@ -425,7 +425,7 @@ export class OpsViewNetworkActivity extends DeesElement {
425
425
  name: 'Block ASN',
426
426
  iconName: 'lucide:radio-tower',
427
427
  type: ['contextmenu'] as any,
428
- actionRelevancyCheckFunc: (actionData: any) => Boolean(this.getIpIntelligence(actionData.item.ip)?.asn),
428
+ actionRelevancyCheckFunc: (entry: any) => Boolean(this.getIpIntelligence(entry.ip)?.asn),
429
429
  actionFunc: async (actionData: any) => {
430
430
  const record = this.getIpIntelligence(actionData.item.ip);
431
431
  await this.createBlockRuleDialog('asn', String(record!.asn), 'Blocked ASN from Network Activity');
@@ -435,7 +435,7 @@ export class OpsViewNetworkActivity extends DeesElement {
435
435
  name: 'Block Organization',
436
436
  iconName: 'lucide:building-2',
437
437
  type: ['contextmenu'] as any,
438
- actionRelevancyCheckFunc: (actionData: any) => Boolean(this.getIpOrganization(this.getIpIntelligence(actionData.item.ip))),
438
+ actionRelevancyCheckFunc: (entry: any) => Boolean(this.getIpOrganization(this.getIpIntelligence(entry.ip))),
439
439
  actionFunc: async (actionData: any) => {
440
440
  const record = this.getIpIntelligence(actionData.item.ip);
441
441
  await this.createBlockRuleDialog('organization', this.getIpOrganization(record), 'Blocked organization from Network Activity');
@@ -445,7 +445,7 @@ export class OpsViewNetworkActivity extends DeesElement {
445
445
  name: 'View Intelligence',
446
446
  iconName: 'lucide:info',
447
447
  type: ['doubleClick', 'contextmenu'] as any,
448
- actionRelevancyCheckFunc: (actionData: any) => Boolean(this.getIpIntelligence(actionData.item.ip)),
448
+ actionRelevancyCheckFunc: (entry: any) => Boolean(this.getIpIntelligence(entry.ip)),
449
449
  actionFunc: async (actionData: any) => {
450
450
  await this.showIpIntelligenceDetails(actionData.item.ip);
451
451
  },
@@ -467,7 +467,7 @@ export class OpsViewNetworkActivity extends DeesElement {
467
467
  name: 'Block Organization',
468
468
  iconName: 'lucide:building-2',
469
469
  type: ['contextmenu'] as any,
470
- actionRelevancyCheckFunc: (actionData: any) => Boolean(actionData.item.organization),
470
+ actionRelevancyCheckFunc: (entry: any) => Boolean(entry.organization),
471
471
  actionFunc: async (actionData: any) => {
472
472
  await this.createBlockRuleDialog('organization', actionData.item.organization, 'Blocked organization from Network Activity');
473
473
  },
@@ -12,6 +12,10 @@ import { type IStatsTile } from '@design.estate/dees-catalog';
12
12
  import * as appstate from '../../appstate.js';
13
13
  import * as interfaces from '../../../dist_ts_interfaces/index.js';
14
14
  import { viewHostCss } from '../shared/css.js';
15
+ import {
16
+ redirectMatchesOwner,
17
+ type TRouteOwnerFilter,
18
+ } from './route-view-filters.js';
15
19
 
16
20
  declare global {
17
21
  interface HTMLElementTagNameMap {
@@ -23,6 +27,8 @@ declare global {
23
27
  export class OpsViewRedirects extends DeesElement {
24
28
  @property({ type: Boolean })
25
29
  accessor embedded = false;
30
+ @property({ type: String })
31
+ accessor ownerFilter: TRouteOwnerFilter = 'all';
26
32
 
27
33
  @state()
28
34
  accessor routeState: appstate.IRouteManagementState = appstate.routeManagementStatePart.getState()!;
@@ -72,7 +78,9 @@ export class OpsViewRedirects extends DeesElement {
72
78
  ];
73
79
 
74
80
  public render(): TemplateResult {
75
- const redirects = this.routeState.httpRedirects || [];
81
+ const redirects = (this.routeState.httpRedirects || []).filter((redirect) => {
82
+ return redirectMatchesOwner(redirect, this.routeState.mergedRoutes, this.ownerFilter);
83
+ });
76
84
  const activeCount = redirects.filter((redirect) => redirect.status === 'active').length;
77
85
  const coveredCount = redirects.filter((redirect) => redirect.status === 'covered').length;
78
86
  const skippedCount = redirects.filter((redirect) => redirect.status === 'skipped').length;
@@ -344,7 +344,7 @@ export class OpsViewRemoteIngress extends DeesElement {
344
344
  name: 'Enable',
345
345
  iconName: 'lucide:play',
346
346
  type: ['inRow', 'contextmenu'] as any,
347
- actionRelevancyCheckFunc: (actionData: any) => !actionData.item.enabled,
347
+ actionRelevancyCheckFunc: (endpoint: any) => !endpoint.enabled,
348
348
  actionFunc: async (actionData: any) => {
349
349
  const edge = actionData.item as interfaces.data.IRemoteIngress;
350
350
  await appstate.remoteIngressStatePart.dispatchAction(
@@ -357,7 +357,7 @@ export class OpsViewRemoteIngress extends DeesElement {
357
357
  name: 'Disable',
358
358
  iconName: 'lucide:pause',
359
359
  type: ['inRow', 'contextmenu'] as any,
360
- actionRelevancyCheckFunc: (actionData: any) => actionData.item.enabled,
360
+ actionRelevancyCheckFunc: (endpoint: any) => endpoint.enabled,
361
361
  actionFunc: async (actionData: any) => {
362
362
  const edge = actionData.item as interfaces.data.IRemoteIngress;
363
363
  await appstate.remoteIngressStatePart.dispatchAction(
@@ -1,9 +1,17 @@
1
1
  import * as appstate from '../../appstate.js';
2
2
  import * as interfaces from '../../../dist_ts_interfaces/index.js';
3
3
  import { viewHostCss } from '../shared/css.js';
4
- import { type DeesAppuiTabs, type IStatsTile } from '@design.estate/dees-catalog';
4
+ import {
5
+ type DeesInputMultitoggle,
6
+ type IStatsTile,
7
+ } from '@design.estate/dees-catalog';
5
8
  import './ops-view-redirects.js';
6
9
  import './ops-view-special-forwards.js';
10
+ import {
11
+ getRouteOwner,
12
+ routeMatchesOwner,
13
+ type TRouteOwnerFilter,
14
+ } from './route-view-filters.js';
7
15
  import type {
8
16
  IRoutePathClassOption as ISzRoutePathClassOption,
9
17
  IRouteSourcePolicyPreset as ISzRouteSourcePolicyPreset,
@@ -21,9 +29,10 @@ import {
21
29
  type TemplateResult,
22
30
  } from '@design.estate/dees-element';
23
31
 
24
- type TRoutesSection = 'standard' | 'special' | 'redirects';
25
- type TRouteOwnerFilter = 'all' | 'operator' | 'gatewayClient' | 'system';
26
- type IMenuItem = DeesAppuiTabs['tabs'][number];
32
+ type TRoutesSection = 'all' | 'standard' | 'redirects' | 'special';
33
+
34
+ const routeTypeOptions = ['All', 'Standard', 'Redirect', 'Special'];
35
+ const routeOwnerOptions = ['All', 'User', 'Gateway Client', 'System'];
27
36
 
28
37
  // TLS dropdown options shared by create and edit dialogs
29
38
  const tlsModeOptions = [
@@ -327,34 +336,6 @@ export class OpsViewRoutes extends DeesElement {
327
336
  @state() accessor activeSection: TRoutesSection = 'standard';
328
337
  @state() accessor ownerFilter: TRouteOwnerFilter = 'all';
329
338
 
330
- private readonly sectionTabs: IMenuItem[] = [
331
- {
332
- key: 'Standard Routes',
333
- iconName: 'lucide:route',
334
- action: () => this.setRouteView('standard'),
335
- },
336
- {
337
- key: 'Special Forwards',
338
- iconName: 'lucide:split',
339
- action: () => this.setRouteView('special'),
340
- },
341
- {
342
- key: 'Redirects',
343
- iconName: 'lucide:cornerDownRight',
344
- action: () => this.setRouteView('redirects'),
345
- },
346
- ];
347
-
348
- private readonly ownerTabs: IMenuItem[] = [
349
- { key: 'All', action: () => this.setRouteOwnerFilter('all') },
350
- { key: 'Operator', action: () => this.setRouteOwnerFilter('operator') },
351
- {
352
- key: 'Gateway Client',
353
- action: () => this.setRouteOwnerFilter('gatewayClient'),
354
- },
355
- { key: 'System', action: () => this.setRouteOwnerFilter('system') },
356
- ];
357
-
358
339
  private readonly popStateHandler = () => {
359
340
  const urlState = this.readUrlState();
360
341
  this.activeSection = urlState.section;
@@ -433,6 +414,25 @@ export class OpsViewRoutes extends DeesElement {
433
414
  gap: 24px;
434
415
  }
435
416
 
417
+ .routeFilters {
418
+ display: flex;
419
+ align-items: flex-start;
420
+ gap: 16px;
421
+ flex-wrap: wrap;
422
+ }
423
+
424
+ .routeFilter {
425
+ min-width: 0;
426
+ max-width: 100%;
427
+ overflow-x: auto;
428
+ }
429
+
430
+ @media (max-width: 760px) {
431
+ .routeFilters {
432
+ flex-direction: column;
433
+ }
434
+ }
435
+
436
436
  .warnings-bar {
437
437
  background: ${cssManager.bdTheme('rgba(255, 170, 0, 0.08)', 'rgba(255, 170, 0, 0.1)')};
438
438
  border: 1px solid ${cssManager.bdTheme('rgba(255, 170, 0, 0.25)', 'rgba(255, 170, 0, 0.3)')};
@@ -467,41 +467,41 @@ export class OpsViewRoutes extends DeesElement {
467
467
 
468
468
  public render(): TemplateResult {
469
469
  const { mergedRoutes, warnings } = this.routeState;
470
+ const standardRoutes = mergedRoutes.filter((route) => !route.metadata?.managedRouteKind);
471
+ const filteredRoutes = standardRoutes.filter((route) => {
472
+ return routeMatchesOwner(route, this.ownerFilter);
473
+ });
470
474
 
471
- const disabledCount = mergedRoutes.filter((mr) => !mr.enabled).length;
472
- const configCount = mergedRoutes.filter((mr) => mr.origin !== 'api').length;
473
- const operatorCount = mergedRoutes.filter((mr) => {
474
- return mr.origin === 'api'
475
- && mr.metadata?.ownerType !== 'gatewayClient'
476
- && !mr.metadata?.managedRouteKind;
477
- }).length;
475
+ const disabledCount = filteredRoutes.filter((route) => !route.enabled).length;
476
+ const systemCount = filteredRoutes.filter((route) => getRouteOwner(route) === 'system').length;
477
+ const userCount = filteredRoutes.filter((route) => getRouteOwner(route) === 'operator').length;
478
478
 
479
479
  const statsTiles: IStatsTile[] = [
480
480
  {
481
481
  id: 'totalRoutes',
482
- title: 'Total Routes',
482
+ title: 'Standard Routes',
483
483
  type: 'number',
484
- value: mergedRoutes.length,
484
+ value: filteredRoutes.length,
485
485
  icon: 'lucide:route',
486
- description: 'All configured routes',
486
+ description: 'Matching non-special routes',
487
487
  color: '#3b82f6',
488
488
  },
489
489
  {
490
490
  id: 'configRoutes',
491
491
  title: 'System Routes',
492
492
  type: 'number',
493
- value: configCount,
493
+ value: systemCount,
494
494
  icon: 'lucide:settings',
495
495
  description: 'From config, email, and DNS',
496
496
  color: '#8b5cf6',
497
497
  },
498
498
  {
499
- id: 'operatorRoutes',
500
- title: 'Operator Routes',
499
+ id: 'userRoutes',
500
+ title: 'User Routes',
501
501
  type: 'number',
502
- value: operatorCount,
502
+ value: userCount,
503
503
  icon: 'lucide:code',
504
- description: 'Created directly by operators',
504
+ description: 'Created directly by users',
505
505
  color: '#0ea5e9',
506
506
  },
507
507
  {
@@ -515,20 +515,6 @@ export class OpsViewRoutes extends DeesElement {
515
515
  },
516
516
  ];
517
517
 
518
- const standardRoutes = mergedRoutes.filter((route) => !route.metadata?.managedRouteKind);
519
- const filteredRoutes = standardRoutes.filter((route) => {
520
- switch (this.ownerFilter) {
521
- case 'operator':
522
- return route.origin === 'api' && route.metadata?.ownerType !== 'gatewayClient';
523
- case 'gatewayClient':
524
- return route.metadata?.ownerType === 'gatewayClient';
525
- case 'system':
526
- return route.origin !== 'api' || route.metadata?.ownerType === 'system';
527
- default:
528
- return true;
529
- }
530
- });
531
-
532
518
  // Map filtered routes to sz-route-list-view format
533
519
  const szRoutes = filteredRoutes.map((mr) => {
534
520
  const tags = [...(mr.route.tags || [])];
@@ -549,69 +535,97 @@ export class OpsViewRoutes extends DeesElement {
549
535
  <dees-heading level="3">Route Management</dees-heading>
550
536
 
551
537
  <div class="routesContainer">
552
- <dees-appui-tabs
553
- .tabs=${this.sectionTabs}
554
- .selectedTab=${this.getSelectedSectionTab()}
555
- ></dees-appui-tabs>
556
-
557
- ${this.activeSection === 'special'
558
- ? html`<ops-view-special-forwards></ops-view-special-forwards>`
559
- : this.activeSection === 'redirects'
560
- ? html`<ops-view-redirects .embedded=${true}></ops-view-redirects>`
561
- : html`
562
- <dees-statsgrid
563
- .tiles=${statsTiles}
564
- .gridActions=${[
565
- {
566
- name: 'Add Route',
567
- iconName: 'lucide:plus',
568
- action: () => this.showCreateRouteDialog(),
569
- },
570
- {
571
- name: 'Refresh',
572
- iconName: 'lucide:refreshCw',
573
- action: () => this.refreshData(),
574
- },
575
- ]}
576
- ></dees-statsgrid>
577
-
578
- <dees-appui-tabs
579
- .tabs=${this.ownerTabs}
580
- .selectedTab=${this.getSelectedOwnerTab()}
581
- ></dees-appui-tabs>
582
-
583
- ${warnings.length > 0
538
+ <div class="routeFilters">
539
+ <div class="routeFilter">
540
+ <dees-input-multitoggle
541
+ id="routeTypeFilter"
542
+ .label=${'Type'}
543
+ .options=${routeTypeOptions}
544
+ .value=${this.getSelectedRouteTypeOption()}
545
+ ></dees-input-multitoggle>
546
+ </div>
547
+ <div class="routeFilter">
548
+ <dees-input-multitoggle
549
+ id="routeOwnerFilter"
550
+ .label=${'Owner'}
551
+ .options=${routeOwnerOptions}
552
+ .value=${this.getSelectedOwnerOption()}
553
+ ></dees-input-multitoggle>
554
+ </div>
555
+ </div>
556
+
557
+ ${this.activeSection === 'all' || this.activeSection === 'standard'
584
558
  ? html`
585
- <div class="warnings-bar">
586
- ${warnings.map(
587
- (w) => html`
588
- <div class="warning-item">
589
- <span class="warning-icon">&#9888;</span>
590
- <span>${w.message}</span>
559
+ ${this.activeSection === 'all'
560
+ ? html`<dees-heading level="4">Standard Routes</dees-heading>`
561
+ : ''}
562
+ <dees-statsgrid
563
+ .tiles=${statsTiles}
564
+ .gridActions=${[
565
+ {
566
+ name: 'Add Route',
567
+ iconName: 'lucide:plus',
568
+ action: () => this.showCreateRouteDialog(),
569
+ },
570
+ {
571
+ name: 'Refresh',
572
+ iconName: 'lucide:refreshCw',
573
+ action: () => this.refreshData(),
574
+ },
575
+ ]}
576
+ ></dees-statsgrid>
577
+
578
+ ${warnings.length > 0
579
+ ? html`
580
+ <div class="warnings-bar">
581
+ ${warnings.map(
582
+ (warning) => html`
583
+ <div class="warning-item">
584
+ <span class="warning-icon">&#9888;</span>
585
+ <span>${warning.message}</span>
586
+ </div>
587
+ `,
588
+ )}
589
+ </div>
590
+ `
591
+ : ''}
592
+
593
+ ${szRoutes.length > 0
594
+ ? html`
595
+ <sz-route-list-view
596
+ .routes=${szRoutes}
597
+ .showActionsFilter=${(route: { id?: string; name?: string }) =>
598
+ this.canEditRoute(route)}
599
+ @route-click=${(event: CustomEvent) => this.handleRouteClick(event)}
600
+ @route-edit=${(event: CustomEvent) => this.handleRouteEdit(event)}
601
+ @route-delete=${(event: CustomEvent) => this.handleRouteDelete(event)}
602
+ ></sz-route-list-view>
603
+ `
604
+ : html`
605
+ <div class="empty-state">
606
+ <p>No standard routes match these filters</p>
607
+ <p>Choose another ownership filter or add a user route.</p>
591
608
  </div>
592
- `,
593
- )}
594
- </div>
609
+ `}
595
610
  `
596
611
  : ''}
597
612
 
598
- ${szRoutes.length > 0
613
+ ${this.activeSection === 'all' || this.activeSection === 'special'
599
614
  ? html`
600
- <sz-route-list-view
601
- .routes=${szRoutes}
602
- .showActionsFilter=${(route: { id?: string; name?: string }) => this.canEditRoute(route)}
603
- @route-click=${(e: CustomEvent) => this.handleRouteClick(e)}
604
- @route-edit=${(e: CustomEvent) => this.handleRouteEdit(e)}
605
- @route-delete=${(e: CustomEvent) => this.handleRouteDelete(e)}
606
- ></sz-route-list-view>
615
+ <ops-view-special-forwards
616
+ .ownerFilter=${this.ownerFilter}
617
+ ></ops-view-special-forwards>
607
618
  `
608
- : html`
609
- <div class="empty-state">
610
- <p>No ${this.getOwnerFilterLabel().toLowerCase()} routes</p>
611
- <p>Choose another ownership filter or add an operator route.</p>
612
- </div>
613
- `}
614
- `}
619
+ : ''}
620
+
621
+ ${this.activeSection === 'all' || this.activeSection === 'redirects'
622
+ ? html`
623
+ <ops-view-redirects
624
+ .embedded=${true}
625
+ .ownerFilter=${this.ownerFilter}
626
+ ></ops-view-redirects>
627
+ `
628
+ : ''}
615
629
  </div>
616
630
  `;
617
631
  }
@@ -1107,28 +1121,52 @@ export class OpsViewRoutes extends DeesElement {
1107
1121
  appstate.routeManagementStatePart.dispatchAction(appstate.fetchMergedRoutesAction, null);
1108
1122
  }
1109
1123
 
1110
- private getSelectedSectionTab(): IMenuItem {
1111
- const index = this.activeSection === 'special'
1112
- ? 1
1113
- : this.activeSection === 'redirects'
1114
- ? 2
1115
- : 0;
1116
- return this.sectionTabs[index];
1124
+ private getSelectedRouteTypeOption(): string {
1125
+ switch (this.activeSection) {
1126
+ case 'all':
1127
+ return 'All';
1128
+ case 'redirects':
1129
+ return 'Redirect';
1130
+ case 'special':
1131
+ return 'Special';
1132
+ default:
1133
+ return 'Standard';
1134
+ }
1117
1135
  }
1118
1136
 
1119
- private getSelectedOwnerTab(): IMenuItem {
1120
- const index = this.ownerFilter === 'operator'
1121
- ? 1
1122
- : this.ownerFilter === 'gatewayClient'
1123
- ? 2
1124
- : this.ownerFilter === 'system'
1125
- ? 3
1126
- : 0;
1127
- return this.ownerTabs[index];
1137
+ private getSelectedOwnerOption(): string {
1138
+ switch (this.ownerFilter) {
1139
+ case 'operator':
1140
+ return 'User';
1141
+ case 'gatewayClient':
1142
+ return 'Gateway Client';
1143
+ case 'system':
1144
+ return 'System';
1145
+ default:
1146
+ return 'All';
1147
+ }
1128
1148
  }
1129
1149
 
1130
- private getOwnerFilterLabel(): string {
1131
- return this.getSelectedOwnerTab().key;
1150
+ private handleRouteTypeChange(toggle: DeesInputMultitoggle): void {
1151
+ const section: TRoutesSection = toggle.selectedOption === 'All'
1152
+ ? 'all'
1153
+ : toggle.selectedOption === 'Redirect'
1154
+ ? 'redirects'
1155
+ : toggle.selectedOption === 'Special'
1156
+ ? 'special'
1157
+ : 'standard';
1158
+ this.setRouteView(section);
1159
+ }
1160
+
1161
+ private handleRouteOwnerChange(toggle: DeesInputMultitoggle): void {
1162
+ const owner: TRouteOwnerFilter = toggle.selectedOption === 'User'
1163
+ ? 'operator'
1164
+ : toggle.selectedOption === 'Gateway Client'
1165
+ ? 'gatewayClient'
1166
+ : toggle.selectedOption === 'System'
1167
+ ? 'system'
1168
+ : 'all';
1169
+ this.setRouteOwnerFilter(owner);
1132
1170
  }
1133
1171
 
1134
1172
  private setRouteView(section: TRoutesSection): void {
@@ -1147,7 +1185,11 @@ export class OpsViewRoutes extends DeesElement {
1147
1185
  const params = new URL(window.location.href).searchParams;
1148
1186
  const tabParam = params.get('tab');
1149
1187
  const ownerParam = params.get('owner');
1150
- const section: TRoutesSection = tabParam === 'special' || tabParam === 'redirects'
1188
+ const section: TRoutesSection = (
1189
+ tabParam === 'all'
1190
+ || tabParam === 'special'
1191
+ || tabParam === 'redirects'
1192
+ )
1151
1193
  ? tabParam
1152
1194
  : 'standard';
1153
1195
  const owner: TRouteOwnerFilter = (
@@ -1214,12 +1256,29 @@ export class OpsViewRoutes extends DeesElement {
1214
1256
  }
1215
1257
 
1216
1258
  private isSystemManagedRoute(merged: interfaces.data.IMergedRoute): boolean {
1217
- return merged.origin !== 'api'
1218
- || merged.metadata?.ownerType === 'gatewayClient'
1259
+ return getRouteOwner(merged) !== 'operator'
1219
1260
  || Boolean(merged.metadata?.managedRouteKind);
1220
1261
  }
1221
1262
 
1222
1263
  async firstUpdated() {
1264
+ const typeToggle = this.shadowRoot?.querySelector(
1265
+ '#routeTypeFilter',
1266
+ ) as DeesInputMultitoggle | null;
1267
+ const ownerToggle = this.shadowRoot?.querySelector(
1268
+ '#routeOwnerFilter',
1269
+ ) as DeesInputMultitoggle | null;
1270
+
1271
+ if (typeToggle) {
1272
+ this.rxSubscriptions.push(typeToggle.changeSubject.subscribe((toggle) => {
1273
+ this.handleRouteTypeChange(toggle);
1274
+ }));
1275
+ }
1276
+ if (ownerToggle) {
1277
+ this.rxSubscriptions.push(ownerToggle.changeSubject.subscribe((toggle) => {
1278
+ this.handleRouteOwnerChange(toggle);
1279
+ }));
1280
+ }
1281
+
1223
1282
  await appstate.routeManagementStatePart.dispatchAction(appstate.fetchMergedRoutesAction, null);
1224
1283
  }
1225
1284
  }