@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
@@ -5,11 +5,15 @@ import {
5
5
  customElement,
6
6
  type TemplateResult,
7
7
  css,
8
- cssManager
8
+ cssManager,
9
+ unsafeCSS
9
10
  } from '@design.estate/dees-element';
10
11
  import * as domtools from '@design.estate/dees-domtools';
12
+ import type { IMonthlyUptime } 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-statusmonth.demo.js';
13
17
 
14
18
  declare global {
15
19
  interface HTMLElementTagNameMap {
@@ -19,7 +23,25 @@ declare global {
19
23
 
20
24
  @customElement('upl-statuspage-statusmonth')
21
25
  export class UplStatuspageStatusmonth extends DeesElement {
22
- public static demo = () => html` <upl-statuspage-statusmonth></upl-statuspage-statusmonth> `;
26
+ public static demo = demoFunc;
27
+
28
+ @property({ type: Array })
29
+ accessor monthlyData: IMonthlyUptime[] = [];
30
+
31
+ @property({ type: String })
32
+ accessor serviceId: string = '';
33
+
34
+ @property({ type: String })
35
+ accessor serviceName: string = 'Service';
36
+
37
+ @property({ type: Boolean })
38
+ accessor loading: boolean = false;
39
+
40
+ @property({ type: Boolean })
41
+ accessor showTooltip: boolean = true;
42
+
43
+ @property({ type: Number })
44
+ accessor monthsToShow: number = 5;
23
45
 
24
46
  constructor() {
25
47
  super();
@@ -27,104 +49,587 @@ export class UplStatuspageStatusmonth extends DeesElement {
27
49
 
28
50
  public static styles = [
29
51
  domtools.elementBasic.staticStyles,
52
+ sharedStyles.commonStyles,
30
53
  css`
31
54
  :host {
32
- position: relative;
33
- padding: 0px 0px 15px 0px;
34
- display: block;
35
- background: ${cssManager.bdTheme('#eeeeeb', '#222222')};;
36
- font-family: Inter;
37
- color: #fff;
55
+ position: relative;
56
+ display: block;
57
+ background: transparent;
58
+ font-family: ${unsafeCSS(sharedStyles.fonts.base)};
59
+ color: ${sharedStyles.colors.text.primary};
60
+ }
61
+
62
+ .container {
63
+ max-width: 1200px;
64
+ margin: 0 auto;
65
+ padding: 0 ${unsafeCSS(sharedStyles.spacing.lg)} ${unsafeCSS(sharedStyles.spacing.lg)} ${unsafeCSS(sharedStyles.spacing.lg)};
66
+ }
67
+
68
+ .mainbox {
69
+ display: grid;
70
+ grid-template-columns: repeat(auto-fit, minmax(260px, 1fr));
71
+ gap: ${unsafeCSS(sharedStyles.spacing.lg)};
72
+ }
73
+
74
+ /* Month card with entrance animation */
75
+ .statusMonth {
76
+ background: ${sharedStyles.colors.background.card};
77
+ padding: ${unsafeCSS(sharedStyles.spacing.lg)};
78
+ border-radius: ${unsafeCSS(sharedStyles.borderRadius.lg)};
79
+ border: 1px solid ${sharedStyles.colors.border.default};
80
+ position: relative;
81
+ transition: all ${unsafeCSS(sharedStyles.durations.normal)} ${unsafeCSS(sharedStyles.easings.default)};
82
+ display: flex;
83
+ flex-direction: column;
84
+ min-height: 280px;
85
+ box-shadow: ${unsafeCSS(sharedStyles.shadows.sm)};
86
+ animation: fadeInUp 0.5s ${unsafeCSS(sharedStyles.easings.default)} both;
87
+ }
88
+
89
+ .statusMonth:nth-child(1) { animation-delay: 0ms; }
90
+ .statusMonth:nth-child(2) { animation-delay: 100ms; }
91
+ .statusMonth:nth-child(3) { animation-delay: 200ms; }
92
+ .statusMonth:nth-child(4) { animation-delay: 300ms; }
93
+ .statusMonth:nth-child(5) { animation-delay: 400ms; }
94
+
95
+ @keyframes fadeInUp {
96
+ from {
97
+ opacity: 0;
98
+ transform: translateY(16px);
99
+ }
100
+ to {
101
+ opacity: 1;
102
+ transform: translateY(0);
103
+ }
104
+ }
105
+
106
+ .statusMonth:hover {
107
+ border-color: ${sharedStyles.colors.border.muted};
108
+ box-shadow: ${unsafeCSS(sharedStyles.shadows.md)};
109
+ transform: translateY(-2px);
110
+ }
111
+
112
+ .month-header {
113
+ font-size: 12px;
114
+ font-weight: 600;
115
+ margin-bottom: ${unsafeCSS(sharedStyles.spacing.md)};
116
+ color: ${sharedStyles.colors.text.primary};
117
+ letter-spacing: 0.04em;
118
+ text-transform: uppercase;
119
+ display: flex;
120
+ align-items: center;
121
+ gap: 8px;
122
+ }
123
+
124
+ .month-header .current-badge {
125
+ font-size: 9px;
126
+ padding: 2px 6px;
127
+ background: ${sharedStyles.colors.status.operational};
128
+ color: white;
129
+ border-radius: ${unsafeCSS(sharedStyles.borderRadius.full)};
130
+ font-weight: 500;
131
+ letter-spacing: 0.02em;
132
+ }
133
+
134
+ .days-container {
135
+ flex: 1;
136
+ display: flex;
137
+ flex-direction: column;
138
+ margin-bottom: ${unsafeCSS(sharedStyles.spacing.lg)};
139
+ }
140
+
141
+ .days-grid {
142
+ display: grid;
143
+ grid-template-columns: repeat(7, 1fr);
144
+ gap: 4px;
145
+ width: 100%;
146
+ }
147
+
148
+ .weekday-label {
149
+ font-size: 9px;
150
+ text-align: center;
151
+ color: ${sharedStyles.colors.text.muted};
152
+ font-weight: 600;
153
+ height: 20px;
154
+ line-height: 20px;
155
+ margin-bottom: ${unsafeCSS(sharedStyles.spacing.xs)};
156
+ text-transform: uppercase;
157
+ letter-spacing: 0.02em;
158
+ }
159
+
160
+ /* Calendar day cell */
161
+ .statusDay {
162
+ aspect-ratio: 1;
163
+ border-radius: 4px;
164
+ cursor: pointer;
165
+ transition: all ${unsafeCSS(sharedStyles.durations.fast)} ${unsafeCSS(sharedStyles.easings.default)};
166
+ position: relative;
167
+ animation: dayFadeIn 0.3s ${unsafeCSS(sharedStyles.easings.default)} both;
168
+ animation-delay: calc(var(--day-index, 0) * 15ms);
169
+ }
170
+
171
+ @keyframes dayFadeIn {
172
+ from {
173
+ opacity: 0;
174
+ transform: scale(0.8);
175
+ }
176
+ to {
177
+ opacity: 1;
178
+ transform: scale(1);
179
+ }
180
+ }
181
+
182
+ .statusDay:hover:not(.empty) {
183
+ transform: scale(1.2);
184
+ box-shadow: 0 4px 8px rgba(0, 0, 0, 0.15);
185
+ z-index: 2;
186
+ }
187
+
188
+ /* Current day highlight */
189
+ .statusDay.today {
190
+ box-shadow: 0 0 0 2px ${sharedStyles.colors.text.primary};
191
+ z-index: 1;
192
+ }
193
+
194
+ .statusDay.today:hover {
195
+ box-shadow: 0 0 0 2px ${sharedStyles.colors.text.primary}, 0 4px 8px rgba(0, 0, 0, 0.15);
196
+ }
197
+
198
+ /* Status colors with intensity variations based on uptime */
199
+ .statusDay.operational {
200
+ background: ${sharedStyles.colors.status.operational};
201
+ }
202
+
203
+ .statusDay.operational.uptime-high {
204
+ background: ${cssManager.bdTheme('#22c55e', '#22c55e')};
205
+ }
206
+
207
+ .statusDay.operational.uptime-mid {
208
+ background: ${cssManager.bdTheme('#4ade80', '#4ade80')};
209
+ }
210
+
211
+ .statusDay.degraded {
212
+ background: ${sharedStyles.colors.status.degraded};
213
+ }
214
+
215
+ .statusDay.partial_outage {
216
+ background: ${sharedStyles.colors.status.partial};
217
+ }
218
+
219
+ .statusDay.major_outage {
220
+ background: ${sharedStyles.colors.status.major};
221
+ animation: dayFadeIn 0.3s ${unsafeCSS(sharedStyles.easings.default)} both,
222
+ majorOutagePulse 2s ease-in-out infinite;
223
+ }
224
+
225
+ @keyframes majorOutagePulse {
226
+ 0%, 100% { opacity: 1; }
227
+ 50% { opacity: 0.8; }
228
+ }
229
+
230
+ .statusDay.maintenance {
231
+ background: ${sharedStyles.colors.status.maintenance};
232
+ }
233
+
234
+ .statusDay.no-data {
235
+ background: ${sharedStyles.colors.background.muted};
236
+ opacity: 0.5;
237
+ }
238
+
239
+ .statusDay.empty {
240
+ background: transparent;
241
+ cursor: default;
242
+ pointer-events: none;
243
+ animation: none;
244
+ }
245
+
246
+ /* Incident count indicator */
247
+ .incident-count {
248
+ position: absolute;
249
+ top: 50%;
250
+ left: 50%;
251
+ transform: translate(-50%, -50%);
252
+ font-size: 8px;
253
+ font-weight: 700;
254
+ color: white;
255
+ text-shadow: 0 1px 2px rgba(0, 0, 0, 0.3);
256
+ line-height: 1;
257
+ }
258
+
259
+ /* Overall uptime footer */
260
+ .overall-uptime {
261
+ font-size: 12px;
262
+ margin-top: auto;
263
+ padding-top: ${unsafeCSS(sharedStyles.spacing.md)};
264
+ color: ${sharedStyles.colors.text.secondary};
265
+ display: flex;
266
+ flex-direction: column;
267
+ gap: 8px;
268
+ border-top: 1px solid ${sharedStyles.colors.border.default};
269
+ }
270
+
271
+ .uptime-stat {
272
+ display: flex;
273
+ justify-content: space-between;
274
+ align-items: center;
275
+ }
276
+
277
+ .uptime-value {
278
+ font-weight: 600;
279
+ color: ${sharedStyles.colors.text.primary};
280
+ font-variant-numeric: tabular-nums;
281
+ font-size: 13px;
282
+ }
283
+
284
+ .uptime-value.good {
285
+ color: ${sharedStyles.colors.status.operational};
286
+ }
287
+
288
+ .uptime-value.warning {
289
+ color: ${sharedStyles.colors.status.degraded};
290
+ }
291
+
292
+ .uptime-value.bad {
293
+ color: ${sharedStyles.colors.status.partial};
294
+ }
295
+
296
+ /* Uptime bar visualization */
297
+ .uptime-bar {
298
+ height: 4px;
299
+ background: ${sharedStyles.colors.background.muted};
300
+ border-radius: 2px;
301
+ overflow: hidden;
302
+ margin-top: 4px;
303
+ }
304
+
305
+ .uptime-bar-fill {
306
+ height: 100%;
307
+ border-radius: 2px;
308
+ transition: width ${unsafeCSS(sharedStyles.durations.slow)} ${unsafeCSS(sharedStyles.easings.default)};
309
+ }
310
+
311
+ .uptime-bar-fill.good {
312
+ background: ${sharedStyles.colors.status.operational};
313
+ }
314
+
315
+ .uptime-bar-fill.warning {
316
+ background: ${sharedStyles.colors.status.degraded};
317
+ }
318
+
319
+ .uptime-bar-fill.bad {
320
+ background: ${sharedStyles.colors.status.partial};
321
+ }
322
+
323
+ /* Loading skeleton */
324
+ .loading-skeleton {
325
+ display: flex;
326
+ flex-direction: column;
327
+ gap: ${unsafeCSS(sharedStyles.spacing.sm)};
328
+ height: 100%;
329
+ }
330
+
331
+ .skeleton-header {
332
+ height: 20px;
333
+ width: 80px;
334
+ background: ${sharedStyles.colors.background.muted};
335
+ border-radius: 4px;
336
+ animation: shimmer 1.5s infinite;
337
+ }
338
+
339
+ .skeleton-grid {
340
+ flex: 1;
341
+ display: grid;
342
+ grid-template-columns: repeat(7, 1fr);
343
+ gap: 3px;
344
+ }
345
+
346
+ .skeleton-day {
347
+ background: ${sharedStyles.colors.background.muted};
348
+ border-radius: 3px;
349
+ animation: shimmer 1.5s infinite;
350
+ animation-delay: calc(var(--index) * 30ms);
351
+ }
352
+
353
+ @keyframes shimmer {
354
+ 0% { opacity: 0.5; }
355
+ 50% { opacity: 1; }
356
+ 100% { opacity: 0.5; }
357
+ }
358
+
359
+ /* Tooltip */
360
+ .tooltip {
361
+ position: absolute;
362
+ background: ${cssManager.bdTheme('#0a0a0a', '#fafafa')};
363
+ color: ${cssManager.bdTheme('#fafafa', '#0a0a0a')};
364
+ padding: 10px 14px;
365
+ border-radius: ${unsafeCSS(sharedStyles.borderRadius.base)};
366
+ font-size: 12px;
367
+ pointer-events: none;
368
+ opacity: 0;
369
+ transition: opacity ${unsafeCSS(sharedStyles.durations.fast)} ${unsafeCSS(sharedStyles.easings.default)},
370
+ transform ${unsafeCSS(sharedStyles.durations.fast)} ${unsafeCSS(sharedStyles.easings.default)};
371
+ z-index: 50;
372
+ white-space: nowrap;
373
+ box-shadow: ${unsafeCSS(sharedStyles.shadows.lg)};
374
+ line-height: 1.5;
375
+ transform: translateX(-50%) translateY(4px);
376
+ }
377
+
378
+ .tooltip.visible {
379
+ opacity: 1;
380
+ transform: translateX(-50%) translateY(0);
381
+ }
382
+
383
+ .tooltip-date {
384
+ font-weight: 600;
385
+ margin-bottom: 6px;
386
+ font-size: 13px;
387
+ }
388
+
389
+ .tooltip-stat {
390
+ font-size: 11px;
391
+ opacity: 0.85;
392
+ display: flex;
393
+ align-items: center;
394
+ gap: 6px;
395
+ }
396
+
397
+ .tooltip-stat + .tooltip-stat {
398
+ margin-top: 2px;
399
+ }
400
+
401
+ .tooltip-uptime-bar {
402
+ height: 3px;
403
+ width: 60px;
404
+ background: rgba(128, 128, 128, 0.3);
405
+ border-radius: 2px;
406
+ overflow: hidden;
407
+ margin-top: 8px;
408
+ }
409
+
410
+ .tooltip-uptime-fill {
411
+ height: 100%;
412
+ border-radius: 2px;
413
+ }
414
+
415
+ .no-data-message {
416
+ grid-column: 1 / -1;
417
+ text-align: center;
418
+ padding: ${unsafeCSS(sharedStyles.spacing['2xl'])};
419
+ color: ${sharedStyles.colors.text.secondary};
420
+ animation: fadeInUp 0.4s ${unsafeCSS(sharedStyles.easings.default)} both;
421
+ }
422
+
423
+ @media (max-width: 640px) {
424
+ .container {
425
+ padding: 0 ${unsafeCSS(sharedStyles.spacing.md)} ${unsafeCSS(sharedStyles.spacing.md)} ${unsafeCSS(sharedStyles.spacing.md)};
38
426
  }
39
427
 
40
428
  .mainbox {
41
- margin: auto;
42
- max-width: 900px;
43
- display: grid;
44
- grid-template-columns: repeat(5, calc(100% / 5 - 80px / 5));
45
- grid-column-gap: 20px;
429
+ grid-template-columns: 1fr;
430
+ gap: ${unsafeCSS(sharedStyles.spacing.md)};
46
431
  }
47
432
 
48
433
  .statusMonth {
49
- background: ${cssManager.bdTheme('#ffffff', '#333333')};;
50
- min-height: 20px;
51
- display: grid;
52
- padding: 10px;
53
- grid-template-columns: repeat(6, auto);
54
- grid-gap: 9px;
55
- border-radius: 3px;
434
+ padding: ${unsafeCSS(sharedStyles.spacing.md)};
435
+ min-height: 260px;
436
+ }
437
+
438
+ .days-grid {
439
+ gap: 3px;
440
+ }
441
+
442
+ .statusDay:hover:not(.empty) {
443
+ transform: scale(1.1);
56
444
  }
57
445
 
58
- .statusMonth .statusDay {
59
- width: 16px;
60
- height: 16px;
61
- background: #2deb51;
62
- border-radius: 3px;
446
+ .loading-skeleton {
447
+ height: 180px;
448
+ padding: ${unsafeCSS(sharedStyles.spacing.md)};
63
449
  }
450
+ }
64
451
  `
65
452
  ]
66
453
 
67
454
  public render(): TemplateResult {
455
+ const totalDays = this.monthlyData.reduce((sum, month) => sum + month.days.length, 0);
456
+
68
457
  return html`
69
- <style></style>
70
- <uplinternal-miniheading>Last 150 days</uplinternal-miniheading>
71
- <div class="mainbox">
72
- <div class="statusMonth">
73
- ${(() => {
74
- let counter = 0;
75
- const returnArray: TemplateResult[] = [];
76
- while (counter < 30) {
77
- counter++;
78
- returnArray.push(html` <div class="statusDay"></div> `);
79
- }
80
- return returnArray;
81
- })()}
82
- </div>
83
- <div class="statusMonth">
84
- ${(() => {
85
- let counter = 0;
86
- const returnArray: TemplateResult[] = [];
87
- while (counter < 30) {
88
- counter++;
89
- returnArray.push(html` <div class="statusDay"></div> `);
90
- }
91
- return returnArray;
92
- })()}
458
+ <div class="container">
459
+ <uplinternal-miniheading>${this.serviceName} - Last ${totalDays} Days</uplinternal-miniheading>
460
+ <div class="mainbox">
461
+ ${this.loading ? html`
462
+ ${Array(this.monthsToShow).fill(0).map((_, index) => html`
463
+ <div class="statusMonth">
464
+ <div class="loading-skeleton">
465
+ <div class="skeleton-header"></div>
466
+ <div class="days-container">
467
+ <div class="skeleton-grid">
468
+ ${Array(42).fill(0).map((_, i) => html`
469
+ <div class="skeleton-day" style="--index: ${i}"></div>
470
+ `)}
471
+ </div>
472
+ </div>
473
+ <div style="height: 48px; border-top: 1px solid ${cssManager.bdTheme('#f3f4f6', '#1f1f1f')}; margin-top: auto; padding-top: 16px;"></div>
474
+ </div>
475
+ </div>
476
+ `)}
477
+ ` : this.monthlyData.length === 0 ? html`
478
+ <div class="no-data-message">No uptime data available</div>
479
+ ` : this.monthlyData.map(month => this.renderMonth(month))}
93
480
  </div>
94
- <div class="statusMonth">
95
- ${(() => {
96
- let counter = 0;
97
- const returnArray: TemplateResult[] = [];
98
- while (counter < 30) {
99
- counter++;
100
- returnArray.push(html` <div class="statusDay"></div> `);
101
- }
102
- return returnArray;
103
- })()}
481
+ ${this.showTooltip ? html`<div class="tooltip" id="tooltip"></div>` : ''}
482
+ </div>
483
+ `;
484
+ }
485
+
486
+ private renderMonth(monthData: IMonthlyUptime): TemplateResult {
487
+ const monthDate = new Date(monthData.month + '-01');
488
+ const monthName = monthDate.toLocaleDateString('en-US', { month: 'short', year: 'numeric' });
489
+ const firstDayOfWeek = new Date(monthDate.getFullYear(), monthDate.getMonth(), 1).getDay();
490
+ const now = new Date();
491
+ const isCurrentMonth = monthDate.getMonth() === now.getMonth() && monthDate.getFullYear() === now.getFullYear();
492
+ const uptimeClass = this.getUptimeClass(monthData.overallUptime);
493
+
494
+ return html`
495
+ <div class="statusMonth" @mouseleave=${this.hideTooltip}>
496
+ <div class="month-header">
497
+ ${monthName}
498
+ ${isCurrentMonth ? html`<span class="current-badge">Current</span>` : ''}
104
499
  </div>
105
- <div class="statusMonth">
106
- ${(() => {
107
- let counter = 0;
108
- const returnArray: TemplateResult[] = [];
109
- while (counter < 30) {
110
- counter++;
111
- returnArray.push(html` <div class="statusDay"></div> `);
112
- }
113
- return returnArray;
114
- })()}
500
+ <div class="days-container">
501
+ <div class="days-grid">
502
+ ${this.renderWeekdayLabels()}
503
+ ${this.renderEmptyDays(firstDayOfWeek)}
504
+ ${monthData.days.map((day, index) => this.renderDay(day, index))}
505
+ ${this.renderTrailingEmptyDays(firstDayOfWeek + monthData.days.length)}
506
+ </div>
115
507
  </div>
116
- <div class="statusMonth">
117
- ${(() => {
118
- let counter = 0;
119
- const returnArray: TemplateResult[] = [];
120
- while (counter < 30) {
121
- counter++;
122
- returnArray.push(html` <div class="statusDay"></div> `);
123
- }
124
- return returnArray;
125
- })()}
508
+ <div class="overall-uptime">
509
+ <div class="uptime-stat">
510
+ <span>Uptime</span>
511
+ <span class="uptime-value ${uptimeClass}">${monthData.overallUptime.toFixed(2)}%</span>
512
+ </div>
513
+ <div class="uptime-bar">
514
+ <div class="uptime-bar-fill ${uptimeClass}" style="width: ${monthData.overallUptime}%"></div>
515
+ </div>
516
+ ${monthData.totalIncidents > 0 ? html`
517
+ <div class="uptime-stat">
518
+ <span>Incidents</span>
519
+ <span class="uptime-value">${monthData.totalIncidents}</span>
520
+ </div>
521
+ ` : ''}
126
522
  </div>
127
523
  </div>
128
524
  `;
129
525
  }
526
+
527
+ private getUptimeClass(uptime: number): string {
528
+ if (uptime >= 99.9) return 'good';
529
+ if (uptime >= 99) return 'warning';
530
+ return 'bad';
531
+ }
532
+
533
+ private renderWeekdayLabels(): TemplateResult[] {
534
+ const weekdays = ['S', 'M', 'T', 'W', 'T', 'F', 'S'];
535
+ return weekdays.map(day => html`<div class="weekday-label">${day}</div>`);
536
+ }
537
+
538
+ private renderEmptyDays(count: number): TemplateResult[] {
539
+ return Array(count).fill(0).map(() => html`<div class="statusDay empty"></div>`);
540
+ }
541
+
542
+ private renderTrailingEmptyDays(totalCells: number): TemplateResult[] {
543
+ const remainder = totalCells % 7;
544
+ const trailingCount = remainder === 0 ? 0 : 7 - remainder;
545
+ return Array(trailingCount).fill(0).map(() => html`<div class="statusDay empty"></div>`);
546
+ }
547
+
548
+ private renderDay(day: any, index: number = 0): TemplateResult {
549
+ const status = day.status || 'no-data';
550
+ const date = new Date(day.date);
551
+ const now = new Date();
552
+ const isToday = date.toDateString() === now.toDateString();
553
+ const uptimeIntensity = this.getUptimeIntensity(day.uptime);
554
+
555
+ return html`
556
+ <div
557
+ class="statusDay ${status} ${isToday ? 'today' : ''} ${status === 'operational' ? uptimeIntensity : ''}"
558
+ style="--day-index: ${index}"
559
+ @mouseenter=${(e: MouseEvent) => this.showTooltip && this.showDayTooltip(e, day)}
560
+ @click=${() => this.handleDayClick(day)}
561
+ >
562
+ ${(status === 'major_outage' || status === 'partial_outage') && day.incidents > 0 ? html`
563
+ <span class="incident-count">${day.incidents}</span>
564
+ ` : ''}
565
+ </div>
566
+ `;
567
+ }
568
+
569
+ private getUptimeIntensity(uptime: number): string {
570
+ if (uptime >= 99.9) return 'uptime-high';
571
+ if (uptime >= 99) return 'uptime-mid';
572
+ return '';
573
+ }
574
+
575
+ private showDayTooltip(event: MouseEvent, day: any) {
576
+ const tooltip = this.shadowRoot?.getElementById('tooltip') as HTMLElement;
577
+ if (!tooltip) return;
578
+
579
+ const date = new Date(day.date);
580
+ const dateStr = date.toLocaleDateString('en-US', {
581
+ weekday: 'short',
582
+ month: 'short',
583
+ day: 'numeric'
584
+ });
585
+
586
+ let statusText = day.status.replace(/_/g, ' ');
587
+ statusText = statusText.charAt(0).toUpperCase() + statusText.slice(1);
588
+
589
+ const uptimeColor = day.uptime >= 99.9 ? '#22c55e' :
590
+ day.uptime >= 99 ? '#fbbf24' : '#f87171';
591
+
592
+ tooltip.innerHTML = `
593
+ <div class="tooltip-date">${dateStr}</div>
594
+ <div class="tooltip-stat">
595
+ <span style="display: inline-block; width: 6px; height: 6px; border-radius: 50%; background: ${uptimeColor};"></span>
596
+ ${statusText}
597
+ </div>
598
+ <div class="tooltip-stat">Uptime: <strong>${day.uptime.toFixed(2)}%</strong></div>
599
+ ${day.incidents > 0 ? `<div class="tooltip-stat">Incidents: <strong>${day.incidents}</strong></div>` : ''}
600
+ ${day.totalDowntime > 0 ? `<div class="tooltip-stat">Downtime: <strong>${day.totalDowntime}m</strong></div>` : ''}
601
+ <div class="tooltip-uptime-bar">
602
+ <div class="tooltip-uptime-fill" style="width: ${day.uptime}%; background: ${uptimeColor};"></div>
603
+ </div>
604
+ `;
605
+
606
+ const rect = (event.target as HTMLElement).getBoundingClientRect();
607
+ const containerRect = this.getBoundingClientRect();
608
+
609
+ tooltip.style.left = `${rect.left - containerRect.left + rect.width / 2}px`;
610
+ tooltip.style.top = `${rect.top - containerRect.top - 10}px`;
611
+ tooltip.style.transform = 'translateX(-50%) translateY(-100%)';
612
+ tooltip.classList.add('visible');
613
+ }
614
+
615
+ private hideTooltip() {
616
+ const tooltip = this.shadowRoot?.getElementById('tooltip') as HTMLElement;
617
+ if (tooltip) {
618
+ tooltip.classList.remove('visible');
619
+ }
620
+ }
621
+
622
+ private handleDayClick(day: any) {
623
+ this.dispatchEvent(new CustomEvent('dayClick', {
624
+ detail: {
625
+ date: day.date,
626
+ uptime: day.uptime,
627
+ incidents: day.incidents,
628
+ status: day.status,
629
+ serviceId: this.serviceId
630
+ },
631
+ bubbles: true,
632
+ composed: true
633
+ }));
634
+ }
130
635
  }