@serve.zone/dcrouter 7.4.3 → 8.1.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.
- package/dist_serve/bundle.js +11567 -3516
- package/dist_ts/00_commitinfo_data.js +1 -1
- package/dist_ts/classes.dcrouter.d.ts +9 -0
- package/dist_ts/classes.dcrouter.js +27 -1
- package/dist_ts/config/classes.api-token-manager.d.ts +38 -0
- package/dist_ts/config/classes.api-token-manager.js +134 -0
- package/dist_ts/config/classes.route-config-manager.d.ts +35 -0
- package/dist_ts/config/classes.route-config-manager.js +231 -0
- package/dist_ts/config/index.d.ts +2 -0
- package/dist_ts/config/index.js +3 -1
- package/dist_ts/opsserver/classes.opsserver.d.ts +2 -0
- package/dist_ts/opsserver/classes.opsserver.js +5 -1
- package/dist_ts/opsserver/handlers/{email-ops.handler.d.ts → api-token.handler.d.ts} +4 -4
- package/dist_ts/opsserver/handlers/api-token.handler.js +66 -0
- package/dist_ts/opsserver/handlers/index.d.ts +2 -0
- package/dist_ts/opsserver/handlers/index.js +3 -1
- package/dist_ts/opsserver/handlers/{radius.handler.d.ts → route-management.handler.d.ts} +6 -1
- package/dist_ts/opsserver/handlers/route-management.handler.js +117 -0
- package/dist_ts_interfaces/data/index.d.ts +1 -0
- package/dist_ts_interfaces/data/index.js +2 -1
- package/dist_ts_interfaces/data/route-management.d.ts +68 -0
- package/dist_ts_interfaces/data/route-management.js +2 -0
- package/dist_ts_interfaces/requests/api-tokens.d.ts +63 -0
- package/dist_ts_interfaces/requests/api-tokens.js +2 -0
- package/dist_ts_interfaces/requests/email-ops.d.ts +51 -108
- package/dist_ts_interfaces/requests/index.d.ts +2 -0
- package/dist_ts_interfaces/requests/index.js +3 -1
- package/dist_ts_interfaces/requests/route-management.d.ts +114 -0
- package/dist_ts_interfaces/requests/route-management.js +2 -0
- package/dist_ts_web/00_commitinfo_data.js +1 -1
- package/dist_ts_web/appstate.d.ts +38 -16
- package/dist_ts_web/appstate.js +226 -177
- package/dist_ts_web/elements/index.d.ts +2 -0
- package/dist_ts_web/elements/index.js +3 -1
- package/dist_ts_web/elements/ops-dashboard.js +11 -1
- package/dist_ts_web/elements/ops-view-apitokens.d.ts +12 -0
- package/dist_ts_web/elements/ops-view-apitokens.js +306 -0
- package/dist_ts_web/elements/ops-view-emails.d.ts +8 -31
- package/dist_ts_web/elements/ops-view-emails.js +54 -769
- package/dist_ts_web/elements/ops-view-logs.d.ts +2 -8
- package/dist_ts_web/elements/ops-view-logs.js +4 -101
- package/dist_ts_web/elements/ops-view-routes.d.ts +12 -0
- package/dist_ts_web/elements/ops-view-routes.js +404 -0
- package/dist_ts_web/plugins.d.ts +2 -1
- package/dist_ts_web/plugins.js +4 -2
- package/dist_ts_web/router.d.ts +1 -7
- package/dist_ts_web/router.js +8 -82
- package/package.json +2 -1
- package/ts/00_commitinfo_data.ts +1 -1
- package/ts/classes.dcrouter.ts +37 -1
- package/ts/config/classes.api-token-manager.ts +155 -0
- package/ts/config/classes.route-config-manager.ts +271 -0
- package/ts/config/index.ts +3 -1
- package/ts/opsserver/classes.opsserver.ts +4 -0
- package/ts/opsserver/handlers/api-token.handler.ts +96 -0
- package/ts/opsserver/handlers/email-ops.handler.ts +177 -225
- package/ts/opsserver/handlers/index.ts +3 -1
- package/ts/opsserver/handlers/route-management.handler.ts +163 -0
- package/ts_web/00_commitinfo_data.ts +1 -1
- package/ts_web/appstate.ts +316 -222
- package/ts_web/elements/index.ts +2 -0
- package/ts_web/elements/ops-dashboard.ts +10 -0
- package/ts_web/elements/ops-view-apitokens.ts +281 -0
- package/ts_web/elements/ops-view-emails.ts +40 -749
- package/ts_web/elements/ops-view-logs.ts +2 -87
- package/ts_web/elements/ops-view-routes.ts +389 -0
- package/ts_web/plugins.ts +4 -0
- package/ts_web/router.ts +7 -82
- package/dist_ts/cache/classes.cache.cleaner.d.ts +0 -47
- package/dist_ts/cache/classes.cache.cleaner.js +0 -130
- package/dist_ts/cache/classes.cached.document.d.ts +0 -76
- package/dist_ts/cache/classes.cached.document.js +0 -100
- package/dist_ts/cache/classes.cachedb.d.ts +0 -60
- package/dist_ts/cache/classes.cachedb.js +0 -126
- package/dist_ts/cache/documents/classes.cached.email.d.ts +0 -125
- package/dist_ts/cache/documents/classes.cached.email.js +0 -337
- package/dist_ts/cache/documents/classes.cached.ip.reputation.d.ts +0 -119
- package/dist_ts/cache/documents/classes.cached.ip.reputation.js +0 -323
- package/dist_ts/cache/documents/index.d.ts +0 -2
- package/dist_ts/cache/documents/index.js +0 -3
- package/dist_ts/cache/index.d.ts +0 -4
- package/dist_ts/cache/index.js +0 -7
- package/dist_ts/monitoring/classes.metricscache.d.ts +0 -32
- package/dist_ts/monitoring/classes.metricscache.js +0 -63
- package/dist_ts/monitoring/classes.metricsmanager.d.ts +0 -169
- package/dist_ts/monitoring/classes.metricsmanager.js +0 -591
- package/dist_ts/monitoring/index.d.ts +0 -1
- package/dist_ts/monitoring/index.js +0 -2
- package/dist_ts/opsserver/handlers/admin.handler.d.ts +0 -31
- package/dist_ts/opsserver/handlers/admin.handler.js +0 -180
- package/dist_ts/opsserver/handlers/certificate.handler.d.ts +0 -34
- package/dist_ts/opsserver/handlers/certificate.handler.js +0 -419
- package/dist_ts/opsserver/handlers/config.handler.d.ts +0 -9
- package/dist_ts/opsserver/handlers/config.handler.js +0 -67
- package/dist_ts/opsserver/handlers/email-ops.handler.js +0 -219
- package/dist_ts/opsserver/handlers/logs.handler.d.ts +0 -17
- package/dist_ts/opsserver/handlers/logs.handler.js +0 -215
- package/dist_ts/opsserver/handlers/radius.handler.js +0 -296
- package/dist_ts/opsserver/handlers/remoteingress.handler.d.ts +0 -8
- package/dist_ts/opsserver/handlers/remoteingress.handler.js +0 -154
- package/dist_ts/opsserver/handlers/security.handler.d.ts +0 -11
- package/dist_ts/opsserver/handlers/security.handler.js +0 -232
- package/dist_ts/opsserver/handlers/stats.handler.d.ts +0 -13
- package/dist_ts/opsserver/handlers/stats.handler.js +0 -400
- package/dist_ts/security/classes.securitylogger.d.ts +0 -140
- package/dist_ts/security/classes.securitylogger.js +0 -235
- package/dist_ts/storage/classes.storagemanager.d.ts +0 -82
- package/dist_ts/storage/classes.storagemanager.js +0 -344
- package/dist_ts/storage/index.d.ts +0 -1
- package/dist_ts/storage/index.js +0 -3
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import * as plugins from '../plugins.js';
|
|
2
1
|
import * as shared from './shared/index.js';
|
|
3
2
|
import * as appstate from '../appstate.js';
|
|
4
3
|
|
|
@@ -20,15 +19,6 @@ export class OpsViewLogs extends DeesElement {
|
|
|
20
19
|
filters: {},
|
|
21
20
|
};
|
|
22
21
|
|
|
23
|
-
@state()
|
|
24
|
-
accessor filterLevel: string | undefined;
|
|
25
|
-
|
|
26
|
-
@state()
|
|
27
|
-
accessor filterCategory: string | undefined;
|
|
28
|
-
|
|
29
|
-
@state()
|
|
30
|
-
accessor filterLimit: number = 100;
|
|
31
|
-
|
|
32
22
|
private lastPushedCount = 0;
|
|
33
23
|
|
|
34
24
|
constructor() {
|
|
@@ -44,63 +34,13 @@ export class OpsViewLogs extends DeesElement {
|
|
|
44
34
|
public static styles = [
|
|
45
35
|
cssManager.defaultStyles,
|
|
46
36
|
shared.viewHostCss,
|
|
47
|
-
css
|
|
48
|
-
.controls {
|
|
49
|
-
display: flex;
|
|
50
|
-
gap: 16px;
|
|
51
|
-
margin-bottom: 24px;
|
|
52
|
-
flex-wrap: wrap;
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
.filterGroup {
|
|
56
|
-
display: flex;
|
|
57
|
-
align-items: center;
|
|
58
|
-
gap: 8px;
|
|
59
|
-
}
|
|
60
|
-
`,
|
|
37
|
+
css``,
|
|
61
38
|
];
|
|
62
39
|
|
|
63
40
|
public render() {
|
|
64
41
|
return html`
|
|
65
42
|
<ops-sectionheading>Logs</ops-sectionheading>
|
|
66
43
|
|
|
67
|
-
<div class="controls">
|
|
68
|
-
<div class="filterGroup">
|
|
69
|
-
<dees-button
|
|
70
|
-
@click=${() => this.fetchLogs()}
|
|
71
|
-
>
|
|
72
|
-
Refresh Logs
|
|
73
|
-
</dees-button>
|
|
74
|
-
</div>
|
|
75
|
-
|
|
76
|
-
<div class="filterGroup">
|
|
77
|
-
<label>Level:</label>
|
|
78
|
-
<dees-input-dropdown
|
|
79
|
-
.options=${['all', 'debug', 'info', 'warn', 'error']}
|
|
80
|
-
.selectedOption=${'all'}
|
|
81
|
-
@selectedOption=${(e: any) => this.updateFilter('level', e.detail)}
|
|
82
|
-
></dees-input-dropdown>
|
|
83
|
-
</div>
|
|
84
|
-
|
|
85
|
-
<div class="filterGroup">
|
|
86
|
-
<label>Category:</label>
|
|
87
|
-
<dees-input-dropdown
|
|
88
|
-
.options=${['all', 'smtp', 'dns', 'security', 'system', 'email']}
|
|
89
|
-
.selectedOption=${'all'}
|
|
90
|
-
@selectedOption=${(e: any) => this.updateFilter('category', e.detail)}
|
|
91
|
-
></dees-input-dropdown>
|
|
92
|
-
</div>
|
|
93
|
-
|
|
94
|
-
<div class="filterGroup">
|
|
95
|
-
<label>Limit:</label>
|
|
96
|
-
<dees-input-dropdown
|
|
97
|
-
.options=${['50', '100', '200', '500']}
|
|
98
|
-
.selectedOption=${'100'}
|
|
99
|
-
@selectedOption=${(e: any) => this.updateFilter('limit', e.detail)}
|
|
100
|
-
></dees-input-dropdown>
|
|
101
|
-
</div>
|
|
102
|
-
</div>
|
|
103
|
-
|
|
104
44
|
<dees-chart-log
|
|
105
45
|
.label=${'Application Logs'}
|
|
106
46
|
.autoScroll=${true}
|
|
@@ -115,7 +55,7 @@ export class OpsViewLogs extends DeesElement {
|
|
|
115
55
|
this.lastPushedCount = 0;
|
|
116
56
|
// Only fetch if state is empty (streaming will handle new entries)
|
|
117
57
|
if (this.logState.recentLogs.length === 0) {
|
|
118
|
-
|
|
58
|
+
await appstate.logStatePart.dispatchAction(appstate.fetchRecentLogsAction, { limit: 100 });
|
|
119
59
|
}
|
|
120
60
|
}
|
|
121
61
|
|
|
@@ -166,29 +106,4 @@ export class OpsViewLogs extends DeesElement {
|
|
|
166
106
|
}));
|
|
167
107
|
}
|
|
168
108
|
|
|
169
|
-
private async fetchLogs() {
|
|
170
|
-
await appstate.logStatePart.dispatchAction(appstate.fetchRecentLogsAction, {
|
|
171
|
-
limit: this.filterLimit,
|
|
172
|
-
level: this.filterLevel as 'debug' | 'info' | 'warn' | 'error' | undefined,
|
|
173
|
-
category: this.filterCategory as 'smtp' | 'dns' | 'security' | 'system' | 'email' | undefined,
|
|
174
|
-
});
|
|
175
|
-
}
|
|
176
|
-
|
|
177
|
-
private updateFilter(type: string, value: string) {
|
|
178
|
-
const resolved = value === 'all' ? undefined : value;
|
|
179
|
-
|
|
180
|
-
switch (type) {
|
|
181
|
-
case 'level':
|
|
182
|
-
this.filterLevel = resolved;
|
|
183
|
-
break;
|
|
184
|
-
case 'category':
|
|
185
|
-
this.filterCategory = resolved;
|
|
186
|
-
break;
|
|
187
|
-
case 'limit':
|
|
188
|
-
this.filterLimit = resolved ? parseInt(resolved, 10) : 100;
|
|
189
|
-
break;
|
|
190
|
-
}
|
|
191
|
-
|
|
192
|
-
this.fetchLogs();
|
|
193
|
-
}
|
|
194
109
|
}
|
|
@@ -0,0 +1,389 @@
|
|
|
1
|
+
import * as appstate from '../appstate.js';
|
|
2
|
+
import * as interfaces from '../../dist_ts_interfaces/index.js';
|
|
3
|
+
import { viewHostCss } from './shared/css.js';
|
|
4
|
+
import { type IStatsTile } from '@design.estate/dees-catalog';
|
|
5
|
+
|
|
6
|
+
import {
|
|
7
|
+
DeesElement,
|
|
8
|
+
css,
|
|
9
|
+
cssManager,
|
|
10
|
+
customElement,
|
|
11
|
+
html,
|
|
12
|
+
state,
|
|
13
|
+
type TemplateResult,
|
|
14
|
+
} from '@design.estate/dees-element';
|
|
15
|
+
|
|
16
|
+
@customElement('ops-view-routes')
|
|
17
|
+
export class OpsViewRoutes extends DeesElement {
|
|
18
|
+
@state() accessor routeState: appstate.IRouteManagementState = {
|
|
19
|
+
mergedRoutes: [],
|
|
20
|
+
warnings: [],
|
|
21
|
+
apiTokens: [],
|
|
22
|
+
isLoading: false,
|
|
23
|
+
error: null,
|
|
24
|
+
lastUpdated: 0,
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
constructor() {
|
|
28
|
+
super();
|
|
29
|
+
const sub = appstate.routeManagementStatePart
|
|
30
|
+
.select((s) => s)
|
|
31
|
+
.subscribe((routeState) => {
|
|
32
|
+
this.routeState = routeState;
|
|
33
|
+
});
|
|
34
|
+
this.rxSubscriptions.push(sub);
|
|
35
|
+
|
|
36
|
+
// Re-fetch routes when user logs in (fixes race condition where
|
|
37
|
+
// the view is created before authentication completes)
|
|
38
|
+
const loginSub = appstate.loginStatePart
|
|
39
|
+
.select((s) => s.isLoggedIn)
|
|
40
|
+
.subscribe((isLoggedIn) => {
|
|
41
|
+
if (isLoggedIn) {
|
|
42
|
+
appstate.routeManagementStatePart.dispatchAction(appstate.fetchMergedRoutesAction, null);
|
|
43
|
+
}
|
|
44
|
+
});
|
|
45
|
+
this.rxSubscriptions.push(loginSub);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
public static styles = [
|
|
49
|
+
cssManager.defaultStyles,
|
|
50
|
+
viewHostCss,
|
|
51
|
+
css`
|
|
52
|
+
.routesContainer {
|
|
53
|
+
display: flex;
|
|
54
|
+
flex-direction: column;
|
|
55
|
+
gap: 24px;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
.warnings-bar {
|
|
59
|
+
background: ${cssManager.bdTheme('rgba(255, 170, 0, 0.08)', 'rgba(255, 170, 0, 0.1)')};
|
|
60
|
+
border: 1px solid ${cssManager.bdTheme('rgba(255, 170, 0, 0.25)', 'rgba(255, 170, 0, 0.3)')};
|
|
61
|
+
border-radius: 8px;
|
|
62
|
+
padding: 12px 16px;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
.warning-item {
|
|
66
|
+
display: flex;
|
|
67
|
+
align-items: center;
|
|
68
|
+
gap: 8px;
|
|
69
|
+
padding: 4px 0;
|
|
70
|
+
font-size: 13px;
|
|
71
|
+
color: ${cssManager.bdTheme('#b45309', '#fa0')};
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
.warning-icon {
|
|
75
|
+
flex-shrink: 0;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
.empty-state {
|
|
79
|
+
text-align: center;
|
|
80
|
+
padding: 48px 24px;
|
|
81
|
+
color: ${cssManager.bdTheme('#6b7280', '#666')};
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
.empty-state p {
|
|
85
|
+
margin: 8px 0;
|
|
86
|
+
}
|
|
87
|
+
`,
|
|
88
|
+
];
|
|
89
|
+
|
|
90
|
+
public render(): TemplateResult {
|
|
91
|
+
const { mergedRoutes, warnings } = this.routeState;
|
|
92
|
+
|
|
93
|
+
const hardcodedCount = mergedRoutes.filter((mr) => mr.source === 'hardcoded').length;
|
|
94
|
+
const programmaticCount = mergedRoutes.filter((mr) => mr.source === 'programmatic').length;
|
|
95
|
+
const disabledCount = mergedRoutes.filter((mr) => !mr.enabled).length;
|
|
96
|
+
|
|
97
|
+
const statsTiles: IStatsTile[] = [
|
|
98
|
+
{
|
|
99
|
+
id: 'totalRoutes',
|
|
100
|
+
title: 'Total Routes',
|
|
101
|
+
type: 'number',
|
|
102
|
+
value: mergedRoutes.length,
|
|
103
|
+
icon: 'lucide:route',
|
|
104
|
+
description: 'All configured routes',
|
|
105
|
+
color: '#3b82f6',
|
|
106
|
+
},
|
|
107
|
+
{
|
|
108
|
+
id: 'hardcoded',
|
|
109
|
+
title: 'Hardcoded',
|
|
110
|
+
type: 'number',
|
|
111
|
+
value: hardcodedCount,
|
|
112
|
+
icon: 'lucide:lock',
|
|
113
|
+
description: 'Routes from constructor config',
|
|
114
|
+
color: '#8b5cf6',
|
|
115
|
+
},
|
|
116
|
+
{
|
|
117
|
+
id: 'programmatic',
|
|
118
|
+
title: 'Programmatic',
|
|
119
|
+
type: 'number',
|
|
120
|
+
value: programmaticCount,
|
|
121
|
+
icon: 'lucide:code',
|
|
122
|
+
description: 'Routes added via API',
|
|
123
|
+
color: '#0ea5e9',
|
|
124
|
+
},
|
|
125
|
+
{
|
|
126
|
+
id: 'disabled',
|
|
127
|
+
title: 'Disabled',
|
|
128
|
+
type: 'number',
|
|
129
|
+
value: disabledCount,
|
|
130
|
+
icon: 'lucide:pauseCircle',
|
|
131
|
+
description: 'Currently disabled routes',
|
|
132
|
+
color: disabledCount > 0 ? '#ef4444' : '#6b7280',
|
|
133
|
+
},
|
|
134
|
+
];
|
|
135
|
+
|
|
136
|
+
// Map merged routes to sz-route-list-view format
|
|
137
|
+
const szRoutes = mergedRoutes.map((mr) => {
|
|
138
|
+
const tags = [...(mr.route.tags || [])];
|
|
139
|
+
tags.push(mr.source);
|
|
140
|
+
if (!mr.enabled) tags.push('disabled');
|
|
141
|
+
if (mr.overridden) tags.push('overridden');
|
|
142
|
+
|
|
143
|
+
return {
|
|
144
|
+
...mr.route,
|
|
145
|
+
enabled: mr.enabled,
|
|
146
|
+
tags,
|
|
147
|
+
id: mr.storedRouteId || mr.route.name || undefined,
|
|
148
|
+
};
|
|
149
|
+
});
|
|
150
|
+
|
|
151
|
+
return html`
|
|
152
|
+
<ops-sectionheading>Route Management</ops-sectionheading>
|
|
153
|
+
|
|
154
|
+
<div class="routesContainer">
|
|
155
|
+
<dees-statsgrid
|
|
156
|
+
.tiles=${statsTiles}
|
|
157
|
+
.gridActions=${[
|
|
158
|
+
{
|
|
159
|
+
name: 'Add Route',
|
|
160
|
+
iconName: 'lucide:plus',
|
|
161
|
+
action: () => this.showCreateRouteDialog(),
|
|
162
|
+
},
|
|
163
|
+
{
|
|
164
|
+
name: 'Refresh',
|
|
165
|
+
iconName: 'lucide:refreshCw',
|
|
166
|
+
action: () => this.refreshData(),
|
|
167
|
+
},
|
|
168
|
+
]}
|
|
169
|
+
></dees-statsgrid>
|
|
170
|
+
|
|
171
|
+
${warnings.length > 0
|
|
172
|
+
? html`
|
|
173
|
+
<div class="warnings-bar">
|
|
174
|
+
${warnings.map(
|
|
175
|
+
(w) => html`
|
|
176
|
+
<div class="warning-item">
|
|
177
|
+
<span class="warning-icon">⚠</span>
|
|
178
|
+
<span>${w.message}</span>
|
|
179
|
+
</div>
|
|
180
|
+
`,
|
|
181
|
+
)}
|
|
182
|
+
</div>
|
|
183
|
+
`
|
|
184
|
+
: ''}
|
|
185
|
+
|
|
186
|
+
${szRoutes.length > 0
|
|
187
|
+
? html`
|
|
188
|
+
<sz-route-list-view
|
|
189
|
+
.routes=${szRoutes}
|
|
190
|
+
@route-click=${(e: CustomEvent) => this.handleRouteClick(e)}
|
|
191
|
+
></sz-route-list-view>
|
|
192
|
+
`
|
|
193
|
+
: html`
|
|
194
|
+
<div class="empty-state">
|
|
195
|
+
<p>No routes configured</p>
|
|
196
|
+
<p>Add a programmatic route or check your constructor configuration.</p>
|
|
197
|
+
</div>
|
|
198
|
+
`}
|
|
199
|
+
</div>
|
|
200
|
+
`;
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
private async handleRouteClick(e: CustomEvent) {
|
|
204
|
+
const clickedRoute = e.detail;
|
|
205
|
+
if (!clickedRoute) return;
|
|
206
|
+
|
|
207
|
+
// Find the corresponding merged route
|
|
208
|
+
const merged = this.routeState.mergedRoutes.find(
|
|
209
|
+
(mr) => mr.route.name === clickedRoute.name,
|
|
210
|
+
);
|
|
211
|
+
if (!merged) return;
|
|
212
|
+
|
|
213
|
+
const { DeesModal } = await import('@design.estate/dees-catalog');
|
|
214
|
+
|
|
215
|
+
if (merged.source === 'hardcoded') {
|
|
216
|
+
const menuOptions = merged.enabled
|
|
217
|
+
? [
|
|
218
|
+
{
|
|
219
|
+
name: 'Disable Route',
|
|
220
|
+
iconName: 'lucide:pause',
|
|
221
|
+
action: async (modalArg: any) => {
|
|
222
|
+
await appstate.routeManagementStatePart.dispatchAction(
|
|
223
|
+
appstate.setRouteOverrideAction,
|
|
224
|
+
{ routeName: merged.route.name!, enabled: false },
|
|
225
|
+
);
|
|
226
|
+
await modalArg.destroy();
|
|
227
|
+
},
|
|
228
|
+
},
|
|
229
|
+
{
|
|
230
|
+
name: 'Close',
|
|
231
|
+
iconName: 'lucide:x',
|
|
232
|
+
action: async (modalArg: any) => await modalArg.destroy(),
|
|
233
|
+
},
|
|
234
|
+
]
|
|
235
|
+
: [
|
|
236
|
+
{
|
|
237
|
+
name: 'Enable Route',
|
|
238
|
+
iconName: 'lucide:play',
|
|
239
|
+
action: async (modalArg: any) => {
|
|
240
|
+
await appstate.routeManagementStatePart.dispatchAction(
|
|
241
|
+
appstate.setRouteOverrideAction,
|
|
242
|
+
{ routeName: merged.route.name!, enabled: true },
|
|
243
|
+
);
|
|
244
|
+
await modalArg.destroy();
|
|
245
|
+
},
|
|
246
|
+
},
|
|
247
|
+
{
|
|
248
|
+
name: 'Remove Override',
|
|
249
|
+
iconName: 'lucide:undo',
|
|
250
|
+
action: async (modalArg: any) => {
|
|
251
|
+
await appstate.routeManagementStatePart.dispatchAction(
|
|
252
|
+
appstate.removeRouteOverrideAction,
|
|
253
|
+
merged.route.name!,
|
|
254
|
+
);
|
|
255
|
+
await modalArg.destroy();
|
|
256
|
+
},
|
|
257
|
+
},
|
|
258
|
+
{
|
|
259
|
+
name: 'Close',
|
|
260
|
+
iconName: 'lucide:x',
|
|
261
|
+
action: async (modalArg: any) => await modalArg.destroy(),
|
|
262
|
+
},
|
|
263
|
+
];
|
|
264
|
+
|
|
265
|
+
await DeesModal.createAndShow({
|
|
266
|
+
heading: `Route: ${merged.route.name}`,
|
|
267
|
+
content: html`
|
|
268
|
+
<div style="color: #ccc; padding: 8px 0;">
|
|
269
|
+
<p>Source: <strong style="color: #88f;">hardcoded</strong></p>
|
|
270
|
+
<p>Status: <strong>${merged.enabled ? 'Enabled' : 'Disabled (overridden)'}</strong></p>
|
|
271
|
+
<p style="color: #888; font-size: 13px;">Hardcoded routes cannot be edited or deleted, but they can be disabled via an override.</p>
|
|
272
|
+
</div>
|
|
273
|
+
`,
|
|
274
|
+
menuOptions,
|
|
275
|
+
});
|
|
276
|
+
} else {
|
|
277
|
+
// Programmatic route
|
|
278
|
+
await DeesModal.createAndShow({
|
|
279
|
+
heading: `Route: ${merged.route.name}`,
|
|
280
|
+
content: html`
|
|
281
|
+
<div style="color: #ccc; padding: 8px 0;">
|
|
282
|
+
<p>Source: <strong style="color: #0af;">programmatic</strong></p>
|
|
283
|
+
<p>Status: <strong>${merged.enabled ? 'Enabled' : 'Disabled'}</strong></p>
|
|
284
|
+
<p>ID: <code style="color: #888;">${merged.storedRouteId}</code></p>
|
|
285
|
+
</div>
|
|
286
|
+
`,
|
|
287
|
+
menuOptions: [
|
|
288
|
+
{
|
|
289
|
+
name: merged.enabled ? 'Disable' : 'Enable',
|
|
290
|
+
iconName: merged.enabled ? 'lucide:pause' : 'lucide:play',
|
|
291
|
+
action: async (modalArg: any) => {
|
|
292
|
+
await appstate.routeManagementStatePart.dispatchAction(
|
|
293
|
+
appstate.toggleRouteAction,
|
|
294
|
+
{ id: merged.storedRouteId!, enabled: !merged.enabled },
|
|
295
|
+
);
|
|
296
|
+
await modalArg.destroy();
|
|
297
|
+
},
|
|
298
|
+
},
|
|
299
|
+
{
|
|
300
|
+
name: 'Delete',
|
|
301
|
+
iconName: 'lucide:trash-2',
|
|
302
|
+
action: async (modalArg: any) => {
|
|
303
|
+
await appstate.routeManagementStatePart.dispatchAction(
|
|
304
|
+
appstate.deleteRouteAction,
|
|
305
|
+
merged.storedRouteId!,
|
|
306
|
+
);
|
|
307
|
+
await modalArg.destroy();
|
|
308
|
+
},
|
|
309
|
+
},
|
|
310
|
+
{
|
|
311
|
+
name: 'Close',
|
|
312
|
+
iconName: 'lucide:x',
|
|
313
|
+
action: async (modalArg: any) => await modalArg.destroy(),
|
|
314
|
+
},
|
|
315
|
+
],
|
|
316
|
+
});
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
private async showCreateRouteDialog() {
|
|
321
|
+
const { DeesModal } = await import('@design.estate/dees-catalog');
|
|
322
|
+
|
|
323
|
+
await DeesModal.createAndShow({
|
|
324
|
+
heading: 'Add Programmatic Route',
|
|
325
|
+
content: html`
|
|
326
|
+
<dees-form>
|
|
327
|
+
<dees-input-text .key=${'name'} .label=${'Route Name'} .required=${true}></dees-input-text>
|
|
328
|
+
<dees-input-text .key=${'ports'} .label=${'Ports (comma-separated)'} .required=${true}></dees-input-text>
|
|
329
|
+
<dees-input-text .key=${'domains'} .label=${'Domains (comma-separated, optional)'}></dees-input-text>
|
|
330
|
+
<dees-input-text .key=${'targetHost'} .label=${'Target Host'} .value=${'localhost'} .required=${true}></dees-input-text>
|
|
331
|
+
<dees-input-text .key=${'targetPort'} .label=${'Target Port'} .required=${true}></dees-input-text>
|
|
332
|
+
</dees-form>
|
|
333
|
+
`,
|
|
334
|
+
menuOptions: [
|
|
335
|
+
{
|
|
336
|
+
name: 'Cancel',
|
|
337
|
+
iconName: 'lucide:x',
|
|
338
|
+
action: async (modalArg: any) => await modalArg.destroy(),
|
|
339
|
+
},
|
|
340
|
+
{
|
|
341
|
+
name: 'Create',
|
|
342
|
+
iconName: 'lucide:plus',
|
|
343
|
+
action: async (modalArg: any) => {
|
|
344
|
+
const form = modalArg.shadowRoot?.querySelector('.content')?.querySelector('dees-form');
|
|
345
|
+
if (!form) return;
|
|
346
|
+
const formData = await form.collectFormData();
|
|
347
|
+
if (!formData.name || !formData.ports) return;
|
|
348
|
+
|
|
349
|
+
const ports = formData.ports.split(',').map((p: string) => parseInt(p.trim(), 10)).filter((p: number) => !isNaN(p));
|
|
350
|
+
const domains = formData.domains
|
|
351
|
+
? formData.domains.split(',').map((d: string) => d.trim()).filter(Boolean)
|
|
352
|
+
: undefined;
|
|
353
|
+
|
|
354
|
+
const route: any = {
|
|
355
|
+
name: formData.name,
|
|
356
|
+
match: {
|
|
357
|
+
ports,
|
|
358
|
+
...(domains && domains.length > 0 ? { domains } : {}),
|
|
359
|
+
},
|
|
360
|
+
action: {
|
|
361
|
+
type: 'forward',
|
|
362
|
+
targets: [
|
|
363
|
+
{
|
|
364
|
+
host: formData.targetHost || 'localhost',
|
|
365
|
+
port: parseInt(formData.targetPort, 10),
|
|
366
|
+
},
|
|
367
|
+
],
|
|
368
|
+
},
|
|
369
|
+
};
|
|
370
|
+
|
|
371
|
+
await appstate.routeManagementStatePart.dispatchAction(
|
|
372
|
+
appstate.createRouteAction,
|
|
373
|
+
{ route },
|
|
374
|
+
);
|
|
375
|
+
await modalArg.destroy();
|
|
376
|
+
},
|
|
377
|
+
},
|
|
378
|
+
],
|
|
379
|
+
});
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
private refreshData() {
|
|
383
|
+
appstate.routeManagementStatePart.dispatchAction(appstate.fetchMergedRoutesAction, null);
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
async firstUpdated() {
|
|
387
|
+
await appstate.routeManagementStatePart.dispatchAction(appstate.fetchMergedRoutesAction, null);
|
|
388
|
+
}
|
|
389
|
+
}
|
package/ts_web/plugins.ts
CHANGED
|
@@ -2,12 +2,16 @@
|
|
|
2
2
|
import * as deesElement from '@design.estate/dees-element';
|
|
3
3
|
import * as deesCatalog from '@design.estate/dees-catalog';
|
|
4
4
|
|
|
5
|
+
// @serve.zone scope
|
|
6
|
+
import * as szCatalog from '@serve.zone/catalog';
|
|
7
|
+
|
|
5
8
|
// TypedSocket for real-time push communication
|
|
6
9
|
import * as typedsocket from '@api.global/typedsocket';
|
|
7
10
|
|
|
8
11
|
export {
|
|
9
12
|
deesElement,
|
|
10
13
|
deesCatalog,
|
|
14
|
+
szCatalog,
|
|
11
15
|
typedsocket,
|
|
12
16
|
}
|
|
13
17
|
|
package/ts_web/router.ts
CHANGED
|
@@ -3,11 +3,9 @@ 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', 'configuration', 'security', 'certificates', 'remoteingress'] as const;
|
|
7
|
-
export const validEmailFolders = ['queued', 'sent', 'failed', 'security'] as const;
|
|
6
|
+
export const validViews = ['overview', 'network', 'emails', 'logs', 'routes', 'apitokens', 'configuration', 'security', 'certificates', 'remoteingress'] as const;
|
|
8
7
|
|
|
9
8
|
export type TValidView = typeof validViews[number];
|
|
10
|
-
export type TValidEmailFolder = typeof validEmailFolders[number];
|
|
11
9
|
|
|
12
10
|
class AppRouter {
|
|
13
11
|
private router: InstanceType<typeof SmartRouter>;
|
|
@@ -27,31 +25,10 @@ class AppRouter {
|
|
|
27
25
|
}
|
|
28
26
|
|
|
29
27
|
private setupRoutes(): void {
|
|
30
|
-
// Main views
|
|
31
28
|
for (const view of validViews) {
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
this.updateViewState('emails');
|
|
36
|
-
this.updateEmailFolder('queued');
|
|
37
|
-
});
|
|
38
|
-
|
|
39
|
-
// Email with folder parameter
|
|
40
|
-
this.router.on('/emails/:folder', async (routeInfo) => {
|
|
41
|
-
const folder = routeInfo.params.folder as string;
|
|
42
|
-
if (validEmailFolders.includes(folder as TValidEmailFolder)) {
|
|
43
|
-
this.updateViewState('emails');
|
|
44
|
-
this.updateEmailFolder(folder as TValidEmailFolder);
|
|
45
|
-
} else {
|
|
46
|
-
// Invalid folder, redirect to queued
|
|
47
|
-
this.navigateTo('/emails/queued');
|
|
48
|
-
}
|
|
49
|
-
});
|
|
50
|
-
} else {
|
|
51
|
-
this.router.on(`/${view}`, async () => {
|
|
52
|
-
this.updateViewState(view);
|
|
53
|
-
});
|
|
54
|
-
}
|
|
29
|
+
this.router.on(`/${view}`, async () => {
|
|
30
|
+
this.updateViewState(view);
|
|
31
|
+
});
|
|
55
32
|
}
|
|
56
33
|
|
|
57
34
|
// Root redirect
|
|
@@ -61,60 +38,32 @@ class AppRouter {
|
|
|
61
38
|
}
|
|
62
39
|
|
|
63
40
|
private setupStateSync(): void {
|
|
64
|
-
// Sync URL when state changes programmatically (not from router)
|
|
65
41
|
appstate.uiStatePart.state.subscribe((uiState) => {
|
|
66
42
|
if (this.suppressStateUpdate) return;
|
|
67
43
|
|
|
68
44
|
const currentPath = window.location.pathname;
|
|
69
|
-
const expectedPath =
|
|
45
|
+
const expectedPath = `/${uiState.activeView}`;
|
|
70
46
|
|
|
71
|
-
|
|
72
|
-
if (!currentPath.startsWith(expectedPath)) {
|
|
47
|
+
if (currentPath !== expectedPath) {
|
|
73
48
|
this.suppressStateUpdate = true;
|
|
74
|
-
|
|
75
|
-
const emailState = appstate.emailOpsStatePart.getState();
|
|
76
|
-
this.router.pushUrl(`/emails/${emailState.currentView}`);
|
|
77
|
-
} else {
|
|
78
|
-
this.router.pushUrl(`/${uiState.activeView}`);
|
|
79
|
-
}
|
|
49
|
+
this.router.pushUrl(expectedPath);
|
|
80
50
|
this.suppressStateUpdate = false;
|
|
81
51
|
}
|
|
82
52
|
});
|
|
83
53
|
}
|
|
84
54
|
|
|
85
|
-
private getExpectedPath(view: string): string {
|
|
86
|
-
if (view === 'emails') {
|
|
87
|
-
return '/emails';
|
|
88
|
-
}
|
|
89
|
-
return `/${view}`;
|
|
90
|
-
}
|
|
91
|
-
|
|
92
55
|
private handleInitialRoute(): void {
|
|
93
56
|
const path = window.location.pathname;
|
|
94
57
|
|
|
95
58
|
if (!path || path === '/') {
|
|
96
|
-
// Redirect root to overview
|
|
97
59
|
this.router.pushUrl('/overview');
|
|
98
60
|
} else {
|
|
99
|
-
// Parse current path and update state
|
|
100
61
|
const segments = path.split('/').filter(Boolean);
|
|
101
62
|
const view = segments[0];
|
|
102
63
|
|
|
103
64
|
if (validViews.includes(view as TValidView)) {
|
|
104
65
|
this.updateViewState(view as TValidView);
|
|
105
|
-
|
|
106
|
-
if (view === 'emails' && segments[1]) {
|
|
107
|
-
const folder = segments[1];
|
|
108
|
-
if (validEmailFolders.includes(folder as TValidEmailFolder)) {
|
|
109
|
-
this.updateEmailFolder(folder as TValidEmailFolder);
|
|
110
|
-
} else {
|
|
111
|
-
this.updateEmailFolder('queued');
|
|
112
|
-
}
|
|
113
|
-
} else if (view === 'emails') {
|
|
114
|
-
this.updateEmailFolder('queued');
|
|
115
|
-
}
|
|
116
66
|
} else {
|
|
117
|
-
// Invalid view, redirect to overview
|
|
118
67
|
this.router.pushUrl('/overview');
|
|
119
68
|
}
|
|
120
69
|
}
|
|
@@ -132,18 +81,6 @@ class AppRouter {
|
|
|
132
81
|
this.suppressStateUpdate = false;
|
|
133
82
|
}
|
|
134
83
|
|
|
135
|
-
private updateEmailFolder(folder: TValidEmailFolder): void {
|
|
136
|
-
this.suppressStateUpdate = true;
|
|
137
|
-
const currentState = appstate.emailOpsStatePart.getState();
|
|
138
|
-
if (currentState.currentView !== folder) {
|
|
139
|
-
appstate.emailOpsStatePart.setState({
|
|
140
|
-
...currentState,
|
|
141
|
-
currentView: folder as appstate.IEmailOpsState['currentView'],
|
|
142
|
-
});
|
|
143
|
-
}
|
|
144
|
-
this.suppressStateUpdate = false;
|
|
145
|
-
}
|
|
146
|
-
|
|
147
84
|
public navigateTo(path: string): void {
|
|
148
85
|
this.router.pushUrl(path);
|
|
149
86
|
}
|
|
@@ -156,22 +93,10 @@ class AppRouter {
|
|
|
156
93
|
}
|
|
157
94
|
}
|
|
158
95
|
|
|
159
|
-
public navigateToEmailFolder(folder: string): void {
|
|
160
|
-
if (validEmailFolders.includes(folder as TValidEmailFolder)) {
|
|
161
|
-
this.navigateTo(`/emails/${folder}`);
|
|
162
|
-
} else {
|
|
163
|
-
this.navigateTo('/emails/queued');
|
|
164
|
-
}
|
|
165
|
-
}
|
|
166
|
-
|
|
167
96
|
public getCurrentView(): string {
|
|
168
97
|
return appstate.uiStatePart.getState().activeView;
|
|
169
98
|
}
|
|
170
99
|
|
|
171
|
-
public getCurrentEmailFolder(): string {
|
|
172
|
-
return appstate.emailOpsStatePart.getState().currentView;
|
|
173
|
-
}
|
|
174
|
-
|
|
175
100
|
public destroy(): void {
|
|
176
101
|
this.router.destroy();
|
|
177
102
|
this.initialized = false;
|