diff --git a/ts_web/elements/upl-statuspage-statusdetails.ts b/ts_web/elements/upl-statuspage-statusdetails.ts index 94c558a..6948464 100644 --- a/ts_web/elements/upl-statuspage-statusdetails.ts +++ b/ts_web/elements/upl-statuspage-statusdetails.ts @@ -27,6 +27,9 @@ export class UplStatuspageStatusdetails extends DeesElement { @property({ type: Array }) public historyData: IStatusHistoryPoint[] = []; + + @property({ type: Array }) + public dataPoints: IStatusHistoryPoint[] = []; @property({ type: String }) public serviceId: string = ''; @@ -61,123 +64,131 @@ export class UplStatuspageStatusdetails extends DeesElement { margin: 0 auto; padding: 0 ${unsafeCSS(spacing.lg)} ${unsafeCSS(spacing.lg)} ${unsafeCSS(spacing.lg)}; } + + .graph-wrapper { + display: flex; + flex-direction: column; + gap: 4px; + } + + .graph-container { + position: relative; + } .mainbox { - background: ${colors.background.card}; - border: 1px solid ${colors.border.default}; - border-radius: ${unsafeCSS(borderRadius.md)}; - padding: ${unsafeCSS(spacing.md)}; - box-shadow: ${unsafeCSS(shadows.sm)}; + background: transparent; + border: none; + border-radius: 0; + padding: 0; } .mainbox .barContainer { position: relative; display: flex; - gap: 2px; + gap: 1px; padding: ${unsafeCSS(spacing.sm)}; + background: ${cssManager.bdTheme('#fafafa', '#0a0a0a')}; + border: 1px solid ${cssManager.bdTheme('#f3f4f6', '#1f1f1f')}; border-radius: ${unsafeCSS(borderRadius.base)}; overflow: hidden; + height: 40px; } .mainbox .barContainer .bar { flex: 1; - height: 48px; - border-radius: ${unsafeCSS(borderRadius.sm)}; + height: 100%; cursor: pointer; - transition: all 0.2s ease; + transition: opacity 0.15s ease; position: relative; } .mainbox .barContainer .bar:hover { - transform: scaleY(1.1); - box-shadow: 0 2px 4px rgba(0, 0, 0, 0.15); + opacity: 0.8; } .mainbox .barContainer .bar.operational { - background: ${colors.status.operational}; + background: ${cssManager.bdTheme('#22c55e', '#22c55e')}; } .mainbox .barContainer .bar.degraded { - background: ${colors.status.degraded}; + background: ${cssManager.bdTheme('#fbbf24', '#fbbf24')}; } .mainbox .barContainer .bar.partial_outage { - background: ${colors.status.partial}; + background: ${cssManager.bdTheme('#f87171', '#f87171')}; } .mainbox .barContainer .bar.major_outage { - background: ${colors.status.major}; + background: ${cssManager.bdTheme('#ef4444', '#ef4444')}; } .mainbox .barContainer .bar.maintenance { - background: ${colors.status.maintenance}; + background: ${cssManager.bdTheme('#60a5fa', '#60a5fa')}; } .mainbox .barContainer .bar.no-data { - background: ${cssManager.bdTheme('#d1d5db', '#52525b')}; - opacity: ${cssManager.bdTheme('0.5', '0.35')}; + background: ${cssManager.bdTheme('#f3f4f6', '#27272a')}; } .time-labels { display: flex; justify-content: space-between; - padding: ${unsafeCSS(spacing.sm)} ${unsafeCSS(spacing.sm)} 0; - font-size: 12px; - color: ${cssManager.bdTheme('#4b5563', '#9ca3af')}; - font-family: ${unsafeCSS(fonts.mono)}; - font-weight: 500; + padding: 0; + margin-top: ${unsafeCSS(spacing.xs)}; + font-size: 10px; + color: ${cssManager.bdTheme('#9ca3af', '#71717a')}; + font-family: ${unsafeCSS(fonts.base)}; + opacity: 0.8; } .tooltip { position: absolute; - background: ${cssManager.bdTheme('#18181b', '#ffffff')}; - color: ${cssManager.bdTheme('#fafafa', '#18181b')}; - padding: ${unsafeCSS(spacing.sm)} ${unsafeCSS(spacing.md)}; - border-radius: ${unsafeCSS(borderRadius.base)}; - font-size: 13px; + background: ${cssManager.bdTheme('#0a0a0a', '#fafafa')}; + color: ${cssManager.bdTheme('#fafafa', '#0a0a0a')}; + padding: 6px 10px; + border-radius: 4px; + font-size: 11px; pointer-events: none; opacity: 0; - transition: opacity 0.2s; - z-index: 10; + transition: opacity 0.15s; + z-index: 50; white-space: nowrap; - box-shadow: ${unsafeCSS(shadows.lg)}; - border: 1px solid ${cssManager.bdTheme('rgba(0, 0, 0, 0.1)', 'rgba(255, 255, 255, 0.1)')}; + box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06); + line-height: 1.4; } .tooltip.visible { - opacity: 0.95; + opacity: 1; } - .tooltip strong { + .tooltip-time { font-weight: 600; display: block; - margin-bottom: ${unsafeCSS(spacing.xs)}; - color: ${cssManager.bdTheme('#fafafa', '#18181b')}; + margin-bottom: 2px; } - .tooltip div { - line-height: 1.4; + .tooltip-stat { + font-size: 10px; + opacity: 0.9; } .loading-skeleton { display: flex; - padding: ${unsafeCSS(spacing.sm)}; - gap: 2px; - background: ${colors.background.secondary}; - border-radius: ${unsafeCSS(borderRadius.base)}; + gap: 1px; + height: 24px; } .loading-skeleton .skeleton-bar { flex: 1; - height: 48px; - background: ${cssManager.bdTheme( - 'linear-gradient(90deg, #f3f4f6 25%, #e5e7eb 50%, #f3f4f6 75%)', - 'linear-gradient(90deg, #1f1f1f 25%, #262626 50%, #1f1f1f 75%)' - )}; - background-size: 200% 100%; - animation: loading 1.5s infinite; - border-radius: ${unsafeCSS(borderRadius.sm)}; + height: 100%; + background: ${cssManager.bdTheme('#f3f4f6', '#27272a')}; + animation: pulse 2s infinite; + } + + @keyframes pulse { + 0%, 100% { opacity: 1; } + 50% { opacity: 0.5; } } @keyframes loading { @@ -185,63 +196,60 @@ export class UplStatuspageStatusdetails extends DeesElement { 100% { background-position: -200% 0; } } - .status-legend { - display: flex; - gap: ${unsafeCSS(spacing.lg)}; - justify-content: center; - margin-top: ${unsafeCSS(spacing.lg)}; - font-size: 13px; - flex-wrap: wrap; - } - - .legend-item { + .stats-row { display: flex; align-items: center; - gap: ${unsafeCSS(spacing.xs)}; - } - - .legend-color { - width: 12px; - height: 12px; - border-radius: ${unsafeCSS(borderRadius.sm)}; + justify-content: space-between; + margin-top: ${unsafeCSS(spacing.lg)}; + padding: ${unsafeCSS(spacing.sm)} 0; + border-top: 1px solid ${cssManager.bdTheme('#f3f4f6', '#1f1f1f')}; + font-size: 12px; } - .legend-color.operational { - background: ${colors.status.operational}; + .stat-item { + display: flex; + align-items: center; + gap: ${unsafeCSS(spacing.sm)}; + color: ${cssManager.bdTheme('#6b7280', '#a1a1aa')}; } - .legend-color.degraded { - background: ${colors.status.degraded}; + .stat-value { + font-weight: 500; + color: ${cssManager.bdTheme('#0a0a0a', '#fafafa')}; + font-variant-numeric: tabular-nums; } - .legend-color.partial_outage { - background: ${colors.status.partial}; - } - - .legend-color.major_outage { - background: ${colors.status.major}; - } - - .legend-color.maintenance { - background: ${colors.status.maintenance}; + .status-indicator { + display: inline-block; + width: 6px; + height: 6px; + border-radius: 50%; + margin-right: 4px; } @media (max-width: 640px) { .container { padding: 0 ${unsafeCSS(spacing.md)} ${unsafeCSS(spacing.md)} ${unsafeCSS(spacing.md)}; } - - .mainbox { + + .graph-container { padding: ${unsafeCSS(spacing.sm)}; } - .time-labels { - font-size: 11px; + .mainbox .barContainer { + height: 32px; + padding: ${unsafeCSS(spacing.xs)}; } - .status-legend { - gap: ${unsafeCSS(spacing.md)}; - font-size: 12px; + .time-labels { + font-size: 9px; + } + + .stats-row { + font-size: 11px; + flex-direction: column; + gap: ${unsafeCSS(spacing.sm)}; + align-items: flex-start; } } `, @@ -253,44 +261,31 @@ export class UplStatuspageStatusdetails extends DeesElement { ${this.serviceName} - Last ${this.hoursToShow} Hours
${this.loading ? html` -
- ${Array(this.hoursToShow).fill(0).map(() => html`
`)} +
+
+
+ ${Array(this.hoursToShow).fill(0).map(() => html`
`)} +
+
` : html` -
- ${this.renderBars()} +
+
+ ${this.renderBars()} +
- ${this.getTimeLabel(0)} - ${this.getTimeLabel(Math.floor(this.hoursToShow / 4))} - ${this.getTimeLabel(Math.floor(this.hoursToShow / 2))} + ${this.getTimeLabel(this.hoursToShow - 1)} ${this.getTimeLabel(Math.floor(this.hoursToShow * 3 / 4))} - Now + ${this.getTimeLabel(Math.floor(this.hoursToShow / 2))} + ${this.getTimeLabel(Math.floor(this.hoursToShow / 4))} + now
`}
- ${!this.loading ? html` -
-
-
- Operational -
-
-
- Degraded -
-
-
- Partial Outage -
-
-
- Major Outage -
-
-
- Maintenance -
+ ${!this.loading && this.getData().length > 0 ? html` +
+ ${this.renderStats()}
` : ''}
@@ -322,14 +317,19 @@ export class UplStatuspageStatusdetails extends DeesElement { return bars; } + private getData(): IStatusHistoryPoint[] { + return this.dataPoints?.length > 0 ? this.dataPoints : this.historyData || []; + } + private findDataPointForTime(timestamp: number): IStatusHistoryPoint | undefined { - if (!this.historyData || this.historyData.length === 0) return undefined; + const data = this.getData(); + if (!data || data.length === 0) return undefined; // Find the closest data point within the same hour const targetHour = new Date(timestamp).getHours(); const targetDate = new Date(timestamp).toDateString(); - return this.historyData.find(point => { + return data.find(point => { const pointDate = new Date(point.timestamp); return pointDate.toDateString() === targetDate && pointDate.getHours() === targetHour; @@ -340,9 +340,9 @@ export class UplStatuspageStatusdetails extends DeesElement { private getTimeLabel(hoursAgo: number): string { const date = new Date(Date.now() - (hoursAgo * 60 * 60 * 1000)); if (hoursAgo >= 24) { - return `${date.getMonth() + 1}/${date.getDate()} ${date.getHours()}:00`; + return `${date.getMonth() + 1}/${date.getDate()}`; } - return `${date.getHours()}:00`; + return `${date.getHours()}h`; } private showTooltip(event: MouseEvent, timestamp: number, status: string, responseTime: number) { @@ -354,17 +354,17 @@ export class UplStatuspageStatusdetails extends DeesElement { const statusStr = status.replace(/_/g, ' ').replace('no-data', 'No Data'); tooltip.innerHTML = ` -
${timeStr}
-
Status: ${statusStr}
- ${responseTime > 0 ? `
Response Time: ${responseTime.toFixed(0)}ms
` : ''} +
${timeStr}
+
Status: ${statusStr}
+ ${responseTime > 0 ? `
Response Time: ${responseTime.toFixed(0)}ms
` : ''} `; const rect = (event.target as HTMLElement).getBoundingClientRect(); const containerRect = this.getBoundingClientRect(); tooltip.style.left = `${rect.left - containerRect.left + rect.width / 2}px`; - tooltip.style.top = `${rect.top - containerRect.top - 60}px`; - tooltip.style.transform = 'translateX(-50%)'; + tooltip.style.top = `${rect.top - containerRect.top - 10}px`; + tooltip.style.transform = 'translate(-50%, -100%)'; tooltip.classList.add('visible'); } @@ -382,4 +382,46 @@ export class UplStatuspageStatusdetails extends DeesElement { composed: true })); } + + private renderStats(): TemplateResult { + const data = this.getData(); + const operational = data.filter(d => d.status === 'operational').length; + const uptime = (operational / data.length) * 100; + const avgResponseTime = data + .filter(d => d.responseTime && d.responseTime > 0) + .reduce((sum, d) => sum + (d.responseTime || 0), 0) / data.length || 0; + + const currentStatus = data[data.length - 1]?.status || 'no-data'; + + return html` +
+ + ${this.formatStatus(currentStatus)} +
+
+ ${uptime.toFixed(1)}% + uptime +
+
+ ${avgResponseTime.toFixed(0)}ms + avg response +
+ `; + } + + private getStatusColor(status: string): string { + const colors: Record = { + operational: '#22c55e', + degraded: '#fbbf24', + partial_outage: '#f87171', + major_outage: '#ef4444', + maintenance: '#60a5fa', + 'no-data': '#e5e7eb' + }; + return colors[status] || '#e5e7eb'; + } + + private formatStatus(status: string): string { + return status.replace(/_/g, ' ').replace(/\b\w/g, l => l.toUpperCase()); + } }