@serve.zone/dcrouter 13.1.3 → 13.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 (69) hide show
  1. package/dist_serve/bundle.js +1202 -1133
  2. package/dist_ts/00_commitinfo_data.js +1 -1
  3. package/dist_ts_web/00_commitinfo_data.js +1 -1
  4. package/dist_ts_web/appstate.d.ts +1 -0
  5. package/dist_ts_web/appstate.js +9 -22
  6. package/dist_ts_web/elements/index.d.ts +2 -6
  7. package/dist_ts_web/elements/index.js +3 -7
  8. package/dist_ts_web/elements/network/index.d.ts +6 -0
  9. package/dist_ts_web/elements/network/index.js +7 -0
  10. package/dist_ts_web/elements/{ops-view-network.d.ts → network/ops-view-network-activity.d.ts} +3 -3
  11. package/dist_ts_web/elements/{ops-view-network.js → network/ops-view-network-activity.js} +20 -30
  12. package/dist_ts_web/elements/network/ops-view-network.d.ts +24 -0
  13. package/dist_ts_web/elements/network/ops-view-network.js +151 -0
  14. package/dist_ts_web/elements/{ops-view-networktargets.d.ts → network/ops-view-networktargets.d.ts} +1 -1
  15. package/dist_ts_web/elements/{ops-view-networktargets.js → network/ops-view-networktargets.js} +6 -6
  16. package/dist_ts_web/elements/{ops-view-routes.d.ts → network/ops-view-routes.d.ts} +1 -1
  17. package/dist_ts_web/elements/{ops-view-routes.js → network/ops-view-routes.js} +5 -6
  18. package/dist_ts_web/elements/{ops-view-sourceprofiles.d.ts → network/ops-view-sourceprofiles.d.ts} +1 -1
  19. package/dist_ts_web/elements/{ops-view-sourceprofiles.js → network/ops-view-sourceprofiles.js} +6 -6
  20. package/dist_ts_web/elements/{ops-view-targetprofiles.d.ts → network/ops-view-targetprofiles.d.ts} +2 -2
  21. package/dist_ts_web/elements/{ops-view-targetprofiles.js → network/ops-view-targetprofiles.js} +7 -7
  22. package/dist_ts_web/elements/ops-dashboard.js +4 -27
  23. package/dist_ts_web/elements/ops-view-apitokens.js +2 -1
  24. package/dist_ts_web/elements/ops-view-certificates.js +2 -1
  25. package/dist_ts_web/elements/ops-view-config.js +3 -3
  26. package/dist_ts_web/elements/ops-view-remoteingress.js +2 -1
  27. package/dist_ts_web/elements/ops-view-vpn.js +2 -1
  28. package/dist_ts_web/elements/security/index.d.ts +5 -0
  29. package/dist_ts_web/elements/security/index.js +6 -0
  30. package/dist_ts_web/elements/security/ops-view-security-authentication.d.ts +13 -0
  31. package/dist_ts_web/elements/security/ops-view-security-authentication.js +156 -0
  32. package/dist_ts_web/elements/security/ops-view-security-blocked.d.ts +15 -0
  33. package/dist_ts_web/elements/security/ops-view-security-blocked.js +152 -0
  34. package/dist_ts_web/elements/security/ops-view-security-emailsecurity.d.ts +14 -0
  35. package/dist_ts_web/elements/security/ops-view-security-emailsecurity.js +196 -0
  36. package/dist_ts_web/elements/security/ops-view-security-overview.d.ts +16 -0
  37. package/dist_ts_web/elements/security/ops-view-security-overview.js +204 -0
  38. package/dist_ts_web/elements/security/ops-view-security.d.ts +23 -0
  39. package/dist_ts_web/elements/security/ops-view-security.js +146 -0
  40. package/dist_ts_web/router.d.ts +5 -3
  41. package/dist_ts_web/router.js +69 -17
  42. package/package.json +1 -1
  43. package/ts/00_commitinfo_data.ts +1 -1
  44. package/ts_web/00_commitinfo_data.ts +1 -1
  45. package/ts_web/appstate.ts +10 -24
  46. package/ts_web/elements/index.ts +2 -6
  47. package/ts_web/elements/network/index.ts +6 -0
  48. package/ts_web/elements/{ops-view-network.ts → network/ops-view-network-activity.ts} +43 -53
  49. package/ts_web/elements/network/ops-view-network.ts +119 -0
  50. package/ts_web/elements/{ops-view-networktargets.ts → network/ops-view-networktargets.ts} +5 -5
  51. package/ts_web/elements/{ops-view-routes.ts → network/ops-view-routes.ts} +4 -5
  52. package/ts_web/elements/{ops-view-sourceprofiles.ts → network/ops-view-sourceprofiles.ts} +5 -5
  53. package/ts_web/elements/{ops-view-targetprofiles.ts → network/ops-view-targetprofiles.ts} +6 -6
  54. package/ts_web/elements/ops-dashboard.ts +3 -26
  55. package/ts_web/elements/ops-view-apitokens.ts +1 -0
  56. package/ts_web/elements/ops-view-certificates.ts +1 -0
  57. package/ts_web/elements/ops-view-config.ts +2 -2
  58. package/ts_web/elements/ops-view-remoteingress.ts +1 -0
  59. package/ts_web/elements/ops-view-vpn.ts +1 -0
  60. package/ts_web/elements/security/index.ts +5 -0
  61. package/ts_web/elements/security/ops-view-security-authentication.ts +120 -0
  62. package/ts_web/elements/security/ops-view-security-blocked.ts +117 -0
  63. package/ts_web/elements/security/ops-view-security-emailsecurity.ts +159 -0
  64. package/ts_web/elements/security/ops-view-security-overview.ts +171 -0
  65. package/ts_web/elements/security/ops-view-security.ts +114 -0
  66. package/ts_web/router.ts +75 -17
  67. package/dist_ts_web/elements/ops-view-security.d.ts +0 -24
  68. package/dist_ts_web/elements/ops-view-security.js +0 -481
  69. package/ts_web/elements/ops-view-security.ts +0 -453
