/** * Status Drawer - Slide-out panel from right side * Shows ONLY: Resources, Endpoints, Providers status * Real-time updates, NO CPU/Memory stats */ class StatusDrawer { constructor(options = {}) { this.options = { apiEndpoint: options.apiEndpoint || '/api/system/status', updateInterval: options.updateInterval || 3000, // 3 seconds ...options }; this.isOpen = false; this.pollTimer = null; this.lastData = null; this.drawerElement = null; this.buttonElement = null; this.createDrawer(); this.createFloatingButton(); } /** * Create floating button */ createFloatingButton() { const button = document.createElement('button'); button.id = 'status-drawer-btn'; button.className = 'status-drawer-floating-btn'; button.setAttribute('aria-label', 'Open status panel'); button.innerHTML = ` `; button.addEventListener('click', () => this.toggle()); document.body.appendChild(button); this.buttonElement = button; } /** * Create drawer panel - ENHANCED with detailed provider metrics */ createDrawer() { const drawer = document.createElement('div'); drawer.id = 'status-drawer'; drawer.className = 'status-drawer status-drawer-enhanced'; drawer.innerHTML = `

System Status

All Providers
Loading...
AI Models
Loading...
Infrastructure
Loading...
Resource Breakdown
Loading...
Recent Errors
Performance
Loading...
`; document.body.appendChild(drawer); this.drawerElement = drawer; // Close button drawer.querySelector('.drawer-close').addEventListener('click', () => this.close()); // Refresh button drawer.querySelector('#refresh-status').addEventListener('click', () => this.fetchStatus()); // Collapsible sections drawer.querySelectorAll('.section-title.collapsible').forEach(title => { title.addEventListener('click', (e) => { const target = title.dataset.target; const content = document.getElementById(target); if (content) { content.classList.toggle('collapsed'); title.classList.toggle('collapsed'); } }); }); } /** * Toggle drawer */ toggle() { if (this.isOpen) { this.close(); } else { this.open(); } } /** * Open drawer */ open() { if (this.isOpen) return; this.isOpen = true; this.drawerElement.classList.add('open'); this.buttonElement.classList.add('hidden'); // Start polling this.startPolling(); } /** * Close drawer */ close() { if (!this.isOpen) return; this.isOpen = false; this.drawerElement.classList.remove('open'); this.buttonElement.classList.remove('hidden'); // Stop polling this.stopPolling(); } /** * Start polling (only when open) */ startPolling() { if (!this.isOpen) return; this.fetchStatus(); this.pollTimer = setTimeout(() => this.startPolling(), this.options.updateInterval); } /** * Stop polling */ stopPolling() { if (this.pollTimer) { clearTimeout(this.pollTimer); this.pollTimer = null; } } /** * Fetch status from API */ async fetchStatus() { if (!this.isOpen) return; try { const response = await fetch(this.options.apiEndpoint); if (!response.ok) throw new Error(`HTTP ${response.status}`); const data = await response.json(); this.updateUI(data); } catch (error) { console.error('Status Drawer: Failed to fetch:', error); this.showError(); } } /** * Update UI with data - ENHANCED */ updateUI(data) { this.lastData = data; // Update all providers with detailed metrics this.updateProvidersDetailed(data.providers_detailed || data.services || []); // Update AI models this.updateAIModels(data.ai_models || {}); // Update infrastructure this.updateInfrastructure(data.infrastructure || {}); // Update resource breakdown this.updateResourceBreakdown(data.resource_breakdown || {}); // Update error details this.updateErrorDetails(data.error_details || []); // Update performance this.updatePerformance(data.performance || {}); // Update timestamp this.updateTimestamp(data.timestamp); } /** * Update providers with detailed metrics */ updateProvidersDetailed(providers) { const container = document.getElementById('providers-list'); if (!container) return; if (!providers.length) { container.innerHTML = '
No providers configured
'; return; } container.innerHTML = providers.map(provider => { const isOnline = provider.status === 'online' || provider.status === 'active'; const statusEmoji = isOnline ? '🟢' : provider.status === 'rate_limited' ? '🔴' : provider.status === 'degraded' ? '🟡' : '⚫'; let statusText = ''; if (isOnline) { statusText = `${provider.response_time_ms || 0}ms | Success: ${provider.success_rate || 100}%`; if (provider.last_check) { const elapsed = Math.floor((Date.now() / 1000) - new Date(provider.last_check).getTime() / 1000); statusText += ` | Last: ${elapsed}s ago`; } } else if (provider.status === 'rate_limited') { statusText = `Rate Limited (${provider.status_code || 429})`; if (provider.cached_until) { statusText += ` | Cached ${provider.cached_until}`; } } else if (provider.status === 'degraded') { statusText = provider.error || 'Degraded performance'; } else { statusText = provider.error || 'Offline'; } const resourceInfo = provider.resource_count ? ` | ${provider.resource_count} resources` : ''; return `
${statusEmoji} ${provider.name}
${statusText}${resourceInfo}
`; }).join(''); } /** * Update AI models section */ updateAIModels(aiModels) { const container = document.getElementById('ai-models-list'); if (!container) return; const transformersStatus = aiModels.transformers_loaded ? '🟢 Loaded (CPU mode)' : '🔴 Not loaded'; const sentimentModels = aiModels.sentiment_models || 0; const hfApiStatus = aiModels.hf_api_active ? '🟢 Active' : '🔴 Inactive'; container.innerHTML = `
Transformers: ${transformersStatus}
Sentiment Models: ${sentimentModels} available
HuggingFace API: ${hfApiStatus}
`; } /** * Update infrastructure section */ updateInfrastructure(infrastructure) { const container = document.getElementById('infrastructure-list'); if (!container) return; const dbStatus = infrastructure.database_status || 'unknown'; const dbEntries = infrastructure.database_entries || 0; const workerStatus = infrastructure.background_worker || 'unknown'; const workerNextRun = infrastructure.worker_next_run || 'N/A'; const wsStatus = infrastructure.websocket_active ? '🟢 Active' : '⚫ Inactive'; container.innerHTML = `
Database: ${dbStatus === 'online' ? '🟢' : '🔴'} SQLite (${dbEntries} cached)
Background Worker: ${workerStatus === 'active' ? '🟢' : '⚫'} ${workerNextRun}
WebSocket: ${wsStatus}
`; } /** * Update resource breakdown section */ updateResourceBreakdown(breakdown) { const container = document.getElementById('resources-breakdown'); if (!container) return; const total = breakdown.total || 0; const bySource = breakdown.by_source || {}; const byCategory = breakdown.by_category || {}; let sourceHTML = ''; for (const [source, count] of Object.entries(bySource)) { sourceHTML += `
${source}: ${count}
`; } let categoryHTML = ''; for (const [category, count] of Object.entries(byCategory)) { categoryHTML += `
${category}: ${count} online
`; } container.innerHTML = `
Total: ${total}+ resources
${sourceHTML}
By Category:
${categoryHTML}
`; } /** * Update error details section */ updateErrorDetails(errors) { const container = document.getElementById('error-list'); if (!container) return; if (!errors || errors.length === 0) { container.innerHTML = '
No recent errors
'; return; } container.innerHTML = errors.map(error => `
${error.provider || 'Unknown'}: ${error.count || 1}x ${error.type || 'error'}
${error.message || 'Unknown error'}
${error.action ? `
Action: ${error.action}
` : ''}
`).join(''); } /** * Update performance section */ updatePerformance(performance) { const container = document.getElementById('performance-metrics'); if (!container) return; const avgResponse = performance.avg_response_ms || 0; const fastest = performance.fastest_provider || 'N/A'; const fastestTime = performance.fastest_time_ms || 0; const cacheHit = performance.cache_hit_rate || 0; container.innerHTML = `
Avg Response: ${avgResponse}ms
Fastest: ${fastest} (${fastestTime}ms)
Cache Hit: ${cacheHit}%
`; } /** * Update endpoints */ updateEndpoints(endpoints) { const container = document.getElementById('endpoints-status'); if (!container) return; if (!endpoints.length) { container.innerHTML = '
No endpoints
'; return; } container.innerHTML = endpoints.map(endpoint => { const statusClass = endpoint.status === 'online' ? 'status-online' : 'status-offline'; return `
${endpoint.path}
${endpoint.avg_response_ms ? `${endpoint.avg_response_ms.toFixed(0)}ms` : '--'} • ${endpoint.success_rate ? `${endpoint.success_rate.toFixed(1)}%` : '--'}
`; }).join(''); } /** * Update providers */ updateProviders(services) { const container = document.getElementById('providers-status'); if (!container) return; if (!services.length) { container.innerHTML = '
No providers
'; return; } container.innerHTML = services.map(service => { const statusClass = service.status === 'online' ? 'status-online' : 'status-offline'; return `
${service.name}
${service.response_time_ms ? `${service.response_time_ms.toFixed(0)}ms` : 'Offline'}
`; }).join(''); } /** * Update coins */ updateCoins(coins) { const container = document.getElementById('coins-status'); if (!container) return; if (!coins.length) { container.innerHTML = '
No coins
'; return; } container.innerHTML = coins.map(coin => { const statusClass = coin.status === 'online' ? 'status-online' : 'status-offline'; return `
${coin.symbol}
${coin.price ? `$${coin.price.toLocaleString()}` : 'Unavailable'}
`; }).join(''); } /** * Update timestamp */ updateTimestamp(timestamp) { const element = document.getElementById('last-update-time'); if (element) { const date = new Date(timestamp * 1000); element.textContent = date.toLocaleTimeString(); } } /** * Show error state */ showError() { const sections = ['resources-summary', 'endpoints-status', 'providers-status', 'coins-status']; sections.forEach(id => { const element = document.getElementById(id); if (element) { element.innerHTML = '
Failed to load
'; } }); } /** * Destroy drawer */ destroy() { this.stopPolling(); if (this.drawerElement) this.drawerElement.remove(); if (this.buttonElement) this.buttonElement.remove(); } } // Export if (typeof window !== 'undefined') { window.StatusDrawer = StatusDrawer; }