@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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@serve.zone/catalog",
3
- "version": "2.2.0",
3
+ "version": "2.4.0",
4
4
  "private": false,
5
5
  "description": "UI component catalog for serve.zone",
6
6
  "main": "dist_ts_web/index.js",
@@ -3,6 +3,6 @@
3
3
  */
4
4
  export const commitinfo = {
5
5
  name: '@serve.zone/catalog',
6
- version: '2.2.0',
6
+ version: '2.4.0',
7
7
  description: 'UI component catalog for serve.zone'
8
8
  }
@@ -49,6 +49,14 @@ export * from './sz-domain-detail-view.js';
49
49
  export * from './sz-mta-list-view.js';
50
50
  export * from './sz-mta-detail-view.js';
51
51
 
52
+ // Route Configuration Views
53
+ export * from './sz-route-card.js';
54
+ export * from './sz-route-list-view.js';
55
+
56
+ // Config Views
57
+ export * from './sz-config-section.js';
58
+ export * from './sz-config-overview.js';
59
+
52
60
  // Demo Views
53
61
  export * from './sz-demo-view-dashboard.js';
54
62
  export * from './sz-demo-view-services.js';
@@ -57,3 +65,5 @@ export * from './sz-demo-view-registries.js';
57
65
  export * from './sz-demo-view-tokens.js';
58
66
  export * from './sz-demo-view-settings.js';
59
67
  export * from './sz-demo-view-mta.js';
