@serve.zone/catalog 1.0.1

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 (122) hide show
  1. package/dist_ts_web/00_commitinfo_data.d.ts +8 -0
  2. package/dist_ts_web/00_commitinfo_data.js +9 -0
  3. package/dist_ts_web/elements/index.d.ts +33 -0
  4. package/dist_ts_web/elements/index.js +45 -0
  5. package/dist_ts_web/elements/sz-certificates-card.d.ts +14 -0
  6. package/dist_ts_web/elements/sz-certificates-card.js +210 -0
  7. package/dist_ts_web/elements/sz-dashboard-view.d.ts +33 -0
  8. package/dist_ts_web/elements/sz-dashboard-view.js +242 -0
  9. package/dist_ts_web/elements/sz-demo-view-dashboard.d.ts +18 -0
  10. package/dist_ts_web/elements/sz-demo-view-dashboard.js +184 -0
  11. package/dist_ts_web/elements/sz-demo-view-network.d.ts +32 -0
  12. package/dist_ts_web/elements/sz-demo-view-network.js +384 -0
  13. package/dist_ts_web/elements/sz-demo-view-registries.d.ts +22 -0
  14. package/dist_ts_web/elements/sz-demo-view-registries.js +240 -0
  15. package/dist_ts_web/elements/sz-demo-view-services.d.ts +32 -0
  16. package/dist_ts_web/elements/sz-demo-view-services.js +468 -0
  17. package/dist_ts_web/elements/sz-demo-view-settings.d.ts +19 -0
  18. package/dist_ts_web/elements/sz-demo-view-settings.js +151 -0
  19. package/dist_ts_web/elements/sz-demo-view-tokens.d.ts +20 -0
  20. package/dist_ts_web/elements/sz-demo-view-tokens.js +141 -0
  21. package/dist_ts_web/elements/sz-dns-ssl-card.d.ts +13 -0
  22. package/dist_ts_web/elements/sz-dns-ssl-card.js +180 -0
  23. package/dist_ts_web/elements/sz-domain-detail-view.d.ts +48 -0
  24. package/dist_ts_web/elements/sz-domain-detail-view.js +789 -0
  25. package/dist_ts_web/elements/sz-login-view.d.ts +18 -0
  26. package/dist_ts_web/elements/sz-login-view.js +384 -0
  27. package/dist_ts_web/elements/sz-network-dns-view.d.ts +20 -0
  28. package/dist_ts_web/elements/sz-network-dns-view.js +244 -0
  29. package/dist_ts_web/elements/sz-network-domains-view.d.ts +28 -0
  30. package/dist_ts_web/elements/sz-network-domains-view.js +312 -0
  31. package/dist_ts_web/elements/sz-network-proxy-view.d.ts +39 -0
  32. package/dist_ts_web/elements/sz-network-proxy-view.js +510 -0
  33. package/dist_ts_web/elements/sz-platform-service-detail-view.d.ts +49 -0
  34. package/dist_ts_web/elements/sz-platform-service-detail-view.js +733 -0
  35. package/dist_ts_web/elements/sz-platform-services-card.d.ts +19 -0
  36. package/dist_ts_web/elements/sz-platform-services-card.js +196 -0
  37. package/dist_ts_web/elements/sz-quick-actions-card.d.ts +19 -0
  38. package/dist_ts_web/elements/sz-quick-actions-card.js +194 -0
  39. package/dist_ts_web/elements/sz-registry-external-view.d.ts +22 -0
  40. package/dist_ts_web/elements/sz-registry-external-view.js +313 -0
  41. package/dist_ts_web/elements/sz-registry-onebox-view.d.ts +14 -0
  42. package/dist_ts_web/elements/sz-registry-onebox-view.js +307 -0
  43. package/dist_ts_web/elements/sz-resource-usage-card.d.ts +25 -0
  44. package/dist_ts_web/elements/sz-resource-usage-card.js +323 -0
  45. package/dist_ts_web/elements/sz-reverse-proxy-card.d.ts +16 -0
  46. package/dist_ts_web/elements/sz-reverse-proxy-card.js +216 -0
  47. package/dist_ts_web/elements/sz-service-create-view.d.ts +67 -0
  48. package/dist_ts_web/elements/sz-service-create-view.js +828 -0
  49. package/dist_ts_web/elements/sz-service-detail-view.d.ts +57 -0
  50. package/dist_ts_web/elements/sz-service-detail-view.js +728 -0
  51. package/dist_ts_web/elements/sz-services-backups-view.d.ts +37 -0
  52. package/dist_ts_web/elements/sz-services-backups-view.js +413 -0
  53. package/dist_ts_web/elements/sz-services-list-view.d.ts +20 -0
  54. package/dist_ts_web/elements/sz-services-list-view.js +272 -0
  55. package/dist_ts_web/elements/sz-settings-view.d.ts +30 -0
  56. package/dist_ts_web/elements/sz-settings-view.js +448 -0
  57. package/dist_ts_web/elements/sz-stat-card.d.ts +17 -0
  58. package/dist_ts_web/elements/sz-stat-card.js +249 -0
  59. package/dist_ts_web/elements/sz-status-grid-cluster.d.ts +19 -0
  60. package/dist_ts_web/elements/sz-status-grid-cluster.js +142 -0
  61. package/dist_ts_web/elements/sz-status-grid-infra.d.ts +17 -0
  62. package/dist_ts_web/elements/sz-status-grid-infra.js +140 -0
  63. package/dist_ts_web/elements/sz-status-grid-network.d.ts +30 -0
  64. package/dist_ts_web/elements/sz-status-grid-network.js +190 -0
  65. package/dist_ts_web/elements/sz-status-grid-services.d.ts +17 -0
  66. package/dist_ts_web/elements/sz-status-grid-services.js +145 -0
  67. package/dist_ts_web/elements/sz-tokens-view.d.ts +26 -0
  68. package/dist_ts_web/elements/sz-tokens-view.js +344 -0
  69. package/dist_ts_web/elements/sz-traffic-card.d.ts +24 -0
  70. package/dist_ts_web/elements/sz-traffic-card.js +255 -0
  71. package/dist_ts_web/index.d.ts +2 -0
  72. package/dist_ts_web/index.js +3 -0
  73. package/dist_ts_web/pages/index.d.ts +3 -0
  74. package/dist_ts_web/pages/index.js +4 -0
  75. package/dist_ts_web/pages/mainpage.d.ts +1 -0
  76. package/dist_ts_web/pages/mainpage.js +46 -0
  77. package/dist_ts_web/pages/sz-demo-app-shell.d.ts +13 -0
  78. package/dist_ts_web/pages/sz-demo-app-shell.js +212 -0
  79. package/dist_ts_web/pages/sz-demo-app.d.ts +2 -0
  80. package/dist_ts_web/pages/sz-demo-app.js +20 -0
  81. package/npmextra.json +24 -0
  82. package/package.json +45 -0
  83. package/ts_web/00_commitinfo_data.ts +8 -0
  84. package/ts_web/elements/index.ts +54 -0
  85. package/ts_web/elements/sz-certificates-card.ts +155 -0
  86. package/ts_web/elements/sz-dashboard-view.ts +217 -0
  87. package/ts_web/elements/sz-demo-view-dashboard.ts +150 -0
  88. package/ts_web/elements/sz-demo-view-network.ts +354 -0
  89. package/ts_web/elements/sz-demo-view-registries.ts +206 -0
  90. package/ts_web/elements/sz-demo-view-services.ts +434 -0
  91. package/ts_web/elements/sz-demo-view-settings.ts +118 -0
  92. package/ts_web/elements/sz-demo-view-tokens.ts +109 -0
  93. package/ts_web/elements/sz-dns-ssl-card.ts +130 -0
  94. package/ts_web/elements/sz-domain-detail-view.ts +766 -0
  95. package/ts_web/elements/sz-login-view.ts +329 -0
  96. package/ts_web/elements/sz-network-dns-view.ts +208 -0
  97. package/ts_web/elements/sz-network-domains-view.ts +273 -0
  98. package/ts_web/elements/sz-network-proxy-view.ts +456 -0
  99. package/ts_web/elements/sz-platform-service-detail-view.ts +714 -0
  100. package/ts_web/elements/sz-platform-services-card.ts +163 -0
  101. package/ts_web/elements/sz-quick-actions-card.ts +161 -0
  102. package/ts_web/elements/sz-registry-external-view.ts +279 -0
  103. package/ts_web/elements/sz-registry-onebox-view.ts +258 -0
  104. package/ts_web/elements/sz-resource-usage-card.ts +284 -0
  105. package/ts_web/elements/sz-reverse-proxy-card.ts +151 -0
  106. package/ts_web/elements/sz-service-create-view.ts +773 -0
  107. package/ts_web/elements/sz-service-detail-view.ts +710 -0
  108. package/ts_web/elements/sz-services-backups-view.ts +390 -0
  109. package/ts_web/elements/sz-services-list-view.ts +237 -0
  110. package/ts_web/elements/sz-settings-view.ts +417 -0
  111. package/ts_web/elements/sz-stat-card.ts +187 -0
  112. package/ts_web/elements/sz-status-grid-cluster.ts +105 -0
  113. package/ts_web/elements/sz-status-grid-infra.ts +88 -0
  114. package/ts_web/elements/sz-status-grid-network.ts +152 -0
  115. package/ts_web/elements/sz-status-grid-services.ts +99 -0
  116. package/ts_web/elements/sz-tokens-view.ts +308 -0
  117. package/ts_web/elements/sz-traffic-card.ts +222 -0
  118. package/ts_web/index.ts +2 -0
  119. package/ts_web/pages/index.ts +3 -0
  120. package/ts_web/pages/mainpage.ts +46 -0
  121. package/ts_web/pages/sz-demo-app-shell.ts +179 -0
  122. package/ts_web/pages/sz-demo-app.ts +20 -0
