@serve.zone/dcrouter 13.2.2 → 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 (61) hide show
  1. package/dist_serve/bundle.js +1198 -1142
  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 -33
  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} +5 -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} +5 -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} +6 -7
  22. package/dist_ts_web/elements/ops-dashboard.js +4 -27
  23. package/dist_ts_web/elements/ops-view-config.js +3 -3
  24. package/dist_ts_web/elements/security/index.d.ts +5 -0
  25. package/dist_ts_web/elements/security/index.js +6 -0
  26. package/dist_ts_web/elements/security/ops-view-security-authentication.d.ts +13 -0
  27. package/dist_ts_web/elements/security/ops-view-security-authentication.js +156 -0
  28. package/dist_ts_web/elements/security/ops-view-security-blocked.d.ts +15 -0
  29. package/dist_ts_web/elements/security/ops-view-security-blocked.js +152 -0
  30. package/dist_ts_web/elements/security/ops-view-security-emailsecurity.d.ts +14 -0
  31. package/dist_ts_web/elements/security/ops-view-security-emailsecurity.js +196 -0
  32. package/dist_ts_web/elements/security/ops-view-security-overview.d.ts +16 -0
  33. package/dist_ts_web/elements/security/ops-view-security-overview.js +204 -0
  34. package/dist_ts_web/elements/security/ops-view-security.d.ts +23 -0
  35. package/dist_ts_web/elements/security/ops-view-security.js +146 -0
  36. package/dist_ts_web/router.d.ts +5 -3
  37. package/dist_ts_web/router.js +69 -17
  38. package/package.json +1 -1
  39. package/ts/00_commitinfo_data.ts +1 -1
  40. package/ts_web/00_commitinfo_data.ts +1 -1
  41. package/ts_web/appstate.ts +10 -24
  42. package/ts_web/elements/index.ts +2 -6
  43. package/ts_web/elements/network/index.ts +6 -0
  44. package/ts_web/elements/{ops-view-network.ts → network/ops-view-network-activity.ts} +43 -56
  45. package/ts_web/elements/network/ops-view-network.ts +119 -0
  46. package/ts_web/elements/{ops-view-networktargets.ts → network/ops-view-networktargets.ts} +4 -5
  47. package/ts_web/elements/{ops-view-routes.ts → network/ops-view-routes.ts} +4 -5
  48. package/ts_web/elements/{ops-view-sourceprofiles.ts → network/ops-view-sourceprofiles.ts} +4 -5
  49. package/ts_web/elements/{ops-view-targetprofiles.ts → network/ops-view-targetprofiles.ts} +5 -6
  50. package/ts_web/elements/ops-dashboard.ts +3 -26
  51. package/ts_web/elements/ops-view-config.ts +2 -2
  52. package/ts_web/elements/security/index.ts +5 -0
  53. package/ts_web/elements/security/ops-view-security-authentication.ts +120 -0
  54. package/ts_web/elements/security/ops-view-security-blocked.ts +117 -0
  55. package/ts_web/elements/security/ops-view-security-emailsecurity.ts +159 -0
  56. package/ts_web/elements/security/ops-view-security-overview.ts +171 -0
  57. package/ts_web/elements/security/ops-view-security.ts +114 -0
  58. package/ts_web/router.ts +75 -17
  59. package/dist_ts_web/elements/ops-view-security.d.ts +0 -24
  60. package/dist_ts_web/elements/ops-view-security.js +0 -484
  61. package/ts_web/elements/ops-view-security.ts +0 -456
@@ -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
 
