import * as plugins from '../plugins.js'; import * as shared from './shared/index.js'; import * as appstate from '../appstate.js'; import { DeesElement, customElement, html, state, css, cssManager, } from '@design.estate/dees-element'; @customElement('ops-view-stats') export class OpsViewStats extends DeesElement { @state() private statsState: appstate.IStatsState = { serverStats: null, emailStats: null, dnsStats: null, securityMetrics: null, lastUpdated: 0, isLoading: false, error: null, }; @state() private uiState: appstate.IUiState = { activeView: 'dashboard', sidebarCollapsed: false, autoRefresh: true, refreshInterval: 30000, theme: 'light', }; constructor() { super(); const statsSubscription = appstate.statsStatePart .select((stateArg) => stateArg) .subscribe((statsState) => { this.statsState = statsState; }); this.rxSubscriptions.push(statsSubscription); const uiSubscription = appstate.uiStatePart .select((stateArg) => stateArg) .subscribe((uiState) => { this.uiState = uiState; }); this.rxSubscriptions.push(uiSubscription); } public static styles = [ cssManager.defaultStyles, shared.viewHostCss, css` .controls { display: flex; justify-content: space-between; align-items: center; margin-bottom: 24px; padding: 16px; background: #f8f9fa; border-radius: 8px; } .refreshButton { display: flex; align-items: center; gap: 8px; } .lastUpdated { font-size: 14px; color: #666; } .statsSection { margin-bottom: 48px; } .sectionTitle { font-size: 24px; font-weight: 600; margin-bottom: 24px; color: #333; } .metricsGrid { display: grid; grid-template-columns: repeat(auto-fit, minmax(250px, 1fr)); gap: 16px; } .metricCard { background: white; border: 1px solid #e9ecef; border-radius: 8px; padding: 20px; transition: all 0.2s ease; } .metricCard:hover { box-shadow: 0 4px 12px rgba(0,0,0,0.1); transform: translateY(-2px); } .metricLabel { font-size: 14px; color: #666; margin-bottom: 8px; } .metricValue { font-size: 28px; font-weight: 700; color: #2196F3; } .metricUnit { font-size: 16px; color: #999; margin-left: 4px; } .chartContainer { background: white; border: 1px solid #e9ecef; border-radius: 8px; padding: 24px; margin-top: 24px; } `, ]; public render() { return html` Statistics
appstate.statsStatePart.dispatchAction(appstate.fetchAllStatsAction, null)} .disabled=${this.statsState.isLoading} > ${this.statsState.isLoading ? html`` : 'Refresh'} appstate.uiStatePart.dispatchAction(appstate.toggleAutoRefreshAction, null)} .type=${this.uiState.autoRefresh ? 'highlighted' : 'normal'} > Auto-refresh: ${this.uiState.autoRefresh ? 'ON' : 'OFF'}
${this.statsState.lastUpdated ? html` Last updated: ${new Date(this.statsState.lastUpdated).toLocaleTimeString()} ` : ''}
${this.statsState.serverStats ? html`

Server Metrics

Uptime
${this.formatUptime(this.statsState.serverStats.uptime)}
CPU Usage
${Math.round((this.statsState.serverStats.cpuUsage.user + this.statsState.serverStats.cpuUsage.system) / 2)}%
Memory Used
${this.formatBytes(this.statsState.serverStats.memoryUsage.heapUsed)}
Active Connections
${this.statsState.serverStats.activeConnections}
` : ''} ${this.statsState.emailStats ? html`

Email Statistics

({ Metric: item.metric, Value: `${item.value} ${item.unit}`, })} >
` : ''} ${this.statsState.dnsStats ? html`

DNS Statistics

Total Queries
${this.formatNumber(this.statsState.dnsStats.totalQueries)}
Cache Hit Rate
${Math.round(this.statsState.dnsStats.cacheHitRate * 100)}%
Average Response Time
${this.statsState.dnsStats.averageResponseTime}ms
Domains Configured
${this.statsState.dnsStats.activeDomains}
` : ''} ${this.statsState.securityMetrics ? html`

Security Metrics

({ 'Security Metric': item.metric, 'Count': item.value, 'Severity': item.severity, })} >
` : ''} `; } private formatUptime(seconds: number): string { const days = Math.floor(seconds / 86400); const hours = Math.floor((seconds % 86400) / 3600); const minutes = Math.floor((seconds % 3600) / 60); if (days > 0) { return `${days}d ${hours}h`; } else if (hours > 0) { return `${hours}h ${minutes}m`; } else { return `${minutes}m`; } } private formatBytes(bytes: number): string { const units = ['B', 'KB', 'MB', 'GB', 'TB']; let size = bytes; let unitIndex = 0; while (size >= 1024 && unitIndex < units.length - 1) { size /= 1024; unitIndex++; } return `${size.toFixed(1)} ${units[unitIndex]}`; } private formatNumber(num: number): string { if (num >= 1000000) { return `${(num / 1000000).toFixed(1)}M`; } else if (num >= 1000) { return `${(num / 1000).toFixed(1)}K`; } return num.toString(); } }