@@ -0,0 +1,773 @@
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
+ declare global {
13
+ interface HTMLElementTagNameMap {
14
+ 'sz-service-create-view': SzServiceCreateView;
15
+ }
16
+ }
17
+
18
+ export interface IRegistry {
19
+ id: string;
20
+ name: string;
21
+ url: string;
22
+ }
23
+
24
+ export interface IPortMapping {
25
+ hostPort: string;
26
+ containerPort: string;
27
+ protocol: 'tcp' | 'udp';
28
+ }
29
+
30
+ export interface IEnvVar {
31
+ key: string;
32
+ value: string;
33
+ }
34
+
35
+ export interface IVolumeMount {
36
+ hostPath: string;
37
+ containerPath: string;
38
+ readOnly: boolean;
39
+ }
40
+
41
+ export interface IServiceConfig {
42
+ name: string;
43
+ image: string;
44
+ ports: IPortMapping[];
45
+ envVars: IEnvVar[];
46
+ volumes: IVolumeMount[];
47
+ cpuLimit: string;
48
+ memoryLimit: string;
49
+ restartPolicy: 'always' | 'on-failure' | 'never';
50
+ networkMode: string;
51
+ }
52
+
53
+ @customElement('sz-service-create-view')
54
+ export class SzServiceCreateView extends DeesElement {
55
+ public static demo = () => html`
56
+ <div style="padding: 24px; max-width: 800px;">
57
+ <sz-service-create-view
58
+ .registries=${[
59
+ { id: '1', name: 'Onebox Registry', url: 'registry.onebox.local' },
60
+ { id: '2', name: 'Docker Hub', url: 'docker.io' },
61
+ ]}
62
+ ></sz-service-create-view>
63
+ </div>
64
+ `;
65
+
66
+ @property({ type: Array })
67
+ public accessor registries: IRegistry[] = [];
68
+
69
+ @property({ type: Boolean })
70
+ public accessor loading: boolean = false;
71
+
72
+ @state()
73
+ private accessor serviceName: string = '';
74
+
75
+ @state()
76
+ private accessor imageUrl: string = '';
77
+
78
+ @state()
79
+ private accessor selectedRegistry: string = '';
80
+
81
+ @state()
82
+ private accessor ports: IPortMapping[] = [{ hostPort: '', containerPort: '', protocol: 'tcp' }];
83
+
84
+ @state()
85
+ private accessor envVars: IEnvVar[] = [{ key: '', value: '' }];
86
+
87
+ @state()
88
+ private accessor volumes: IVolumeMount[] = [];
89
+
90
+ @state()
91
+ private accessor cpuLimit: string = '';
92
+
93
+ @state()
94
+ private accessor memoryLimit: string = '';
95
+
96
+ @state()
97
+ private accessor restartPolicy: 'always' | 'on-failure' | 'never' = 'always';
98
+
99
+ @state()
100
+ private accessor networkMode: string = 'bridge';
101
+
102
+ @state()
103
+ private accessor showAdvanced: boolean = false;
104
+
105
+ public static styles = [
106
+ cssManager.defaultStyles,
107
+ css`
108
+ :host {
109
+ display: block;
110
+ }
111
+
112
+ .header {
113
+ display: flex;
114
+ justify-content: space-between;
115
+ align-items: center;
116
+ margin-bottom: 24px;
117
+ }
118
+
119
+ .header-title {
120
+ font-size: 20px;
121
+ font-weight: 600;
122
+ color: ${cssManager.bdTheme('#18181b', '#fafafa')};
123
+ }
124
+
125
+ .header-subtitle {
126
+ font-size: 14px;
127
+ color: ${cssManager.bdTheme('#71717a', '#a1a1aa')};
128
+ margin-top: 4px;
129
+ }
130
+
131
+ .section {
132
+ background: ${cssManager.bdTheme('#ffffff', '#09090b')};
133
+ border: 1px solid ${cssManager.bdTheme('#e4e4e7', '#27272a')};
134
+ border-radius: 8px;
135
+ padding: 20px;
136
+ margin-bottom: 16px;
137
+ }
138
+
139
+ .section-title {
140
+ font-size: 15px;
141
+ font-weight: 600;
142
+ color: ${cssManager.bdTheme('#18181b', '#fafafa')};
143
+ margin-bottom: 16px;
144
+ display: flex;
145
+ align-items: center;
146
+ gap: 8px;
147
+ }
148
+
149
+ .section-title svg {
150
+ width: 18px;
151
+ height: 18px;
152
+ color: ${cssManager.bdTheme('#71717a', '#a1a1aa')};
153
+ }
154
+
155
+ .form-row {
156
+ display: grid;
157
+ grid-template-columns: 1fr 1fr;
158
+ gap: 16px;
159
+ margin-bottom: 16px;
160
+ }
161
+
162
+ .form-row.single {
163
+ grid-template-columns: 1fr;
164
+ }
165
+
166
+ .form-group {
167
+ display: flex;
168
+ flex-direction: column;
169
+ gap: 6px;
170
+ }
171
+
172
+ .form-label {
173
+ font-size: 13px;
174
+ font-weight: 500;
175
+ color: ${cssManager.bdTheme('#18181b', '#fafafa')};
176
+ }
177
+
178
+ .form-label .required {
179
+ color: ${cssManager.bdTheme('#ef4444', '#f87171')};
180
+ }
181
+
182
+ .form-hint {
183
+ font-size: 12px;
184
+ color: ${cssManager.bdTheme('#71717a', '#a1a1aa')};
185
+ }
186
+
187
+ .form-input,
188
+ .form-select {
189
+ width: 100%;
190
+ padding: 10px 12px;
191
+ background: ${cssManager.bdTheme('#ffffff', '#18181b')};
192
+ border: 1px solid ${cssManager.bdTheme('#e4e4e7', '#27272a')};
193
+ border-radius: 6px;
194
+ font-size: 14px;
195
+ color: ${cssManager.bdTheme('#18181b', '#fafafa')};
196
+ outline: none;
197
+ transition: border-color 200ms ease;
198
+ box-sizing: border-box;
199
+ }
200
+
201
+ .form-input:focus,
202
+ .form-select:focus {
203
+ border-color: ${cssManager.bdTheme('#3b82f6', '#60a5fa')};
204
+ }
205
+
206
+ .form-input::placeholder {
207
+ color: ${cssManager.bdTheme('#a1a1aa', '#52525b')};
208
+ }
209
+
210
+ .form-select {
211
+ cursor: pointer;
212
+ }
213
+
214
+ .dynamic-list {
215
+ display: flex;
216
+ flex-direction: column;
217
+ gap: 8px;
218
+ }
219
+
220
+ .dynamic-row {
221
+ display: flex;
222
+ gap: 8px;
223
+ align-items: flex-start;
224
+ }
225
+
226
+ .dynamic-row .form-input {
227
+ flex: 1;
228
+ }
229
+
230
+ .dynamic-row .form-select {
231
+ width: 80px;
232
+ flex-shrink: 0;
233
+ }
234
+
235
+ .remove-button {
236
+ padding: 10px;
237
+ background: transparent;
238
+ border: 1px solid ${cssManager.bdTheme('#e4e4e7', '#27272a')};
239
+ border-radius: 6px;
240
+ color: ${cssManager.bdTheme('#71717a', '#a1a1aa')};
241
+ cursor: pointer;
242
+ transition: all 200ms ease;
243
+ flex-shrink: 0;
244
+ }
245
+
246
+ .remove-button:hover {
247
+ background: ${cssManager.bdTheme('#fee2e2', 'rgba(239, 68, 68, 0.2)')};
248
+ border-color: ${cssManager.bdTheme('#fecaca', 'rgba(239, 68, 68, 0.3)')};
249
+ color: ${cssManager.bdTheme('#dc2626', '#ef4444')};
250
+ }
251
+
252
+ .add-button {
253
+ display: inline-flex;
254
+ align-items: center;
255
+ gap: 6px;
256
+ padding: 8px 12px;
257
+ background: transparent;
258
+ border: 1px dashed ${cssManager.bdTheme('#e4e4e7', '#27272a')};
259
+ border-radius: 6px;
260
+ font-size: 13px;
261
+ color: ${cssManager.bdTheme('#71717a', '#a1a1aa')};
262
+ cursor: pointer;
263
+ transition: all 200ms ease;
264
+ margin-top: 8px;
265
+ }
266
+
267
+ .add-button:hover {
268
+ background: ${cssManager.bdTheme('#f4f4f5', '#18181b')};
269
+ border-color: ${cssManager.bdTheme('#a1a1aa', '#52525b')};
270
+ color: ${cssManager.bdTheme('#18181b', '#fafafa')};
271
+ }
272
+
273
+ .add-button svg {
274
+ width: 14px;
275
+ height: 14px;
276
+ }
277
+
278
+ .toggle-advanced {
279
+ display: flex;
280
+ align-items: center;
281
+ gap: 8px;
282
+ padding: 12px 0;
283
+ font-size: 14px;
284
+ font-weight: 500;
285
+ color: ${cssManager.bdTheme('#3b82f6', '#60a5fa')};
286
+ cursor: pointer;
287
+ background: none;
288
+ border: none;
289
+ }
290
+
291
+ .toggle-advanced svg {
292
+ width: 16px;
293
+ height: 16px;
294
+ transition: transform 200ms ease;
295
+ }
296
+
297
+ .toggle-advanced.open svg {
298
+ transform: rotate(180deg);
299
+ }
300
+
301
+ .checkbox-row {
302
+ display: flex;
303
+ align-items: center;
304
+ gap: 8px;
305
+ }
306
+
307
+ .checkbox {
308
+ width: 18px;
309
+ height: 18px;
310
+ accent-color: ${cssManager.bdTheme('#3b82f6', '#60a5fa')};
311
+ }
312
+
313
+ .actions {
314
+ display: flex;
315
+ justify-content: flex-end;
316
+ gap: 12px;
317
+ padding-top: 16px;
318
+ border-top: 1px solid ${cssManager.bdTheme('#e4e4e7', '#27272a')};
319
+ margin-top: 8px;
320
+ }
321
+
322
+ .button {
323
+ padding: 10px 20px;
324
+ border-radius: 6px;
325
+ font-size: 14px;
326
+ font-weight: 500;
327
+ cursor: pointer;
328
+ transition: all 200ms ease;
329
+ display: inline-flex;
330
+ align-items: center;
331
+ gap: 8px;
332
+ }
333
+
334
+ .button.secondary {
335
+ background: ${cssManager.bdTheme('#ffffff', '#09090b')};
336
+ border: 1px solid ${cssManager.bdTheme('#e4e4e7', '#27272a')};
337
+ color: ${cssManager.bdTheme('#18181b', '#fafafa')};
338
+ }
339
+
340
+ .button.secondary:hover {
341
+ background: ${cssManager.bdTheme('#f4f4f5', '#18181b')};
342
+ }
343
+
344
+ .button.primary {
345
+ background: ${cssManager.bdTheme('#18181b', '#fafafa')};
346
+ border: none;
347
+ color: ${cssManager.bdTheme('#fafafa', '#18181b')};
348
+ }
349
+
350
+ .button.primary:hover:not(:disabled) {
351
+ opacity: 0.9;
352
+ }
353
+
354
+ .button.primary:disabled {
355
+ opacity: 0.6;
356
+ cursor: not-allowed;
357
+ }
358
+
359
+ .spinner {
360
+ width: 16px;
361
+ height: 16px;
362
+ border: 2px solid transparent;
363
+ border-top-color: currentColor;
364
+ border-radius: 50%;
365
+ animation: spin 0.8s linear infinite;
366
+ }
367
+
368
+ @keyframes spin {
369
+ to {
370
+ transform: rotate(360deg);
371
+ }
372
+ }
373
+ `,
374
+ ];
375
+
376
+ public render(): TemplateResult {
377
+ return html`
378
+ <div class="header">
379
+ <div>
380
+ <div class="header-title">Deploy New Service</div>
381
+ <div class="header-subtitle">Configure and deploy a new Docker container</div>
382
+ </div>
383
+ </div>
384
+
385
+ <!-- Basic Info Section -->
386
+ <div class="section">
387
+ <div class="section-title">
388
+ <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
389
+ <rect x="3" y="3" width="18" height="18" rx="2" ry="2"></rect>
390
+ <line x1="9" y1="9" x2="15" y2="9"></line>
391
+ <line x1="9" y1="15" x2="15" y2="15"></line>
392
+ </svg>
393
+ Basic Information
394
+ </div>
395
+ <div class="form-row">
396
+ <div class="form-group">
397
+ <label class="form-label">Service Name <span class="required">*</span></label>
398
+ <input
399
+ type="text"
400
+ class="form-input"
401
+ placeholder="my-service"
402
+ .value=${this.serviceName}
403
+ @input=${(e: Event) => this.serviceName = (e.target as HTMLInputElement).value}
404
+ >
405
+ <div class="form-hint">Unique name for the service (alphanumeric and hyphens)</div>
406
+ </div>
407
+ <div class="form-group">
408
+ <label class="form-label">Registry</label>
409
+ <select
410
+ class="form-select"
411
+ .value=${this.selectedRegistry}
412
+ @change=${(e: Event) => this.selectedRegistry = (e.target as HTMLSelectElement).value}
413
+ >
414
+ <option value="">Custom Image URL</option>
415
+ ${this.registries.map(reg => html`
416
+ <option value=${reg.id}>${reg.name}</option>
417
+ `)}
418
+ </select>
419
+ </div>
420
+ </div>
421
+ <div class="form-row single">
422
+ <div class="form-group">
423
+ <label class="form-label">Image <span class="required">*</span></label>
424
+ <input
425
+ type="text"
426
+ class="form-input"
427
+ placeholder="nginx:latest or registry.example.com/image:tag"
428
+ .value=${this.imageUrl}
429
+ @input=${(e: Event) => this.imageUrl = (e.target as HTMLInputElement).value}
430
+ >
431
+ <div class="form-hint">Docker image to deploy (include tag)</div>
432
+ </div>
433
+ </div>
434
+ </div>
435
+
436
+ <!-- Port Configuration -->
437
+ <div class="section">
438
+ <div class="section-title">
439
+ <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
440
+ <circle cx="12" cy="12" r="10"></circle>
441
+ <line x1="2" y1="12" x2="22" y2="12"></line>
442
+ <path d="M12 2a15.3 15.3 0 0 1 4 10 15.3 15.3 0 0 1-4 10 15.3 15.3 0 0 1-4-10 15.3 15.3 0 0 1 4-10z"></path>
443
+ </svg>
444
+ Port Configuration
445
+ </div>
446
+ <div class="dynamic-list">
447
+ ${this.ports.map((port, index) => html`
448
+ <div class="dynamic-row">
449
+ <input
450
+ type="text"
451
+ class="form-input"
452
+ placeholder="Host Port"
453
+ .value=${port.hostPort}
454
+ @input=${(e: Event) => this.updatePort(index, 'hostPort', (e.target as HTMLInputElement).value)}
455
+ >
456
+ <input
457
+ type="text"
458
+ class="form-input"
459
+ placeholder="Container Port"
460
+ .value=${port.containerPort}
461
+ @input=${(e: Event) => this.updatePort(index, 'containerPort', (e.target as HTMLInputElement).value)}
462
+ >
463
+ <select
464
+ class="form-select"
465
+ .value=${port.protocol}
466
+ @change=${(e: Event) => this.updatePort(index, 'protocol', (e.target as HTMLSelectElement).value)}
467
+ >
468
+ <option value="tcp">TCP</option>
469
+ <option value="udp">UDP</option>
470
+ </select>
471
+ ${this.ports.length > 1 ? html`
472
+ <button class="remove-button" @click=${() => this.removePort(index)}>
473
+ <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
474
+ <line x1="18" y1="6" x2="6" y2="18"></line>
475
+ <line x1="6" y1="6" x2="18" y2="18"></line>
476
+ </svg>
477
+ </button>
478
+ ` : ''}
479
+ </div>
480
+ `)}
481
+ </div>
482
+ <button class="add-button" @click=${() => this.addPort()}>
483
+ <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
484
+ <line x1="12" y1="5" x2="12" y2="19"></line>
485
+ <line x1="5" y1="12" x2="19" y2="12"></line>
486
+ </svg>
487
+ Add Port Mapping
488
+ </button>
489
+ </div>
490
+
491
+ <!-- Environment Variables -->
492
+ <div class="section">
493
+ <div class="section-title">
494
+ <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
495
+ <polyline points="4 17 10 11 4 5"></polyline>
496
+ <line x1="12" y1="19" x2="20" y2="19"></line>
497
+ </svg>
498
+ Environment Variables
499
+ </div>
500
+ <div class="dynamic-list">
501
+ ${this.envVars.map((env, index) => html`
502
+ <div class="dynamic-row">
503
+ <input
504
+ type="text"
505
+ class="form-input"
506
+ placeholder="KEY"
507
+ .value=${env.key}
508
+ @input=${(e: Event) => this.updateEnvVar(index, 'key', (e.target as HTMLInputElement).value)}
509
+ >
510
+ <input
511
+ type="text"
512
+ class="form-input"
513
+ placeholder="value"
514
+ .value=${env.value}
515
+ @input=${(e: Event) => this.updateEnvVar(index, 'value', (e.target as HTMLInputElement).value)}
516
+ >
517
+ ${this.envVars.length > 1 ? html`
518
+ <button class="remove-button" @click=${() => this.removeEnvVar(index)}>
519
+ <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
520
+ <line x1="18" y1="6" x2="6" y2="18"></line>
521
+ <line x1="6" y1="6" x2="18" y2="18"></line>
522
+ </svg>
523
+ </button>
524
+ ` : ''}
525
+ </div>
526
+ `)}
527
+ </div>
528
+ <button class="add-button" @click=${() => this.addEnvVar()}>
529
+ <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
530
+ <line x1="12" y1="5" x2="12" y2="19"></line>
531
+ <line x1="5" y1="12" x2="19" y2="12"></line>
532
+ </svg>
533
+ Add Environment Variable
534
+ </button>
535
+ </div>
536
+
537
+ <!-- Advanced Options Toggle -->
538
+ <button
539
+ class="toggle-advanced ${this.showAdvanced ? 'open' : ''}"
540
+ @click=${() => this.showAdvanced = !this.showAdvanced}
541
+ >
542
+ <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
543
+ <polyline points="6 9 12 15 18 9"></polyline>
544
+ </svg>
545
+ Advanced Options
546
+ </button>
547
+
548
+ ${this.showAdvanced ? html`
549
+ <!-- Volumes -->
550
+ <div class="section">
551
+ <div class="section-title">
552
+ <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
553
+ <path d="M22 19a2 2 0 0 1-2 2H4a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h5l2 3h9a2 2 0 0 1 2 2z"></path>
554
+ </svg>
555
+ Volume Mounts
556
+ </div>
557
+ <div class="dynamic-list">
558
+ ${this.volumes.length === 0 ? html`
559
+ <div class="form-hint">No volumes configured</div>
560
+ ` : this.volumes.map((vol, index) => html`
561
+ <div class="dynamic-row">
562
+ <input
563
+ type="text"
564
+ class="form-input"
565
+ placeholder="/host/path"
566
+ .value=${vol.hostPath}
567
+ @input=${(e: Event) => this.updateVolume(index, 'hostPath', (e.target as HTMLInputElement).value)}
568
+ >
569
+ <input
570
+ type="text"
571
+ class="form-input"
572
+ placeholder="/container/path"
573
+ .value=${vol.containerPath}
574
+ @input=${(e: Event) => this.updateVolume(index, 'containerPath', (e.target as HTMLInputElement).value)}
575
+ >
576
+ <div class="checkbox-row">
577
+ <input
578
+ type="checkbox"
579
+ class="checkbox"
580
+ ?checked=${vol.readOnly}
581
+ @change=${(e: Event) => this.updateVolume(index, 'readOnly', (e.target as HTMLInputElement).checked)}
582
+ >
583
+ <span class="form-hint">RO</span>
584
+ </div>
585
+ <button class="remove-button" @click=${() => this.removeVolume(index)}>
586
+ <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
587
+ <line x1="18" y1="6" x2="6" y2="18"></line>
588
+ <line x1="6" y1="6" x2="18" y2="18"></line>
589
+ </svg>
590
+ </button>
591
+ </div>
592
+ `)}
593
+ </div>
594
+ <button class="add-button" @click=${() => this.addVolume()}>
595
+ <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
596
+ <line x1="12" y1="5" x2="12" y2="19"></line>
597
+ <line x1="5" y1="12" x2="19" y2="12"></line>
598
+ </svg>
599
+ Add Volume Mount
600
+ </button>
601
+ </div>
602
+
603
+ <!-- Resource Limits -->
604
+ <div class="section">
605
+ <div class="section-title">
606
+ <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
607
+ <rect x="1" y="4" width="22" height="16" rx="2" ry="2"></rect>
608
+ <line x1="1" y1="10" x2="23" y2="10"></line>
609
+ </svg>
610
+ Resource Limits
611
+ </div>
612
+ <div class="form-row">
613
+ <div class="form-group">
614
+ <label class="form-label">CPU Limit</label>
615
+ <input
616
+ type="text"
617
+ class="form-input"
618
+ placeholder="e.g., 1.0 or 0.5"
619
+ .value=${this.cpuLimit}
620
+ @input=${(e: Event) => this.cpuLimit = (e.target as HTMLInputElement).value}
621
+ >
622
+ <div class="form-hint">Number of CPUs (leave empty for unlimited)</div>
623
+ </div>
624
+ <div class="form-group">
625
+ <label class="form-label">Memory Limit</label>
626
+ <input
627
+ type="text"
628
+ class="form-input"
629
+ placeholder="e.g., 512m or 1g"
630
+ .value=${this.memoryLimit}
631
+ @input=${(e: Event) => this.memoryLimit = (e.target as HTMLInputElement).value}
632
+ >
633
+ <div class="form-hint">Memory limit (leave empty for unlimited)</div>
634
+ </div>
635
+ </div>
636
+ </div>
637
+
638
+ <!-- Restart Policy & Network -->
639
+ <div class="section">
640
+ <div class="section-title">
641
+ <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
642
+ <circle cx="12" cy="12" r="3"></circle>
643
+ <path d="M19.4 15a1.65 1.65 0 0 0 .33 1.82l.06.06a2 2 0 0 1 0 2.83 2 2 0 0 1-2.83 0l-.06-.06a1.65 1.65 0 0 0-1.82-.33 1.65 1.65 0 0 0-1 1.51V21a2 2 0 0 1-2 2 2 2 0 0 1-2-2v-.09A1.65 1.65 0 0 0 9 19.4a1.65 1.65 0 0 0-1.82.33l-.06.06a2 2 0 0 1-2.83 0 2 2 0 0 1 0-2.83l.06-.06a1.65 1.65 0 0 0 .33-1.82 1.65 1.65 0 0 0-1.51-1H3a2 2 0 0 1-2-2 2 2 0 0 1 2-2h.09A1.65 1.65 0 0 0 4.6 9a1.65 1.65 0 0 0-.33-1.82l-.06-.06a2 2 0 0 1 0-2.83 2 2 0 0 1 2.83 0l.06.06a1.65 1.65 0 0 0 1.82.33H9a1.65 1.65 0 0 0 1-1.51V3a2 2 0 0 1 2-2 2 2 0 0 1 2 2v.09a1.65 1.65 0 0 0 1 1.51 1.65 1.65 0 0 0 1.82-.33l.06-.06a2 2 0 0 1 2.83 0 2 2 0 0 1 0 2.83l-.06.06a1.65 1.65 0 0 0-.33 1.82V9a1.65 1.65 0 0 0 1.51 1H21a2 2 0 0 1 2 2 2 2 0 0 1-2 2h-.09a1.65 1.65 0 0 0-1.51 1z"></path>
644
+ </svg>
645
+ Container Settings
646
+ </div>
647
+ <div class="form-row">
648
+ <div class="form-group">
649
+ <label class="form-label">Restart Policy</label>
650
+ <select
651
+ class="form-select"
652
+ .value=${this.restartPolicy}
653
+ @change=${(e: Event) => this.restartPolicy = (e.target as HTMLSelectElement).value as any}
654
+ >
655
+ <option value="always">Always</option>
656
+ <option value="on-failure">On Failure</option>
657
+ <option value="never">Never</option>
658
+ </select>
659
+ </div>
660
+ <div class="form-group">
661
+ <label class="form-label">Network Mode</label>
662
+ <select
663
+ class="form-select"
664
+ .value=${this.networkMode}
665
+ @change=${(e: Event) => this.networkMode = (e.target as HTMLSelectElement).value}
666
+ >
667
+ <option value="bridge">Bridge</option>
668
+ <option value="host">Host</option>
669
+ <option value="none">None</option>
670
+ </select>
671
+ </div>
672
+ </div>
673
+ </div>
674
+ ` : ''}
675
+
676
+ <div class="actions">
677
+ <button class="button secondary" @click=${() => this.handleCancel()}>Cancel</button>
678
+ <button
679
+ class="button primary"
680
+ ?disabled=${this.loading || !this.isValid()}
681
+ @click=${() => this.handleCreate()}
682
+ >
683
+ ${this.loading ? html`<div class="spinner"></div>` : ''}
684
+ ${this.loading ? 'Deploying...' : 'Deploy Service'}
685
+ </button>
686
+ </div>
687
+ `;
688
+ }
689
+
690
+ private isValid(): boolean {
691
+ return this.serviceName.trim() !== '' && this.imageUrl.trim() !== '';
692
+ }
693
+
694
+ private addPort() {
695
+ this.ports = [...this.ports, { hostPort: '', containerPort: '', protocol: 'tcp' }];
696
+ }
697
+
698
+ private removePort(index: number) {
699
+ this.ports = this.ports.filter((_, i) => i !== index);
700
+ }
701
+
702
+ private updatePort(index: number, field: keyof IPortMapping, value: string) {
703
+ const newPorts = [...this.ports];
704
+ (newPorts[index] as any)[field] = value;
705
+ this.ports = newPorts;
706
+ }
707
+
708
+ private addEnvVar() {
709
+ this.envVars = [...this.envVars, { key: '', value: '' }];
710
+ }
711
+
712
+ private removeEnvVar(index: number) {
713
+ this.envVars = this.envVars.filter((_, i) => i !== index);
714
+ }
715
+
716
+ private updateEnvVar(index: number, field: keyof IEnvVar, value: string) {
717
+ const newEnvVars = [...this.envVars];
718
+ newEnvVars[index][field] = value;
719
+ this.envVars = newEnvVars;
720
+ }
721
+
722
+ private addVolume() {
723
+ this.volumes = [...this.volumes, { hostPath: '', containerPath: '', readOnly: false }];
724
+ }
725
+
726
+ private removeVolume(index: number) {
727
+ this.volumes = this.volumes.filter((_, i) => i !== index);
728
+ }
729
+
730
+ private updateVolume(index: number, field: keyof IVolumeMount, value: string | boolean) {
731
+ const newVolumes = [...this.volumes];
732
+ (newVolumes[index] as any)[field] = value;
733
+ this.volumes = newVolumes;
734
+ }
735
+
736
+ private handleCancel() {
737
+ this.dispatchEvent(new CustomEvent('cancel', { bubbles: true, composed: true }));
738
+ }
739
+
740
+ private handleCreate() {
741
+ const config: IServiceConfig = {
742
+ name: this.serviceName.trim(),
743
+ image: this.imageUrl.trim(),
744
+ ports: this.ports.filter(p => p.hostPort && p.containerPort),
745
+ envVars: this.envVars.filter(e => e.key),
746
+ volumes: this.volumes.filter(v => v.hostPath && v.containerPath),
747
+ cpuLimit: this.cpuLimit,
748
+ memoryLimit: this.memoryLimit,
749
+ restartPolicy: this.restartPolicy,
750
+ networkMode: this.networkMode,
751
+ };
752
+
753
+ this.dispatchEvent(new CustomEvent('create-service', {
754
+ detail: config,
755
+ bubbles: true,
756
+ composed: true,
757
+ }));
758
+ }
759
+
760
+ public reset() {
761
+ this.serviceName = '';
762
+ this.imageUrl = '';
763
+ this.selectedRegistry = '';
764
+ this.ports = [{ hostPort: '', containerPort: '', protocol: 'tcp' }];
765
+ this.envVars = [{ key: '', value: '' }];
766
+ this.volumes = [];
767
+ this.cpuLimit = '';
768
+ this.memoryLimit = '';
769
+ this.restartPolicy = 'always';
770
+ this.networkMode = 'bridge';
771
+ this.showAdvanced = false;
772
+ }
773
+ }