@uptime.link/statuspage 1.0.74 → 1.2.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 (96) hide show
  1. package/dist_bundle/bundle.js +5019 -519
  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 +679 -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 +846 -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 +373 -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 +937 -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 +549 -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 +408 -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 +397 -62
  40. package/dist_ts_web/elements/upl-statuspage-statusmonth.d.ts +17 -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 +662 -103
  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 +102 -0
  57. package/dist_ts_web/styles/shared.styles.js +494 -0
  58. package/dist_watch/bundle.js +52265 -32033
  59. package/dist_watch/bundle.js.map +4 -4
  60. package/dist_watch/index.html +1 -0
  61. package/npmextra.json +9 -3
  62. package/package.json +19 -19
  63. package/readme.hints.md +292 -0
  64. package/readme.md +326 -149
  65. package/readme.plan.md +261 -0
  66. package/ts_web/00_commitinfo_data.ts +1 -1
  67. package/ts_web/elements/index.ts +6 -0
  68. package/ts_web/elements/internal/uplinternal-miniheading.ts +24 -17
  69. package/ts_web/elements/upl-statuspage-assetsselector.demo.ts +607 -0
  70. package/ts_web/elements/upl-statuspage-assetsselector.ts +600 -18
  71. package/ts_web/elements/upl-statuspage-footer.demo.ts +744 -0
  72. package/ts_web/elements/upl-statuspage-footer.ts +662 -30
  73. package/ts_web/elements/upl-statuspage-header.demo.ts +241 -0
  74. package/ts_web/elements/upl-statuspage-header.ts +289 -52
  75. package/ts_web/elements/upl-statuspage-incidents.demo.ts +1216 -0
  76. package/ts_web/elements/upl-statuspage-incidents.ts +840 -26
  77. package/ts_web/elements/upl-statuspage-pagetitle.demo.ts +25 -0
  78. package/ts_web/elements/upl-statuspage-pagetitle.ts +89 -0
  79. package/ts_web/elements/upl-statuspage-statsgrid.demo.ts +315 -0
  80. package/ts_web/elements/upl-statuspage-statsgrid.ts +478 -0
  81. package/ts_web/elements/upl-statuspage-statusbar.demo.ts +393 -0
  82. package/ts_web/elements/upl-statuspage-statusbar.ts +332 -20
  83. package/ts_web/elements/upl-statuspage-statusdetails.demo.ts +754 -0
  84. package/ts_web/elements/upl-statuspage-statusdetails.ts +321 -37
  85. package/ts_web/elements/upl-statuspage-statusmonth.demo.ts +876 -0
  86. package/ts_web/elements/upl-statuspage-statusmonth.ts +584 -79
  87. package/ts_web/interfaces/index.ts +95 -0
  88. package/ts_web/pages/index.ts +4 -1
  89. package/ts_web/pages/statuspage-allgreen.ts +412 -0
  90. package/ts_web/pages/statuspage-demo.ts +653 -0
  91. package/ts_web/pages/statuspage-maintenance.ts +570 -0
  92. package/ts_web/pages/statuspage-outage.ts +568 -0
  93. package/ts_web/styles/shared.styles.ts +531 -0
  94. package/dist_ts_web/pages/page1.d.ts +0 -1
  95. package/dist_ts_web/pages/page1.js +0 -11
  96. package/ts_web/pages/page1.ts +0 -11
