@serve.zone/catalog 2.2.0 → 2.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.
- package/dist_ts_web/00_commitinfo_data.js +1 -1
- package/dist_ts_web/elements/index.d.ts +6 -0
- package/dist_ts_web/elements/index.js +9 -1
- package/dist_ts_web/elements/sz-config-overview.d.ts +14 -0
- package/dist_ts_web/elements/sz-config-overview.js +141 -0
- package/dist_ts_web/elements/sz-config-section.d.ts +31 -0
- package/dist_ts_web/elements/sz-config-section.js +591 -0
- package/dist_ts_web/elements/sz-demo-view-config.d.ts +11 -0
- package/dist_ts_web/elements/sz-demo-view-config.js +193 -0
- package/dist_ts_web/elements/sz-demo-view-routes.d.ts +22 -0
- package/dist_ts_web/elements/sz-demo-view-routes.js +388 -0
- package/dist_ts_web/elements/sz-route-card.d.ts +78 -0
- package/dist_ts_web/elements/sz-route-card.js +648 -0
- package/dist_ts_web/elements/sz-route-list-view.d.ts +21 -0
- package/dist_ts_web/elements/sz-route-list-view.js +372 -0
- package/dist_ts_web/pages/sz-demo-app-shell.js +8 -2
- package/dist_watch/bundle.js +2074 -152
- package/dist_watch/bundle.js.map +4 -4
- package/package.json +1 -1
- package/ts_web/00_commitinfo_data.ts +1 -1
- package/ts_web/elements/index.ts +10 -0
- package/ts_web/elements/sz-config-overview.ts +92 -0
- package/ts_web/elements/sz-config-section.ts +531 -0
- package/ts_web/elements/sz-demo-view-config.ts +164 -0
- package/ts_web/elements/sz-demo-view-routes.ts +362 -0
- package/ts_web/elements/sz-route-card.ts +667 -0
- package/ts_web/elements/sz-route-list-view.ts +326 -0
- package/ts_web/pages/sz-demo-app-shell.ts +7 -1
|
@@ -0,0 +1,667 @@
|
|
|
1
|
+
import {
|
|
2
|
+
DeesElement,
|
|
3
|
+
customElement,
|
|
4
|
+
html,
|
|
5
|
+
css,
|
|
6
|
+
cssManager,
|
|
7
|
+
property,
|
|
8
|
+
type TemplateResult,
|
|
9
|
+
} from '@design.estate/dees-element';
|
|
10
|
+
|
|
11
|
+
declare global {
|
|
12
|
+
interface HTMLElementTagNameMap {
|
|
13
|
+
'sz-route-card': SzRouteCard;
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
// Simplified route types for display purposes
|
|
18
|
+
export type TRouteActionType = 'forward' | 'socket-handler';
|
|
19
|
+
export type TTlsMode = 'passthrough' | 'terminate' | 'terminate-and-reencrypt';
|
|
20
|
+
export type TPortRange = number | number[] | Array<{ from: number; to: number }>;
|
|
21
|
+
|
|
22
|
+
export interface IRouteMatch {
|
|
23
|
+
ports: TPortRange;
|
|
24
|
+
domains?: string | string[];
|
|
25
|
+
path?: string;
|
|
26
|
+
clientIp?: string[];
|
|
27
|
+
tlsVersion?: string[];
|
|
28
|
+
headers?: Record<string, string>;
|
|
29
|
+
protocol?: 'http' | 'tcp';
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export interface IRouteTarget {
|
|
33
|
+
host: string | string[];
|
|
34
|
+
port: number | 'preserve';
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export interface IRouteTls {
|
|
38
|
+
mode: TTlsMode;
|
|
39
|
+
certificate?: 'auto' | { key: string; cert: string };
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export interface IRouteAction {
|
|
43
|
+
type: TRouteActionType;
|
|
44
|
+
targets?: IRouteTarget[];
|
|
45
|
+
tls?: IRouteTls;
|
|
46
|
+
websocket?: { enabled: boolean };
|
|
47
|
+
loadBalancing?: { algorithm: 'round-robin' | 'least-connections' | 'ip-hash' };
|
|
48
|
+
forwardingEngine?: 'node' | 'nftables';
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export interface IRouteSecurity {
|
|
52
|
+
ipAllowList?: string[];
|
|
53
|
+
ipBlockList?: string[];
|
|
54
|
+
maxConnections?: number;
|
|
55
|
+
rateLimit?: { enabled: boolean; maxRequests: number; window: number };
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
export interface IRouteConfig {
|
|
59
|
+
id?: string;
|
|
60
|
+
match: IRouteMatch;
|
|
61
|
+
action: IRouteAction;
|
|
62
|
+
security?: IRouteSecurity;
|
|
63
|
+
headers?: { request?: Record<string, string>; response?: Record<string, string> };
|
|
64
|
+
name?: string;
|
|
65
|
+
description?: string;
|
|
66
|
+
priority?: number;
|
|
67
|
+
tags?: string[];
|
|
68
|
+
enabled?: boolean;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
function formatPorts(ports: TPortRange): string {
|
|
72
|
+
if (typeof ports === 'number') return String(ports);
|
|
73
|
+
if (Array.isArray(ports)) {
|
|
74
|
+
return ports
|
|
75
|
+
.map((p) => {
|
|
76
|
+
if (typeof p === 'number') return String(p);
|
|
77
|
+
return `${p.from}\u2013${p.to}`;
|
|
78
|
+
})
|
|
79
|
+
.join(', ');
|
|
80
|
+
}
|
|
81
|
+
return String(ports);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
function formatTargets(targets: IRouteTarget[]): string[] {
|
|
85
|
+
const result: string[] = [];
|
|
86
|
+
for (const t of targets) {
|
|
87
|
+
const hosts = Array.isArray(t.host) ? t.host : [t.host];
|
|
88
|
+
const portStr = t.port === 'preserve' ? '(preserve)' : String(t.port);
|
|
89
|
+
for (const h of hosts) {
|
|
90
|
+
result.push(`${h}:${portStr}`);
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
return result;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
@customElement('sz-route-card')
|
|
97
|
+
export class SzRouteCard extends DeesElement {
|
|
98
|
+
public static demo = () => html`
|
|
99
|
+
<div style="padding: 24px; max-width: 520px;">
|
|
100
|
+
<sz-route-card
|
|
101
|
+
.route=${{
|
|
102
|
+
name: 'API Gateway',
|
|
103
|
+
description: 'Main API gateway with TLS termination and load balancing',
|
|
104
|
+
enabled: true,
|
|
105
|
+
priority: 10,
|
|
106
|
+
tags: ['web', 'api', 'production'],
|
|
107
|
+
match: {
|
|
108
|
+
ports: [443, 8443],
|
|
109
|
+
domains: ['api.example.com', '*.api.serve.zone'],
|
|
110
|
+
path: '/api/*',
|
|
111
|
+
protocol: 'http' as const,
|
|
112
|
+
clientIp: ['10.0.0.0/8'],
|
|
113
|
+
},
|
|
114
|
+
action: {
|
|
115
|
+
type: 'forward' as const,
|
|
116
|
+
targets: [
|
|
117
|
+
{ host: ['10.0.0.1', '10.0.0.2'], port: 8080 },
|
|
118
|
+
],
|
|
119
|
+
tls: { mode: 'terminate' as const, certificate: 'auto' as const },
|
|
120
|
+
websocket: { enabled: true },
|
|
121
|
+
loadBalancing: { algorithm: 'round-robin' as const },
|
|
122
|
+
forwardingEngine: 'nftables' as const,
|
|
123
|
+
},
|
|
124
|
+
security: {
|
|
125
|
+
ipAllowList: ['10.0.0.0/8'],
|
|
126
|
+
ipBlockList: ['192.168.100.0/24'],
|
|
127
|
+
rateLimit: { enabled: true, maxRequests: 100, window: 60 },
|
|
128
|
+
maxConnections: 1000,
|
|
129
|
+
},
|
|
130
|
+
} satisfies IRouteConfig}
|
|
131
|
+
></sz-route-card>
|
|
132
|
+
</div>
|
|
133
|
+
`;
|
|
134
|
+
|
|
135
|
+
public static demoGroups = ['Routes'];
|
|
136
|
+
|
|
137
|
+
@property({ type: Object })
|
|
138
|
+
public accessor route: IRouteConfig | null = null;
|
|
139
|
+
|
|
140
|
+
public static styles = [
|
|
141
|
+
cssManager.defaultStyles,
|
|
142
|
+
css`
|
|
143
|
+
:host {
|
|
144
|
+
display: block;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
.card {
|
|
148
|
+
background: ${cssManager.bdTheme('#ffffff', '#09090b')};
|
|
149
|
+
border: 1px solid ${cssManager.bdTheme('#e4e4e7', '#27272a')};
|
|
150
|
+
border-radius: 8px;
|
|
151
|
+
padding: 20px;
|
|
152
|
+
transition: border-color 200ms ease, box-shadow 200ms ease;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
.card:hover {
|
|
156
|
+
border-color: ${cssManager.bdTheme('#d4d4d8', '#3f3f46')};
|
|
157
|
+
box-shadow: 0 2px 8px ${cssManager.bdTheme('rgba(0,0,0,0.06)', 'rgba(0,0,0,0.2)')};
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
/* Header */
|
|
161
|
+
.header {
|
|
162
|
+
display: flex;
|
|
163
|
+
align-items: flex-start;
|
|
164
|
+
justify-content: space-between;
|
|
165
|
+
gap: 12px;
|
|
166
|
+
margin-bottom: 4px;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
.header-left {
|
|
170
|
+
display: flex;
|
|
171
|
+
align-items: center;
|
|
172
|
+
gap: 8px;
|
|
173
|
+
min-width: 0;
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
.status-dot {
|
|
177
|
+
width: 8px;
|
|
178
|
+
height: 8px;
|
|
179
|
+
border-radius: 50%;
|
|
180
|
+
flex-shrink: 0;
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
.status-dot.enabled {
|
|
184
|
+
background: ${cssManager.bdTheme('#22c55e', '#22c55e')};
|
|
185
|
+
box-shadow: 0 0 6px ${cssManager.bdTheme('rgba(34,197,94,0.4)', 'rgba(34,197,94,0.3)')};
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
.status-dot.disabled {
|
|
189
|
+
background: ${cssManager.bdTheme('#a1a1aa', '#52525b')};
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
.route-name {
|
|
193
|
+
font-size: 15px;
|
|
194
|
+
font-weight: 600;
|
|
195
|
+
color: ${cssManager.bdTheme('#18181b', '#fafafa')};
|
|
196
|
+
white-space: nowrap;
|
|
197
|
+
overflow: hidden;
|
|
198
|
+
text-overflow: ellipsis;
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
.header-badges {
|
|
202
|
+
display: flex;
|
|
203
|
+
gap: 6px;
|
|
204
|
+
flex-shrink: 0;
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
.badge {
|
|
208
|
+
display: inline-flex;
|
|
209
|
+
align-items: center;
|
|
210
|
+
padding: 2px 8px;
|
|
211
|
+
border-radius: 9999px;
|
|
212
|
+
font-size: 11px;
|
|
213
|
+
font-weight: 500;
|
|
214
|
+
white-space: nowrap;
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
.badge.forward {
|
|
218
|
+
background: ${cssManager.bdTheme('#dbeafe', 'rgba(59, 130, 246, 0.2)')};
|
|
219
|
+
color: ${cssManager.bdTheme('#2563eb', '#60a5fa')};
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
.badge.socket-handler {
|
|
223
|
+
background: ${cssManager.bdTheme('#ede9fe', 'rgba(139, 92, 246, 0.2)')};
|
|
224
|
+
color: ${cssManager.bdTheme('#7c3aed', '#a78bfa')};
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
.badge.enabled {
|
|
228
|
+
background: ${cssManager.bdTheme('#dcfce7', 'rgba(34, 197, 94, 0.2)')};
|
|
229
|
+
color: ${cssManager.bdTheme('#16a34a', '#22c55e')};
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
.badge.disabled {
|
|
233
|
+
background: ${cssManager.bdTheme('#f4f4f5', 'rgba(113, 113, 122, 0.2)')};
|
|
234
|
+
color: ${cssManager.bdTheme('#71717a', '#a1a1aa')};
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
.description {
|
|
238
|
+
font-size: 13px;
|
|
239
|
+
color: ${cssManager.bdTheme('#71717a', '#a1a1aa')};
|
|
240
|
+
margin-bottom: 8px;
|
|
241
|
+
line-height: 1.4;
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
.meta-row {
|
|
245
|
+
display: flex;
|
|
246
|
+
align-items: center;
|
|
247
|
+
justify-content: space-between;
|
|
248
|
+
flex-wrap: wrap;
|
|
249
|
+
gap: 6px;
|
|
250
|
+
margin-bottom: 16px;
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
.tags {
|
|
254
|
+
display: flex;
|
|
255
|
+
flex-wrap: wrap;
|
|
256
|
+
gap: 4px;
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
.tag {
|
|
260
|
+
padding: 2px 8px;
|
|
261
|
+
background: ${cssManager.bdTheme('#f4f4f5', '#27272a')};
|
|
262
|
+
border-radius: 4px;
|
|
263
|
+
font-size: 11px;
|
|
264
|
+
font-weight: 500;
|
|
265
|
+
color: ${cssManager.bdTheme('#52525b', '#a1a1aa')};
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
.priority {
|
|
269
|
+
font-size: 11px;
|
|
270
|
+
color: ${cssManager.bdTheme('#a1a1aa', '#71717a')};
|
|
271
|
+
font-weight: 500;
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
/* Sections */
|
|
275
|
+
.section {
|
|
276
|
+
border-left: 3px solid;
|
|
277
|
+
padding: 10px 14px;
|
|
278
|
+
margin-bottom: 12px;
|
|
279
|
+
border-radius: 0 6px 6px 0;
|
|
280
|
+
background: ${cssManager.bdTheme('#fafafa', '#0a0a0a')};
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
.section:last-of-type {
|
|
284
|
+
margin-bottom: 0;
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
.section.match {
|
|
288
|
+
border-left-color: ${cssManager.bdTheme('#3b82f6', '#3b82f6')};
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
.section.action {
|
|
292
|
+
border-left-color: ${cssManager.bdTheme('#22c55e', '#22c55e')};
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
.section.security {
|
|
296
|
+
border-left-color: ${cssManager.bdTheme('#f59e0b', '#f59e0b')};
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
.section-label {
|
|
300
|
+
font-size: 10px;
|
|
301
|
+
font-weight: 600;
|
|
302
|
+
text-transform: uppercase;
|
|
303
|
+
letter-spacing: 0.08em;
|
|
304
|
+
color: ${cssManager.bdTheme('#a1a1aa', '#71717a')};
|
|
305
|
+
margin-bottom: 8px;
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
.field-row {
|
|
309
|
+
display: flex;
|
|
310
|
+
gap: 8px;
|
|
311
|
+
margin-bottom: 5px;
|
|
312
|
+
font-size: 13px;
|
|
313
|
+
line-height: 1.5;
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
.field-row:last-child {
|
|
317
|
+
margin-bottom: 0;
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
.field-key {
|
|
321
|
+
color: ${cssManager.bdTheme('#71717a', '#a1a1aa')};
|
|
322
|
+
min-width: 64px;
|
|
323
|
+
flex-shrink: 0;
|
|
324
|
+
font-weight: 500;
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
.field-value {
|
|
328
|
+
color: ${cssManager.bdTheme('#18181b', '#e4e4e7')};
|
|
329
|
+
word-break: break-all;
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
.domain-chip {
|
|
333
|
+
display: inline-flex;
|
|
334
|
+
padding: 1px 6px;
|
|
335
|
+
background: ${cssManager.bdTheme('#eff6ff', 'rgba(59, 130, 246, 0.1)')};
|
|
336
|
+
border-radius: 3px;
|
|
337
|
+
font-size: 12px;
|
|
338
|
+
margin-right: 4px;
|
|
339
|
+
margin-bottom: 2px;
|
|
340
|
+
font-family: monospace;
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
.domain-chip.glob {
|
|
344
|
+
background: ${cssManager.bdTheme('#fef3c7', 'rgba(245, 158, 11, 0.15)')};
|
|
345
|
+
color: ${cssManager.bdTheme('#92400e', '#fbbf24')};
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
.mono {
|
|
349
|
+
font-family: monospace;
|
|
350
|
+
font-size: 12px;
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
.protocol-badge {
|
|
354
|
+
display: inline-flex;
|
|
355
|
+
padding: 1px 6px;
|
|
356
|
+
border-radius: 3px;
|
|
357
|
+
font-size: 11px;
|
|
358
|
+
font-weight: 500;
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
.protocol-badge.http {
|
|
362
|
+
background: ${cssManager.bdTheme('#dbeafe', 'rgba(59, 130, 246, 0.2)')};
|
|
363
|
+
color: ${cssManager.bdTheme('#2563eb', '#60a5fa')};
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
.protocol-badge.tcp {
|
|
367
|
+
background: ${cssManager.bdTheme('#dcfce7', 'rgba(34, 197, 94, 0.2)')};
|
|
368
|
+
color: ${cssManager.bdTheme('#16a34a', '#22c55e')};
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
.tls-badge {
|
|
372
|
+
display: inline-flex;
|
|
373
|
+
padding: 1px 6px;
|
|
374
|
+
border-radius: 3px;
|
|
375
|
+
font-size: 11px;
|
|
376
|
+
font-weight: 500;
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
.tls-badge.auto {
|
|
380
|
+
background: ${cssManager.bdTheme('#dcfce7', 'rgba(34, 197, 94, 0.2)')};
|
|
381
|
+
color: ${cssManager.bdTheme('#16a34a', '#22c55e')};
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
.tls-badge.custom {
|
|
385
|
+
background: ${cssManager.bdTheme('#ffedd5', 'rgba(249, 115, 22, 0.2)')};
|
|
386
|
+
color: ${cssManager.bdTheme('#c2410c', '#fb923c')};
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
.engine-badge {
|
|
390
|
+
display: inline-flex;
|
|
391
|
+
padding: 1px 6px;
|
|
392
|
+
border-radius: 3px;
|
|
393
|
+
font-size: 11px;
|
|
394
|
+
font-weight: 500;
|
|
395
|
+
background: ${cssManager.bdTheme('#fae8ff', 'rgba(168, 85, 247, 0.2)')};
|
|
396
|
+
color: ${cssManager.bdTheme('#7e22ce', '#c084fc')};
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
.header-pair {
|
|
400
|
+
display: inline;
|
|
401
|
+
font-family: monospace;
|
|
402
|
+
font-size: 12px;
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
/* Feature icons */
|
|
406
|
+
.features-row {
|
|
407
|
+
display: flex;
|
|
408
|
+
flex-wrap: wrap;
|
|
409
|
+
gap: 10px;
|
|
410
|
+
margin-top: 14px;
|
|
411
|
+
padding-top: 12px;
|
|
412
|
+
border-top: 1px solid ${cssManager.bdTheme('#f4f4f5', '#1a1a1a')};
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
.feature {
|
|
416
|
+
display: flex;
|
|
417
|
+
align-items: center;
|
|
418
|
+
gap: 4px;
|
|
419
|
+
font-size: 11px;
|
|
420
|
+
font-weight: 500;
|
|
421
|
+
color: ${cssManager.bdTheme('#71717a', '#a1a1aa')};
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
.feature-icon {
|
|
425
|
+
font-size: 13px;
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
.no-route {
|
|
429
|
+
text-align: center;
|
|
430
|
+
padding: 24px;
|
|
431
|
+
color: ${cssManager.bdTheme('#a1a1aa', '#52525b')};
|
|
432
|
+
font-size: 13px;
|
|
433
|
+
}
|
|
434
|
+
`,
|
|
435
|
+
];
|
|
436
|
+
|
|
437
|
+
public render(): TemplateResult {
|
|
438
|
+
if (!this.route) {
|
|
439
|
+
return html`<div class="card"><div class="no-route">No route data</div></div>`;
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
const r = this.route;
|
|
443
|
+
const isEnabled = r.enabled !== false;
|
|
444
|
+
const match = r.match;
|
|
445
|
+
const action = r.action;
|
|
446
|
+
const security = r.security;
|
|
447
|
+
|
|
448
|
+
return html`
|
|
449
|
+
<div class="card">
|
|
450
|
+
<!-- Header -->
|
|
451
|
+
<div class="header">
|
|
452
|
+
<div class="header-left">
|
|
453
|
+
<span class="status-dot ${isEnabled ? 'enabled' : 'disabled'}"></span>
|
|
454
|
+
<span class="route-name">${r.name || r.id || 'Unnamed Route'}</span>
|
|
455
|
+
</div>
|
|
456
|
+
<div class="header-badges">
|
|
457
|
+
<span class="badge ${action.type}">${action.type}</span>
|
|
458
|
+
<span class="badge ${isEnabled ? 'enabled' : 'disabled'}">${isEnabled ? 'enabled' : 'disabled'}</span>
|
|
459
|
+
</div>
|
|
460
|
+
</div>
|
|
461
|
+
|
|
462
|
+
${r.description ? html`<div class="description">${r.description}</div>` : ''}
|
|
463
|
+
|
|
464
|
+
<div class="meta-row">
|
|
465
|
+
${r.tags && r.tags.length > 0
|
|
466
|
+
? html`<div class="tags">${r.tags.map((t) => html`<span class="tag">${t}</span>`)}</div>`
|
|
467
|
+
: html`<div></div>`}
|
|
468
|
+
${r.priority != null ? html`<span class="priority">Priority: ${r.priority}</span>` : ''}
|
|
469
|
+
</div>
|
|
470
|
+
|
|
471
|
+
<!-- Match Section -->
|
|
472
|
+
<div class="section match">
|
|
473
|
+
<div class="section-label">Match</div>
|
|
474
|
+
<div class="field-row">
|
|
475
|
+
<span class="field-key">Ports</span>
|
|
476
|
+
<span class="field-value mono">${formatPorts(match.ports)}</span>
|
|
477
|
+
</div>
|
|
478
|
+
${match.domains
|
|
479
|
+
? html`
|
|
480
|
+
<div class="field-row">
|
|
481
|
+
<span class="field-key">Domains</span>
|
|
482
|
+
<span class="field-value">${this.renderDomains(match.domains)}</span>
|
|
483
|
+
</div>
|
|
484
|
+
`
|
|
485
|
+
: ''}
|
|
486
|
+
${match.path
|
|
487
|
+
? html`
|
|
488
|
+
<div class="field-row">
|
|
489
|
+
<span class="field-key">Path</span>
|
|
490
|
+
<span class="field-value mono">${match.path}</span>
|
|
491
|
+
</div>
|
|
492
|
+
`
|
|
493
|
+
: ''}
|
|
494
|
+
${match.protocol
|
|
495
|
+
? html`
|
|
496
|
+
<div class="field-row">
|
|
497
|
+
<span class="field-key">Protocol</span>
|
|
498
|
+
<span class="field-value">
|
|
499
|
+
<span class="protocol-badge ${match.protocol}">${match.protocol}</span>
|
|
500
|
+
</span>
|
|
501
|
+
</div>
|
|
502
|
+
`
|
|
503
|
+
: ''}
|
|
504
|
+
${match.clientIp && match.clientIp.length > 0
|
|
505
|
+
? html`
|
|
506
|
+
<div class="field-row">
|
|
507
|
+
<span class="field-key">Client</span>
|
|
508
|
+
<span class="field-value mono">${match.clientIp.join(', ')}</span>
|
|
509
|
+
</div>
|
|
510
|
+
`
|
|
511
|
+
: ''}
|
|
512
|
+
${match.tlsVersion && match.tlsVersion.length > 0
|
|
513
|
+
? html`
|
|
514
|
+
<div class="field-row">
|
|
515
|
+
<span class="field-key">TLS Ver</span>
|
|
516
|
+
<span class="field-value">${match.tlsVersion.join(', ')}</span>
|
|
517
|
+
</div>
|
|
518
|
+
`
|
|
519
|
+
: ''}
|
|
520
|
+
${match.headers
|
|
521
|
+
? html`
|
|
522
|
+
<div class="field-row">
|
|
523
|
+
<span class="field-key">Headers</span>
|
|
524
|
+
<span class="field-value">
|
|
525
|
+
${Object.entries(match.headers).map(
|
|
526
|
+
([k, v]) => html`<span class="header-pair">${k}=${v}</span> `
|
|
527
|
+
)}
|
|
528
|
+
</span>
|
|
529
|
+
</div>
|
|
530
|
+
`
|
|
531
|
+
: ''}
|
|
532
|
+
</div>
|
|
533
|
+
|
|
534
|
+
<!-- Action Section -->
|
|
535
|
+
<div class="section action">
|
|
536
|
+
<div class="section-label">Action</div>
|
|
537
|
+
${action.targets && action.targets.length > 0
|
|
538
|
+
? html`
|
|
539
|
+
<div class="field-row">
|
|
540
|
+
<span class="field-key">Targets</span>
|
|
541
|
+
<span class="field-value mono">${formatTargets(action.targets).join(', ')}</span>
|
|
542
|
+
</div>
|
|
543
|
+
`
|
|
544
|
+
: ''}
|
|
545
|
+
${action.tls
|
|
546
|
+
? html`
|
|
547
|
+
<div class="field-row">
|
|
548
|
+
<span class="field-key">TLS</span>
|
|
549
|
+
<span class="field-value">
|
|
550
|
+
${action.tls.mode}
|
|
551
|
+
${action.tls.certificate
|
|
552
|
+
? action.tls.certificate === 'auto'
|
|
553
|
+
? html` <span class="tls-badge auto">auto cert</span>`
|
|
554
|
+
: html` <span class="tls-badge custom">custom cert</span>`
|
|
555
|
+
: ''}
|
|
556
|
+
</span>
|
|
557
|
+
</div>
|
|
558
|
+
`
|
|
559
|
+
: ''}
|
|
560
|
+
${action.forwardingEngine
|
|
561
|
+
? html`
|
|
562
|
+
<div class="field-row">
|
|
563
|
+
<span class="field-key">Engine</span>
|
|
564
|
+
<span class="field-value"><span class="engine-badge">${action.forwardingEngine}</span></span>
|
|
565
|
+
</div>
|
|
566
|
+
`
|
|
567
|
+
: ''}
|
|
568
|
+
${action.loadBalancing
|
|
569
|
+
? html`
|
|
570
|
+
<div class="field-row">
|
|
571
|
+
<span class="field-key">LB</span>
|
|
572
|
+
<span class="field-value">${action.loadBalancing.algorithm}</span>
|
|
573
|
+
</div>
|
|
574
|
+
`
|
|
575
|
+
: ''}
|
|
576
|
+
${action.websocket?.enabled
|
|
577
|
+
? html`
|
|
578
|
+
<div class="field-row">
|
|
579
|
+
<span class="field-key">WS</span>
|
|
580
|
+
<span class="field-value"><span class="badge enabled">enabled</span></span>
|
|
581
|
+
</div>
|
|
582
|
+
`
|
|
583
|
+
: ''}
|
|
584
|
+
</div>
|
|
585
|
+
|
|
586
|
+
<!-- Security Section -->
|
|
587
|
+
${security
|
|
588
|
+
? html`
|
|
589
|
+
<div class="section security">
|
|
590
|
+
<div class="section-label">Security</div>
|
|
591
|
+
${security.ipAllowList && security.ipAllowList.length > 0
|
|
592
|
+
? html`
|
|
593
|
+
<div class="field-row">
|
|
594
|
+
<span class="field-key">Allow</span>
|
|
595
|
+
<span class="field-value mono">${security.ipAllowList.join(', ')}</span>
|
|
596
|
+
</div>
|
|
597
|
+
`
|
|
598
|
+
: ''}
|
|
599
|
+
${security.ipBlockList && security.ipBlockList.length > 0
|
|
600
|
+
? html`
|
|
601
|
+
<div class="field-row">
|
|
602
|
+
<span class="field-key">Block</span>
|
|
603
|
+
<span class="field-value mono">${security.ipBlockList.join(', ')}</span>
|
|
604
|
+
</div>
|
|
605
|
+
`
|
|
606
|
+
: ''}
|
|
607
|
+
${security.rateLimit?.enabled
|
|
608
|
+
? html`
|
|
609
|
+
<div class="field-row">
|
|
610
|
+
<span class="field-key">Rate</span>
|
|
611
|
+
<span class="field-value">${security.rateLimit.maxRequests} req / ${security.rateLimit.window}s</span>
|
|
612
|
+
</div>
|
|
613
|
+
`
|
|
614
|
+
: ''}
|
|
615
|
+
${security.maxConnections
|
|
616
|
+
? html`
|
|
617
|
+
<div class="field-row">
|
|
618
|
+
<span class="field-key">Max Conn</span>
|
|
619
|
+
<span class="field-value">${security.maxConnections}</span>
|
|
620
|
+
</div>
|
|
621
|
+
`
|
|
622
|
+
: ''}
|
|
623
|
+
</div>
|
|
624
|
+
`
|
|
625
|
+
: ''}
|
|
626
|
+
|
|
627
|
+
<!-- Feature Icons Row -->
|
|
628
|
+
${this.renderFeatures()}
|
|
629
|
+
</div>
|
|
630
|
+
`;
|
|
631
|
+
}
|
|
632
|
+
|
|
633
|
+
private renderDomains(domains: string | string[]): TemplateResult {
|
|
634
|
+
const list = Array.isArray(domains) ? domains : [domains];
|
|
635
|
+
return html`${list.map(
|
|
636
|
+
(d) =>
|
|
637
|
+
html`<span class="domain-chip ${d.includes('*') ? 'glob' : ''}">${d}</span>`
|
|
638
|
+
)}`;
|
|
639
|
+
}
|
|
640
|
+
|
|
641
|
+
private renderFeatures(): TemplateResult {
|
|
642
|
+
if (!this.route) return html``;
|
|
643
|
+
const features: TemplateResult[] = [];
|
|
644
|
+
const action = this.route.action;
|
|
645
|
+
const security = this.route.security;
|
|
646
|
+
const headers = this.route.headers;
|
|
647
|
+
|
|
648
|
+
if (action.tls) {
|
|
649
|
+
features.push(html`<span class="feature"><span class="feature-icon">🔒</span>TLS</span>`);
|
|
650
|
+
}
|
|
651
|
+
if (action.websocket?.enabled) {
|
|
652
|
+
features.push(html`<span class="feature"><span class="feature-icon">↔</span>WS</span>`);
|
|
653
|
+
}
|
|
654
|
+
if (action.loadBalancing) {
|
|
655
|
+
features.push(html`<span class="feature"><span class="feature-icon">⚖</span>LB</span>`);
|
|
656
|
+
}
|
|
657
|
+
if (security) {
|
|
658
|
+
features.push(html`<span class="feature"><span class="feature-icon">🛡</span>Security</span>`);
|
|
659
|
+
}
|
|
660
|
+
if (headers) {
|
|
661
|
+
features.push(html`<span class="feature"><span class="feature-icon">⚙</span>Headers</span>`);
|
|
662
|
+
}
|
|
663
|
+
|
|
664
|
+
if (features.length === 0) return html``;
|
|
665
|
+
return html`<div class="features-row">${features}</div>`;
|
|
666
|
+
}
|
|
667
|
+
}
|