@serve.zone/dcrouter 13.2.2 → 13.4.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 (81) hide show
  1. package/dist_serve/bundle.js +1499 -1413
  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 +14 -38
  6. package/dist_ts_web/elements/access/index.d.ts +1 -0
  7. package/dist_ts_web/elements/access/index.js +2 -0
  8. package/dist_ts_web/elements/{ops-view-apitokens.d.ts → access/ops-view-apitokens.d.ts} +1 -1
  9. package/dist_ts_web/elements/{ops-view-apitokens.js → access/ops-view-apitokens.js} +4 -4
  10. package/dist_ts_web/elements/email/index.d.ts +2 -0
  11. package/dist_ts_web/elements/email/index.js +3 -0
  12. package/dist_ts_web/elements/email/ops-view-email-security.d.ts +14 -0
  13. package/dist_ts_web/elements/email/ops-view-email-security.js +197 -0
  14. package/dist_ts_web/elements/{ops-view-emails.d.ts → email/ops-view-emails.d.ts} +2 -2
  15. package/dist_ts_web/elements/{ops-view-emails.js → email/ops-view-emails.js} +5 -5
  16. package/dist_ts_web/elements/index.d.ts +5 -12
  17. package/dist_ts_web/elements/index.js +6 -13
  18. package/dist_ts_web/elements/network/index.d.ts +7 -0
  19. package/dist_ts_web/elements/network/index.js +8 -0
  20. package/dist_ts_web/elements/{ops-view-network.d.ts → network/ops-view-network-activity.d.ts} +3 -3
  21. package/dist_ts_web/elements/{ops-view-network.js → network/ops-view-network-activity.js} +20 -32
  22. package/dist_ts_web/elements/{ops-view-networktargets.d.ts → network/ops-view-networktargets.d.ts} +1 -1
  23. package/dist_ts_web/elements/{ops-view-networktargets.js → network/ops-view-networktargets.js} +5 -5
  24. package/dist_ts_web/elements/{ops-view-remoteingress.d.ts → network/ops-view-remoteingress.d.ts} +1 -1
  25. package/dist_ts_web/elements/{ops-view-remoteingress.js → network/ops-view-remoteingress.js} +5 -5
  26. package/dist_ts_web/elements/{ops-view-routes.d.ts → network/ops-view-routes.d.ts} +1 -1
  27. package/dist_ts_web/elements/{ops-view-routes.js → network/ops-view-routes.js} +5 -5
  28. package/dist_ts_web/elements/{ops-view-sourceprofiles.d.ts → network/ops-view-sourceprofiles.d.ts} +1 -1
  29. package/dist_ts_web/elements/{ops-view-sourceprofiles.js → network/ops-view-sourceprofiles.js} +5 -5
  30. package/dist_ts_web/elements/{ops-view-targetprofiles.d.ts → network/ops-view-targetprofiles.d.ts} +2 -2
  31. package/dist_ts_web/elements/{ops-view-targetprofiles.js → network/ops-view-targetprofiles.js} +6 -6
  32. package/dist_ts_web/elements/{ops-view-vpn.d.ts → network/ops-view-vpn.d.ts} +2 -2
  33. package/dist_ts_web/elements/{ops-view-vpn.js → network/ops-view-vpn.js} +6 -6
  34. package/dist_ts_web/elements/ops-dashboard.d.ts +8 -2
  35. package/dist_ts_web/elements/ops-dashboard.js +101 -83
  36. package/dist_ts_web/elements/overview/index.d.ts +2 -0
  37. package/dist_ts_web/elements/overview/index.js +3 -0
  38. package/dist_ts_web/elements/{ops-view-config.d.ts → overview/ops-view-config.d.ts} +2 -2
  39. package/dist_ts_web/elements/{ops-view-config.js → overview/ops-view-config.js} +9 -9
  40. package/dist_ts_web/elements/{ops-view-overview.d.ts → overview/ops-view-overview.d.ts} +2 -2
  41. package/dist_ts_web/elements/{ops-view-overview.js → overview/ops-view-overview.js} +4 -4
  42. package/dist_ts_web/elements/security/index.d.ts +3 -0
  43. package/dist_ts_web/elements/security/index.js +4 -0
  44. package/dist_ts_web/elements/security/ops-view-security-authentication.d.ts +13 -0
  45. package/dist_ts_web/elements/security/ops-view-security-authentication.js +157 -0
  46. package/dist_ts_web/elements/security/ops-view-security-blocked.d.ts +15 -0
  47. package/dist_ts_web/elements/security/ops-view-security-blocked.js +153 -0
  48. package/dist_ts_web/elements/security/ops-view-security-overview.d.ts +16 -0
  49. package/dist_ts_web/elements/security/ops-view-security-overview.js +205 -0
  50. package/dist_ts_web/router.d.ts +5 -3
  51. package/dist_ts_web/router.js +75 -17
  52. package/package.json +2 -2
  53. package/ts/00_commitinfo_data.ts +1 -1
  54. package/ts_web/00_commitinfo_data.ts +1 -1
  55. package/ts_web/appstate.ts +15 -42
  56. package/ts_web/elements/access/index.ts +1 -0
  57. package/ts_web/elements/{ops-view-apitokens.ts → access/ops-view-apitokens.ts} +3 -3
  58. package/ts_web/elements/email/index.ts +2 -0
  59. package/ts_web/elements/email/ops-view-email-security.ts +160 -0
  60. package/ts_web/elements/{ops-view-emails.ts → email/ops-view-emails.ts} +4 -4
  61. package/ts_web/elements/index.ts +6 -13
  62. package/ts_web/elements/network/index.ts +7 -0
  63. package/ts_web/elements/{ops-view-network.ts → network/ops-view-network-activity.ts} +43 -55
  64. package/ts_web/elements/{ops-view-networktargets.ts → network/ops-view-networktargets.ts} +4 -4
  65. package/ts_web/elements/{ops-view-remoteingress.ts → network/ops-view-remoteingress.ts} +4 -4
  66. package/ts_web/elements/{ops-view-routes.ts → network/ops-view-routes.ts} +4 -4
  67. package/ts_web/elements/{ops-view-sourceprofiles.ts → network/ops-view-sourceprofiles.ts} +4 -4
  68. package/ts_web/elements/{ops-view-targetprofiles.ts → network/ops-view-targetprofiles.ts} +5 -5
  69. package/ts_web/elements/{ops-view-vpn.ts → network/ops-view-vpn.ts} +5 -5
  70. package/ts_web/elements/ops-dashboard.ts +125 -90
  71. package/ts_web/elements/overview/index.ts +2 -0
  72. package/ts_web/elements/{ops-view-config.ts → overview/ops-view-config.ts} +8 -8
  73. package/ts_web/elements/{ops-view-overview.ts → overview/ops-view-overview.ts} +3 -3
  74. package/ts_web/elements/security/index.ts +3 -0
  75. package/ts_web/elements/security/ops-view-security-authentication.ts +121 -0
  76. package/ts_web/elements/security/ops-view-security-blocked.ts +118 -0
  77. package/ts_web/elements/security/ops-view-security-overview.ts +172 -0
  78. package/ts_web/router.ts +81 -17
  79. package/dist_ts_web/elements/ops-view-security.d.ts +0 -24
  80. package/dist_ts_web/elements/ops-view-security.js +0 -484
  81. package/ts_web/elements/ops-view-security.ts +0 -456
