@uptime.link/statuspage 1.0.74 → 1.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (95) hide show
  1. package/dist_bundle/bundle.js +4096 -504
  2. package/dist_bundle/bundle.js.map +4 -4
  3. package/dist_ts_web/00_commitinfo_data.js +2 -2
  4. package/dist_ts_web/elements/index.d.ts +3 -0
  5. package/dist_ts_web/elements/index.js +6 -1
  6. package/dist_ts_web/elements/internal/uplinternal-miniheading.d.ts +1 -0
  7. package/dist_ts_web/elements/internal/uplinternal-miniheading.js +78 -28
  8. package/dist_ts_web/elements/upl-statuspage-assetsselector.d.ts +14 -0
  9. package/dist_ts_web/elements/upl-statuspage-assetsselector.demo.d.ts +1 -0
  10. package/dist_ts_web/elements/upl-statuspage-assetsselector.demo.js +575 -0
  11. package/dist_ts_web/elements/upl-statuspage-assetsselector.js +605 -43
  12. package/dist_ts_web/elements/upl-statuspage-footer.d.ts +46 -2
  13. package/dist_ts_web/elements/upl-statuspage-footer.demo.d.ts +1 -0
  14. package/dist_ts_web/elements/upl-statuspage-footer.demo.js +679 -0
  15. package/dist_ts_web/elements/upl-statuspage-footer.js +792 -61
  16. package/dist_ts_web/elements/upl-statuspage-header.d.ts +5 -1
  17. package/dist_ts_web/elements/upl-statuspage-header.demo.d.ts +1 -0
  18. package/dist_ts_web/elements/upl-statuspage-header.demo.js +220 -0
  19. package/dist_ts_web/elements/upl-statuspage-header.js +313 -86
  20. package/dist_ts_web/elements/upl-statuspage-incidents.d.ts +22 -4
  21. package/dist_ts_web/elements/upl-statuspage-incidents.demo.d.ts +1 -0
  22. package/dist_ts_web/elements/upl-statuspage-incidents.demo.js +1147 -0
  23. package/dist_ts_web/elements/upl-statuspage-incidents.js +750 -74
  24. package/dist_ts_web/elements/upl-statuspage-pagetitle.d.ts +15 -0
  25. package/dist_ts_web/elements/upl-statuspage-pagetitle.demo.d.ts +1 -0
  26. package/dist_ts_web/elements/upl-statuspage-pagetitle.demo.js +25 -0
  27. package/dist_ts_web/elements/upl-statuspage-pagetitle.js +148 -0
  28. package/dist_ts_web/elements/upl-statuspage-statsgrid.d.ts +23 -0
  29. package/dist_ts_web/elements/upl-statuspage-statsgrid.demo.d.ts +1 -0
  30. package/dist_ts_web/elements/upl-statuspage-statsgrid.demo.js +295 -0
  31. package/dist_ts_web/elements/upl-statuspage-statsgrid.js +374 -0
  32. package/dist_ts_web/elements/upl-statuspage-statusbar.d.ts +4 -0
  33. package/dist_ts_web/elements/upl-statuspage-statusbar.demo.d.ts +1 -0
  34. package/dist_ts_web/elements/upl-statuspage-statusbar.demo.js +365 -0
  35. package/dist_ts_web/elements/upl-statuspage-statusbar.js +357 -44
  36. package/dist_ts_web/elements/upl-statuspage-statusdetails.d.ts +14 -0
  37. package/dist_ts_web/elements/upl-statuspage-statusdetails.demo.d.ts +1 -0
  38. package/dist_ts_web/elements/upl-statuspage-statusdetails.demo.js +706 -0
  39. package/dist_ts_web/elements/upl-statuspage-statusdetails.js +373 -63
  40. package/dist_ts_web/elements/upl-statuspage-statusmonth.d.ts +15 -0
  41. package/dist_ts_web/elements/upl-statuspage-statusmonth.demo.d.ts +1 -0
  42. package/dist_ts_web/elements/upl-statuspage-statusmonth.demo.js +798 -0
  43. package/dist_ts_web/elements/upl-statuspage-statusmonth.js +474 -100
  44. package/dist_ts_web/interfaces/index.d.ts +84 -0
  45. package/dist_ts_web/interfaces/index.js +4 -0
  46. package/dist_ts_web/pages/index.d.ts +4 -1
  47. package/dist_ts_web/pages/index.js +5 -2
  48. package/dist_ts_web/pages/statuspage-allgreen.d.ts +1 -0
  49. package/dist_ts_web/pages/statuspage-allgreen.js +386 -0
  50. package/dist_ts_web/pages/statuspage-demo.d.ts +1 -0
  51. package/dist_ts_web/pages/statuspage-demo.js +616 -0
  52. package/dist_ts_web/pages/statuspage-maintenance.d.ts +1 -0
  53. package/dist_ts_web/pages/statuspage-maintenance.js +544 -0
  54. package/dist_ts_web/pages/statuspage-outage.d.ts +1 -0
  55. package/dist_ts_web/pages/statuspage-outage.js +543 -0
  56. package/dist_ts_web/styles/shared.styles.d.ts +80 -0
  57. package/dist_ts_web/styles/shared.styles.js +351 -0
  58. package/dist_watch/bundle.js +51691 -32432
  59. package/dist_watch/bundle.js.map +4 -4
  60. package/npmextra.json +9 -3
  61. package/package.json +19 -19
  62. package/readme.hints.md +292 -0
  63. package/readme.md +326 -149
  64. package/readme.plan.md +261 -0
  65. package/ts_web/00_commitinfo_data.ts +1 -1
  66. package/ts_web/elements/index.ts +6 -0
  67. package/ts_web/elements/internal/uplinternal-miniheading.ts +24 -17
  68. package/ts_web/elements/upl-statuspage-assetsselector.demo.ts +607 -0
  69. package/ts_web/elements/upl-statuspage-assetsselector.ts +526 -18
  70. package/ts_web/elements/upl-statuspage-footer.demo.ts +744 -0
  71. package/ts_web/elements/upl-statuspage-footer.ts +608 -30
  72. package/ts_web/elements/upl-statuspage-header.demo.ts +241 -0
  73. package/ts_web/elements/upl-statuspage-header.ts +220 -52
  74. package/ts_web/elements/upl-statuspage-incidents.demo.ts +1216 -0
  75. package/ts_web/elements/upl-statuspage-incidents.ts +649 -26
  76. package/ts_web/elements/upl-statuspage-pagetitle.demo.ts +25 -0
  77. package/ts_web/elements/upl-statuspage-pagetitle.ts +89 -0
  78. package/ts_web/elements/upl-statuspage-statsgrid.demo.ts +315 -0
  79. package/ts_web/elements/upl-statuspage-statsgrid.ts +306 -0
  80. package/ts_web/elements/upl-statuspage-statusbar.demo.ts +393 -0
  81. package/ts_web/elements/upl-statuspage-statusbar.ts +281 -20
  82. package/ts_web/elements/upl-statuspage-statusdetails.demo.ts +754 -0
  83. package/ts_web/elements/upl-statuspage-statusdetails.ts +297 -38
  84. package/ts_web/elements/upl-statuspage-statusmonth.demo.ts +876 -0
  85. package/ts_web/elements/upl-statuspage-statusmonth.ts +397 -76
  86. package/ts_web/interfaces/index.ts +95 -0
  87. package/ts_web/pages/index.ts +4 -1
  88. package/ts_web/pages/statuspage-allgreen.ts +412 -0
  89. package/ts_web/pages/statuspage-demo.ts +653 -0
  90. package/ts_web/pages/statuspage-maintenance.ts +570 -0
  91. package/ts_web/pages/statuspage-outage.ts +568 -0
  92. package/ts_web/styles/shared.styles.ts +367 -0
  93. package/dist_ts_web/pages/page1.d.ts +0 -1
  94. package/dist_ts_web/pages/page1.js +0 -11
  95. package/ts_web/pages/page1.ts +0 -11