68
+ export * from './sz-demo-view-routes.js';
69
+ export * from './sz-demo-view-config.js';
@@ -0,0 +1,92 @@
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-config-overview': SzConfigOverview;
14
+ }
15
+ }
16
+
17
+ @customElement('sz-config-overview')
18
+ export class SzConfigOverview extends DeesElement {
19
+ public static demo = () => html`<sz-config-overview
20
+ heading="Configuration"
21
+ infoText="This is a read-only view of the current running configuration."
22
+ >
23
+ <div style="padding: 20px; text-align: center; color: #71717a; font-size: 14px;">
24
+ Place &lt;sz-config-section&gt; elements here
25
+ </div>
26
+ </sz-config-overview>`;
27
+
28
+ public static demoGroups = ['Configuration'];
29
+
30
+ @property({ type: String })
31
+ public accessor heading: string = '';
32
+
33
+ @property({ type: String })
34
+ public accessor infoText: string = '';
35
+
36
+ public static styles = [
37
+ cssManager.defaultStyles,
38
+ css`
39
+ :host {
40
+ display: block;
41
+ }
42
+
43
+ .heading {
44
+ font-size: 20px;
45
+ font-weight: 600;
46
+ color: ${cssManager.bdTheme('#18181b', '#fafafa')};
47
+ margin-bottom: 16px;
48
+ }
49
+
50
+ .info-banner {
51
+ display: flex;
52
+ align-items: flex-start;
53
+ gap: 12px;
54
+ padding: 14px 18px;
55
+ margin-bottom: 20px;
56
+ border-radius: 8px;
57
+ background: ${cssManager.bdTheme('#eff6ff', 'rgba(59,130,246,0.08)')};
58
+ border: 1px solid ${cssManager.bdTheme('#bfdbfe', 'rgba(59,130,246,0.2)')};
59
+ color: ${cssManager.bdTheme('#1e40af', '#93c5fd')};
60
+ font-size: 13px;
61
+ line-height: 1.5;
62
+ }
63
+
64
+ .info-banner dees-icon {
65
+ flex-shrink: 0;
66
+ font-size: 18px;
67
+ margin-top: 1px;
68
+ }
69
+
70
+ ::slotted(sz-config-section) {
71
+ margin-bottom: 12px;
72
+ }
73
+
74
+ ::slotted(sz-config-section:last-child) {
75
+ margin-bottom: 0;
76
+ }
77
+ `,
78
+ ];
79
+
80
+ public render(): TemplateResult {
81
+ return html`
82
+ ${this.heading ? html`<div class="heading">${this.heading}</div>` : ''}
83
+ ${this.infoText ? html`
84
+ <div class="info-banner">
85
+ <dees-icon .icon=${'lucide:info'}></dees-icon>
86
+ <span>${this.infoText}</span>
87
+ </div>
88
+ ` : ''}
89
+ <slot></slot>
90
+ `;
91
+ }
92
+ }
@@ -0,0 +1,531 @@
1
+ import {
2
+ DeesElement,
3
+ customElement,
4
+ html,
5
+ css,
6
+ cssManager,
7
+ property,
8
+ state,
9
+ type TemplateResult,
10
+ } from '@design.estate/dees-element';
11
+
12
+ export interface IConfigField {
13
+ key: string;
14
+ value: string | number | boolean | string[] | null;
15
+ type?: 'text' | 'boolean' | 'badge' | 'pills' | 'code' | 'link';
16
+ description?: string;
17
+ linkTo?: string;
18
+ }
19
+
20
+ declare global {
21
+ interface HTMLElementTagNameMap {
22
+ 'sz-config-section': SzConfigSection;
23
+ }
24
+ }
25
+
26
+ @customElement('sz-config-section')
27
+ export class SzConfigSection extends DeesElement {
28
+ public static demo = () => html`
29
+ <sz-config-section
30
+ title="SmartProxy"
31
+ subtitle="HTTP/HTTPS and TCP/SNI reverse proxy"
32
+ icon="lucide:network"
33
+ status="enabled"
34
+ .fields=${[
35
+ { key: 'Route Count', value: 12 },
36
+ { key: 'ACME Enabled', value: true, type: 'boolean' },
37
+ { key: 'Account Email', value: 'admin@example.com' },
38
+ { key: 'Use Production', value: true, type: 'boolean' },
39
+ { key: 'Auto Renew', value: true, type: 'boolean' },
40
+ { key: 'Renew Threshold', value: '30 days' },
41
+ ] as IConfigField[]}
42
+ ></sz-config-section>
43
+ <sz-config-section
44
+ title="Email Server"
45
+ subtitle="SMTP email handling with smartmta"
46
+ icon="lucide:mail"
47
+ status="disabled"
48
+ .fields=${[
49
+ { key: 'Ports', value: ['25', '465', '587'], type: 'pills' },
50
+ { key: 'Hostname', value: null },
51
+ { key: 'Domains', value: ['example.com', 'mail.example.com'], type: 'pills' },
52
+ ] as IConfigField[]}
53
+ ></sz-config-section>
54
+ <sz-config-section
55
+ title="DNS Server"
56
+ subtitle="Authoritative DNS with smartdns"
57
+ icon="lucide:globe"
58
+ status="not-configured"
59
+ collapsible
60
+ .fields=${[
61
+ { key: 'Port', value: 53 },
62
+ { key: 'NS Domains', value: ['ns1.example.com', 'ns2.example.com'], type: 'pills' },
63
+ ] as IConfigField[]}
64
+ ></sz-config-section>
65
+ `;
66
+
67
+ public static demoGroups = ['Configuration'];
68
+
69
+ @property({ type: String })
70
+ public accessor title: string = '';
71
+
72
+ @property({ type: String })
73
+ public accessor subtitle: string = '';
74
+
75
+ @property({ type: String })
76
+ public accessor icon: string = '';
77
+
78
+ @property({ type: String })
79
+ public accessor status: 'enabled' | 'disabled' | 'not-configured' | 'warning' = 'enabled';
80
+
81
+ @property({ type: Array })
82
+ public accessor fields: IConfigField[] = [];
83
+
84
+ @property({ type: Boolean })
85
+ public accessor collapsible: boolean = false;
86
+
87
+ @property({ type: Boolean })
88
+ public accessor collapsed: boolean = false;
89
+
90
+ @state()
91
+ accessor isCollapsed: boolean = false;
92
+
93
+ public static styles = [
94
+ cssManager.defaultStyles,
95
+ css`
96
+ :host {
97
+ display: block;
98
+ margin-bottom: 16px;
99
+ }
100
+
101
+ .section {
102
+ background: ${cssManager.bdTheme('#ffffff', '#09090b')};
103
+ border: 1px solid ${cssManager.bdTheme('#e4e4e7', '#27272a')};
104
+ border-radius: 8px;
105
+ overflow: hidden;
106
+ }
107
+
108
+ .section-header {
109
+ display: flex;
110
+ align-items: center;
111
+ justify-content: space-between;
112
+ padding: 14px 20px;
113
+ background: ${cssManager.bdTheme('#f4f4f5', '#18181b')};
114
+ border-bottom: 1px solid ${cssManager.bdTheme('#e4e4e7', '#27272a')};
115
+ cursor: default;
116
+ user-select: none;
117
+ }
118
+
119
+ :host([collapsible]) .section-header {
120
+ cursor: pointer;
121
+ }
122
+
123
+ :host([collapsible]) .section-header:hover {
124
+ background: ${cssManager.bdTheme('#ebebed', '#1c1c1f')};
125
+ }
126
+
127
+ .header-left {
128
+ display: flex;
129
+ align-items: center;
130
+ gap: 12px;
131
+ min-width: 0;
132
+ }
133
+
134
+ .header-icon {
135
+ display: flex;
136
+ align-items: center;
137
+ justify-content: center;
138
+ width: 36px;
139
+ height: 36px;
140
+ background: ${cssManager.bdTheme('#e4e4e7', '#27272a')};
141
+ border-radius: 8px;
142
+ flex-shrink: 0;
143
+ }
144
+
145
+ .header-icon dees-icon {
146
+ font-size: 18px;
147
+ color: ${cssManager.bdTheme('#52525b', '#a1a1aa')};
148
+ }
149
+
150
+ .header-text {
151
+ min-width: 0;
152
+ }
153
+
154
+ .header-title {
155
+ font-size: 15px;
156
+ font-weight: 600;
157
+ color: ${cssManager.bdTheme('#18181b', '#fafafa')};
158
+ line-height: 1.3;
159
+ }
160
+
161
+ .header-subtitle {
162
+ font-size: 12px;
163
+ color: ${cssManager.bdTheme('#71717a', '#a1a1aa')};
164
+ line-height: 1.3;
165
+ margin-top: 1px;
166
+ }
167
+
168
+ .header-right {
169
+ display: flex;
170
+ align-items: center;
171
+ gap: 10px;
172
+ flex-shrink: 0;
173
+ }
174
+
175
+ /* Status badge */
176
+ .status-badge {
177
+ display: inline-flex;
178
+ align-items: center;
179
+ gap: 6px;
180
+ padding: 3px 10px;
181
+ border-radius: 9999px;
182
+ font-size: 12px;
183
+ font-weight: 500;
184
+ white-space: nowrap;
185
+ }
186
+
187
+ .status-badge.enabled {
188
+ background: ${cssManager.bdTheme('#dcfce7', 'rgba(34,197,94,0.2)')};
189
+ color: ${cssManager.bdTheme('#16a34a', '#22c55e')};
190
+ }
191
+
192
+ .status-badge.disabled {
193
+ background: ${cssManager.bdTheme('#fee2e2', 'rgba(239,68,68,0.2)')};
194
+ color: ${cssManager.bdTheme('#dc2626', '#ef4444')};
195
+ }
196
+
197
+ .status-badge.not-configured {
198
+ background: ${cssManager.bdTheme('#f4f4f5', 'rgba(113,113,122,0.2)')};
199
+ color: ${cssManager.bdTheme('#71717a', '#a1a1aa')};
200
+ }
201
+
202
+ .status-badge.warning {
203
+ background: ${cssManager.bdTheme('#fef3c7', 'rgba(245,158,11,0.15)')};
204
+ color: ${cssManager.bdTheme('#92400e', '#fbbf24')};
205
+ }
206
+
207
+ .status-dot {
208
+ width: 7px;
209
+ height: 7px;
210
+ border-radius: 50%;
211
+ }
212
+
213
+ .status-badge.enabled .status-dot {
214
+ background: ${cssManager.bdTheme('#16a34a', '#22c55e')};
215
+ }
216
+
217
+ .status-badge.disabled .status-dot {
218
+ background: ${cssManager.bdTheme('#dc2626', '#ef4444')};
219
+ }
220
+
221
+ .status-badge.not-configured .status-dot {
222
+ background: ${cssManager.bdTheme('#a1a1aa', '#52525b')};
223
+ }
224
+
225
+ .status-badge.warning .status-dot {
226
+ background: ${cssManager.bdTheme('#f59e0b', '#fbbf24')};
227
+ }
228
+
229
+ /* Chevron */
230
+ .chevron {
231
+ display: flex;
232
+ align-items: center;
233
+ transition: transform 200ms ease;
234
+ }
235
+
236
+ .chevron.collapsed {
237
+ transform: rotate(-90deg);
238
+ }
239
+
240
+ .chevron dees-icon {
241
+ font-size: 16px;
242
+ color: ${cssManager.bdTheme('#a1a1aa', '#52525b')};
243
+ }
244
+
245
+ /* Content */
246
+ .section-content {
247
+ padding: 0;
248
+ }
249
+
250
+ .section-content.collapsed {
251
+ display: none;
252
+ }
253
+
254
+ /* Field rows */
255
+ .field-row {
256
+ display: flex;
257
+ align-items: flex-start;
258
+ justify-content: space-between;
259
+ padding: 10px 20px;
260
+ border-bottom: 1px solid ${cssManager.bdTheme('#f4f4f5', '#1a1a1e')};
261
+ gap: 16px;
262
+ }
263
+
264
+ .field-row:last-child {
265
+ border-bottom: none;
266
+ }
267
+
268
+ .field-key {
269
+ font-size: 13px;
270
+ font-weight: 500;
271
+ color: ${cssManager.bdTheme('#71717a', '#a1a1aa')};
272
+ flex-shrink: 0;
273
+ min-width: 140px;
274
+ padding-top: 1px;
275
+ }
276
+
277
+ .field-value {
278
+ font-size: 13px;
279
+ font-family: 'SF Mono', Monaco, 'Cascadia Code', monospace;
280
+ color: ${cssManager.bdTheme('#18181b', '#fafafa')};
281
+ text-align: right;
282
+ word-break: break-all;
283
+ }
284
+
285
+ .field-value.null-value {
286
+ color: ${cssManager.bdTheme('#a1a1aa', '#52525b')};
287
+ font-style: italic;
288
+ font-family: inherit;
289
+ }
290
+
291
+ /* Boolean display */
292
+ .bool-value {
293
+ display: inline-flex;
294
+ align-items: center;
295
+ gap: 5px;
296
+ font-family: inherit;
297
+ font-size: 13px;
298
+ font-weight: 500;
299
+ }
300
+
301
+ .bool-value.true {
302
+ color: ${cssManager.bdTheme('#16a34a', '#22c55e')};
303
+ }
304
+
305
+ .bool-value.false {
306
+ color: ${cssManager.bdTheme('#dc2626', '#ef4444')};
307
+ }
308
+
309
+ .bool-dot {
310
+ width: 6px;
311
+ height: 6px;
312
+ border-radius: 50%;
313
+ }
314
+
315
+ .bool-value.true .bool-dot {
316
+ background: ${cssManager.bdTheme('#16a34a', '#22c55e')};
317
+ }
318
+
319
+ .bool-value.false .bool-dot {
320
+ background: ${cssManager.bdTheme('#dc2626', '#ef4444')};
321
+ }
322
+
323
+ /* Pills */
324
+ .pills {
325
+ display: flex;
326
+ flex-wrap: wrap;
327
+ gap: 5px;
328
+ justify-content: flex-end;
329
+ }
330
+
331
+ .pill {
332
+ display: inline-flex;
333
+ align-items: center;
334
+ padding: 2px 9px;
335
+ border-radius: 9999px;
336
+ font-size: 12px;
337
+ font-weight: 500;
338
+ font-family: 'SF Mono', Monaco, 'Cascadia Code', monospace;
339
+ background: ${cssManager.bdTheme('#eff6ff', 'rgba(59,130,246,0.1)')};
340
+ color: ${cssManager.bdTheme('#2563eb', '#60a5fa')};
341
+ }
342
+
343
+ /* Code value */
344
+ .code-value {
345
+ font-family: 'SF Mono', Monaco, 'Cascadia Code', monospace;
346
+ font-size: 12px;
347
+ background: ${cssManager.bdTheme('#f4f4f5', '#18181b')};
348
+ padding: 2px 8px;
349
+ border-radius: 4px;
350
+ color: ${cssManager.bdTheme('#18181b', '#fafafa')};
351
+ }
352
+
353
+ /* Link value */
354
+ .link-value {
355
+ color: ${cssManager.bdTheme('#2563eb', '#60a5fa')};
356
+ cursor: pointer;
357
+ text-decoration: none;
358
+ font-family: inherit;
359
+ font-size: 13px;
360
+ }
361
+
362
+ .link-value:hover {
363
+ text-decoration: underline;
364
+ }
365
+
366
+ /* Description hint */
367
+ .field-description {
368
+ font-size: 11px;
369
+ color: ${cssManager.bdTheme('#a1a1aa', '#52525b')};
370
+ margin-top: 3px;
371
+ text-align: right;
372
+ }
373
+
374
+ /* Slot for custom content */
375
+ .slot-content {
376
+ border-top: 1px solid ${cssManager.bdTheme('#f4f4f5', '#1a1a1e')};
377
+ }
378
+
379
+ .slot-content:empty {
380
+ display: none;
381
+ border-top: none;
382
+ }
383
+
384
+ /* Badge type */
385
+ .badge-value {
386
+ display: inline-flex;
387
+ align-items: center;
388
+ padding: 2px 9px;
389
+ border-radius: 9999px;
390
+ font-size: 12px;
391
+ font-weight: 500;
392
+ background: ${cssManager.bdTheme('#f4f4f5', '#27272a')};
393
+ color: ${cssManager.bdTheme('#52525b', '#a1a1aa')};
394
+ }
395
+ `,
396
+ ];
397
+
398
+ async connectedCallback() {
399
+ await super.connectedCallback();
400
+ this.isCollapsed = this.collapsed;
401
+ if (this.collapsible) {
402
+ this.setAttribute('collapsible', '');
403
+ }
404
+ }
405
+
406
+ public render(): TemplateResult {
407
+ const statusLabels: Record<string, string> = {
408
+ 'enabled': 'Enabled',
409
+ 'disabled': 'Disabled',
410
+ 'not-configured': 'Not Configured',
411
+ 'warning': 'Warning',
412
+ };
413
+
414
+ return html`
415
+ <div class="section">
416
+ <div
417
+ class="section-header"
418
+ @click=${() => {
419
+ if (this.collapsible) {
420
+ this.isCollapsed = !this.isCollapsed;
421
+ }
422
+ }}
423
+ >
424
+ <div class="header-left">
425
+ ${this.icon ? html`
426
+ <div class="header-icon">
427
+ <dees-icon .icon=${this.icon}></dees-icon>
428
+ </div>
429
+ ` : ''}
430
+ <div class="header-text">
431
+ <div class="header-title">${this.title}</div>
432
+ ${this.subtitle ? html`<div class="header-subtitle">${this.subtitle}</div>` : ''}
433
+ </div>
434
+ </div>
435
+ <div class="header-right">
436
+ ${this.status ? html`
437
+ <span class="status-badge ${this.status}">
438
+ <span class="status-dot"></span>
439
+ ${statusLabels[this.status] || this.status}
440
+ </span>
441
+ ` : ''}
442
+ ${this.collapsible ? html`
443
+ <span class="chevron ${this.isCollapsed ? 'collapsed' : ''}">
444
+ <dees-icon .icon=${'lucide:chevronDown'}></dees-icon>
445
+ </span>
446
+ ` : ''}
447
+ </div>
448
+ </div>
449
+ <div class="section-content ${this.isCollapsed ? 'collapsed' : ''}">
450
+ ${this.fields.map(field => this.renderField(field))}
451
+ <div class="slot-content">
452
+ <slot></slot>
453
+ </div>
454
+ </div>
455
+ </div>
456
+ `;
457
+ }
458
+
459
+ private renderField(field: IConfigField): TemplateResult {
460
+ return html`
461
+ <div class="field-row">
462
+ <div class="field-key">${field.key}</div>
463
+ <div>
464
+ ${this.renderFieldValue(field)}
465
+ ${field.description ? html`<div class="field-description">${field.description}</div>` : ''}
466
+ </div>
467
+ </div>
468
+ `;
469
+ }
470
+
471
+ private renderFieldValue(field: IConfigField): TemplateResult {
472
+ const value = field.value;
473
+ const type = field.type || this.inferType(value);
474
+
475
+ // Null / undefined
476
+ if (value === null || value === undefined) {
477
+ return html`<span class="field-value null-value">Not configured</span>`;
478
+ }
479
+
480
+ switch (type) {
481
+ case 'boolean':
482
+ return html`
483
+ <span class="bool-value ${value ? 'true' : 'false'}">
484
+ <span class="bool-dot"></span>
485
+ ${value ? 'Enabled' : 'Disabled'}
486
+ </span>
487
+ `;
488
+
489
+ case 'pills':
490
+ if (Array.isArray(value) && value.length === 0) {
491
+ return html`<span class="field-value null-value">None</span>`;
492
+ }
493
+ return html`
494
+ <div class="pills">
495
+ ${(value as string[]).map(v => html`<span class="pill">${v}</span>`)}
496
+ </div>
497
+ `;
498
+
499
+ case 'code':
500
+ return html`<span class="code-value">${String(value)}</span>`;
501
+
502
+ case 'badge':
503
+ return html`<span class="badge-value">${String(value)}</span>`;
504
+
505
+ case 'link':
506
+ return html`
507
+ <span
508
+ class="link-value"
509
+ @click=${() => {
510
+ if (field.linkTo) {
511
+ this.dispatchEvent(new CustomEvent('navigate', {
512
+ detail: { target: field.linkTo },
513
+ bubbles: true,
514
+ composed: true,
515
+ }));
516
+ }
517
+ }}
518
+ >${String(value)}</span>
519
+ `;
520
+
521
+ default:
522
+ return html`<span class="field-value">${String(value)}</span>`;
523
+ }
524
+ }
525
+
526
+ private inferType(value: unknown): string {
527
+ if (typeof value === 'boolean') return 'boolean';
528
+ if (Array.isArray(value)) return 'pills';
529
+ return 'text';
530
+ }
531
+ }