@@ -0,0 +1,478 @@
1
+ import {
2
+ DeesElement,
3
+ property,
4
+ html,
5
+ customElement,
6
+ type TemplateResult,
7
+ css,
8
+ cssManager,
9
+ unsafeCSS,
10
+ } from '@design.estate/dees-element';
11
+ import * as domtools from '@design.estate/dees-domtools';
12
+ import * as sharedStyles from '../styles/shared.styles.js';
13
+
14
+ import './internal/uplinternal-miniheading.js';
15
+ import { demoFunc } from './upl-statuspage-statsgrid.demo.js';
16
+
17
+ declare global {
18
+ interface HTMLElementTagNameMap {
19
+ 'upl-statuspage-statsgrid': UplStatuspageStatsgrid;
20
+ }
21
+ }
22
+
23
+ @customElement('upl-statuspage-statsgrid')
24
+ export class UplStatuspageStatsgrid extends DeesElement {
25
+ public static demo = demoFunc;
26
+
27
+ @property({ type: Number })
28
+ accessor uptime: number = 99.99;
29
+
30
+ @property({ type: Number })
31
+ accessor avgResponseTime: number = 125;
32
+
33
+ @property({ type: Number })
34
+ accessor totalIncidents: number = 0;
35
+
36
+ @property({ type: Number })
37
+ accessor affectedServices: number = 0;
38
+
39
+ @property({ type: Number })
40
+ accessor totalServices: number = 0;
41
+
42
+ @property({ type: String })
43
+ accessor currentStatus: string = 'operational';
44
+
45
+ @property({ type: Boolean })
46
+ accessor loading: boolean = false;
47
+
48
+ @property({ type: String })
49
+ accessor timePeriod: string = '90 days';
50
+
51
+ constructor() {
52
+ super();
53
+ }
54
+
55
+ public static styles = [
56
+ domtools.elementBasic.staticStyles,
57
+ sharedStyles.commonStyles,
58
+ css`
59
+ :host {
60
+ display: block;
61
+ background: transparent;
62
+ font-family: ${unsafeCSS(sharedStyles.fonts.base)};
63
+ color: ${sharedStyles.colors.text.primary};
64
+ }
65
+
66
+ .container {
67
+ max-width: 1200px;
68
+ margin: 0 auto;
69
+ padding: 0 ${unsafeCSS(sharedStyles.spacing.lg)} ${unsafeCSS(sharedStyles.spacing.lg)} ${unsafeCSS(sharedStyles.spacing.lg)};
70
+ }
71
+
72
+ .stats-grid {
73
+ display: grid;
74
+ grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
75
+ gap: ${unsafeCSS(sharedStyles.spacing.md)};
76
+ }
77
+
78
+ .stat-card {
79
+ background: ${sharedStyles.colors.background.card};
80
+ border: 1px solid ${sharedStyles.colors.border.default};
81
+ border-radius: ${unsafeCSS(sharedStyles.borderRadius.lg)};
82
+ padding: ${unsafeCSS(sharedStyles.spacing.lg)};
83
+ transition: all ${unsafeCSS(sharedStyles.durations.normal)} ${unsafeCSS(sharedStyles.easings.default)};
84
+ position: relative;
85
+ overflow: hidden;
86
+ animation: fadeInUp 0.4s ${unsafeCSS(sharedStyles.easings.default)} both;
87
+ }
88
+
89
+ .stat-card:nth-child(1) { animation-delay: 0ms; }
90
+ .stat-card:nth-child(2) { animation-delay: 50ms; }
91
+ .stat-card:nth-child(3) { animation-delay: 100ms; }
92
+ .stat-card:nth-child(4) { animation-delay: 150ms; }
93
+
94
+ @keyframes fadeInUp {
95
+ from {
96
+ opacity: 0;
97
+ transform: translateY(12px);
98
+ }
99
+ to {
100
+ opacity: 1;
101
+ transform: translateY(0);
102
+ }
103
+ }
104
+
105
+ /* Status-colored top accent */
106
+ .stat-card::before {
107
+ content: '';
108
+ position: absolute;
109
+ top: 0;
110
+ left: 0;
111
+ right: 0;
112
+ height: 3px;
113
+ background: ${sharedStyles.colors.border.muted};
114
+ transition: all ${unsafeCSS(sharedStyles.durations.fast)} ${unsafeCSS(sharedStyles.easings.default)};
115
+ }
116
+
117
+ .stat-card.status-card::before {
118
+ background: ${sharedStyles.colors.status.operational};
119
+ }
120
+
121
+ .stat-card.status-card.degraded::before {
122
+ background: ${sharedStyles.colors.status.degraded};
123
+ }
124
+
125
+ .stat-card.status-card.partial_outage::before {
126
+ background: ${sharedStyles.colors.status.partial};
127
+ }
128
+
129
+ .stat-card.status-card.major_outage::before {
130
+ background: ${sharedStyles.colors.status.major};
131
+ }
132
+
133
+ .stat-card.status-card.maintenance::before {
134
+ background: ${sharedStyles.colors.status.maintenance};
135
+ }
136
+
137
+ .stat-card.uptime-card::before {
138
+ background: ${sharedStyles.colors.status.operational};
139
+ }
140
+
141
+ .stat-card.response-card::before {
142
+ background: ${sharedStyles.colors.status.maintenance};
143
+ }
144
+
145
+ .stat-card.incident-card::before {
146
+ background: ${sharedStyles.colors.status.partial};
147
+ }
148
+
149
+ .stat-card:hover {
150
+ border-color: ${sharedStyles.colors.border.muted};
151
+ box-shadow: ${unsafeCSS(sharedStyles.shadows.md)};
152
+ transform: translateY(-3px);
153
+ }
154
+
155
+ .stat-card:hover::before {
156
+ height: 4px;
157
+ }
158
+
159
+ .stat-label {
160
+ font-size: 11px;
161
+ color: ${sharedStyles.colors.text.muted};
162
+ text-transform: uppercase;
163
+ letter-spacing: 0.06em;
164
+ font-weight: 600;
165
+ margin-bottom: ${unsafeCSS(sharedStyles.spacing.sm)};
166
+ display: flex;
167
+ align-items: center;
168
+ gap: 6px;
169
+ }
170
+
171
+ .stat-label svg {
172
+ width: 14px;
173
+ height: 14px;
174
+ opacity: 0.7;
175
+ }
176
+
177
+ .stat-value {
178
+ font-size: 28px;
179
+ font-weight: 700;
180
+ color: ${sharedStyles.colors.text.primary};
181
+ font-variant-numeric: tabular-nums;
182
+ line-height: 1.2;
183
+ letter-spacing: -0.02em;
184
+ transition: transform ${unsafeCSS(sharedStyles.durations.fast)} ${unsafeCSS(sharedStyles.easings.default)};
185
+ }
186
+
187
+ .stat-card:hover .stat-value {
188
+ transform: scale(1.02);
189
+ }
190
+
191
+ .stat-unit {
192
+ font-size: 16px;
193
+ font-weight: 500;
194
+ color: ${sharedStyles.colors.text.secondary};
195
+ margin-left: 2px;
196
+ }
197
+
198
+ .stat-change {
199
+ font-size: 12px;
200
+ margin-top: ${unsafeCSS(sharedStyles.spacing.sm)};
201
+ display: flex;
202
+ align-items: center;
203
+ gap: 4px;
204
+ padding: 4px 8px;
205
+ border-radius: ${unsafeCSS(sharedStyles.borderRadius.sm)};
206
+ width: fit-content;
207
+ }
208
+
209
+ .stat-change.positive {
210
+ color: ${sharedStyles.colors.status.operational};
211
+ background: ${cssManager.bdTheme('rgba(22, 163, 74, 0.08)', 'rgba(34, 197, 94, 0.12)')};
212
+ }
213
+
214
+ .stat-change.negative {
215
+ color: ${sharedStyles.colors.status.partial};
216
+ background: ${cssManager.bdTheme('rgba(239, 68, 68, 0.08)', 'rgba(248, 113, 113, 0.12)')};
217
+ }
218
+
219
+ .stat-change.neutral {
220
+ color: ${sharedStyles.colors.text.muted};
221
+ background: ${sharedStyles.colors.background.muted};
222
+ }
223
+
224
+ /* Status text color variations */
225
+ .stat-value.operational {
226
+ color: ${sharedStyles.colors.status.operational};
227
+ }
228
+
229
+ .stat-value.degraded {
230
+ color: ${sharedStyles.colors.status.degraded};
231
+ }
232
+
233
+ .stat-value.partial_outage {
234
+ color: ${sharedStyles.colors.status.partial};
235
+ }
236
+
237
+ .stat-value.major_outage {
238
+ color: ${sharedStyles.colors.status.major};
239
+ }
240
+
241
+ .stat-value.maintenance {
242
+ color: ${sharedStyles.colors.status.maintenance};
243
+ }
244
+
245
+ .loading-skeleton {
246
+ display: grid;
247
+ grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
248
+ gap: ${unsafeCSS(sharedStyles.spacing.md)};
249
+ }
250
+
251
+ .skeleton-card {
252
+ background: ${sharedStyles.colors.background.card};
253
+ border: 1px solid ${sharedStyles.colors.border.default};
254
+ border-radius: ${unsafeCSS(sharedStyles.borderRadius.lg)};
255
+ padding: ${unsafeCSS(sharedStyles.spacing.lg)};
256
+ height: 110px;
257
+ position: relative;
258
+ overflow: hidden;
259
+ }
260
+
261
+ .skeleton-card::before {
262
+ content: '';
263
+ position: absolute;
264
+ top: 0;
265
+ left: 0;
266
+ right: 0;
267
+ height: 3px;
268
+ background: ${sharedStyles.colors.background.muted};
269
+ }
270
+
271
+ .skeleton-label {
272
+ height: 12px;
273
+ width: 80px;
274
+ background: ${sharedStyles.colors.background.muted};
275
+ border-radius: 4px;
276
+ margin-bottom: ${unsafeCSS(sharedStyles.spacing.sm)};
277
+ animation: shimmer 1.5s infinite;
278
+ }
279
+
280
+ .skeleton-value {
281
+ height: 32px;
282
+ width: 100px;
283
+ background: ${sharedStyles.colors.background.muted};
284
+ border-radius: 6px;
285
+ animation: shimmer 1.5s infinite;
286
+ animation-delay: 0.1s;
287
+ }
288
+
289
+ .skeleton-change {
290
+ height: 20px;
291
+ width: 70px;
292
+ background: ${sharedStyles.colors.background.muted};
293
+ border-radius: 4px;
294
+ margin-top: ${unsafeCSS(sharedStyles.spacing.sm)};
295
+ animation: shimmer 1.5s infinite;
296
+ animation-delay: 0.2s;
297
+ }
298
+
299
+ @keyframes shimmer {
300
+ 0% { opacity: 0.5; }
301
+ 50% { opacity: 1; }
302
+ 100% { opacity: 0.5; }
303
+ }
304
+
305
+ .status-indicator {
306
+ display: inline-block;
307
+ width: 8px;
308
+ height: 8px;
309
+ border-radius: 50%;
310
+ flex-shrink: 0;
311
+ }
312
+
313
+ .status-indicator.operational {
314
+ background: ${sharedStyles.colors.status.operational};
315
+ box-shadow: 0 0 0 2px ${cssManager.bdTheme('rgba(22, 163, 74, 0.2)', 'rgba(34, 197, 94, 0.25)')};
316
+ animation: statusPulse 2s ease-in-out infinite;
317
+ }
318
+
319
+ @keyframes statusPulse {
320
+ 0%, 100% { box-shadow: 0 0 0 2px ${cssManager.bdTheme('rgba(22, 163, 74, 0.2)', 'rgba(34, 197, 94, 0.25)')}; }
321
+ 50% { box-shadow: 0 0 0 4px ${cssManager.bdTheme('rgba(22, 163, 74, 0.1)', 'rgba(34, 197, 94, 0.15)')}; }
322
+ }
323
+
324
+ .status-indicator.degraded {
325
+ background: ${sharedStyles.colors.status.degraded};
326
+ box-shadow: 0 0 0 2px ${cssManager.bdTheme('rgba(217, 119, 6, 0.2)', 'rgba(251, 191, 36, 0.25)')};
327
+ }
328
+
329
+ .status-indicator.partial_outage {
330
+ background: ${sharedStyles.colors.status.partial};
331
+ box-shadow: 0 0 0 2px ${cssManager.bdTheme('rgba(239, 68, 68, 0.2)', 'rgba(248, 113, 113, 0.25)')};
332
+ }
333
+
334
+ .status-indicator.major_outage {
335
+ background: ${sharedStyles.colors.status.major};
336
+ box-shadow: 0 0 0 2px ${cssManager.bdTheme('rgba(185, 28, 28, 0.2)', 'rgba(239, 68, 68, 0.25)')};
337
+ animation: majorPulse 1s ease-in-out infinite;
338
+ }
339
+
340
+ @keyframes majorPulse {
341
+ 0%, 100% { opacity: 1; }
342
+ 50% { opacity: 0.6; }
343
+ }
344
+
345
+ .status-indicator.maintenance {
346
+ background: ${sharedStyles.colors.status.maintenance};
347
+ box-shadow: 0 0 0 2px ${cssManager.bdTheme('rgba(37, 99, 235, 0.2)', 'rgba(96, 165, 250, 0.25)')};
348
+ }
349
+
350
+ @media (max-width: 640px) {
351
+ .container {
352
+ padding: 0 ${unsafeCSS(sharedStyles.spacing.md)} ${unsafeCSS(sharedStyles.spacing.md)} ${unsafeCSS(sharedStyles.spacing.md)};
353
+ }
354
+
355
+ .stats-grid {
356
+ grid-template-columns: repeat(2, 1fr);
357
+ gap: ${unsafeCSS(sharedStyles.spacing.sm)};
358
+ }
359
+
360
+ .stat-card {
361
+ padding: ${unsafeCSS(sharedStyles.spacing.md)};
362
+ }
363
+
364
+ .stat-value {
365
+ font-size: 22px;
366
+ }
367
+
368
+ .stat-label {
369
+ font-size: 10px;
370
+ }
371
+
372
+ .stat-label svg {
373
+ width: 12px;
374
+ height: 12px;
375
+ }
376
+ }
377
+ `,
378
+ ];
379
+
380
+ public render(): TemplateResult {
381
+ return html`
382
+ <div class="container">
383
+ ${this.loading ? html`
384
+ <div class="loading-skeleton">
385
+ ${Array(4).fill(0).map(() => html`
386
+ <div class="skeleton-card">
387
+ <div class="skeleton-label"></div>
388
+ <div class="skeleton-value"></div>
389
+ </div>
390
+ `)}
391
+ </div>
392
+ ` : html`
393
+ <div class="stats-grid">
394
+ <div class="stat-card status-card ${this.currentStatus}">
395
+ <div class="stat-label">
396
+ <span class="status-indicator ${this.currentStatus}"></span>
397
+ Current Status
398
+ </div>
399
+ <div class="stat-value ${this.currentStatus}">
400
+ ${this.formatStatus(this.currentStatus)}
401
+ </div>
402
+ </div>
403
+
404
+ <div class="stat-card uptime-card">
405
+ <div class="stat-label">
406
+ <svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
407
+ <polyline points="22 12 18 12 15 21 9 3 6 12 2 12"></polyline>
408
+ </svg>
409
+ Uptime
410
+ </div>
411
+ <div class="stat-value">
412
+ ${this.uptime.toFixed(2)}<span class="stat-unit">%</span>
413
+ </div>
414
+ <div class="stat-change neutral">
415
+ Last ${this.timePeriod}
416
+ </div>
417
+ </div>
418
+
419
+ <div class="stat-card response-card">
420
+ <div class="stat-label">
421
+ <svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
422
+ <circle cx="12" cy="12" r="10"></circle>
423
+ <polyline points="12 6 12 12 16 14"></polyline>
424
+ </svg>
425
+ Avg Response
426
+ </div>
427
+ <div class="stat-value">
428
+ ${this.avgResponseTime}<span class="stat-unit">ms</span>
429
+ </div>
430
+ ${this.renderResponseChange()}
431
+ </div>
432
+
433
+ <div class="stat-card incident-card">
434
+ <div class="stat-label">
435
+ <svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
436
+ <path d="M10.29 3.86L1.82 18a2 2 0 0 0 1.71 3h16.94a2 2 0 0 0 1.71-3L13.71 3.86a2 2 0 0 0-3.42 0z"></path>
437
+ <line x1="12" y1="9" x2="12" y2="13"></line>
438
+ <line x1="12" y1="17" x2="12.01" y2="17"></line>
439
+ </svg>
440
+ Incidents
441
+ </div>
442
+ <div class="stat-value">
443
+ ${this.totalIncidents}
444
+ </div>
445
+ <div class="stat-change neutral">
446
+ ${this.affectedServices} of ${this.totalServices} services
447
+ </div>
448
+ </div>
449
+ </div>
450
+ `}
451
+ </div>
452
+ `;
453
+ }
454
+
455
+ private formatStatus(status: string): string {
456
+ const statusMap: Record<string, string> = {
457
+ operational: 'Operational',
458
+ degraded: 'Degraded',
459
+ partial_outage: 'Partial Outage',
460
+ major_outage: 'Major Outage',
461
+ maintenance: 'Maintenance',
462
+ };
463
+ return statusMap[status] || 'Unknown';
464
+ }
465
+
466
+ private renderResponseChange(): TemplateResult {
467
+ // This could be enhanced with actual trend data
468
+ const trend = this.avgResponseTime < 200 ? 'positive' : this.avgResponseTime > 500 ? 'negative' : 'neutral';
469
+ const icon = trend === 'positive' ? '↓' : trend === 'negative' ? '↑' : '→';
470
+
471
+ return html`
472
+ <div class="stat-change ${trend}">
473
+ <span>${icon}</span>
474
+ <span>${trend === 'positive' ? 'Fast' : trend === 'negative' ? 'Slow' : 'Normal'}</span>
475
+ </div>
476
+ `;
477
+ }
478
+ }