@@ -323,7 +322,6 @@ export class OpsViewNetwork extends DeesElement {
323
322
  <!-- Requests Table -->
324
323
  <dees-table
325
324
  .data=${this.networkRequests}
326
- .showColumnFilters=${true}
327
325
  .displayFunction=${(req: INetworkRequest) => ({
328
326
  Time: new Date(req.timestamp).toLocaleTimeString(),
329
327
  Protocol: html`<span class="protocolBadge ${req.protocol}">${req.protocol.toUpperCase()}</span>`,
@@ -358,7 +356,7 @@ export class OpsViewNetwork extends DeesElement {
358
356
 
359
357
  private async showRequestDetails(request: INetworkRequest) {
360
358
  const { DeesModal } = await import('@design.estate/dees-catalog');
361
-
359
+
362
360
  await DeesModal.createAndShow({
363
361
  heading: 'Request Details',
364
362
  content: html`
@@ -401,10 +399,10 @@ export class OpsViewNetwork extends DeesElement {
401
399
  if (!statusCode) {
402
400
  return html`<span class="statusBadge warning">N/A</span>`;
403
401
  }
404
-
402
+
405
403
  const statusClass = statusCode >= 200 && statusCode < 300 ? 'success' :
406
404
  statusCode >= 400 ? 'error' : 'warning';
407
-
405
+
408
406
  return html`<span class="statusBadge ${statusClass}">${statusCode}</span>`;
409
407
  }
410
408
 
@@ -427,26 +425,26 @@ export class OpsViewNetwork extends DeesElement {
427
425
  const units = ['B', 'KB', 'MB', 'GB'];
428
426
  let size = bytes;
429
427
  let unitIndex = 0;
430
-
428
+
431
429
  while (size >= 1024 && unitIndex < units.length - 1) {
432
430
  size /= 1024;
433
431
  unitIndex++;
434
432
  }
435
-
433
+
436
434
  return `${size.toFixed(1)} ${units[unitIndex]}`;
437
435
  }
438
-
436
+
439
437
  private formatBitsPerSecond(bytesPerSecond: number): string {
440
438
  const bitsPerSecond = bytesPerSecond * 8; // Convert bytes to bits
441
439
  const units = ['bit/s', 'kbit/s', 'Mbit/s', 'Gbit/s'];
442
440
  let size = bitsPerSecond;
443
441
  let unitIndex = 0;
444
-
442
+
445
443
  while (size >= 1000 && unitIndex < units.length - 1) {
446
444
  size /= 1000; // Use 1000 for bits (not 1024)
447
445
  unitIndex++;
448
446
  }
449
-
447
+
450
448
  return `${size.toFixed(1)} ${units[unitIndex]}`;
451
449
  }
452
450
 
@@ -521,18 +519,9 @@ export class OpsViewNetwork extends DeesElement {
521
519
  ];
522
520
 
523
521
  return html`
524
- <dees-statsgrid
522
+ <dees-statsgrid
525
523
  .tiles=${tiles}
526
524
  .minTileWidth=${200}
527
- .gridActions=${[
528
- {
529
- name: 'Export Data',
530
- iconName: 'lucide:FileOutput',
531
- action: async () => {
532
- console.log('Export feature coming soon');
533
- },
534
- },
535
- ]}
536
525
  ></dees-statsgrid>
537
526
  `;
538
527
  }
@@ -604,7 +593,6 @@ export class OpsViewNetwork extends DeesElement {
604
593
  return html`
605
594
  <dees-table
606
595
  .data=${this.networkState.topIPs}
607
- .showColumnFilters=${true}
608
596
  .displayFunction=${(ipData: { ip: string; count: number }) => {
609
597
  const bw = bandwidthByIP.get(ipData.ip);
610
598
  return {
@@ -632,7 +620,6 @@ export class OpsViewNetwork extends DeesElement {
632
620
  return html`
633
621
  <dees-table
634
622
  .data=${backends}
635
- .showColumnFilters=${true}
636
623
  .displayFunction=${(item: interfaces.data.IBackendInfo) => {
637
624
  const totalErrors = item.connectErrors + item.handshakeErrors + item.requestErrors;
638
625
  const protocolClass = item.protocol.toLowerCase().replace(/[^a-z0-9]/g, '');
@@ -735,12 +722,12 @@ export class OpsViewNetwork extends DeesElement {
735
722
  // Only update if connections changed significantly
736
723
  const newConnectionCount = this.networkState.connections.length;
737
724
  const oldConnectionCount = this.networkRequests.length;
738
-
725
+
739
726
  // Check if we need to update the network requests array
740
- const shouldUpdate = newConnectionCount !== oldConnectionCount ||
727
+ const shouldUpdate = newConnectionCount !== oldConnectionCount ||
741
728
  newConnectionCount === 0 ||
742
729
  (newConnectionCount > 0 && this.networkRequests.length === 0);
743
-
730
+
744
731
  if (shouldUpdate) {
745
732
  // Convert connection data to network requests format
746
733
  if (newConnectionCount > 0) {
@@ -763,62 +750,62 @@ export class OpsViewNetwork extends DeesElement {
763
750
  this.networkRequests = [];
764
751
  }
765
752
  }
766
-
753
+
767
754
  // Load server-side throughput history into chart (once)
768
755
  if (!this.historyLoaded && this.networkState.throughputHistory && this.networkState.throughputHistory.length > 0) {
769
756
  this.loadThroughputHistory();
770
757
  }
771
758
  }
772
-
759
+
773
760
  private startTrafficUpdateTimer() {
774
761
  this.stopTrafficUpdateTimer(); // Clear any existing timer
775
762
  this.trafficUpdateTimer = setInterval(() => {
776
763
  this.addTrafficDataPoint();
777
- }, OpsViewNetwork.UPDATE_INTERVAL_MS);
764
+ }, OpsViewNetworkActivity.UPDATE_INTERVAL_MS);
778
765
  }
779
-
766
+
780
767
  private addTrafficDataPoint() {
781
768
  const now = Date.now();
782
-
769
+
783
770
  // Throttle chart updates to avoid excessive re-renders
784
771
  if (now - this.lastChartUpdate < this.chartUpdateThreshold) {
785
772
  return;
786
773
  }
787
-
774
+
788
775
  const throughput = this.calculateThroughput();
789
-
776
+
790
777
  // Convert to Mbps (bytes * 8 / 1,000,000)
791
778
  const throughputInMbps = (throughput.in * 8) / 1000000;
792
779
  const throughputOutMbps = (throughput.out * 8) / 1000000;
793
-
780
+
794
781
  // Add new data points
795
782
  const timestamp = new Date(now).toISOString();
796
-
783
+
797
784
  const newDataPointIn = {
798
785
  x: timestamp,
799
786
  y: Math.round(throughputInMbps * 10) / 10
800
787
  };
801
-
788
+
802
789
  const newDataPointOut = {
803
790
  x: timestamp,
804
791
  y: Math.round(throughputOutMbps * 10) / 10
805
792
  };
806
-
793
+
807
794
  // In-place mutation then reassign for Lit reactivity (avoids 4 intermediate arrays)
808
- if (this.trafficDataIn.length >= OpsViewNetwork.MAX_DATA_POINTS) {
795
+ if (this.trafficDataIn.length >= OpsViewNetworkActivity.MAX_DATA_POINTS) {
809
796
  this.trafficDataIn.shift();
810
797
  this.trafficDataOut.shift();
811
798
  }
812
799
  this.trafficDataIn = [...this.trafficDataIn, newDataPointIn];
813
800
  this.trafficDataOut = [...this.trafficDataOut, newDataPointOut];
814
-
801
+
815
802
  this.lastChartUpdate = now;
816
803
  }
817
-
804
+
818
805
  private stopTrafficUpdateTimer() {
819
806
  if (this.trafficUpdateTimer) {
820
807
  clearInterval(this.trafficUpdateTimer);
821
808
  this.trafficUpdateTimer = null;
822
809
  }
823
810
  }
824
- }
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,7 +63,7 @@ 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
@@ -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,7 +63,7 @@ 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
@@ -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,7 +76,7 @@ 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