@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
@@ -7,7 +7,12 @@ import {
7
7
  type TemplateResult,
8
8
  css,
9
9
  cssManager,
10
+ unsafeCSS,
10
11
  } from '@design.estate/dees-element';
12
+ import type { IIncidentDetails } from '../interfaces/index.js';
13
+ import * as sharedStyles from '../styles/shared.styles.js';
14
+ import './internal/uplinternal-miniheading.js';
15
+ import { demoFunc } from './upl-statuspage-incidents.demo.js';
11
16
 
12
17
  declare global {
13
18
  interface HTMLElementTagNameMap {
@@ -18,73 +23,882 @@ declare global {
18
23
  @customElement('upl-statuspage-incidents')
19
24
  export class UplStatuspageIncidents extends DeesElement {
20
25
  // STATIC
21
- public static demo = () => html` <upl-statuspage-incidents></upl-statuspage-incidents> `;
26
+ public static demo = demoFunc;
22
27
 
23
28
  // INSTANCE
24
29
  @property({
25
30
  type: Array,
26
31
  })
27
- public currentIncidences: plugins.uplInterfaces.data.IIncident[] = [];
32
+ accessor currentIncidents: IIncidentDetails[] = [];
28
33
 
29
34
  @property({
30
35
  type: Array,
31
36
  })
32
- public pastIncidences: plugins.uplInterfaces.data.IIncident[] = [];
37
+ accessor pastIncidents: IIncidentDetails[] = [];
33
38
 
34
39
  @property({
35
40
  type: Boolean,
36
41
  })
37
- public whitelabel = false;
42
+ accessor whitelabel = false;
43
+
44
+ @property({
45
+ type: Boolean,
46
+ })
47
+ accessor loading = false;
48
+
49
+ @property({
50
+ type: Number,
51
+ })
52
+ accessor daysToShow = 90;
53
+
54
+ @property({
55
+ type: Array,
56
+ })
57
+ accessor subscribedIncidentIds: string[] = [];
58
+
59
+ @property({
60
+ type: Object,
61
+ state: true,
62
+ })
63
+ private accessor expandedIncidents: Set<string> = new Set();
64
+
65
+ @property({
66
+ type: Object,
67
+ state: true,
68
+ })
69
+ private accessor subscribedIncidents: Set<string> = new Set();
38
70
 
39
71
  constructor() {
40
72
  super();
41
73
  }
42
74
 
75
+ async connectedCallback() {
76
+ await super.connectedCallback();
77
+ // Initialize subscribed incidents from the property
78
+ if (this.subscribedIncidentIds.length > 0) {
79
+ this.subscribedIncidents = new Set(this.subscribedIncidentIds);
80
+ }
81
+ }
82
+
83
+ updated(changedProperties: Map<string, any>) {
84
+ super.updated(changedProperties);
85
+ if (changedProperties.has('subscribedIncidentIds')) {
86
+ this.subscribedIncidents = new Set(this.subscribedIncidentIds);
87
+ }
88
+ }
89
+
43
90
  public static styles = [
44
91
  plugins.domtools.elementBasic.staticStyles,
92
+ sharedStyles.commonStyles,
45
93
  css`
46
94
  :host {
47
95
  display: block;
48
- background: ${cssManager.bdTheme('#eeeeeb', '#222222')};
49
- font-family: Inter;
50
- color: ${cssManager.bdTheme('#333333', '#ffffff')};
96
+ background: transparent;
97
+ font-family: ${unsafeCSS(sharedStyles.fonts.base)};
98
+ color: ${sharedStyles.colors.text.primary};
51
99
  }
52
100
 
53
- .mainbox {
54
- max-width: 900px;
55
- margin: auto;
101
+ .container {
102
+ max-width: 1200px;
103
+ margin: 0 auto;
104
+ padding: 0 ${unsafeCSS(sharedStyles.spacing.lg)} ${unsafeCSS(sharedStyles.spacing.lg)} ${unsafeCSS(sharedStyles.spacing.lg)};
56
105
  }
57
106
 
58
107
  .noIncidentBox {
59
- background: ${cssManager.bdTheme('#ffffff', '#333333')};;
60
- padding: 10px;
61
- margin-bottom: 15px;
62
- border-radius: 3px;
108
+ background: ${sharedStyles.colors.background.card};
109
+ padding: ${unsafeCSS(sharedStyles.spacing.xl)};
110
+ margin-bottom: ${unsafeCSS(sharedStyles.spacing.lg)};
111
+ border-radius: ${unsafeCSS(sharedStyles.borderRadius.lg)};
112
+ border: 1px solid ${sharedStyles.colors.border.default};
113
+ text-align: center;
114
+ color: ${sharedStyles.colors.text.secondary};
115
+ box-shadow: ${unsafeCSS(sharedStyles.shadows.sm)};
116
+ animation: fadeInUp 0.4s ${unsafeCSS(sharedStyles.easings.default)} both;
117
+ }
118
+
119
+ /* Staggered entrance animations */
120
+ .incident-card {
121
+ background: ${sharedStyles.colors.background.card};
122
+ border-radius: ${unsafeCSS(sharedStyles.borderRadius.lg)};
123
+ margin-bottom: ${unsafeCSS(sharedStyles.spacing.lg)};
124
+ overflow: hidden;
125
+ box-shadow: ${unsafeCSS(sharedStyles.shadows.sm)};
126
+ border: 1px solid ${sharedStyles.colors.border.default};
127
+ transition: all ${unsafeCSS(sharedStyles.durations.normal)} ${unsafeCSS(sharedStyles.easings.default)};
128
+ animation: fadeInUp 0.4s ${unsafeCSS(sharedStyles.easings.default)} both;
129
+ }
130
+
131
+ .incident-card:nth-child(1) { animation-delay: 0ms; }
132
+ .incident-card:nth-child(2) { animation-delay: 50ms; }
133
+ .incident-card:nth-child(3) { animation-delay: 100ms; }
134
+ .incident-card:nth-child(4) { animation-delay: 150ms; }
135
+ .incident-card:nth-child(5) { animation-delay: 200ms; }
136
+
137
+ .incident-card:hover {
138
+ transform: translateY(-2px);
139
+ box-shadow: ${unsafeCSS(sharedStyles.shadows.md)};
140
+ border-color: ${sharedStyles.colors.border.muted};
141
+ }
142
+
143
+ .incident-card.expanded {
144
+ box-shadow: ${unsafeCSS(sharedStyles.shadows.lg)};
145
+ }
146
+
147
+ /* Active incident pulse effect */
148
+ .incident-card.active-incident {
149
+ animation: fadeInUp 0.4s ${unsafeCSS(sharedStyles.easings.default)} both,
150
+ incident-pulse 3s ease-in-out infinite;
151
+ }
152
+
153
+ @keyframes fadeInUp {
154
+ from {
155
+ opacity: 0;
156
+ transform: translateY(16px);
157
+ }
158
+ to {
159
+ opacity: 1;
160
+ transform: translateY(0);
161
+ }
162
+ }
163
+
164
+ @keyframes incident-pulse {
165
+ 0%, 100% {
166
+ box-shadow: ${unsafeCSS(sharedStyles.shadows.sm)};
167
+ }
168
+ 50% {
169
+ box-shadow: ${unsafeCSS(sharedStyles.shadows.md)},
170
+ 0 0 0 2px ${cssManager.bdTheme('rgba(239, 68, 68, 0.15)', 'rgba(248, 113, 113, 0.2)')};
171
+ }
172
+ }
173
+
174
+ .incident-header {
175
+ padding: ${unsafeCSS(sharedStyles.spacing.lg)} ${unsafeCSS(sharedStyles.spacing.xl)};
176
+ border-left: 4px solid;
177
+ display: flex;
178
+ align-items: start;
179
+ justify-content: space-between;
180
+ gap: ${unsafeCSS(sharedStyles.spacing.md)};
181
+ cursor: pointer;
182
+ transition: background-color ${unsafeCSS(sharedStyles.durations.fast)} ${unsafeCSS(sharedStyles.easings.default)};
183
+ position: relative;
184
+ }
185
+
186
+ .incident-header:hover {
187
+ background: ${cssManager.bdTheme('rgba(0, 0, 0, 0.02)', 'rgba(255, 255, 255, 0.02)')};
188
+ }
189
+
190
+ .incident-header.critical {
191
+ border-left-color: ${sharedStyles.colors.status.major};
192
+ }
193
+
194
+ .incident-header.major {
195
+ border-left-color: ${sharedStyles.colors.status.partial};
196
+ }
197
+
198
+ .incident-header.minor {
199
+ border-left-color: ${sharedStyles.colors.status.degraded};
200
+ }
201
+
202
+ .incident-header.maintenance {
203
+ border-left-color: ${sharedStyles.colors.status.maintenance};
204
+ }
205
+
206
+ .incident-title {
207
+ font-size: 17px;
208
+ font-weight: 600;
209
+ margin: 0;
210
+ line-height: 1.4;
211
+ letter-spacing: -0.01em;
212
+ }
213
+
214
+ .incident-meta {
215
+ display: flex;
216
+ gap: ${unsafeCSS(sharedStyles.spacing.lg)};
217
+ margin-top: ${unsafeCSS(sharedStyles.spacing.sm)};
218
+ font-size: 13px;
219
+ color: ${sharedStyles.colors.text.secondary};
220
+ flex-wrap: wrap;
221
+ }
222
+
223
+ .incident-meta span {
224
+ display: flex;
225
+ align-items: center;
226
+ gap: 4px;
227
+ }
228
+
229
+ .incident-status {
230
+ display: inline-flex;
231
+ align-items: center;
232
+ gap: 6px;
233
+ padding: 6px 12px;
234
+ border-radius: ${unsafeCSS(sharedStyles.borderRadius.full)};
235
+ font-size: 11px;
236
+ font-weight: 600;
237
+ text-transform: uppercase;
238
+ letter-spacing: 0.04em;
239
+ flex-shrink: 0;
240
+ transition: all ${unsafeCSS(sharedStyles.durations.fast)} ${unsafeCSS(sharedStyles.easings.default)};
241
+ }
242
+
243
+ .incident-status.investigating {
244
+ background: ${cssManager.bdTheme('#fef3c7', '#78350f')};
245
+ color: ${cssManager.bdTheme('#92400e', '#fbbf24')};
246
+ }
247
+
248
+ .incident-status.identified {
249
+ background: ${cssManager.bdTheme('#e9d5ff', '#581c87')};
250
+ color: ${cssManager.bdTheme('#6b21a8', '#d8b4fe')};
251
+ }
252
+
253
+ .incident-status.monitoring {
254
+ background: ${cssManager.bdTheme('#dbeafe', '#1e3a8a')};
255
+ color: ${cssManager.bdTheme('#1e40af', '#93c5fd')};
256
+ }
257
+
258
+ .incident-status.resolved {
259
+ background: ${cssManager.bdTheme('#d1fae5', '#064e3b')};
260
+ color: ${cssManager.bdTheme('#047857', '#6ee7b7')};
261
+ }
262
+
263
+ .incident-status.postmortem {
264
+ background: ${cssManager.bdTheme('#e5e7eb', '#374151')};
265
+ color: ${cssManager.bdTheme('#4b5563', '#d1d5db')};
266
+ }
267
+
268
+ /* Pulse for investigating status */
269
+ .incident-status.investigating .status-dot {
270
+ animation: status-pulse 1.5s ease-in-out infinite;
271
+ }
272
+
273
+ @keyframes status-pulse {
274
+ 0%, 100% { opacity: 1; transform: scale(1); }
275
+ 50% { opacity: 0.6; transform: scale(1.2); }
276
+ }
277
+
278
+ .incident-body {
279
+ padding: 0 ${unsafeCSS(sharedStyles.spacing.xl)} ${unsafeCSS(sharedStyles.spacing.xl)} ${unsafeCSS(sharedStyles.spacing.xl)};
280
+ animation: slideDown 0.3s ${unsafeCSS(sharedStyles.easings.default)};
281
+ }
282
+
283
+ @keyframes slideDown {
284
+ from {
285
+ opacity: 0;
286
+ transform: translateY(-8px);
287
+ }
288
+ to {
289
+ opacity: 1;
290
+ transform: translateY(0);
291
+ }
292
+ }
293
+
294
+ .incident-impact {
295
+ margin: ${unsafeCSS(sharedStyles.spacing.md)} 0;
296
+ padding: ${unsafeCSS(sharedStyles.spacing.md)} ${unsafeCSS(sharedStyles.spacing.lg)};
297
+ background: ${sharedStyles.colors.background.secondary};
298
+ border-radius: ${unsafeCSS(sharedStyles.borderRadius.base)};
299
+ font-size: 14px;
300
+ line-height: 1.6;
301
+ border-left: 3px solid ${sharedStyles.colors.border.muted};
302
+ }
303
+
304
+ .affected-services {
305
+ margin-top: ${unsafeCSS(sharedStyles.spacing.lg)};
306
+ }
307
+
308
+ .affected-services-title {
309
+ font-size: 12px;
310
+ font-weight: 600;
311
+ margin-bottom: ${unsafeCSS(sharedStyles.spacing.sm)};
312
+ color: ${sharedStyles.colors.text.secondary};
313
+ text-transform: uppercase;
314
+ letter-spacing: 0.04em;
315
+ }
316
+
317
+ .service-tag {
318
+ display: inline-block;
319
+ padding: 4px 10px;
320
+ margin: 2px;
321
+ background: ${sharedStyles.colors.background.muted};
322
+ border-radius: ${unsafeCSS(sharedStyles.borderRadius.sm)};
323
+ font-size: 12px;
324
+ color: ${sharedStyles.colors.text.secondary};
325
+ transition: all ${unsafeCSS(sharedStyles.durations.fast)} ${unsafeCSS(sharedStyles.easings.default)};
326
+ }
327
+
328
+ .service-tag:hover {
329
+ background: ${sharedStyles.colors.background.secondary};
330
+ color: ${sharedStyles.colors.text.primary};
331
+ }
332
+
333
+ /* Timeline visualization for updates */
334
+ .incident-updates {
335
+ margin-top: ${unsafeCSS(sharedStyles.spacing.xl)};
336
+ border-top: 1px solid ${sharedStyles.colors.border.default};
337
+ padding-top: ${unsafeCSS(sharedStyles.spacing.lg)};
338
+ }
339
+
340
+ .updates-title {
341
+ font-size: 12px;
342
+ font-weight: 600;
343
+ text-transform: uppercase;
344
+ letter-spacing: 0.04em;
345
+ margin: 0 0 ${unsafeCSS(sharedStyles.spacing.lg)} 0;
346
+ color: ${sharedStyles.colors.text.secondary};
347
+ }
348
+
349
+ .timeline {
350
+ position: relative;
351
+ padding-left: 24px;
352
+ }
353
+
354
+ /* Vertical connector line */
355
+ .timeline::before {
356
+ content: '';
357
+ position: absolute;
358
+ left: 5px;
359
+ top: 8px;
360
+ bottom: 8px;
361
+ width: 2px;
362
+ background: ${cssManager.bdTheme(
363
+ 'linear-gradient(to bottom, #e5e7eb 0%, #d1d5db 50%, #e5e7eb 100%)',
364
+ 'linear-gradient(to bottom, #27272a 0%, #3f3f46 50%, #27272a 100%)'
365
+ )};
366
+ border-radius: 1px;
367
+ }
368
+
369
+ .update-item {
370
+ position: relative;
371
+ padding-left: ${unsafeCSS(sharedStyles.spacing.lg)};
372
+ padding-bottom: ${unsafeCSS(sharedStyles.spacing.lg)};
373
+ animation: fadeInUp 0.3s ${unsafeCSS(sharedStyles.easings.default)} both;
374
+ }
375
+
376
+ .update-item:last-child {
377
+ padding-bottom: 0;
378
+ }
379
+
380
+ /* Timeline dot */
381
+ .update-item::before {
382
+ content: '';
383
+ position: absolute;
384
+ left: -22px;
385
+ top: 4px;
386
+ width: 12px;
387
+ height: 12px;
388
+ border-radius: 50%;
389
+ background: ${sharedStyles.colors.background.card};
390
+ border: 2px solid ${sharedStyles.colors.border.muted};
391
+ z-index: 1;
392
+ transition: all ${unsafeCSS(sharedStyles.durations.fast)} ${unsafeCSS(sharedStyles.easings.default)};
393
+ }
394
+
395
+ .update-item:first-child::before {
396
+ border-color: ${sharedStyles.colors.status.operational};
397
+ background: ${sharedStyles.colors.status.operational};
398
+ box-shadow: 0 0 0 3px ${cssManager.bdTheme('rgba(22, 163, 74, 0.15)', 'rgba(34, 197, 94, 0.2)')};
399
+ }
400
+
401
+ .update-item:hover::before {
402
+ transform: scale(1.2);
403
+ border-color: ${sharedStyles.colors.text.secondary};
404
+ }
405
+
406
+ .update-time {
407
+ font-size: 11px;
408
+ color: ${sharedStyles.colors.text.muted};
409
+ margin-bottom: 4px;
410
+ font-family: ${unsafeCSS(sharedStyles.fonts.mono)};
411
+ display: flex;
412
+ align-items: center;
413
+ gap: 8px;
414
+ }
415
+
416
+ .update-status-badge {
417
+ display: inline-flex;
418
+ padding: 2px 6px;
419
+ border-radius: 4px;
420
+ font-size: 10px;
421
+ font-weight: 600;
422
+ text-transform: uppercase;
423
+ letter-spacing: 0.02em;
424
+ background: ${sharedStyles.colors.background.muted};
425
+ color: ${sharedStyles.colors.text.secondary};
426
+ }
427
+
428
+ .update-message {
429
+ font-size: 14px;
430
+ line-height: 1.6;
431
+ color: ${sharedStyles.colors.text.primary};
432
+ }
433
+
434
+ .update-author {
435
+ font-size: 12px;
436
+ color: ${sharedStyles.colors.text.muted};
437
+ margin-top: 4px;
438
+ display: flex;
439
+ align-items: center;
440
+ gap: 4px;
441
+ }
442
+
443
+ .loading-skeleton {
444
+ height: 140px;
445
+ background: ${cssManager.bdTheme(
446
+ 'linear-gradient(90deg, #f3f4f6 25%, #e5e7eb 50%, #f3f4f6 75%)',
447
+ 'linear-gradient(90deg, #1f1f1f 25%, #262626 50%, #1f1f1f 75%)'
448
+ )};
449
+ background-size: 200% 100%;
450
+ animation: shimmer 1.5s infinite;
451
+ border-radius: ${unsafeCSS(sharedStyles.borderRadius.lg)};
452
+ margin-bottom: ${unsafeCSS(sharedStyles.spacing.lg)};
453
+ }
454
+
455
+ @keyframes shimmer {
456
+ 0% { background-position: 200% 0; }
457
+ 100% { background-position: -200% 0; }
458
+ }
459
+
460
+ .show-more {
461
+ text-align: center;
462
+ margin-top: ${unsafeCSS(sharedStyles.spacing.xl)};
463
+ }
464
+
465
+ .show-more-button {
466
+ display: inline-flex;
467
+ align-items: center;
468
+ justify-content: center;
469
+ gap: 8px;
470
+ padding: 10px 20px;
471
+ background: transparent;
472
+ border: 1px solid ${sharedStyles.colors.border.default};
473
+ border-radius: ${unsafeCSS(sharedStyles.borderRadius.base)};
474
+ cursor: pointer;
475
+ font-size: 14px;
476
+ font-weight: 500;
477
+ transition: all ${unsafeCSS(sharedStyles.durations.fast)} ${unsafeCSS(sharedStyles.easings.default)};
478
+ color: ${sharedStyles.colors.text.primary};
479
+ font-family: ${unsafeCSS(sharedStyles.fonts.base)};
480
+ }
481
+
482
+ .show-more-button:hover {
483
+ background: ${sharedStyles.colors.background.secondary};
484
+ border-color: ${sharedStyles.colors.border.muted};
485
+ transform: translateY(-2px);
486
+ box-shadow: ${unsafeCSS(sharedStyles.shadows.sm)};
487
+ }
488
+
489
+ .show-more-button:active {
490
+ transform: translateY(0);
491
+ }
492
+
493
+ .incident-actions {
494
+ display: flex;
495
+ gap: ${unsafeCSS(sharedStyles.spacing.md)};
496
+ align-items: center;
497
+ margin-top: ${unsafeCSS(sharedStyles.spacing.lg)};
498
+ padding-top: ${unsafeCSS(sharedStyles.spacing.lg)};
499
+ border-top: 1px solid ${sharedStyles.colors.border.default};
500
+ }
501
+
502
+ .subscribe-button {
503
+ display: inline-flex;
504
+ align-items: center;
505
+ gap: 6px;
506
+ padding: 8px 14px;
507
+ background: transparent;
508
+ border: 1px solid ${sharedStyles.colors.border.default};
509
+ border-radius: ${unsafeCSS(sharedStyles.borderRadius.base)};
510
+ cursor: pointer;
511
+ font-size: 13px;
512
+ font-weight: 500;
513
+ transition: all ${unsafeCSS(sharedStyles.durations.fast)} ${unsafeCSS(sharedStyles.easings.default)};
514
+ color: ${sharedStyles.colors.text.primary};
515
+ font-family: ${unsafeCSS(sharedStyles.fonts.base)};
516
+ }
517
+
518
+ .subscribe-button:hover {
519
+ background: ${sharedStyles.colors.background.secondary};
520
+ border-color: ${sharedStyles.colors.border.muted};
521
+ transform: translateY(-1px);
522
+ }
523
+
524
+ .subscribe-button.subscribed {
525
+ background: ${cssManager.bdTheme('#f0fdf4', '#064e3b')};
526
+ border-color: ${cssManager.bdTheme('#86efac', '#047857')};
527
+ color: ${cssManager.bdTheme('#047857', '#86efac')};
528
+ }
529
+
530
+ .subscribe-button.subscribed:hover {
531
+ background: ${cssManager.bdTheme('#dcfce7', '#065f46')};
532
+ }
533
+
534
+ .collapsed-hint {
535
+ font-size: 12px;
536
+ color: ${sharedStyles.colors.text.secondary};
537
+ text-align: center;
538
+ margin-top: ${unsafeCSS(sharedStyles.spacing.md)};
539
+ opacity: 0.8;
540
+ }
541
+
542
+ /* Expand icon animation */
543
+ .expand-icon {
544
+ transition: transform ${unsafeCSS(sharedStyles.durations.normal)} ${unsafeCSS(sharedStyles.easings.default)};
545
+ }
546
+
547
+ .expand-icon.rotated {
548
+ transform: rotate(180deg);
549
+ }
550
+
551
+ @media (max-width: 640px) {
552
+ .container {
553
+ padding: 0 ${unsafeCSS(sharedStyles.spacing.md)} ${unsafeCSS(sharedStyles.spacing.md)} ${unsafeCSS(sharedStyles.spacing.md)};
554
+ }
555
+
556
+ .incident-header {
557
+ padding: ${unsafeCSS(sharedStyles.spacing.md)};
558
+ }
559
+
560
+ .incident-body {
561
+ padding: 0 ${unsafeCSS(sharedStyles.spacing.md)} ${unsafeCSS(sharedStyles.spacing.md)} ${unsafeCSS(sharedStyles.spacing.md)};
562
+ }
563
+
564
+ .incident-meta {
565
+ flex-direction: column;
566
+ gap: ${unsafeCSS(sharedStyles.spacing.xs)};
567
+ }
568
+
569
+ .timeline {
570
+ padding-left: 20px;
571
+ }
572
+
573
+ .timeline::before {
574
+ left: 4px;
575
+ }
576
+
577
+ .update-item::before {
578
+ left: -18px;
579
+ width: 10px;
580
+ height: 10px;
581
+ }
63
582
  }
64
583
  `,
65
584
  ];
66
585
 
67
586
  public render(): TemplateResult {
68
587
  return html`
69
- <style></style>
70
- <div class="mainbox">
71
- <uplinternal-miniheading> Current Incidents </uplinternal-miniheading>
72
- ${this.currentIncidences.length
73
- ? html``
74
- : html` <div class="noIncidentBox">No incidents ongoing.</div> `}
75
- <uplinternal-miniheading> Past Incidents </uplinternal-miniheading>
76
- ${this.pastIncidences.length
77
- ? html``
78
- : html` <div class="noIncidentBox">No past incidents in the last 90 days.</div> `}
588
+ <div class="container">
589
+ <uplinternal-miniheading>Current Incidents</uplinternal-miniheading>
590
+ ${this.loading ? html`
591
+ <div class="loading-skeleton"></div>
592
+ ` : this.currentIncidents.length ? html`
593
+ ${this.currentIncidents.map(incident => this.renderIncident(incident, true))}
594
+ ` :
595
+ html`<div class="noIncidentBox">No incidents ongoing.</div>`
596
+ }
597
+
598
+ <uplinternal-miniheading>Past Incidents</uplinternal-miniheading>
599
+ ${this.loading ? html`
600
+ <div class="loading-skeleton"></div>
601
+ <div class="loading-skeleton"></div>
602
+ ` : this.pastIncidents.length ?
603
+ this.pastIncidents.slice(0, 5).map(incident => this.renderIncident(incident, false)) :
604
+ html`<div class="noIncidentBox">No past incidents in the last ${this.daysToShow} days.</div>`
605
+ }
606
+
607
+ ${this.pastIncidents.length > 5 && !this.loading ? html`
608
+ <div class="show-more">
609
+ <button class="show-more-button" @click=${this.handleShowMore}>
610
+ Show ${this.pastIncidents.length - 5} more incidents
611
+ </button>
612
+ </div>
613
+ ` : ''}
614
+ </div>
615
+ `;
616
+ }
617
+
618
+ private renderIncident(incident: IIncidentDetails, isCurrent: boolean): TemplateResult {
619
+ const latestUpdate = incident.updates[incident.updates.length - 1];
620
+ const duration = incident.endTime ?
621
+ this.formatDuration(incident.endTime - incident.startTime) :
622
+ this.formatDuration(Date.now() - incident.startTime);
623
+
624
+ const isActive = isCurrent && latestUpdate?.status !== 'resolved';
625
+
626
+ return html`
627
+ <div class="incident-card ${this.expandedIncidents.has(incident.id) ? 'expanded' : ''} ${isActive ? 'active-incident' : ''}">
628
+ <div class="incident-header ${incident.severity}" @click=${() => this.toggleIncident(incident.id)}>
629
+ <div>
630
+ <h3 class="incident-title">${incident.title}</h3>
631
+ <div class="incident-meta">
632
+ <span>Started: ${this.formatDate(incident.startTime)}</span>
633
+ <span>Duration: ${duration}</span>
634
+ ${incident.endTime ? html`
635
+ <span>Ended: ${this.formatDate(incident.endTime)}</span>
636
+ ` : ''}
637
+ </div>
638
+ ${!this.expandedIncidents.has(incident.id) ? html`
639
+ <div style="
640
+ margin-top: ${unsafeCSS(sharedStyles.spacing.sm)};
641
+ font-size: 13px;
642
+ color: ${sharedStyles.colors.text.secondary};
643
+ display: flex;
644
+ align-items: center;
645
+ gap: ${unsafeCSS(sharedStyles.spacing.md)};
646
+ ">
647
+ ${incident.impact ? html`
648
+ <span style="
649
+ overflow: hidden;
650
+ text-overflow: ellipsis;
651
+ white-space: nowrap;
652
+ max-width: 500px;
653
+ ">${incident.impact}</span>
654
+ ` : ''}
655
+ <span style="
656
+ font-size: 12px;
657
+ color: ${cssManager.bdTheme('#9ca3af', '#71717a')};
658
+ ">
659
+ ${incident.updates.length} update${incident.updates.length !== 1 ? 's' : ''}
660
+ </span>
661
+ </div>
662
+ ` : ''}
663
+ </div>
664
+ <div style="display: flex; align-items: center; gap: ${unsafeCSS(sharedStyles.spacing.md)};">
665
+ <div class="incident-status ${latestUpdate.status}">
666
+ ${this.getStatusIcon(latestUpdate.status)}
667
+ ${latestUpdate.status.replace(/_/g, ' ')}
668
+ </div>
669
+ <div class="expand-icon" style="
670
+ font-size: 10px;
671
+ color: ${cssManager.bdTheme('#6b7280', '#a1a1aa')};
672
+ transition: transform 0.2s ease;
673
+ display: flex;
674
+ align-items: center;
675
+ justify-content: center;
676
+ width: 24px;
677
+ height: 24px;
678
+ border-radius: 4px;
679
+ background: ${cssManager.bdTheme('#f3f4f6', '#27272a')};
680
+ ${this.expandedIncidents.has(incident.id) ? 'transform: rotate(180deg);' : ''}
681
+ ">
682
+
683
+ </div>
684
+ </div>
685
+ </div>
686
+
687
+ ${this.expandedIncidents.has(incident.id) ? html`
688
+ <div class="incident-body">
689
+ <div class="incident-impact">
690
+ <strong>Impact:</strong> ${incident.impact}
691
+ </div>
692
+
693
+ ${incident.affectedServices.length > 0 ? html`
694
+ <div class="affected-services">
695
+ <div class="affected-services-title">Affected Services:</div>
696
+ ${incident.affectedServices.map(service => html`
697
+ <span class="service-tag">${service}</span>
698
+ `)}
699
+ </div>
700
+ ` : ''}
701
+
702
+ ${incident.updates.length > 0 ? html`
703
+ <div class="incident-updates">
704
+ <h4 class="updates-title">Updates</h4>
705
+ <div class="timeline">
706
+ ${incident.updates.slice(-3).reverse().map((update, index) => this.renderUpdate(update, index))}
707
+ </div>
708
+ </div>
709
+ ` : ''}
710
+
711
+ ${incident.rootCause && isCurrent === false ? html`
712
+ <div class="incident-impact" style="margin-top: 12px;">
713
+ <strong>Root Cause:</strong> ${incident.rootCause}
714
+ </div>
715
+ ` : ''}
716
+
717
+ ${incident.resolution && isCurrent === false ? html`
718
+ <div class="incident-impact" style="margin-top: 12px;">
719
+ <strong>Resolution:</strong> ${incident.resolution}
720
+ </div>
721
+ ` : ''}
722
+
723
+ <div class="incident-actions">
724
+ <button
725
+ class="subscribe-button ${this.isSubscribedToIncident(incident.id) ? 'subscribed' : ''}"
726
+ @click=${(e: Event) => {
727
+ e.stopPropagation();
728
+ this.handleIncidentSubscribe(incident);
729
+ }}
730
+ >
731
+ ${this.isSubscribedToIncident(incident.id) ? html`
732
+ <svg width="14" height="14" viewBox="0 0 14 14" fill="none" xmlns="http://www.w3.org/2000/svg">
733
+ <path d="M11.6667 3.5L5.25 9.91667L2.33334 7" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
734
+ </svg>
735
+ Subscribed to updates
736
+ ` : html`
737
+ <svg width="14" height="14" viewBox="0 0 14 14" fill="none" xmlns="http://www.w3.org/2000/svg">
738
+ <path d="M10.5 5.25V8.75C10.5 9.34674 10.2629 9.91903 9.84099 10.341C9.41903 10.7629 8.84674 11 8.25 11L3.75 11C3.15326 11 2.58097 10.7629 2.15901 10.341C1.73705 9.91903 1.5 9.34674 1.5 8.75V4.25C1.5 3.65326 1.73705 3.08097 2.15901 2.65901C2.58097 2.23705 3.15326 2 3.75 2H7.25" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
739
+ <path d="M9 1.5H12.5M12.5 1.5V5M12.5 1.5L6 8" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
740
+ </svg>
741
+ Subscribe to updates
742
+ `}
743
+ </button>
744
+ ${isCurrent ? html`
745
+ <span style="
746
+ font-size: 12px;
747
+ color: ${cssManager.bdTheme('#6b7280', '#a1a1aa')};
748
+ ">Get notified when this incident is updated or resolved</span>
749
+ ` : ''}
750
+ </div>
751
+ </div>
752
+ ` : ''}
79
753
  </div>
80
754
  `;
81
755
  }
82
756
 
757
+ private renderUpdate(update: any, index: number = 0): TemplateResult {
758
+ return html`
759
+ <div class="update-item" style="animation-delay: ${index * 50}ms">
760
+ <div class="update-time">
761
+ ${this.formatDate(update.timestamp)}
762
+ ${update.status ? html`<span class="update-status-badge">${update.status}</span>` : ''}
763
+ </div>
764
+ <div class="update-message">${update.message}</div>
765
+ ${update.author ? html`
766
+ <div class="update-author">
767
+ <svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
768
+ <path d="M20 21v-2a4 4 0 0 0-4-4H8a4 4 0 0 0-4 4v2"></path>
769
+ <circle cx="12" cy="7" r="4"></circle>
770
+ </svg>
771
+ ${update.author}
772
+ </div>
773
+ ` : ''}
774
+ </div>
775
+ `;
776
+ }
777
+
778
+ private getStatusIcon(status: string): TemplateResult {
779
+ return html`<span class="status-dot" style="
780
+ display: inline-block;
781
+ width: 6px;
782
+ height: 6px;
783
+ border-radius: 50%;
784
+ background: ${status === 'resolved' ? sharedStyles.colors.status.operational :
785
+ status === 'monitoring' ? sharedStyles.colors.status.maintenance :
786
+ status === 'identified' ? sharedStyles.colors.status.degraded :
787
+ sharedStyles.colors.status.partial};
788
+ "></span>`;
789
+ }
790
+
791
+ private formatDate(timestamp: number): string {
792
+ const date = new Date(timestamp);
793
+ const now = Date.now();
794
+ const diff = now - timestamp;
795
+
796
+ // Less than 1 hour ago
797
+ if (diff < 60 * 60 * 1000) {
798
+ const minutes = Math.floor(diff / (60 * 1000));
799
+ return `${minutes} minute${minutes !== 1 ? 's' : ''} ago`;
800
+ }
801
+
802
+ // Less than 24 hours ago
803
+ if (diff < 24 * 60 * 60 * 1000) {
804
+ const hours = Math.floor(diff / (60 * 60 * 1000));
805
+ return `${hours} hour${hours !== 1 ? 's' : ''} ago`;
806
+ }
807
+
808
+ // Less than 7 days ago
809
+ if (diff < 7 * 24 * 60 * 60 * 1000) {
810
+ const days = Math.floor(diff / (24 * 60 * 60 * 1000));
811
+ return `${days} day${days !== 1 ? 's' : ''} ago`;
812
+ }
813
+
814
+ // Default to full date
815
+ return date.toLocaleDateString('en-US', {
816
+ month: 'short',
817
+ day: 'numeric',
818
+ year: date.getFullYear() !== new Date().getFullYear() ? 'numeric' : undefined
819
+ });
820
+ }
821
+
822
+ private formatDuration(milliseconds: number): string {
823
+ const minutes = Math.floor(milliseconds / (60 * 1000));
824
+ const hours = Math.floor(minutes / 60);
825
+ const days = Math.floor(hours / 24);
826
+
827
+ if (days > 0) {
828
+ return `${days}d ${hours % 24}h`;
829
+ } else if (hours > 0) {
830
+ return `${hours}h ${minutes % 60}m`;
831
+ } else {
832
+ return `${minutes}m`;
833
+ }
834
+ }
835
+
836
+ private toggleIncident(incidentId: string) {
837
+ const newExpanded = new Set(this.expandedIncidents);
838
+ if (newExpanded.has(incidentId)) {
839
+ newExpanded.delete(incidentId);
840
+ } else {
841
+ newExpanded.add(incidentId);
842
+ }
843
+ this.expandedIncidents = newExpanded;
844
+ }
845
+
846
+ private handleIncidentClick(incident: IIncidentDetails) {
847
+ this.dispatchEvent(new CustomEvent('incidentClick', {
848
+ detail: { incident },
849
+ bubbles: true,
850
+ composed: true
851
+ }));
852
+ }
853
+
854
+ private handleShowMore() {
855
+ // This would typically load more incidents or navigate to a full list
856
+ console.log('Show more incidents');
857
+ }
858
+
859
+ private isSubscribedToIncident(incidentId: string): boolean {
860
+ return this.subscribedIncidents.has(incidentId);
861
+ }
862
+
863
+ private handleIncidentSubscribe(incident: IIncidentDetails) {
864
+ const newSubscribed = new Set(this.subscribedIncidents);
865
+ if (newSubscribed.has(incident.id)) {
866
+ newSubscribed.delete(incident.id);
867
+ this.dispatchEvent(new CustomEvent('incidentUnsubscribe', {
868
+ detail: {
869
+ incident,
870
+ incidentId: incident.id
871
+ },
872
+ bubbles: true,
873
+ composed: true
874
+ }));
875
+ } else {
876
+ newSubscribed.add(incident.id);
877
+ this.dispatchEvent(new CustomEvent('incidentSubscribe', {
878
+ detail: {
879
+ incident,
880
+ incidentId: incident.id,
881
+ incidentTitle: incident.title,
882
+ affectedServices: incident.affectedServices
883
+ },
884
+ bubbles: true,
885
+ composed: true
886
+ }));
887
+ }
888
+ this.subscribedIncidents = newSubscribed;
889
+ }
890
+
83
891
  public dispatchReportNewIncident() {
84
- this.dispatchEvent(new CustomEvent('reportNewIncident', {}));
892
+ this.dispatchEvent(new CustomEvent('reportNewIncident', {
893
+ bubbles: true,
894
+ composed: true
895
+ }));
85
896
  }
86
897
 
87
898
  public dispatchStatusSubscribe() {
88
- this.dispatchEvent(new CustomEvent('statusSubscribe', {}));
899
+ this.dispatchEvent(new CustomEvent('statusSubscribe', {
900
+ bubbles: true,
901
+ composed: true
902
+ }));
89
903
  }
90
904
  }