@@ -0,0 +1,172 @@
1
+ import * as appstate from '../../appstate.js';
2
+ import { viewHostCss } from '../shared/css.js';
3
+
4
+ import {
5
+ DeesElement,
6
+ customElement,
7
+ html,
8
+ state,
9
+ css,
10
+ cssManager,
11
+ type TemplateResult,
12
+ } from '@design.estate/dees-element';
13
+ import { type IStatsTile } from '@design.estate/dees-catalog';
14
+
15
+ declare global {
16
+ interface HTMLElementTagNameMap {
17
+ 'ops-view-security-overview': OpsViewSecurityOverview;
18
+ }
19
+ }
20
+
21
+ @customElement('ops-view-security-overview')
22
+ export class OpsViewSecurityOverview extends DeesElement {
23
+ @state()
24
+ accessor statsState: appstate.IStatsState = appstate.statsStatePart.getState()!;
25
+
26
+ constructor() {
27
+ super();
28
+ const sub = appstate.statsStatePart
29
+ .select((s) => s)
30
+ .subscribe((s) => {
31
+ this.statsState = s;
32
+ });
33
+ this.rxSubscriptions.push(sub);
34
+ }
35
+
36
+ public static styles = [
37
+ cssManager.defaultStyles,
38
+ viewHostCss,
39
+ css`
40
+ h2 {
41
+ margin: 32px 0 16px 0;
42
+ font-size: 24px;
43
+ font-weight: 600;
44
+ color: ${cssManager.bdTheme('#333', '#ccc')};
45
+ }
46
+ dees-statsgrid {
47
+ margin-bottom: 32px;
48
+ }
49
+ `,
50
+ ];
51
+
52
+ public render(): TemplateResult {
53
+ const metrics = this.statsState.securityMetrics;
54
+
55
+ if (!metrics) {
56
+ return html`
57
+ <div class="loadingMessage">
58
+ <p>Loading security metrics...</p>
59
+ </div>
60
+ `;
61
+ }
62
+
63
+ const threatLevel = this.calculateThreatLevel(metrics);
64
+ const threatScore = this.getThreatScore(metrics);
65
+
66
+ // Derive active sessions from recent successful auth events (last hour)
67
+ const allEvents: any[] = metrics.recentEvents || [];
68
+ const oneHourAgo = Date.now() - 3600000;
69
+ const recentAuthSuccesses = allEvents.filter(
70
+ (evt: any) => evt.type === 'authentication' && evt.success === true && evt.timestamp >= oneHourAgo
71
+ ).length;
72
+
73
+ const tiles: IStatsTile[] = [
74
+ {
75
+ id: 'threatLevel',
76
+ title: 'Threat Level',
77
+ value: threatScore,
78
+ type: 'gauge',
79
+ icon: 'lucide:Shield',
80
+ gaugeOptions: {
81
+ min: 0,
82
+ max: 100,
83
+ thresholds: [
84
+ { value: 0, color: '#ef4444' },
85
+ { value: 30, color: '#f59e0b' },
86
+ { value: 70, color: '#22c55e' },
87
+ ],
88
+ },
89
+ description: `Status: ${threatLevel.toUpperCase()}`,
90
+ },
91
+ {
92
+ id: 'blockedThreats',
93
+ title: 'Blocked Threats',
94
+ value: (metrics.blockedIPs?.length || 0) + metrics.spamDetected,
95
+ type: 'number',
96
+ icon: 'lucide:ShieldCheck',
97
+ color: '#ef4444',
98
+ description: 'Total threats blocked today',
99
+ },
100
+ {
101
+ id: 'activeSessions',
102
+ title: 'Active Sessions',
103
+ value: recentAuthSuccesses,
104
+ type: 'number',
105
+ icon: 'lucide:Users',
106
+ color: '#22c55e',
107
+ description: 'Authenticated in last hour',
108
+ },
109
+ {
110
+ id: 'authFailures',
111
+ title: 'Auth Failures',
112
+ value: metrics.authenticationFailures,
113
+ type: 'number',
114
+ icon: 'lucide:LockOpen',
115
+ color: metrics.authenticationFailures > 10 ? '#ef4444' : '#f59e0b',
116
+ description: 'Failed login attempts today',
117
+ },
118
+ ];
119
+
120
+ return html`
121
+ <dees-heading level="hr">Overview</dees-heading>
122
+
123
+ <dees-statsgrid
124
+ .tiles=${tiles}
125
+ .minTileWidth=${200}
126
+ ></dees-statsgrid>
127
+
128
+ <h2>Recent Security Events</h2>
129
+ <dees-table
130
+ .heading1=${'Security Events'}
131
+ .heading2=${'Last 24 hours'}
132
+ .data=${this.getSecurityEvents(metrics)}
133
+ .displayFunction=${(item) => ({
134
+ 'Time': new Date(item.timestamp).toLocaleTimeString(),
135
+ 'Event': item.event,
136
+ 'Severity': item.severity,
137
+ 'Details': item.details,
138
+ })}
139
+ ></dees-table>
140
+ `;
141
+ }
142
+
143
+ private calculateThreatLevel(metrics: any): string {
144
+ const score = this.getThreatScore(metrics);
145
+ if (score < 30) return 'alert';
146
+ if (score < 70) return 'warning';
147
+ return 'success';
148
+ }
149
+
150
+ private getThreatScore(metrics: any): number {
151
+ // Simple scoring algorithm
152
+ let score = 100;
153
+ const blockedCount = Array.isArray(metrics.blockedIPs) ? metrics.blockedIPs.length : (metrics.blockedIPs || 0);
154
+ score -= blockedCount * 2;
155
+ score -= (metrics.authenticationFailures || 0) * 1;
156
+ score -= (metrics.spamDetected || 0) * 0.5;
157
+ score -= (metrics.malwareDetected || 0) * 3;
158
+ score -= (metrics.phishingDetected || 0) * 3;
159
+ score -= (metrics.suspiciousActivities || 0) * 2;
160
+ return Math.max(0, Math.min(100, Math.round(score)));
161
+ }
162
+
163
+ private getSecurityEvents(metrics: any): any[] {
164
+ const events: any[] = metrics.recentEvents || [];
165
+ return events.map((evt: any) => ({
166
+ timestamp: evt.timestamp,
167
+ event: evt.message,
168
+ severity: evt.level === 'critical' ? 'critical' : evt.level === 'error' ? 'high' : evt.level === 'warn' ? 'warning' : 'info',
169
+ details: evt.ipAddress ? `IP: ${evt.ipAddress}` : evt.domain ? `Domain: ${evt.domain}` : evt.type,
170
+ }));
171
+ }
172
+ }
package/ts_web/router.ts CHANGED
@@ -3,9 +3,37 @@ import * as appstate from './appstate.js';
3
3
 
