// Dashboard Tab Component import { healthService } from '../services/health.service.js'; import { poseService } from '../services/pose.service.js'; export class DashboardTab { constructor(containerElement) { this.container = containerElement; this.statsElements = {}; this.healthSubscription = null; this.statsInterval = null; } // Initialize component async init() { this.cacheElements(); await this.loadInitialData(); this.startMonitoring(); } // Cache DOM elements cacheElements() { // System stats const statsContainer = this.container.querySelector('.system-stats'); if (statsContainer) { this.statsElements = { bodyRegions: statsContainer.querySelector('[data-stat="body-regions"] .stat-value'), samplingRate: statsContainer.querySelector('[data-stat="sampling-rate"] .stat-value'), accuracy: statsContainer.querySelector('[data-stat="accuracy"] .stat-value'), hardwareCost: statsContainer.querySelector('[data-stat="hardware-cost"] .stat-value') }; } // Status indicators this.statusElements = { apiStatus: this.container.querySelector('.api-status'), streamStatus: this.container.querySelector('.stream-status'), hardwareStatus: this.container.querySelector('.hardware-status') }; } // Load initial data async loadInitialData() { try { // Get API info const info = await healthService.getApiInfo(); this.updateApiInfo(info); // Get current stats const stats = await poseService.getStats(1); this.updateStats(stats); } catch (error) { console.error('Failed to load dashboard data:', error); this.showError('Failed to load dashboard data'); } } // Start monitoring startMonitoring() { // Subscribe to health updates this.healthSubscription = healthService.subscribeToHealth(health => { this.updateHealthStatus(health); }); // Start periodic stats updates this.statsInterval = setInterval(() => { this.updateLiveStats(); }, 5000); // Start health monitoring healthService.startHealthMonitoring(30000); } // Update API info display updateApiInfo(info) { // Update version const versionElement = this.container.querySelector('.api-version'); if (versionElement && info.version) { versionElement.textContent = `v${info.version}`; } // Update environment const envElement = this.container.querySelector('.api-environment'); if (envElement && info.environment) { envElement.textContent = info.environment; envElement.className = `api-environment env-${info.environment}`; } // Update features status if (info.features) { this.updateFeatures(info.features); } } // Update features display updateFeatures(features) { const featuresContainer = this.container.querySelector('.features-status'); if (!featuresContainer) return; featuresContainer.innerHTML = ''; Object.entries(features).forEach(([feature, enabled]) => { const featureElement = document.createElement('div'); featureElement.className = `feature-item ${enabled ? 'enabled' : 'disabled'}`; featureElement.innerHTML = ` ${this.formatFeatureName(feature)} ${enabled ? '✓' : '✗'} `; featuresContainer.appendChild(featureElement); }); } // Update health status updateHealthStatus(health) { if (!health) return; // Update overall status const overallStatus = this.container.querySelector('.overall-health'); if (overallStatus) { overallStatus.className = `overall-health status-${health.status}`; overallStatus.textContent = health.status.toUpperCase(); } // Update component statuses if (health.components) { Object.entries(health.components).forEach(([component, status]) => { this.updateComponentStatus(component, status); }); } // Update metrics if (health.metrics) { this.updateSystemMetrics(health.metrics); } } // Update component status updateComponentStatus(component, status) { const element = this.container.querySelector(`[data-component="${component}"]`); if (element) { element.className = `component-status status-${status.status}`; element.querySelector('.status-text').textContent = status.status; if (status.message) { element.querySelector('.status-message').textContent = status.message; } } } // Update system metrics updateSystemMetrics(metrics) { // CPU usage const cpuElement = this.container.querySelector('.cpu-usage'); if (cpuElement && metrics.cpu_percent !== undefined) { cpuElement.textContent = `${metrics.cpu_percent.toFixed(1)}%`; this.updateProgressBar('cpu', metrics.cpu_percent); } // Memory usage const memoryElement = this.container.querySelector('.memory-usage'); if (memoryElement && metrics.memory_percent !== undefined) { memoryElement.textContent = `${metrics.memory_percent.toFixed(1)}%`; this.updateProgressBar('memory', metrics.memory_percent); } // Disk usage const diskElement = this.container.querySelector('.disk-usage'); if (diskElement && metrics.disk_percent !== undefined) { diskElement.textContent = `${metrics.disk_percent.toFixed(1)}%`; this.updateProgressBar('disk', metrics.disk_percent); } } // Update progress bar updateProgressBar(type, percent) { const progressBar = this.container.querySelector(`.progress-bar[data-type="${type}"]`); if (progressBar) { const fill = progressBar.querySelector('.progress-fill'); if (fill) { fill.style.width = `${percent}%`; fill.className = `progress-fill ${this.getProgressClass(percent)}`; } } } // Get progress class based on percentage getProgressClass(percent) { if (percent >= 90) return 'critical'; if (percent >= 75) return 'warning'; return 'normal'; } // Update live statistics async updateLiveStats() { try { // Get current pose data const currentPose = await poseService.getCurrentPose(); this.updatePoseStats(currentPose); // Get zones summary const zonesSummary = await poseService.getZonesSummary(); this.updateZonesDisplay(zonesSummary); } catch (error) { console.error('Failed to update live stats:', error); } } // Update pose statistics updatePoseStats(poseData) { if (!poseData) return; // Update person count const personCount = this.container.querySelector('.person-count'); if (personCount) { personCount.textContent = poseData.total_persons || 0; } // Update average confidence const avgConfidence = this.container.querySelector('.avg-confidence'); if (avgConfidence && poseData.persons) { const confidences = poseData.persons.map(p => p.confidence); const avg = confidences.length > 0 ? (confidences.reduce((a, b) => a + b, 0) / confidences.length * 100).toFixed(1) : 0; avgConfidence.textContent = `${avg}%`; } } // Update zones display updateZonesDisplay(zonesSummary) { const zonesContainer = this.container.querySelector('.zones-summary'); if (!zonesContainer || !zonesSummary) return; zonesContainer.innerHTML = ''; Object.entries(zonesSummary.zones).forEach(([zoneId, data]) => { const zoneElement = document.createElement('div'); zoneElement.className = 'zone-item'; zoneElement.innerHTML = ` ${data.name || zoneId} ${data.person_count} `; zonesContainer.appendChild(zoneElement); }); } // Update statistics updateStats(stats) { if (!stats) return; // Update detection count const detectionCount = this.container.querySelector('.detection-count'); if (detectionCount && stats.total_detections !== undefined) { detectionCount.textContent = this.formatNumber(stats.total_detections); } // Update accuracy if available if (this.statsElements.accuracy && stats.average_confidence !== undefined) { this.statsElements.accuracy.textContent = `${(stats.average_confidence * 100).toFixed(1)}%`; } } // Format feature name formatFeatureName(name) { return name.replace(/_/g, ' ') .split(' ') .map(word => word.charAt(0).toUpperCase() + word.slice(1)) .join(' '); } // Format large numbers formatNumber(num) { if (num >= 1000000) { return `${(num / 1000000).toFixed(1)}M`; } if (num >= 1000) { return `${(num / 1000).toFixed(1)}K`; } return num.toString(); } // Show error message showError(message) { const errorContainer = this.container.querySelector('.error-container'); if (errorContainer) { errorContainer.textContent = message; errorContainer.style.display = 'block'; setTimeout(() => { errorContainer.style.display = 'none'; }, 5000); } } // Clean up dispose() { if (this.healthSubscription) { this.healthSubscription(); } if (this.statsInterval) { clearInterval(this.statsInterval); } healthService.stopHealthMonitoring(); } }