@@ -30,6 +30,7 @@ export interface IConfigState {
30
30
 
31
31
  export interface IUiState {
32
32
  activeView: string;
33
+ activeSubview: string | null;
33
34
  sidebarCollapsed: boolean;
34
35
  autoRefresh: boolean;
35
36
  refreshInterval: number; // milliseconds
@@ -116,16 +117,24 @@ export const configStatePart = await appState.getStatePart<IConfigState>(
116
117
  // Determine initial view from URL path
117
118
  const getInitialView = (): string => {
118
119
  const path = typeof window !== 'undefined' ? window.location.pathname : '/';
119
- const validViews = ['overview', 'network', 'emails', 'logs', 'routes', 'apitokens', 'configuration', 'security', 'certificates', 'remoteingress', 'sourceprofiles', 'networktargets', 'targetprofiles'];
120
+ const validViews = ['overview', 'network', 'emails', 'logs', 'apitokens', 'configuration', 'security', 'certificates', 'remoteingress', 'vpn'];
120
121
  const segments = path.split('/').filter(Boolean);
121
122
  const view = segments[0];
122
123
  return validViews.includes(view) ? view : 'overview';
123
124
  };
124
125
 
126
+ // Determine initial subview (second URL segment) from the path
127
+ const getInitialSubview = (): string | null => {
128
+ const path = typeof window !== 'undefined' ? window.location.pathname : '/';
129
+ const segments = path.split('/').filter(Boolean);
130
+ return segments[1] ?? null;
131
+ };
132
+
125
133
  export const uiStatePart = await appState.getStatePart<IUiState>(
126
134
  'ui',
127
135
  {
128
136
  activeView: getInitialView(),
137
+ activeSubview: getInitialSubview(),
129
138
  sidebarCollapsed: false,
130
139
  autoRefresh: true,
131
140
  refreshInterval: 1000, // 1 second
@@ -435,15 +444,6 @@ export const setActiveViewAction = uiStatePart.createAction<string>(async (state
435
444
  }, 100);
436
445
  }
437
446
 
438
- // If switching to routes view, ensure we fetch route data
439
- if (viewName === 'routes' && currentState.activeView !== 'routes') {
440
- setTimeout(() => {
441
- routeManagementStatePart.dispatchAction(fetchMergedRoutesAction, null);
442
- // Also fetch profiles/targets for the Create Route dropdowns
443
- profilesTargetsStatePart.dispatchAction(fetchProfilesAndTargetsAction, null);
444
- }, 100);
445
- }
446
-
447
447
  // If switching to apitokens view, ensure we fetch token data
448
448
  if (viewName === 'apitokens' && currentState.activeView !== 'apitokens') {
449
449
  setTimeout(() => {
@@ -458,20 +458,6 @@ export const setActiveViewAction = uiStatePart.createAction<string>(async (state
458
458
  }, 100);
459
459
  }
460
460
 
461
- // If switching to security profiles or network targets views, fetch profiles/targets data
462
- if ((viewName === 'sourceprofiles' || viewName === 'networktargets') && currentState.activeView !== viewName) {
463
- setTimeout(() => {
464
- profilesTargetsStatePart.dispatchAction(fetchProfilesAndTargetsAction, null);
465
- }, 100);
466
- }
467
-
468
- // If switching to target profiles view, fetch target profiles data
469
- if (viewName === 'targetprofiles' && currentState.activeView !== viewName) {
470
- setTimeout(() => {
471
- targetProfilesStatePart.dispatchAction(fetchTargetProfilesAction, null);
472
- }, 100);
473
- }
474
-
475
461
  return {
476
462
  ...currentState,
477
463
  activeView: viewName,
@@ -1,16 +1,12 @@
1
1
  export * from './ops-dashboard.js';
2
2
  export * from './ops-view-overview.js';
3
- export * from './ops-view-network.js';
3
+ export * from './network/index.js';
4
4
  export * from './ops-view-emails.js';
5
5
  export * from './ops-view-logs.js';
6
6
  export * from './ops-view-config.js';
7
- export * from './ops-view-routes.js';
8
7
  export * from './ops-view-apitokens.js';
9
- export * from './ops-view-security.js';
8
+ export * from './security/index.js';
10
9
  export * from './ops-view-certificates.js';
11
10
  export * from './ops-view-remoteingress.js';
12
11
  export * from './ops-view-vpn.js';
13
- export * from './ops-view-sourceprofiles.js';
14
- export * from './ops-view-networktargets.js';
15
- export * from './ops-view-targetprofiles.js';
16
12
  export * from './shared/index.js';
@@ -0,0 +1,6 @@
1
+ export * from './ops-view-network.js';
2
+ export * from './ops-view-network-activity.js';
3
+ export * from './ops-view-routes.js';
4
+ export * from './ops-view-sourceprofiles.js';
5
+ export * from './ops-view-networktargets.js';
6
+ export * from './ops-view-targetprofiles.js';
@@ -1,12 +1,11 @@
1
1
  import { DeesElement, property, html, customElement, type TemplateResult, css, state, cssManager } from '@design.estate/dees-element';
2
- import * as appstate from '../appstate.js';
3
- import * as interfaces from '../../dist_ts_interfaces/index.js';
4
- import { viewHostCss } from './shared/css.js';
2
+ import * as appstate from '../../appstate.js';
3
+ import * as interfaces from '../../../dist_ts_interfaces/index.js';
5
4
  import { type IStatsTile } from '@design.estate/dees-catalog';
6
5
 
7
6
  declare global {
8
7
  interface HTMLElementTagNameMap {
9
- 'ops-view-network': OpsViewNetwork;
8
+ 'ops-view-network-activity': OpsViewNetworkActivity;
10
9
  }
11
10
  }
12
11
 
@@ -26,14 +25,14 @@ interface INetworkRequest {
26
25
  route?: string;
27
26
  }
28
27
 
29
- @customElement('ops-view-network')
30
- export class OpsViewNetwork extends DeesElement {
28
+ @customElement('ops-view-network-activity')
29
+ export class OpsViewNetworkActivity extends DeesElement {
31
30
  /** How far back the traffic chart shows */
32
31
  private static readonly CHART_WINDOW_MS = 5 * 60 * 1000; // 5 minutes
33
32
  /** How often a new data point is added */
34
33
  private static readonly UPDATE_INTERVAL_MS = 1000; // 1 second
35
34
  /** Derived: max data points the buffer holds */
36
- private static readonly MAX_DATA_POINTS = OpsViewNetwork.CHART_WINDOW_MS / OpsViewNetwork.UPDATE_INTERVAL_MS;
35
+ private static readonly MAX_DATA_POINTS = OpsViewNetworkActivity.CHART_WINDOW_MS / OpsViewNetworkActivity.UPDATE_INTERVAL_MS;
37
36
 
38
37
  @state()
39
38
  accessor statsState = appstate.statsStatePart.getState()!;
@@ -50,10 +49,10 @@ export class OpsViewNetwork extends DeesElement {
50
49
 
51
50
  @state()
52
51
  accessor trafficDataOut: Array<{ x: string | number; y: number }> = [];
53
-
52
+
54
53
  // Track if we need to update the chart to avoid unnecessary re-renders
55
54
  private lastChartUpdate = 0;
56
- private chartUpdateThreshold = OpsViewNetwork.UPDATE_INTERVAL_MS; // Minimum ms between chart updates
55
+ private chartUpdateThreshold = OpsViewNetworkActivity.UPDATE_INTERVAL_MS; // Minimum ms between chart updates
57
56
 
58
57
  private trafficUpdateTimer: any = null;
59
58
  private requestsPerSecHistory: number[] = []; // Track requests/sec over time for trend
@@ -101,17 +100,17 @@ export class OpsViewNetwork extends DeesElement {
101
100
  this.updateNetworkData();
102
101
  });
103
102
  this.rxSubscriptions.push(statsUnsubscribe);
104
-
103
+
105
104
  const networkUnsubscribe = appstate.networkStatePart.select().subscribe((state) => {
106
105
  this.networkState = state;
107
106
  this.updateNetworkData();
108
107
  });
109
108
  this.rxSubscriptions.push(networkUnsubscribe);
110
109
  }
111
-
110
+
112
111
  private initializeTrafficData() {
113
112
  const now = Date.now();
114
- const { MAX_DATA_POINTS, UPDATE_INTERVAL_MS } = OpsViewNetwork;
113
+ const { MAX_DATA_POINTS, UPDATE_INTERVAL_MS } = OpsViewNetworkActivity;
115
114
 
116
115
  // Initialize with empty data points for both in and out
117
116
  const emptyData = Array.from({ length: MAX_DATA_POINTS }, (_, i) => {
@@ -148,7 +147,7 @@ export class OpsViewNetwork extends DeesElement {
148
147
  y: Math.round((p.out * 8) / 1000000 * 10) / 10,
149
148
  }));
150
149
 
151
- const { MAX_DATA_POINTS, UPDATE_INTERVAL_MS } = OpsViewNetwork;
150
+ const { MAX_DATA_POINTS, UPDATE_INTERVAL_MS } = OpsViewNetworkActivity;
152
151
 
153
152
  // Use history as the chart data, keeping the most recent points within the window
154
153
  const sliceStart = Math.max(0, historyIn.length - MAX_DATA_POINTS);
@@ -176,8 +175,8 @@ export class OpsViewNetwork extends DeesElement {
176
175
 
177
176
  public static styles = [
178
177
  cssManager.defaultStyles,
179
- viewHostCss,
180
178
  css`
179
+ :host { display: block; }
181
180
  .networkContainer {
182
181
  display: flex;
183
182
  flex-direction: column;
@@ -285,8 +284,8 @@ export class OpsViewNetwork extends DeesElement {
285
284
 
286
285
  public render() {
287
286
  return html`
288
- <dees-heading level="2">Network Activity</dees-heading>
289
-
287
+ <dees-heading level="hr">Network Activity</dees-heading>
288
+
290
289
  <div class="networkContainer">
291
290
  <!-- Stats Grid -->
292
291
  ${this.renderNetworkStats()}
@@ -307,7 +306,7 @@ export class OpsViewNetwork extends DeesElement {
307
306
  }
308
307
  ]}
309
308
  .realtimeMode=${true}
310
- .rollingWindow=${OpsViewNetwork.CHART_WINDOW_MS}
309
+ .rollingWindow=${OpsViewNetworkActivity.CHART_WINDOW_MS}
311
310
  .yAxisFormatter=${(val: number) => `${val} Mbit/s`}
312
311
  ></dees-chart-area>
313
312
 
@@ -357,7 +356,7 @@ export class OpsViewNetwork extends DeesElement {
357
356
 
358
357
  private async showRequestDetails(request: INetworkRequest) {
359
358
  const { DeesModal } = await import('@design.estate/dees-catalog');
360
-
359
+
361
360
  await DeesModal.createAndShow({
362
361
  heading: 'Request Details',
363
362
  content: html`
@@ -400,10 +399,10 @@ export class OpsViewNetwork extends DeesElement {
400
399
  if (!statusCode) {
401
400
  return html`<span class="statusBadge warning">N/A</span>`;
402
401
  }
403
-
402
+
404
403
  const statusClass = statusCode >= 200 && statusCode < 300 ? 'success' :
405
404
  statusCode >= 400 ? 'error' : 'warning';
406
-
405
+
407
406
  return html`<span class="statusBadge ${statusClass}">${statusCode}</span>`;
408
407
  }
409
408
 
@@ -426,26 +425,26 @@ export class OpsViewNetwork extends DeesElement {
426
425
  const units = ['B', 'KB', 'MB', 'GB'];
427
426
  let size = bytes;
428
427
  let unitIndex = 0;
429
-
428
+
430
429
  while (size >= 1024 && unitIndex < units.length - 1) {
431
430
  size /= 1024;
432
431
  unitIndex++;
433
432
  }
434
-
433
+
435
434
  return `${size.toFixed(1)} ${units[unitIndex]}`;
436
435
  }
437
-
436
+
438
437
  private formatBitsPerSecond(bytesPerSecond: number): string {
439
438
  const bitsPerSecond = bytesPerSecond * 8; // Convert bytes to bits
440
439
  const units = ['bit/s', 'kbit/s', 'Mbit/s', 'Gbit/s'];
441
440
  let size = bitsPerSecond;
442
441
  let unitIndex = 0;
443
-
442
+
444
443
  while (size >= 1000 && unitIndex < units.length - 1) {
445
444
  size /= 1000; // Use 1000 for bits (not 1024)
446
445
  unitIndex++;
447
446
  }
448
-
447
+
449
448
  return `${size.toFixed(1)} ${units[unitIndex]}`;
450
449
  }
451
450
 
@@ -520,18 +519,9 @@ export class OpsViewNetwork extends DeesElement {
520
519
  ];
521
520
 
522
521
  return html`
523
- <dees-statsgrid
522
+ <dees-statsgrid
524
523
  .tiles=${tiles}
525
524
  .minTileWidth=${200}
526
- .gridActions=${[
527
- {
528
- name: 'Export Data',
529
- iconName: 'lucide:FileOutput',
530
- action: async () => {
531
- console.log('Export feature coming soon');
532
- },
533
- },
534
- ]}
535
525
  ></dees-statsgrid>
536
526
  `;
537
527
  }
@@ -732,12 +722,12 @@ export class OpsViewNetwork extends DeesElement {
732
722
  // Only update if connections changed significantly
733
723
  const newConnectionCount = this.networkState.connections.length;
734
724
  const oldConnectionCount = this.networkRequests.length;
735
-
725
+
736
726
  // Check if we need to update the network requests array
737
- const shouldUpdate = newConnectionCount !== oldConnectionCount ||
727
+ const shouldUpdate = newConnectionCount !== oldConnectionCount ||
738
728
  newConnectionCount === 0 ||
739
729
  (newConnectionCount > 0 && this.networkRequests.length === 0);
740
-
730
+
741
731
  if (shouldUpdate) {
742
732
  // Convert connection data to network requests format
743
733
  if (newConnectionCount > 0) {
@@ -760,62 +750,62 @@ export class OpsViewNetwork extends DeesElement {
760
750
  this.networkRequests = [];
761
751
  }
762
752
  }
763
-
753
+
764
754
  // Load server-side throughput history into chart (once)
765
755
  if (!this.historyLoaded && this.networkState.throughputHistory && this.networkState.throughputHistory.length > 0) {
766
756
  this.loadThroughputHistory();
767
757
  }
768
758
  }
769
-
759
+
770
760
  private startTrafficUpdateTimer() {
771
761
  this.stopTrafficUpdateTimer(); // Clear any existing timer
772
762
  this.trafficUpdateTimer = setInterval(() => {
773
763
  this.addTrafficDataPoint();
774
- }, OpsViewNetwork.UPDATE_INTERVAL_MS);
764
+ }, OpsViewNetworkActivity.UPDATE_INTERVAL_MS);
775
765
  }
776
-
766
+
777
767
  private addTrafficDataPoint() {
778
768
  const now = Date.now();
779
-
769
+
780
770
  // Throttle chart updates to avoid excessive re-renders
781
771
  if (now - this.lastChartUpdate < this.chartUpdateThreshold) {
782
772
  return;
783
773
  }
784
-
774
+
785
775
  const throughput = this.calculateThroughput();
786
-
776
+
787
777
  // Convert to Mbps (bytes * 8 / 1,000,000)
788
778
  const throughputInMbps = (throughput.in * 8) / 1000000;
789
779
  const throughputOutMbps = (throughput.out * 8) / 1000000;
790
-
780
+
791
781
  // Add new data points
792
782
  const timestamp = new Date(now).toISOString();
793
-
783
+
794
784
  const newDataPointIn = {
795
785
  x: timestamp,
796
786
  y: Math.round(throughputInMbps * 10) / 10
797
787
  };
798
-
788
+
799
789
  const newDataPointOut = {
800
790
  x: timestamp,
801
791
  y: Math.round(throughputOutMbps * 10) / 10
802
792
  };
803
-
793
+
804
794
  // In-place mutation then reassign for Lit reactivity (avoids 4 intermediate arrays)
805
- if (this.trafficDataIn.length >= OpsViewNetwork.MAX_DATA_POINTS) {
795
+ if (this.trafficDataIn.length >= OpsViewNetworkActivity.MAX_DATA_POINTS) {
806
796
  this.trafficDataIn.shift();
807
797
  this.trafficDataOut.shift();
808
798
  }
809
799
  this.trafficDataIn = [...this.trafficDataIn, newDataPointIn];
810
800
  this.trafficDataOut = [...this.trafficDataOut, newDataPointOut];
811
-
801
+
812
802
  this.lastChartUpdate = now;
813
803
  }
814
-
804
+
815
805
  private stopTrafficUpdateTimer() {
816
806
  if (this.trafficUpdateTimer) {
817
807
  clearInterval(this.trafficUpdateTimer);
818
808
  this.trafficUpdateTimer = null;
819
809
  }
820
810
  }
821
- }
811
+ }
@@ -0,0 +1,119 @@
1
+ import * as appstate from '../../appstate.js';
2
+ import { appRouter } from '../../router.js';
3
+ import { viewHostCss } from '../shared/css.js';
4
+
5
+ import {
6
+ DeesElement,
7
+ customElement,
8
+ html,
9
+ state,
10
+ css,
11
+ cssManager,
12
+ type TemplateResult,
13
+ } from '@design.estate/dees-element';
14
+
15
+ // Side-effect imports register the subview custom elements
16
+ import './ops-view-network-activity.js';
17
+ import './ops-view-routes.js';
18
+ import './ops-view-sourceprofiles.js';
19
+ import './ops-view-networktargets.js';
20
+ import './ops-view-targetprofiles.js';
21
+
22
+ declare global {
23
+ interface HTMLElementTagNameMap {
24
+ 'ops-view-network': OpsViewNetwork;
25
+ }
26
+ }
27
+
28
+ type TNetworkTab = 'activity' | 'routes' | 'sourceprofiles' | 'networktargets' | 'targetprofiles';
29
+
30
+ @customElement('ops-view-network')
31
+ export class OpsViewNetwork extends DeesElement {
32
+ @state()
33
+ accessor selectedTab: TNetworkTab = 'activity';
34
+
35
+ private tabLabelMap: Record<TNetworkTab, string> = {
36
+ 'activity': 'Network Activity',
37
+ 'routes': 'Routes',
38
+ 'sourceprofiles': 'Source Profiles',
39
+ 'networktargets': 'Network Targets',
40
+ 'targetprofiles': 'Target Profiles',
41
+ };
42
+
43
+ private labelToTab: Record<string, TNetworkTab> = {
44
+ 'Network Activity': 'activity',
45
+ 'Routes': 'routes',
46
+ 'Source Profiles': 'sourceprofiles',
47
+ 'Network Targets': 'networktargets',
48
+ 'Target Profiles': 'targetprofiles',
49
+ };
50
+
51
+ private static isNetworkTab(s: string | null): s is TNetworkTab {
52
+ return s === 'activity' || s === 'routes' || s === 'sourceprofiles'
53
+ || s === 'networktargets' || s === 'targetprofiles';
54
+ }
55
+
56
+ constructor() {
57
+ super();
58
+ // Read initial subview from state (URL-driven)
59
+ const initialState = appstate.uiStatePart.getState()!;
60
+ if (OpsViewNetwork.isNetworkTab(initialState.activeSubview)) {
61
+ this.selectedTab = initialState.activeSubview;
62
+ }
63
+ // Subscribe to future changes (back/forward navigation, direct URL entry)
64
+ const sub = appstate.uiStatePart.select((s) => s.activeSubview).subscribe((sub) => {
65
+ if (OpsViewNetwork.isNetworkTab(sub) && sub !== this.selectedTab) {
66
+ this.selectedTab = sub;
67
+ }
68
+ });
69
+ this.rxSubscriptions.push(sub);
70
+ }
71
+
72
+ async firstUpdated() {
73
+ const toggle = this.shadowRoot!.querySelector('dees-input-multitoggle') as any;
74
+ if (toggle) {
75
+ const sub = toggle.changeSubject.subscribe(() => {
76
+ const tab = this.labelToTab[toggle.selectedOption];
77
+ if (tab && tab !== this.selectedTab) {
78
+ // Push URL → router updates state → subscription updates selectedTab
79
+ appRouter.navigateToView('network', tab);
80
+ }
81
+ });
82
+ this.rxSubscriptions.push(sub);
83
+ }
84
+ }
85
+
86
+ public static styles = [
87
+ cssManager.defaultStyles,
88
+ viewHostCss,
89
+ css`
90
+ dees-input-multitoggle {
91
+ margin-bottom: 24px;
92
+ }
93
+ `,
94
+ ];
95
+
96
+ public render(): TemplateResult {
97
+ return html`
98
+ <dees-heading level="2">Network</dees-heading>
99
+
100
+ <dees-input-multitoggle
101
+ .type=${'single'}
102
+ .options=${['Network Activity', 'Routes', 'Source Profiles', 'Network Targets', 'Target Profiles']}
103
+ .selectedOption=${this.tabLabelMap[this.selectedTab]}
104
+ ></dees-input-multitoggle>
105
+
106
+ ${this.renderTabContent()}
107
+ `;
108
+ }
109
+
110
+ private renderTabContent(): TemplateResult {
111
+ switch (this.selectedTab) {
112
+ case 'activity': return html`<ops-view-network-activity></ops-view-network-activity>`;
113
+ case 'routes': return html`<ops-view-routes></ops-view-routes>`;
114
+ case 'sourceprofiles': return html`<ops-view-sourceprofiles></ops-view-sourceprofiles>`;
115
+ case 'networktargets': return html`<ops-view-networktargets></ops-view-networktargets>`;
116
+ case 'targetprofiles': return html`<ops-view-targetprofiles></ops-view-targetprofiles>`;
117
+ }
118
+ }
119
+ }
@@ -7,9 +7,8 @@ import {
7
7
  state,
8
8
  cssManager,
9
9
  } from '@design.estate/dees-element';
10
- import * as appstate from '../appstate.js';
11
- import * as interfaces from '../../dist_ts_interfaces/index.js';
12
- import { viewHostCss } from './shared/css.js';
10
+ import * as appstate from '../../appstate.js';
11
+ import * as interfaces from '../../../dist_ts_interfaces/index.js';
13
12
  import { type IStatsTile } from '@design.estate/dees-catalog';
14
13
 
15
14
  declare global {
@@ -38,8 +37,8 @@ export class OpsViewNetworkTargets extends DeesElement {
38
37
 
39
38
  public static styles = [
40
39
  cssManager.defaultStyles,
41
- viewHostCss,
42
40
  css`
41
+ :host { display: block; }
43
42
  .targetsContainer {
44
43
  display: flex;
45
44
  flex-direction: column;
@@ -64,13 +63,14 @@ export class OpsViewNetworkTargets extends DeesElement {
64
63
  ];
65
64
 
66
65
  return html`
67
- <dees-heading level="2">Network Targets</dees-heading>
66
+ <dees-heading level="hr">Network Targets</dees-heading>
68
67
  <div class="targetsContainer">
69
68
  <dees-statsgrid .tiles=${statsTiles}></dees-statsgrid>
70
69
  <dees-table
71
70
  .heading1=${'Network Targets'}
72
71
  .heading2=${'Reusable host:port destinations for routes'}
73
72
  .data=${targets}
73
+ .showColumnFilters=${true}
74
74
  .displayFunction=${(target: interfaces.data.INetworkTarget) => ({
75
75
  Name: target.name,
76
76
  Host: Array.isArray(target.host) ? target.host.join(', ') : target.host,
@@ -1,6 +1,5 @@
1
- import * as appstate from '../appstate.js';
2
- import * as interfaces from '../../dist_ts_interfaces/index.js';
3
- import { viewHostCss } from './shared/css.js';
1
+ import * as appstate from '../../appstate.js';
2
+ import * as interfaces from '../../../dist_ts_interfaces/index.js';
4
3
  import { type IStatsTile } from '@design.estate/dees-catalog';
5
4
 
6
5
  import {
@@ -97,8 +96,8 @@ export class OpsViewRoutes extends DeesElement {
97
96
 
98
97
  public static styles = [
99
98
  cssManager.defaultStyles,
100
- viewHostCss,
101
99
  css`
100
+ :host { display: block; }
102
101
  .routesContainer {
103
102
  display: flex;
104
103
  flex-direction: column;
@@ -200,7 +199,7 @@ export class OpsViewRoutes extends DeesElement {
200
199
  });
201
200
 
202
201
  return html`
203
- <dees-heading level="2">Route Management</dees-heading>
202
+ <dees-heading level="hr">Route Management</dees-heading>
204
203
 
205
204
  <div class="routesContainer">
206
205
  <dees-statsgrid
@@ -7,9 +7,8 @@ import {
7
7
  state,
8
8
  cssManager,
9
9
  } from '@design.estate/dees-element';
10
- import * as appstate from '../appstate.js';
11
- import * as interfaces from '../../dist_ts_interfaces/index.js';
12
- import { viewHostCss } from './shared/css.js';
10
+ import * as appstate from '../../appstate.js';
11
+ import * as interfaces from '../../../dist_ts_interfaces/index.js';
13
12
  import { type IStatsTile } from '@design.estate/dees-catalog';
14
13
 
15
14
  declare global {
@@ -38,8 +37,8 @@ export class OpsViewSourceProfiles extends DeesElement {
38
37
 
39
38
  public static styles = [
40
39
  cssManager.defaultStyles,
41
- viewHostCss,
42
40
  css`
41
+ :host { display: block; }
43
42
  .profilesContainer {
44
43
  display: flex;
45
44
  flex-direction: column;
@@ -64,13 +63,14 @@ export class OpsViewSourceProfiles extends DeesElement {
64
63
  ];
65
64
 
66
65
  return html`
67
- <dees-heading level="2">Source Profiles</dees-heading>
66
+ <dees-heading level="hr">Source Profiles</dees-heading>
68
67
  <div class="profilesContainer">
69
68
  <dees-statsgrid .tiles=${statsTiles}></dees-statsgrid>
70
69
  <dees-table
71
70
  .heading1=${'Source Profiles'}
72
71
  .heading2=${'Reusable source configurations for routes'}
73
72
  .data=${profiles}
73
+ .showColumnFilters=${true}
74
74
  .displayFunction=${(profile: interfaces.data.ISourceProfile) => ({
75
75
  Name: profile.name,
76
76
  Description: profile.description || '-',
@@ -7,10 +7,9 @@ import {
7
7
  state,
8
8
  cssManager,
9
9
  } from '@design.estate/dees-element';
10
- import * as plugins from '../plugins.js';
11
- import * as appstate from '../appstate.js';
12
- import * as interfaces from '../../dist_ts_interfaces/index.js';
13
- import { viewHostCss } from './shared/css.js';
10
+ import * as plugins from '../../plugins.js';
11
+ import * as appstate from '../../appstate.js';
12
+ import * as interfaces from '../../../dist_ts_interfaces/index.js';
14
13
  import { type IStatsTile } from '@design.estate/dees-catalog';
15
14
 
16
15
  declare global {
@@ -39,8 +38,8 @@ export class OpsViewTargetProfiles extends DeesElement {
39
38
 
40
39
  public static styles = [
41
40
  cssManager.defaultStyles,
42
- viewHostCss,
43
41
  css`
42
+ :host { display: block; }
44
43
  .profilesContainer {
45
44
  display: flex;
46
45
  flex-direction: column;
@@ -77,13 +76,14 @@ export class OpsViewTargetProfiles extends DeesElement {
77
76
  ];
78
77
 
79
78
  return html`
80
- <dees-heading level="2">Target Profiles</dees-heading>
79
+ <dees-heading level="hr">Target Profiles</dees-heading>
81
80
  <div class="profilesContainer">
82
81
  <dees-statsgrid .tiles=${statsTiles}></dees-statsgrid>
83
82
  <dees-table
84
83
  .heading1=${'Target Profiles'}
85
84
  .heading2=${'Define what resources VPN clients can access'}
86
85
  .data=${profiles}
86
+ .showColumnFilters=${true}
87
87
  .displayFunction=${(profile: interfaces.data.ITargetProfile) => ({
88
88
  Name: profile.name,
89
89
  Description: profile.description || '-',