4
4
  const SmartRouter = plugins.domtools.plugins.smartrouter.SmartRouter;
5
5
 
6
- export const validViews = ['overview', 'network', 'emails', 'logs', 'routes', 'apitokens', 'configuration', 'security', 'certificates', 'remoteingress', 'vpn', 'sourceprofiles', 'networktargets', 'targetprofiles'] as const;
6
+ // Flat top-level views (no subviews)
7
+ const flatViews = ['logs', 'certificates'] as const;
8
+
9
+ // Tabbed views and their valid subviews
10
+ const subviewMap: Record<string, readonly string[]> = {
11
+ overview: ['stats', 'configuration'] as const,
12
+ network: ['activity', 'routes', 'sourceprofiles', 'networktargets', 'targetprofiles', 'remoteingress', 'vpn'] as const,
13
+ email: ['log', 'security'] as const,
14
+ access: ['apitokens'] as const,
15
+ security: ['overview', 'blocked', 'authentication'] as const,
16
+ };
17
+
18
+ // Default subview when user visits the bare parent URL
19
+ const defaultSubview: Record<string, string> = {
20
+ overview: 'stats',
21
+ network: 'activity',
22
+ email: 'log',
23
+ access: 'apitokens',
24
+ security: 'overview',
25
+ };
26
+
27
+ export const validTopLevelViews = [...flatViews, ...Object.keys(subviewMap)] as const;
28
+ export type TValidView = typeof validTopLevelViews[number];
29
+
30
+ export function isValidView(view: string): boolean {
31
+ return (validTopLevelViews as readonly string[]).includes(view);
32
+ }
7
33
 
