import { DeesElement, customElement, html, css, cssManager, property, type TemplateResult, } from '@design.estate/dees-element'; declare global { interface HTMLElementTagNameMap { 'sz-traffic-card': SzTrafficCard; } } export interface ITrafficData { requests: number; errors: number; errorPercent: number; avgResponse: number; reqPerMin: number; status2xx: number; status3xx: number; status4xx: number; status5xx: number; } @customElement('sz-traffic-card') export class SzTrafficCard extends DeesElement { public static demo = () => html`
`; public static demoGroups = ['Dashboard']; @property({ type: Object }) public accessor data: ITrafficData = { requests: 0, errors: 0, errorPercent: 0, avgResponse: 0, reqPerMin: 0, status2xx: 0, status3xx: 0, status4xx: 0, status5xx: 0, }; public static styles = [ cssManager.defaultStyles, css` :host { display: block; height: 100%; } .card { background: ${cssManager.bdTheme('#ffffff', '#09090b')}; border: 1px solid ${cssManager.bdTheme('#e4e4e7', '#27272a')}; border-radius: 8px; padding: 20px; height: 100%; box-sizing: border-box; } .header { margin-bottom: 16px; } .title { font-size: 16px; font-weight: 600; color: ${cssManager.bdTheme('#18181b', '#fafafa')}; } .subtitle { font-size: 13px; color: ${cssManager.bdTheme('#71717a', '#a1a1aa')}; margin-top: 2px; } .metrics { display: grid; grid-template-columns: repeat(2, 1fr); gap: 16px; margin-bottom: 16px; } .metric { display: flex; flex-direction: column; gap: 2px; } .metric-label { font-size: 13px; color: ${cssManager.bdTheme('#71717a', '#a1a1aa')}; } .metric-value { font-size: 18px; font-weight: 600; color: ${cssManager.bdTheme('#18181b', '#fafafa')}; } .status-bar-container { padding-top: 12px; border-top: 1px solid ${cssManager.bdTheme('#f4f4f5', '#27272a')}; } .status-bar { display: flex; height: 8px; border-radius: 4px; overflow: hidden; background: ${cssManager.bdTheme('#f4f4f5', '#27272a')}; margin-bottom: 8px; } .status-segment { height: 100%; transition: width 300ms ease; } .status-2xx { background: ${cssManager.bdTheme('#22c55e', '#22c55e')}; } .status-3xx { background: ${cssManager.bdTheme('#3b82f6', '#60a5fa')}; } .status-4xx { background: ${cssManager.bdTheme('#facc15', '#facc15')}; } .status-5xx { background: ${cssManager.bdTheme('#ef4444', '#ef4444')}; } .status-legend { display: flex; justify-content: space-between; } .legend-item { font-size: 12px; color: ${cssManager.bdTheme('#71717a', '#a1a1aa')}; } `, ]; public render(): TemplateResult { const total = this.data.status2xx + this.data.status3xx + this.data.status4xx + this.data.status5xx; const p2xx = total > 0 ? (this.data.status2xx / total) * 100 : 0; const p3xx = total > 0 ? (this.data.status3xx / total) * 100 : 0; const p4xx = total > 0 ? (this.data.status4xx / total) * 100 : 0; const p5xx = total > 0 ? (this.data.status5xx / total) * 100 : 0; return html`
Traffic (Last Hour)
Request metrics from access logs
Requests ${this.formatNumber(this.data.requests)}
Errors ${this.data.errors} (${this.data.errorPercent}%)
Avg Response ${this.data.avgResponse}ms
Req/min ${this.data.reqPerMin}
2xx 3xx 4xx 5xx
`; } private formatNumber(num: number): string { if (num >= 1000000) return (num / 1000000).toFixed(1) + 'M'; if (num >= 1000) return (num / 1000).toFixed(1) + 'K'; return num.toString(); } }