@serve.zone/catalog 2.6.2 → 2.8.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 +1 -0
- package/dist_ts_web/elements/index.js +2 -1
- package/dist_ts_web/elements/sz-app-store-view.d.ts +40 -0
- package/dist_ts_web/elements/sz-app-store-view.js +588 -0
- package/dist_ts_web/elements/sz-service-detail-view.js +12 -91
- package/package.json +1 -1
- package/ts_web/00_commitinfo_data.ts +1 -1
- package/ts_web/elements/index.ts +1 -0
- package/ts_web/elements/sz-app-store-view.ts +568 -0
- package/ts_web/elements/sz-service-detail-view.ts +11 -91
|
@@ -0,0 +1,568 @@
|
|
|
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-app-store-view': SzAppStoreView;
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export interface IAppTemplate {
|
|
19
|
+
id: string;
|
|
20
|
+
name: string;
|
|
21
|
+
description: string;
|
|
22
|
+
category: string;
|
|
23
|
+
iconUrl?: string;
|
|
24
|
+
iconName?: string;
|
|
25
|
+
image: string;
|
|
26
|
+
port: number;
|
|
27
|
+
envVars?: Array<{ key: string; value: string; description: string; required?: boolean }>;
|
|
28
|
+
volumes?: string[];
|
|
29
|
+
enableMongoDB?: boolean;
|
|
30
|
+
enableS3?: boolean;
|
|
31
|
+
enableClickHouse?: boolean;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
@customElement('sz-app-store-view')
|
|
35
|
+
export class SzAppStoreView extends DeesElement {
|
|
36
|
+
public static demo = () => html`
|
|
37
|
+
<div style="padding: 24px; max-width: 1200px;">
|
|
38
|
+
<sz-app-store-view
|
|
39
|
+
.apps=${[
|
|
40
|
+
{
|
|
41
|
+
id: 'wordpress',
|
|
42
|
+
name: 'WordPress',
|
|
43
|
+
description: 'The world\'s most popular open-source content management system for building websites and blogs.',
|
|
44
|
+
category: 'CMS',
|
|
45
|
+
iconName: 'file-text',
|
|
46
|
+
image: 'wordpress:latest',
|
|
47
|
+
port: 80,
|
|
48
|
+
envVars: [
|
|
49
|
+
{ key: 'WORDPRESS_DB_HOST', value: '', description: 'Database host address', required: true },
|
|
50
|
+
{ key: 'WORDPRESS_DB_USER', value: 'wordpress', description: 'Database username', required: true },
|
|
51
|
+
{ key: 'WORDPRESS_DB_PASSWORD', value: '', description: 'Database password', required: true },
|
|
52
|
+
{ key: 'WORDPRESS_DB_NAME', value: 'wordpress', description: 'Database name', required: true },
|
|
53
|
+
],
|
|
54
|
+
volumes: ['/var/www/html'],
|
|
55
|
+
enableMongoDB: false,
|
|
56
|
+
enableS3: false,
|
|
57
|
+
},
|
|
58
|
+
{
|
|
59
|
+
id: 'gitea',
|
|
60
|
+
name: 'Gitea',
|
|
61
|
+
description: 'A lightweight, self-hosted Git service. Painless setup for your own code hosting platform.',
|
|
62
|
+
category: 'Development',
|
|
63
|
+
iconName: 'git-branch',
|
|
64
|
+
image: 'gitea/gitea:latest',
|
|
65
|
+
port: 3000,
|
|
66
|
+
envVars: [
|
|
67
|
+
{ key: 'GITEA__database__DB_TYPE', value: 'sqlite3', description: 'Database type', required: true },
|
|
68
|
+
{ key: 'GITEA__server__ROOT_URL', value: '', description: 'Public URL of the Gitea instance', required: false },
|
|
69
|
+
],
|
|
70
|
+
volumes: ['/data'],
|
|
71
|
+
},
|
|
72
|
+
{
|
|
73
|
+
id: 'ghost',
|
|
74
|
+
name: 'Ghost',
|
|
75
|
+
description: 'A powerful open-source publishing platform for professional bloggers and content creators.',
|
|
76
|
+
category: 'CMS',
|
|
77
|
+
iconName: 'book-open',
|
|
78
|
+
image: 'ghost:latest',
|
|
79
|
+
port: 2368,
|
|
80
|
+
envVars: [
|
|
81
|
+
{ key: 'url', value: '', description: 'Public URL for the Ghost site', required: true },
|
|
82
|
+
{ key: 'database__client', value: 'sqlite3', description: 'Database client type', required: false },
|
|
83
|
+
],
|
|
84
|
+
volumes: ['/var/lib/ghost/content'],
|
|
85
|
+
},
|
|
86
|
+
{
|
|
87
|
+
id: 'nginx',
|
|
88
|
+
name: 'Nginx',
|
|
89
|
+
description: 'High-performance HTTP server and reverse proxy with low resource consumption.',
|
|
90
|
+
category: 'Web Server',
|
|
91
|
+
iconName: 'globe',
|
|
92
|
+
image: 'nginx:alpine',
|
|
93
|
+
port: 80,
|
|
94
|
+
volumes: ['/usr/share/nginx/html', '/etc/nginx/conf.d'],
|
|
95
|
+
},
|
|
96
|
+
{
|
|
97
|
+
id: 'redis',
|
|
98
|
+
name: 'Redis',
|
|
99
|
+
description: 'In-memory data store used as a database, cache, streaming engine, and message broker.',
|
|
100
|
+
category: 'Database',
|
|
101
|
+
iconName: 'database',
|
|
102
|
+
image: 'redis:alpine',
|
|
103
|
+
port: 6379,
|
|
104
|
+
},
|
|
105
|
+
{
|
|
106
|
+
id: 'postgres',
|
|
107
|
+
name: 'PostgreSQL',
|
|
108
|
+
description: 'Advanced open-source relational database with strong reliability and feature set.',
|
|
109
|
+
category: 'Database',
|
|
110
|
+
iconName: 'database',
|
|
111
|
+
image: 'postgres:16-alpine',
|
|
112
|
+
port: 5432,
|
|
113
|
+
envVars: [
|
|
114
|
+
{ key: 'POSTGRES_USER', value: 'postgres', description: 'Superuser username', required: true },
|
|
115
|
+
{ key: 'POSTGRES_PASSWORD', value: '', description: 'Superuser password', required: true },
|
|
116
|
+
{ key: 'POSTGRES_DB', value: 'postgres', description: 'Default database name', required: false },
|
|
117
|
+
],
|
|
118
|
+
volumes: ['/var/lib/postgresql/data'],
|
|
119
|
+
},
|
|
120
|
+
]}
|
|
121
|
+
></sz-app-store-view>
|
|
122
|
+
</div>
|
|
123
|
+
`;
|
|
124
|
+
|
|
125
|
+
public static demoGroups = ['Services'];
|
|
126
|
+
|
|
127
|
+
@property({ type: Array })
|
|
128
|
+
public accessor apps: IAppTemplate[] = [];
|
|
129
|
+
|
|
130
|
+
@state()
|
|
131
|
+
private accessor selectedCategory: string = 'All';
|
|
132
|
+
|
|
133
|
+
@state()
|
|
134
|
+
private accessor searchQuery: string = '';
|
|
135
|
+
|
|
136
|
+
public static styles = [
|
|
137
|
+
cssManager.defaultStyles,
|
|
138
|
+
css`
|
|
139
|
+
:host {
|
|
140
|
+
display: block;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
.header {
|
|
144
|
+
margin-bottom: 24px;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
.header-title {
|
|
148
|
+
font-size: 20px;
|
|
149
|
+
font-weight: 600;
|
|
150
|
+
color: ${cssManager.bdTheme('#18181b', '#fafafa')};
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
.header-subtitle {
|
|
154
|
+
font-size: 14px;
|
|
155
|
+
color: ${cssManager.bdTheme('#71717a', '#a1a1aa')};
|
|
156
|
+
margin-top: 4px;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
.filter-bar {
|
|
160
|
+
display: flex;
|
|
161
|
+
flex-wrap: wrap;
|
|
162
|
+
align-items: center;
|
|
163
|
+
gap: 12px;
|
|
164
|
+
margin-bottom: 24px;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
.category-pills {
|
|
168
|
+
display: flex;
|
|
169
|
+
flex-wrap: wrap;
|
|
170
|
+
gap: 8px;
|
|
171
|
+
flex: 1;
|
|
172
|
+
min-width: 0;
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
.category-pill {
|
|
176
|
+
padding: 6px 14px;
|
|
177
|
+
border-radius: 9999px;
|
|
178
|
+
font-size: 13px;
|
|
179
|
+
font-weight: 500;
|
|
180
|
+
cursor: pointer;
|
|
181
|
+
transition: all 200ms ease;
|
|
182
|
+
border: 1px solid ${cssManager.bdTheme('#e4e4e7', '#27272a')};
|
|
183
|
+
background: ${cssManager.bdTheme('#ffffff', '#09090b')};
|
|
184
|
+
color: ${cssManager.bdTheme('#71717a', '#a1a1aa')};
|
|
185
|
+
white-space: nowrap;
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
.category-pill:hover {
|
|
189
|
+
border-color: ${cssManager.bdTheme('#d4d4d8', '#3f3f46')};
|
|
190
|
+
color: ${cssManager.bdTheme('#18181b', '#fafafa')};
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
.category-pill.active {
|
|
194
|
+
background: ${cssManager.bdTheme('#18181b', '#fafafa')};
|
|
195
|
+
color: ${cssManager.bdTheme('#fafafa', '#18181b')};
|
|
196
|
+
border-color: ${cssManager.bdTheme('#18181b', '#fafafa')};
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
.search-input {
|
|
200
|
+
width: 240px;
|
|
201
|
+
padding: 8px 12px 8px 36px;
|
|
202
|
+
background: ${cssManager.bdTheme('#ffffff', '#18181b')};
|
|
203
|
+
border: 1px solid ${cssManager.bdTheme('#e4e4e7', '#27272a')};
|
|
204
|
+
border-radius: 6px;
|
|
205
|
+
font-size: 14px;
|
|
206
|
+
color: ${cssManager.bdTheme('#18181b', '#fafafa')};
|
|
207
|
+
outline: none;
|
|
208
|
+
transition: border-color 200ms ease;
|
|
209
|
+
box-sizing: border-box;
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
.search-input:focus {
|
|
213
|
+
border-color: ${cssManager.bdTheme('#3b82f6', '#60a5fa')};
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
.search-input::placeholder {
|
|
217
|
+
color: ${cssManager.bdTheme('#a1a1aa', '#52525b')};
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
.search-wrapper {
|
|
221
|
+
position: relative;
|
|
222
|
+
flex-shrink: 0;
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
.search-icon {
|
|
226
|
+
position: absolute;
|
|
227
|
+
left: 10px;
|
|
228
|
+
top: 50%;
|
|
229
|
+
transform: translateY(-50%);
|
|
230
|
+
width: 16px;
|
|
231
|
+
height: 16px;
|
|
232
|
+
color: ${cssManager.bdTheme('#a1a1aa', '#52525b')};
|
|
233
|
+
pointer-events: none;
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
.app-grid {
|
|
237
|
+
display: grid;
|
|
238
|
+
grid-template-columns: repeat(3, 1fr);
|
|
239
|
+
gap: 16px;
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
@media (max-width: 1024px) {
|
|
243
|
+
.app-grid {
|
|
244
|
+
grid-template-columns: repeat(2, 1fr);
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
@media (max-width: 640px) {
|
|
249
|
+
.app-grid {
|
|
250
|
+
grid-template-columns: 1fr;
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
.filter-bar {
|
|
254
|
+
flex-direction: column;
|
|
255
|
+
align-items: stretch;
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
.search-input {
|
|
259
|
+
width: 100%;
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
.app-card {
|
|
264
|
+
background: ${cssManager.bdTheme('#ffffff', '#09090b')};
|
|
265
|
+
border: 1px solid ${cssManager.bdTheme('#e4e4e7', '#27272a')};
|
|
266
|
+
border-radius: 8px;
|
|
267
|
+
padding: 20px;
|
|
268
|
+
display: flex;
|
|
269
|
+
flex-direction: column;
|
|
270
|
+
gap: 12px;
|
|
271
|
+
transition: all 200ms ease;
|
|
272
|
+
cursor: default;
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
.app-card:hover {
|
|
276
|
+
border-color: ${cssManager.bdTheme('#d4d4d8', '#3f3f46')};
|
|
277
|
+
box-shadow: 0 4px 12px ${cssManager.bdTheme('rgba(0,0,0,0.05)', 'rgba(0,0,0,0.2)')};
|
|
278
|
+
transform: translateY(-1px);
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
.card-top {
|
|
282
|
+
display: flex;
|
|
283
|
+
align-items: flex-start;
|
|
284
|
+
gap: 14px;
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
.app-icon {
|
|
288
|
+
width: 44px;
|
|
289
|
+
height: 44px;
|
|
290
|
+
border-radius: 10px;
|
|
291
|
+
background: ${cssManager.bdTheme('#f4f4f5', '#18181b')};
|
|
292
|
+
display: flex;
|
|
293
|
+
align-items: center;
|
|
294
|
+
justify-content: center;
|
|
295
|
+
flex-shrink: 0;
|
|
296
|
+
overflow: hidden;
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
.app-icon svg {
|
|
300
|
+
width: 22px;
|
|
301
|
+
height: 22px;
|
|
302
|
+
color: ${cssManager.bdTheme('#18181b', '#fafafa')};
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
.app-icon img {
|
|
306
|
+
width: 100%;
|
|
307
|
+
height: 100%;
|
|
308
|
+
object-fit: cover;
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
.app-icon .letter-fallback {
|
|
312
|
+
font-size: 20px;
|
|
313
|
+
font-weight: 600;
|
|
314
|
+
color: ${cssManager.bdTheme('#18181b', '#fafafa')};
|
|
315
|
+
user-select: none;
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
.card-info {
|
|
319
|
+
flex: 1;
|
|
320
|
+
min-width: 0;
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
.app-name {
|
|
324
|
+
font-size: 15px;
|
|
325
|
+
font-weight: 600;
|
|
326
|
+
color: ${cssManager.bdTheme('#18181b', '#fafafa')};
|
|
327
|
+
line-height: 1.3;
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
.category-badge {
|
|
331
|
+
display: inline-flex;
|
|
332
|
+
align-items: center;
|
|
333
|
+
padding: 2px 8px;
|
|
334
|
+
border-radius: 9999px;
|
|
335
|
+
font-size: 11px;
|
|
336
|
+
font-weight: 500;
|
|
337
|
+
margin-top: 4px;
|
|
338
|
+
background: ${cssManager.bdTheme('#eff6ff', 'rgba(59, 130, 246, 0.15)')};
|
|
339
|
+
color: ${cssManager.bdTheme('#2563eb', '#60a5fa')};
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
.app-description {
|
|
343
|
+
font-size: 13px;
|
|
344
|
+
line-height: 1.5;
|
|
345
|
+
color: ${cssManager.bdTheme('#71717a', '#a1a1aa')};
|
|
346
|
+
display: -webkit-box;
|
|
347
|
+
-webkit-line-clamp: 2;
|
|
348
|
+
-webkit-box-orient: vertical;
|
|
349
|
+
overflow: hidden;
|
|
350
|
+
text-overflow: ellipsis;
|
|
351
|
+
flex: 1;
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
.card-footer {
|
|
355
|
+
display: flex;
|
|
356
|
+
align-items: center;
|
|
357
|
+
justify-content: space-between;
|
|
358
|
+
padding-top: 12px;
|
|
359
|
+
border-top: 1px solid ${cssManager.bdTheme('#f4f4f5', '#1a1a1e')};
|
|
360
|
+
margin-top: auto;
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
.image-tag {
|
|
364
|
+
font-family: monospace;
|
|
365
|
+
font-size: 12px;
|
|
366
|
+
color: ${cssManager.bdTheme('#a1a1aa', '#52525b')};
|
|
367
|
+
overflow: hidden;
|
|
368
|
+
text-overflow: ellipsis;
|
|
369
|
+
white-space: nowrap;
|
|
370
|
+
max-width: 160px;
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
.deploy-button {
|
|
374
|
+
display: inline-flex;
|
|
375
|
+
align-items: center;
|
|
376
|
+
gap: 6px;
|
|
377
|
+
padding: 7px 16px;
|
|
378
|
+
border-radius: 6px;
|
|
379
|
+
font-size: 13px;
|
|
380
|
+
font-weight: 500;
|
|
381
|
+
cursor: pointer;
|
|
382
|
+
transition: all 200ms ease;
|
|
383
|
+
border: none;
|
|
384
|
+
background: ${cssManager.bdTheme('#18181b', '#fafafa')};
|
|
385
|
+
color: ${cssManager.bdTheme('#fafafa', '#18181b')};
|
|
386
|
+
white-space: nowrap;
|
|
387
|
+
flex-shrink: 0;
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
.deploy-button:hover {
|
|
391
|
+
opacity: 0.85;
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
.deploy-button svg {
|
|
395
|
+
width: 14px;
|
|
396
|
+
height: 14px;
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
.empty-state {
|
|
400
|
+
grid-column: 1 / -1;
|
|
401
|
+
padding: 64px 24px;
|
|
402
|
+
text-align: center;
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
.empty-icon {
|
|
406
|
+
width: 48px;
|
|
407
|
+
height: 48px;
|
|
408
|
+
color: ${cssManager.bdTheme('#d4d4d8', '#3f3f46')};
|
|
409
|
+
margin: 0 auto 16px;
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
.empty-title {
|
|
413
|
+
font-size: 16px;
|
|
414
|
+
font-weight: 600;
|
|
415
|
+
color: ${cssManager.bdTheme('#18181b', '#fafafa')};
|
|
416
|
+
margin-bottom: 4px;
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
.empty-description {
|
|
420
|
+
font-size: 14px;
|
|
421
|
+
color: ${cssManager.bdTheme('#71717a', '#a1a1aa')};
|
|
422
|
+
}
|
|
423
|
+
`,
|
|
424
|
+
];
|
|
425
|
+
|
|
426
|
+
private get categories(): string[] {
|
|
427
|
+
const cats = new Set(this.apps.map((app) => app.category));
|
|
428
|
+
return ['All', ...Array.from(cats).sort()];
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
private get filteredApps(): IAppTemplate[] {
|
|
432
|
+
let result = this.apps;
|
|
433
|
+
|
|
434
|
+
if (this.selectedCategory !== 'All') {
|
|
435
|
+
result = result.filter((app) => app.category === this.selectedCategory);
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
if (this.searchQuery.trim()) {
|
|
439
|
+
const query = this.searchQuery.trim().toLowerCase();
|
|
440
|
+
result = result.filter(
|
|
441
|
+
(app) =>
|
|
442
|
+
app.name.toLowerCase().includes(query) ||
|
|
443
|
+
app.description.toLowerCase().includes(query) ||
|
|
444
|
+
app.category.toLowerCase().includes(query)
|
|
445
|
+
);
|
|
446
|
+
}
|
|
447
|
+
|
|
448
|
+
return result;
|
|
449
|
+
}
|
|
450
|
+
|
|
451
|
+
public render(): TemplateResult {
|
|
452
|
+
const filtered = this.filteredApps;
|
|
453
|
+
|
|
454
|
+
return html`
|
|
455
|
+
<div class="header">
|
|
456
|
+
<div class="header-title">App Store</div>
|
|
457
|
+
<div class="header-subtitle">Deploy popular applications with one click</div>
|
|
458
|
+
</div>
|
|
459
|
+
|
|
460
|
+
<div class="filter-bar">
|
|
461
|
+
<div class="category-pills">
|
|
462
|
+
${this.categories.map(
|
|
463
|
+
(cat) => html`
|
|
464
|
+
<button
|
|
465
|
+
class="category-pill ${this.selectedCategory === cat ? 'active' : ''}"
|
|
466
|
+
@click=${() => { this.selectedCategory = cat; }}
|
|
467
|
+
>
|
|
468
|
+
${cat}
|
|
469
|
+
</button>
|
|
470
|
+
`
|
|
471
|
+
)}
|
|
472
|
+
</div>
|
|
473
|
+
<div class="search-wrapper">
|
|
474
|
+
<svg class="search-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
|
475
|
+
<circle cx="11" cy="11" r="8"></circle>
|
|
476
|
+
<line x1="21" y1="21" x2="16.65" y2="16.65"></line>
|
|
477
|
+
</svg>
|
|
478
|
+
<input
|
|
479
|
+
type="text"
|
|
480
|
+
class="search-input"
|
|
481
|
+
placeholder="Search apps..."
|
|
482
|
+
.value=${this.searchQuery}
|
|
483
|
+
@input=${(e: Event) => { this.searchQuery = (e.target as HTMLInputElement).value; }}
|
|
484
|
+
>
|
|
485
|
+
</div>
|
|
486
|
+
</div>
|
|
487
|
+
|
|
488
|
+
<div class="app-grid">
|
|
489
|
+
${filtered.length > 0
|
|
490
|
+
? filtered.map((app) => this.renderAppCard(app))
|
|
491
|
+
: html`
|
|
492
|
+
<div class="empty-state">
|
|
493
|
+
<svg class="empty-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5">
|
|
494
|
+
<circle cx="11" cy="11" r="8"></circle>
|
|
495
|
+
<line x1="21" y1="21" x2="16.65" y2="16.65"></line>
|
|
496
|
+
<line x1="8" y1="11" x2="14" y2="11"></line>
|
|
497
|
+
</svg>
|
|
498
|
+
<div class="empty-title">No apps found</div>
|
|
499
|
+
<div class="empty-description">
|
|
500
|
+
Try adjusting your search or filter to find what you're looking for.
|
|
501
|
+
</div>
|
|
502
|
+
</div>
|
|
503
|
+
`}
|
|
504
|
+
</div>
|
|
505
|
+
`;
|
|
506
|
+
}
|
|
507
|
+
|
|
508
|
+
private renderAppCard(app: IAppTemplate): TemplateResult {
|
|
509
|
+
return html`
|
|
510
|
+
<div class="app-card">
|
|
511
|
+
<div class="card-top">
|
|
512
|
+
<div class="app-icon">
|
|
513
|
+
${app.iconUrl
|
|
514
|
+
? html`<img src="${app.iconUrl}" alt="${app.name}">`
|
|
515
|
+
: app.iconName
|
|
516
|
+
? this.renderIconByName(app.iconName)
|
|
517
|
+
: html`<span class="letter-fallback">${app.name.charAt(0).toUpperCase()}</span>`}
|
|
518
|
+
</div>
|
|
519
|
+
<div class="card-info">
|
|
520
|
+
<div class="app-name">${app.name}</div>
|
|
521
|
+
<div class="category-badge">${app.category}</div>
|
|
522
|
+
</div>
|
|
523
|
+
</div>
|
|
524
|
+
<div class="app-description">${app.description}</div>
|
|
525
|
+
<div class="card-footer">
|
|
526
|
+
<span class="image-tag" title="${app.image}">${app.image}</span>
|
|
527
|
+
<button class="deploy-button" @click=${() => this.handleDeploy(app)}>
|
|
528
|
+
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
|
529
|
+
<polyline points="20 12 20 22 4 22 4 12"></polyline>
|
|
530
|
+
<rect x="2" y="7" width="20" height="5"></rect>
|
|
531
|
+
<line x1="12" y1="22" x2="12" y2="7"></line>
|
|
532
|
+
<path d="M12 7H7.5a2.5 2.5 0 0 1 0-5C11 2 12 7 12 7z"></path>
|
|
533
|
+
<path d="M12 7h4.5a2.5 2.5 0 0 0 0-5C13 2 12 7 12 7z"></path>
|
|
534
|
+
</svg>
|
|
535
|
+
Deploy
|
|
536
|
+
</button>
|
|
537
|
+
</div>
|
|
538
|
+
</div>
|
|
539
|
+
`;
|
|
540
|
+
}
|
|
541
|
+
|
|
542
|
+
private renderIconByName(name: string): TemplateResult {
|
|
543
|
+
const icons: Record<string, TemplateResult> = {
|
|
544
|
+
'file-text': html`<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"></path><polyline points="14 2 14 8 20 8"></polyline><line x1="16" y1="13" x2="8" y2="13"></line><line x1="16" y1="17" x2="8" y2="17"></line><polyline points="10 9 9 9 8 9"></polyline></svg>`,
|
|
545
|
+
'git-branch': html`<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><line x1="6" y1="3" x2="6" y2="15"></line><circle cx="18" cy="6" r="3"></circle><circle cx="6" cy="18" r="3"></circle><path d="M18 9a9 9 0 0 1-9 9"></path></svg>`,
|
|
546
|
+
'book-open': html`<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M2 3h6a4 4 0 0 1 4 4v14a3 3 0 0 0-3-3H2z"></path><path d="M22 3h-6a4 4 0 0 0-4 4v14a3 3 0 0 1 3-3h7z"></path></svg>`,
|
|
547
|
+
'globe': html`<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><circle cx="12" cy="12" r="10"></circle><line x1="2" y1="12" x2="22" y2="12"></line><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></svg>`,
|
|
548
|
+
'database': html`<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><ellipse cx="12" cy="5" rx="9" ry="3"></ellipse><path d="M21 12c0 1.66-4 3-9 3s-9-1.34-9-3"></path><path d="M3 5v14c0 1.66 4 3 9 3s9-1.34 9-3V5"></path></svg>`,
|
|
549
|
+
'server': html`<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><rect x="2" y="2" width="20" height="8" rx="2" ry="2"></rect><rect x="2" y="14" width="20" height="8" rx="2" ry="2"></rect><line x1="6" y1="6" x2="6.01" y2="6"></line><line x1="6" y1="18" x2="6.01" y2="18"></line></svg>`,
|
|
550
|
+
'package': html`<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><line x1="16.5" y1="9.4" x2="7.5" y2="4.21"></line><path d="M21 16V8a2 2 0 0 0-1-1.73l-7-4a2 2 0 0 0-2 0l-7 4A2 2 0 0 0 3 8v8a2 2 0 0 0 1 1.73l7 4a2 2 0 0 0 2 0l7-4A2 2 0 0 0 21 16z"></path><polyline points="3.27 6.96 12 12.01 20.73 6.96"></polyline><line x1="12" y1="22.08" x2="12" y2="12"></line></svg>`,
|
|
551
|
+
'mail': html`<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M4 4h16c1.1 0 2 .9 2 2v12c0 1.1-.9 2-2 2H4c-1.1 0-2-.9-2-2V6c0-1.1.9-2 2-2z"></path><polyline points="22,6 12,13 2,6"></polyline></svg>`,
|
|
552
|
+
'shield': html`<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M12 22s8-4 8-10V5l-8-3-8 3v7c0 6 8 10 8 10z"></path></svg>`,
|
|
553
|
+
'monitor': html`<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><rect x="2" y="3" width="20" height="14" rx="2" ry="2"></rect><line x1="8" y1="21" x2="16" y2="21"></line><line x1="12" y1="17" x2="12" y2="21"></line></svg>`,
|
|
554
|
+
};
|
|
555
|
+
|
|
556
|
+
return icons[name] || html`<span class="letter-fallback">?</span>`;
|
|
557
|
+
}
|
|
558
|
+
|
|
559
|
+
private handleDeploy(app: IAppTemplate) {
|
|
560
|
+
this.dispatchEvent(
|
|
561
|
+
new CustomEvent('deploy-app', {
|
|
562
|
+
detail: { app },
|
|
563
|
+
bubbles: true,
|
|
564
|
+
composed: true,
|
|
565
|
+
})
|
|
566
|
+
);
|
|
567
|
+
}
|
|
568
|
+
}
|
|
@@ -396,68 +396,6 @@ export class SzServiceDetailView extends DeesElement {
|
|
|
396
396
|
color: ${cssManager.bdTheme('#18181b', '#fafafa')};
|
|
397
397
|
}
|
|
398
398
|
|
|
399
|
-
.logs-header {
|
|
400
|
-
display: flex;
|
|
401
|
-
justify-content: space-between;
|
|
402
|
-
align-items: center;
|
|
403
|
-
}
|
|
404
|
-
|
|
405
|
-
.logs-actions {
|
|
406
|
-
display: flex;
|
|
407
|
-
gap: 8px;
|
|
408
|
-
align-items: center;
|
|
409
|
-
}
|
|
410
|
-
|
|
411
|
-
.stream-button {
|
|
412
|
-
display: inline-flex;
|
|
413
|
-
align-items: center;
|
|
414
|
-
gap: 6px;
|
|
415
|
-
padding: 6px 12px;
|
|
416
|
-
background: ${cssManager.bdTheme('#2563eb', '#3b82f6')};
|
|
417
|
-
border: none;
|
|
418
|
-
border-radius: 4px;
|
|
419
|
-
font-size: 13px;
|
|
420
|
-
font-weight: 500;
|
|
421
|
-
color: white;
|
|
422
|
-
cursor: pointer;
|
|
423
|
-
}
|
|
424
|
-
|
|
425
|
-
.stream-button.streaming {
|
|
426
|
-
background: ${cssManager.bdTheme('#dc2626', '#ef4444')};
|
|
427
|
-
}
|
|
428
|
-
|
|
429
|
-
.clear-button {
|
|
430
|
-
padding: 6px 12px;
|
|
431
|
-
background: transparent;
|
|
432
|
-
border: 1px solid ${cssManager.bdTheme('#e4e4e7', '#27272a')};
|
|
433
|
-
border-radius: 4px;
|
|
434
|
-
font-size: 13px;
|
|
435
|
-
color: ${cssManager.bdTheme('#71717a', '#a1a1aa')};
|
|
436
|
-
cursor: pointer;
|
|
437
|
-
}
|
|
438
|
-
|
|
439
|
-
.logs-container {
|
|
440
|
-
padding: 16px;
|
|
441
|
-
font-family: monospace;
|
|
442
|
-
font-size: 12px;
|
|
443
|
-
max-height: 300px;
|
|
444
|
-
overflow-y: auto;
|
|
445
|
-
background: ${cssManager.bdTheme('#fafafa', '#0a0a0a')};
|
|
446
|
-
}
|
|
447
|
-
|
|
448
|
-
.log-entry {
|
|
449
|
-
padding: 2px 0;
|
|
450
|
-
color: ${cssManager.bdTheme('#71717a', '#a1a1aa')};
|
|
451
|
-
white-space: pre-wrap;
|
|
452
|
-
word-break: break-all;
|
|
453
|
-
}
|
|
454
|
-
|
|
455
|
-
.empty-logs {
|
|
456
|
-
padding: 24px;
|
|
457
|
-
text-align: center;
|
|
458
|
-
color: ${cssManager.bdTheme('#71717a', '#a1a1aa')};
|
|
459
|
-
}
|
|
460
|
-
|
|
461
399
|
.tag-badge {
|
|
462
400
|
display: inline-flex;
|
|
463
401
|
padding: 2px 8px;
|
|
@@ -621,35 +559,17 @@ export class SzServiceDetailView extends DeesElement {
|
|
|
621
559
|
</div>
|
|
622
560
|
</div>
|
|
623
561
|
|
|
624
|
-
<
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
? html`<rect x="6" y="6" width="12" height="12" rx="1"/>`
|
|
636
|
-
: html`<polygon points="5,3 19,12 5,21"/>`
|
|
637
|
-
}
|
|
638
|
-
</svg>
|
|
639
|
-
${this.streaming ? 'Stop' : 'Stream'}
|
|
640
|
-
</button>
|
|
641
|
-
<button class="clear-button" @click=${() => this.handleClearLogs()}>Clear logs</button>
|
|
642
|
-
</div>
|
|
643
|
-
</div>
|
|
644
|
-
</div>
|
|
645
|
-
<div class="logs-container">
|
|
646
|
-
${this.logs.length > 0 ? this.logs.map(log => html`
|
|
647
|
-
<div class="log-entry">${log.timestamp} ${log.message}</div>
|
|
648
|
-
`) : html`
|
|
649
|
-
<div class="empty-logs">Click "Stream" to start live log streaming</div>
|
|
650
|
-
`}
|
|
651
|
-
</div>
|
|
652
|
-
</div>
|
|
562
|
+
<dees-chart-log
|
|
563
|
+
.label=${'Service Logs'}
|
|
564
|
+
.logEntries=${this.logs.map(log => ({
|
|
565
|
+
timestamp: log.timestamp?.includes?.('T') ? log.timestamp : new Date(log.timestamp || Date.now()).toISOString(),
|
|
566
|
+
level: (log.level || 'info') as 'debug' | 'info' | 'warn' | 'error',
|
|
567
|
+
message: log.message,
|
|
568
|
+
}))}
|
|
569
|
+
.autoScroll=${true}
|
|
570
|
+
.maxEntries=${2000}
|
|
571
|
+
.showMetrics=${true}
|
|
572
|
+
></dees-chart-log>
|
|
653
573
|
</div>
|
|
654
574
|
|
|
655
575
|
<div class="sidebar">
|