8
- export type TValidView = typeof validViews[number];
34
+ export function isValidSubview(view: string, subview: string): boolean {
35
+ return subviewMap[view]?.includes(subview) ?? false;
36
+ }
9
37
 
10
38
  class AppRouter {
11
39
  private router: InstanceType<typeof SmartRouter>;
@@ -25,10 +53,25 @@ class AppRouter {
25
53
  }
26
54
 
27
55
  private setupRoutes(): void {
28
- for (const view of validViews) {
56
+ // Flat views
57
+ for (const view of flatViews) {
58
+ this.router.on(`/${view}`, async () => {
59
+ this.updateViewState(view, null);
60
+ });
61
+ }
62
+
63
+ // Tabbed views
64
+ for (const view of Object.keys(subviewMap)) {
65
+ // Bare parent → redirect to default subview
29
66
  this.router.on(`/${view}`, async () => {
30
- this.updateViewState(view);
67
+ this.navigateTo(`/${view}/${defaultSubview[view]}`);
31
68
  });
69
+ // Each valid subview
70
+ for (const sub of subviewMap[view]) {
71
+ this.router.on(`/${view}/${sub}`, async () => {
72
+ this.updateViewState(view, sub);
73
+ });
74
+ }
32
75
  }
33
76
 
34
77
  // Root redirect
@@ -42,7 +85,9 @@ class AppRouter {
42
85
  if (this.suppressStateUpdate) return;
43
86
 
44
87
  const currentPath = window.location.pathname;
45
- const expectedPath = `/${uiState.activeView}`;
88
+ const expectedPath = uiState.activeSubview
89
+ ? `/${uiState.activeView}/${uiState.activeSubview}`
90
+ : `/${uiState.activeView}`;
46
91
 
47
92
  if (currentPath !== expectedPath) {
48
93
  this.suppressStateUpdate = true;
@@ -57,25 +102,38 @@ class AppRouter {
57
102
 
58
103
  if (!path || path === '/') {
59
104
  this.router.pushUrl('/overview');
60
- } else {
61
- const segments = path.split('/').filter(Boolean);
62
- const view = segments[0];
105
+ return;
106
+ }
107
+
108
+ const segments = path.split('/').filter(Boolean);
109
+ const view = segments[0];
110
+ const sub = segments[1];
63
111
 
64
- if (validViews.includes(view as TValidView)) {
65
- this.updateViewState(view as TValidView);
112
+ if (!isValidView(view)) {
113
+ this.router.pushUrl('/overview');
114
+ return;
115
+ }
116
+
117
+ if (subviewMap[view]) {
118
+ if (sub && isValidSubview(view, sub)) {
119
+ this.updateViewState(view, sub);
66
120
  } else {
67
- this.router.pushUrl('/overview');
121
+ // Bare parent or invalid sub → default subview
122
+ this.router.pushUrl(`/${view}/${defaultSubview[view]}`);
68
123
  }
124
+ } else {
125
+ this.updateViewState(view, null);
69
126
  }
70
127
  }
71
128
 
72
- private updateViewState(view: string): void {
129
+ private updateViewState(view: string, subview: string | null): void {
73
130
  this.suppressStateUpdate = true;
74
131
  const currentState = appstate.uiStatePart.getState()!;
75
- if (currentState.activeView !== view) {
132
+ if (currentState.activeView !== view || currentState.activeSubview !== subview) {
76
133
  appstate.uiStatePart.setState({
77
134
  ...currentState,
78
135
  activeView: view,
136
+ activeSubview: subview,
79
137
  } as appstate.IUiState);
80
138
  }
81
139
  this.suppressStateUpdate = false;
@@ -85,11 +143,17 @@ class AppRouter {
85
143
  this.router.pushUrl(path);
86
144
  }
87
145
 
88
- public navigateToView(view: string): void {
89
- if (validViews.includes(view as TValidView)) {
90
- this.navigateTo(`/${view}`);
91
- } else {
146
+ public navigateToView(view: string, subview?: string): void {
147
+ if (!isValidView(view)) {
92
148
  this.navigateTo('/overview');
149
+ return;
150
+ }
151
+ if (subview && isValidSubview(view, subview)) {
152
+ this.navigateTo(`/${view}/${subview}`);
153
+ } else if (subviewMap[view]) {
154
+ this.navigateTo(`/${view}/${defaultSubview[view]}`);
155
+ } else {
156
+ this.navigateTo(`/${view}`);
93
157
  }
94
158
  }
95
159
 
@@ -1,24 +0,0 @@
1
- import * as plugins from '../plugins.js';
2
- import * as appstate from '../appstate.js';
3
- import { DeesElement } from '@design.estate/dees-element';
4
- export declare class OpsViewSecurity extends DeesElement {
5
- accessor statsState: appstate.IStatsState;
6
- accessor selectedTab: 'overview' | 'blocked' | 'authentication' | 'email-security';
7
- private tabLabelMap;
8
- private labelToTab;
9
- constructor();
10
- firstUpdated(): Promise<void>;
11
- static styles: plugins.deesElement.CSSResult[];
12
- render(): plugins.deesElement.TemplateResult<1>;
13
- private renderTabContent;
14
- private renderOverview;
15
- private renderBlockedIPs;
16
- private renderAuthentication;
17
- private renderEmailSecurity;
18
- private calculateThreatLevel;
19
- private getThreatScore;
20
- private getSecurityEvents;
21
- private clearBlockedIPs;
22
- private unblockIP;
23
- private saveEmailSecuritySettings;
24
- }