@@ -6,10 +6,14 @@ import {
6
6
  type TemplateResult,
7
7
  cssManager,
8
8
  css,
9
+ unsafeCSS,
9
10
  } from '@design.estate/dees-element';
10
11
  import * as domtools from '@design.estate/dees-domtools';
12
+ import type { IServiceStatus } from '../interfaces/index.js';
13
+ import * as sharedStyles from '../styles/shared.styles.js';
11
14
 
12
15
  import './internal/uplinternal-miniheading.js';
16
+ import { demoFunc } from './upl-statuspage-assetsselector.demo.js';
13
17
 
14
18
  declare global {
15
19
  interface HTMLElementTagNameMap {
@@ -19,9 +23,25 @@ declare global {
19
23
 
20
24
  @customElement('upl-statuspage-assetsselector')
21
25
  export class UplStatuspageAssetsselector extends DeesElement {
22
- public static demo = () => html`
23
- <upl-statuspage-assetsselector></upl-statuspage-assetsselector>
24
- `;
26
+ public static demo = demoFunc;
27
+
28
+ @property({ type: Array })
29
+ accessor services: IServiceStatus[] = [];
30
+
31
+ @property({ type: String })
32
+ accessor filterText: string = '';
33
+
34
+ @property({ type: String })
35
+ accessor filterCategory: string = 'all';
36
+
37
+ @property({ type: Boolean })
38
+ accessor showOnlySelected: boolean = false;
39
+
40
+ @property({ type: Boolean })
41
+ accessor loading: boolean = false;
42
+
43
+ @property({ type: Boolean })
44
+ accessor expanded: boolean = false;
25
45
 
26
46
  constructor() {
27
47
  super();
@@ -29,35 +49,523 @@ export class UplStatuspageAssetsselector extends DeesElement {
29
49
 
30
50
  public static styles = [
31
51
  cssManager.defaultStyles,
52
+ sharedStyles.commonStyles,
32
53
  css`
33
54
  :host {
34
- padding: 0px 0px 15px 0px;
35
55
  display: block;
36
- background: ${cssManager.bdTheme('#eeeeeb', '#222222')};
37
- font-family: Inter;
38
- color: #fff;
56
+ background: transparent;
57
+ font-family: ${unsafeCSS(sharedStyles.fonts.base)};
58
+ color: ${sharedStyles.colors.text.primary};
59
+ }
60
+
61
+ .container {
62
+ max-width: 1200px;
63
+ margin: 0 auto;
64
+ padding: 0 ${unsafeCSS(sharedStyles.spacing.lg)} ${unsafeCSS(sharedStyles.spacing.lg)} ${unsafeCSS(sharedStyles.spacing.lg)};
65
+ }
66
+
67
+ .controls {
68
+ display: flex;
69
+ gap: ${unsafeCSS(sharedStyles.spacing.sm)};
70
+ margin-bottom: ${unsafeCSS(sharedStyles.spacing.md)};
71
+ flex-wrap: wrap;
72
+ align-items: center;
73
+ }
74
+
75
+ .search-input {
76
+ flex: 1;
77
+ min-width: 200px;
78
+ padding: ${unsafeCSS(sharedStyles.spacing.xs)} ${unsafeCSS(sharedStyles.spacing.sm)};
79
+ border: 1px solid ${cssManager.bdTheme('#e5e7eb', '#27272a')};
80
+ border-radius: ${unsafeCSS(sharedStyles.borderRadius.base)};
81
+ background: ${cssManager.bdTheme('#ffffff', '#0a0a0a')};
82
+ color: ${cssManager.bdTheme('#0a0a0a', '#fafafa')};
83
+ font-size: 13px;
84
+ font-family: ${unsafeCSS(sharedStyles.fonts.base)};
85
+ transition: all 0.2s ease;
86
+ height: 32px;
87
+ }
88
+
89
+ .search-input:focus {
90
+ outline: none;
91
+ border-color: ${cssManager.bdTheme('#0a0a0a', '#fafafa')};
92
+ }
93
+
94
+ .search-input::placeholder {
95
+ color: ${cssManager.bdTheme('#9ca3af', '#71717a')};
96
+ }
97
+
98
+ .filter-button {
99
+ display: inline-flex;
100
+ align-items: center;
101
+ justify-content: center;
102
+ padding: ${unsafeCSS(sharedStyles.spacing.xs)} ${unsafeCSS(sharedStyles.spacing.sm)};
103
+ border: 1px solid ${cssManager.bdTheme('#e5e7eb', '#27272a')};
104
+ border-radius: ${unsafeCSS(sharedStyles.borderRadius.base)};
105
+ background: transparent;
106
+ color: ${cssManager.bdTheme('#6b7280', '#a1a1aa')};
107
+ cursor: pointer;
108
+ font-size: 13px;
109
+ font-weight: 400;
110
+ font-family: ${unsafeCSS(sharedStyles.fonts.base)};
111
+ transition: all 0.2s ease;
112
+ height: 32px;
113
+ }
114
+
115
+ .filter-button:hover {
116
+ border-color: ${cssManager.bdTheme('#0a0a0a', '#fafafa')};
117
+ color: ${cssManager.bdTheme('#0a0a0a', '#fafafa')};
118
+ }
119
+
120
+ .filter-button.active {
121
+ background: ${cssManager.bdTheme('#0a0a0a', '#fafafa')};
122
+ color: ${cssManager.bdTheme('#fafafa', '#0a0a0a')};
123
+ border-color: ${cssManager.bdTheme('#0a0a0a', '#fafafa')};
124
+ }
125
+
126
+ .selected-services {
127
+ display: flex;
128
+ flex-wrap: wrap;
129
+ gap: ${unsafeCSS(sharedStyles.spacing.sm)};
130
+ align-items: center;
131
+ }
132
+
133
+ .service-pill {
134
+ display: inline-flex;
135
+ align-items: center;
136
+ gap: ${unsafeCSS(sharedStyles.spacing.xs)};
137
+ padding: ${unsafeCSS(sharedStyles.spacing.xs)} ${unsafeCSS(sharedStyles.spacing.md)};
138
+ background: ${cssManager.bdTheme('#ffffff', '#0a0a0a')};
139
+ border: 1px solid ${cssManager.bdTheme('#e5e7eb', '#27272a')};
140
+ border-radius: ${unsafeCSS(sharedStyles.borderRadius.full)};
141
+ font-size: 13px;
142
+ transition: all 0.2s ease;
143
+ }
144
+
145
+ .service-pill:hover {
146
+ border-color: ${cssManager.bdTheme('#0a0a0a', '#fafafa')};
147
+ }
148
+
149
+ .service-pill .status-dot {
150
+ width: 6px;
151
+ height: 6px;
152
+ border-radius: 50%;
153
+ }
154
+
155
+ .manage-button {
156
+ display: inline-flex;
157
+ align-items: center;
158
+ gap: ${unsafeCSS(sharedStyles.spacing.xs)};
159
+ padding: ${unsafeCSS(sharedStyles.spacing.xs)} ${unsafeCSS(sharedStyles.spacing.md)};
160
+ background: transparent;
161
+ border: 1px solid ${cssManager.bdTheme('#e5e7eb', '#27272a')};
162
+ border-radius: ${unsafeCSS(sharedStyles.borderRadius.base)};
163
+ font-size: 13px;
164
+ font-weight: 500;
165
+ cursor: pointer;
166
+ transition: all 0.2s ease;
167
+ color: ${cssManager.bdTheme('#6b7280', '#a1a1aa')};
168
+ font-family: ${unsafeCSS(sharedStyles.fonts.base)};
169
+ }
170
+
171
+ .manage-button:hover {
172
+ border-color: ${cssManager.bdTheme('#0a0a0a', '#fafafa')};
173
+ color: ${cssManager.bdTheme('#0a0a0a', '#fafafa')};
174
+ }
175
+
176
+ .expandable-section {
177
+ margin-top: ${unsafeCSS(sharedStyles.spacing.lg)};
178
+ overflow: hidden;
179
+ transition: all 0.3s ease;
180
+ }
181
+
182
+ .expandable-content {
183
+ padding: ${unsafeCSS(sharedStyles.spacing.lg)};
184
+ background: ${cssManager.bdTheme('#fafafa', '#0a0a0a')};
185
+ border: 1px solid ${cssManager.bdTheme('#e5e7eb', '#27272a')};
186
+ border-radius: ${unsafeCSS(sharedStyles.borderRadius.base)};
187
+ }
188
+
189
+ .assets-grid {
190
+ display: grid;
191
+ grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));
192
+ gap: ${unsafeCSS(sharedStyles.spacing.sm)};
193
+ margin-top: ${unsafeCSS(sharedStyles.spacing.md)};
194
+ }
195
+
196
+ .asset-card {
197
+ display: flex;
198
+ align-items: center;
199
+ padding: ${unsafeCSS(sharedStyles.spacing.sm)} ${unsafeCSS(sharedStyles.spacing.md)};
200
+ background: ${cssManager.bdTheme('#ffffff', '#0a0a0a')};
201
+ border-radius: ${unsafeCSS(sharedStyles.borderRadius.base)};
202
+ cursor: pointer;
203
+ transition: all 0.2s ease;
204
+ border: 1px solid ${cssManager.bdTheme('#e5e7eb', '#27272a')};
205
+ gap: ${unsafeCSS(sharedStyles.spacing.sm)};
206
+ }
207
+
208
+ .asset-card:hover {
209
+ border-color: ${cssManager.bdTheme('#d1d5db', '#3f3f46')};
210
+ }
211
+
212
+ .asset-card.selected {
213
+ border-color: ${cssManager.bdTheme('#0a0a0a', '#fafafa')};
214
+ background: ${cssManager.bdTheme('#f9fafb', '#0f0f0f')};
215
+ }
216
+
217
+ .asset-checkbox {
218
+ width: 16px;
219
+ height: 16px;
220
+ cursor: pointer;
221
+ accent-color: ${sharedStyles.colors.text.primary};
222
+ flex-shrink: 0;
39
223
  }
40
224
 
41
- .mainbox {
42
- margin: auto;
43
- max-width: 900px;
225
+ .asset-info {
226
+ flex: 1;
227
+ min-width: 0;
228
+ }
229
+
230
+ .asset-name {
231
+ font-weight: 600;
232
+ font-size: 14px;
233
+ margin-bottom: ${unsafeCSS(sharedStyles.spacing.xs)};
234
+ overflow: hidden;
235
+ text-overflow: ellipsis;
236
+ white-space: nowrap;
237
+ }
238
+
239
+ .asset-description {
240
+ font-size: 13px;
241
+ color: ${sharedStyles.colors.text.secondary};
242
+ overflow: hidden;
243
+ text-overflow: ellipsis;
244
+ white-space: nowrap;
245
+ }
246
+
247
+ .asset-status {
248
+ display: flex;
249
+ align-items: center;
250
+ gap: ${unsafeCSS(sharedStyles.spacing.xs)};
251
+ flex-shrink: 0;
252
+ }
253
+
254
+ .status-indicator {
255
+ width: 8px;
256
+ height: 8px;
257
+ border-radius: ${unsafeCSS(sharedStyles.borderRadius.full)};
258
+ }
259
+
260
+ .status-indicator.operational, .status-dot.operational { background: #22c55e; }
261
+ .status-indicator.degraded, .status-dot.degraded { background: #fbbf24; }
262
+ .status-indicator.partial_outage, .status-dot.partial_outage { background: #f87171; }
263
+ .status-indicator.major_outage, .status-dot.major_outage { background: #ef4444; }
264
+ .status-indicator.maintenance, .status-dot.maintenance { background: #60a5fa; }
265
+
266
+ .status-text {
267
+ font-size: 12px;
268
+ text-transform: capitalize;
269
+ color: ${sharedStyles.colors.text.secondary};
270
+ }
271
+
272
+ .loading-message,
273
+ .no-results {
274
+ grid-column: 1 / -1;
275
+ text-align: center;
276
+ padding: ${unsafeCSS(sharedStyles.spacing.xl)};
277
+ color: ${cssManager.bdTheme('#9ca3af', '#71717a')};
278
+ font-size: 13px;
279
+ }
280
+
281
+ .summary {
282
+ text-align: right;
283
+ font-size: 12px;
284
+ margin-top: ${unsafeCSS(sharedStyles.spacing.md)};
285
+ color: ${cssManager.bdTheme('#9ca3af', '#71717a')};
286
+ }
287
+
288
+ .no-services {
289
+ padding: ${unsafeCSS(sharedStyles.spacing.xl)};
44
290
  text-align: center;
45
- height: 50px;
46
- border-radius: 3px;
47
- background: ${cssManager.bdTheme('#ffffff', '#333333')};;
291
+ color: ${cssManager.bdTheme('#9ca3af', '#71717a')};
292
+ font-size: 13px;
293
+ }
294
+
295
+ @media (max-width: 640px) {
296
+ .container {
297
+ padding: 0 ${unsafeCSS(sharedStyles.spacing.md)} ${unsafeCSS(sharedStyles.spacing.md)} ${unsafeCSS(sharedStyles.spacing.md)};
298
+ }
299
+
300
+ .controls {
301
+ flex-direction: column;
302
+ align-items: stretch;
303
+ }
304
+
305
+ .search-input {
306
+ width: 100%;
307
+ }
308
+
309
+ .selected-services {
310
+ flex-direction: column;
311
+ align-items: stretch;
312
+ }
313
+
314
+ .service-pill {
315
+ width: auto;
316
+ }
317
+
318
+ .expandable-content {
319
+ padding: ${unsafeCSS(sharedStyles.spacing.md)};
320
+ }
321
+
322
+ .assets-grid {
323
+ grid-template-columns: 1fr;
324
+ }
325
+
326
+ .asset-card {
327
+ padding: ${unsafeCSS(sharedStyles.spacing.sm)};
328
+ }
48
329
  }
49
330
  `,
50
331
  ]
51
332
 
52
333
  public render(): TemplateResult {
334
+ const selectedServices = this.services.filter(s => s.selected);
335
+ const selectedCount = selectedServices.length;
336
+ const categories = this.getUniqueCategories();
337
+
53
338
  return html`
54
- <style>
339
+ <div class="container">
340
+ <uplinternal-miniheading>Monitored Assets</uplinternal-miniheading>
341
+
342
+ <div class="selected-services">
343
+ ${selectedCount === 0 ? html`
344
+ <span style="color: ${cssManager.bdTheme('#9ca3af', '#71717a')}; font-size: 13px;">
345
+ No services selected
346
+ </span>
347
+ ` : selectedCount > 5 && !this.expanded ? html`
348
+ ${selectedServices.slice(0, 4).map(service => html`
349
+ <div class="service-pill">
350
+ <span class="status-dot ${service.currentStatus}"></span>
351
+ <span>${service.displayName}</span>
352
+ </div>
353
+ `)}
354
+ <span style="color: ${cssManager.bdTheme('#6b7280', '#a1a1aa')}; font-size: 12px;">
355
+ +${selectedCount - 4} more
356
+ </span>
357
+ ` : selectedServices.map(service => html`
358
+ <div class="service-pill">
359
+ <span class="status-dot ${service.currentStatus}"></span>
360
+ <span>${service.displayName}</span>
361
+ </div>
362
+ `)}
363
+
364
+ <button
365
+ class="manage-button"
366
+ @click=${() => { this.expanded = !this.expanded; }}
367
+ >
368
+ ${this.expanded ? 'Close' : 'Manage Services'}
369
+ <svg
370
+ width="10"
371
+ height="6"
372
+ viewBox="0 0 10 6"
373
+ fill="none"
374
+ xmlns="http://www.w3.org/2000/svg"
375
+ style="transform: rotate(${this.expanded ? '180deg' : '0'}); transition: transform 0.2s;"
376
+ >
377
+ <path
378
+ d="M1 1L5 5L9 1"
379
+ stroke="currentColor"
380
+ stroke-width="1.5"
381
+ stroke-linecap="round"
382
+ stroke-linejoin="round"
383
+ />
384
+ </svg>
385
+ </button>
386
+ </div>
55
387
 
56
- </style>
57
- <uplinternal-miniheading>Monitored Assets</uplinternal-miniheading>
58
- <div class="mainbox">
59
- Hello!
388
+ ${this.expanded ? html`
389
+ <div class="expandable-section">
390
+ <div class="expandable-content">
391
+ <div class="controls">
392
+ <input
393
+ type="text"
394
+ class="search-input"
395
+ placeholder="Search services..."
396
+ .value=${this.filterText}
397
+ @input=${(e: Event) => {
398
+ this.filterText = (e.target as HTMLInputElement).value;
399
+ }}
400
+ />
401
+
402
+ <button
403
+ class="filter-button ${this.filterCategory === 'all' ? 'active' : ''}"
404
+ @click=${() => { this.filterCategory = 'all'; }}
405
+ >
406
+ All
407
+ </button>
408
+
409
+ ${categories.map(category => html`
410
+ <button
411
+ class="filter-button ${this.filterCategory === category ? 'active' : ''}"
412
+ @click=${() => { this.filterCategory = category; }}
413
+ >
414
+ ${category}
415
+ </button>
416
+ `)}
417
+
418
+ <button
419
+ class="filter-button ${this.showOnlySelected ? 'active' : ''}"
420
+ @click=${() => { this.showOnlySelected = !this.showOnlySelected; }}
421
+ >
422
+ ${this.showOnlySelected ? 'Show All' : 'Selected Only'}
423
+ </button>
424
+
425
+ <button
426
+ class="filter-button"
427
+ @click=${() => this.selectAll()}
428
+ >
429
+ Select All
430
+ </button>
431
+
432
+ <button
433
+ class="filter-button"
434
+ @click=${() => this.selectNone()}
435
+ >
436
+ Select None
437
+ </button>
438
+ </div>
439
+
440
+ <div class="assets-grid">
441
+ ${this.loading ? html`
442
+ <div class="loading-message">Loading services...</div>
443
+ ` : this.renderServiceGrid()}
444
+ </div>
445
+
446
+ <div class="summary">
447
+ ${selectedCount} of ${this.services.length} services selected
448
+ </div>
449
+ </div>
450
+ </div>
451
+ ` : ''}
60
452
  </div>
61
453
  `;
62
454
  }
455
+
456
+ private getFilteredServices(): IServiceStatus[] {
457
+ return this.services.filter(service => {
458
+ // Apply text filter
459
+ if (this.filterText && !service.displayName.toLowerCase().includes(this.filterText.toLowerCase()) &&
460
+ (!service.description || !service.description.toLowerCase().includes(this.filterText.toLowerCase()))) {
461
+ return false;
462
+ }
463
+
464
+ // Apply category filter
465
+ if (this.filterCategory !== 'all' && service.category !== this.filterCategory) {
466
+ return false;
467
+ }
468
+
469
+ // Apply selected filter
470
+ if (this.showOnlySelected && !service.selected) {
471
+ return false;
472
+ }
473
+
474
+ return true;
475
+ });
476
+ }
477
+
478
+ private getUniqueCategories(): string[] {
479
+ const categories = new Set<string>();
480
+ this.services.forEach(service => {
481
+ if (service.category) {
482
+ categories.add(service.category);
483
+ }
484
+ });
485
+ return Array.from(categories).sort();
486
+ }
487
+
488
+ private toggleService(serviceId: string) {
489
+ const service = this.services.find(s => s.id === serviceId);
490
+ if (service) {
491
+ service.selected = !service.selected;
492
+ this.requestUpdate();
493
+
494
+ this.dispatchEvent(new CustomEvent('selectionChanged', {
495
+ detail: {
496
+ serviceId,
497
+ selected: service.selected,
498
+ selectedServices: this.services.filter(s => s.selected).map(s => s.id)
499
+ },
500
+ bubbles: true,
501
+ composed: true
502
+ }));
503
+ }
504
+ }
505
+
506
+ private selectAll() {
507
+ const filteredServices = this.getFilteredServices();
508
+ filteredServices.forEach(service => {
509
+ service.selected = true;
510
+ });
511
+ this.requestUpdate();
512
+ this.emitSelectionUpdate();
513
+ }
514
+
515
+ private selectNone() {
516
+ const filteredServices = this.getFilteredServices();
517
+ filteredServices.forEach(service => {
518
+ service.selected = false;
519
+ });
520
+ this.requestUpdate();
521
+ this.emitSelectionUpdate();
522
+ }
523
+
524
+ private emitSelectionUpdate() {
525
+ this.dispatchEvent(new CustomEvent('selectionChanged', {
526
+ detail: {
527
+ selectedServices: this.services.filter(s => s.selected).map(s => s.id)
528
+ },
529
+ bubbles: true,
530
+ composed: true
531
+ }));
532
+ }
533
+
534
+ private renderServiceGrid(): TemplateResult {
535
+ const filteredServices = this.getFilteredServices();
536
+
537
+ if (filteredServices.length === 0) {
538
+ return html`<div class="no-results">No services found matching your criteria</div>`;
539
+ }
540
+
541
+ return html`${filteredServices.map(service => html`
542
+ <div
543
+ class="asset-card ${service.selected ? 'selected' : ''}"
544
+ @click=${() => this.toggleService(service.id)}
545
+ >
546
+ <input
547
+ type="checkbox"
548
+ class="asset-checkbox"
549
+ .checked=${service.selected}
550
+ @click=${(e: Event) => e.stopPropagation()}
551
+ @change=${(e: Event) => {
552
+ e.stopPropagation();
553
+ this.toggleService(service.id);
554
+ }}
555
+ />
556
+
557
+ <div class="asset-info">
558
+ <div class="asset-name">${service.displayName}</div>
559
+ ${service.description ? html`
560
+ <div class="asset-description">${service.description}</div>
561
+ ` : ''}
562
+ </div>
563
+
564
+ <div class="asset-status">
565
+ <div class="status-indicator ${service.currentStatus}"></div>
566
+ <div class="status-text">${service.currentStatus.replace(/_/g, ' ')}</div>
567
+ </div>
568
+ </div>
569
+ `)}`;
570
+ }
63
571
  }