import { ref, computed, onUnmounted } from 'vue' import { useAuthStore } from '~/stores/auth' // Types export interface HealthMetrics { total_assets: number total_organizations: number critical_alerts_24h: number system_status: 'healthy' | 'degraded' | 'critical' uptime_percentage: number response_time_ms: number database_connections: number active_users: number last_updated: string } export interface SystemAlert { id: string severity: 'info' | 'warning' | 'critical' title: string description: string timestamp: string component: string resolved: boolean } export interface HealthMonitorState { metrics: HealthMetrics | null alerts: SystemAlert[] loading: boolean error: string | null lastUpdated: Date | null } // Mock data for development/testing (only for alerts since no backend endpoint yet) const generateMockMetrics = (): HealthMetrics => { return { total_assets: Math.floor(Math.random() * 10000) + 5000, total_organizations: Math.floor(Math.random() * 500) + 100, critical_alerts_24h: Math.floor(Math.random() * 10), system_status: Math.random() > 0.8 ? 'degraded' : Math.random() > 0.95 ? 'critical' : 'healthy', uptime_percentage: 99.5 + (Math.random() * 0.5 - 0.25), // 99.25% - 99.75% response_time_ms: Math.floor(Math.random() * 100) + 50, database_connections: Math.floor(Math.random() * 50) + 10, active_users: Math.floor(Math.random() * 1000) + 500, last_updated: new Date().toISOString() } } const generateMockAlerts = (count: number = 5): SystemAlert[] => { const severities: SystemAlert['severity'][] = ['info', 'warning', 'critical'] const components = ['Database', 'API Gateway', 'Redis', 'PostgreSQL', 'Docker', 'Network', 'Authentication', 'File Storage'] const titles = [ 'High memory usage detected', 'Database connection pool exhausted', 'API response time above threshold', 'Redis cache miss rate increased', 'Disk space running low', 'Network latency spike', 'Authentication service slow response', 'Backup job failed' ] const alerts: SystemAlert[] = [] for (let i = 0; i < count; i++) { const severity = severities[Math.floor(Math.random() * severities.length)] const isResolved = Math.random() > 0.7 alerts.push({ id: `alert_${Date.now()}_${i}`, severity, title: titles[Math.floor(Math.random() * titles.length)], description: `Detailed description of the ${severity} alert in the ${components[Math.floor(Math.random() * components.length)]} component.`, timestamp: new Date(Date.now() - Math.random() * 24 * 60 * 60 * 1000).toISOString(), // Within last 24 hours component: components[Math.floor(Math.random() * components.length)], resolved: isResolved }) } return alerts } // API Service class HealthMonitorApiService { private baseUrl = '/api/v1/admin' // Using proxy from nuxt.config.ts private delay = (ms: number) => new Promise(resolve => setTimeout(resolve, ms)) // Get health metrics async getHealthMetrics(): Promise { try { console.log('Fetching health metrics from:', `${this.baseUrl}/health-monitor`) const response = await fetch(`${this.baseUrl}/health-monitor`, { headers: this.getAuthHeaders() }) if (!response.ok) { const errorText = await response.text() console.error('Health monitor API error:', response.status, response.statusText, errorText) // Specific error handling if (response.status === 401) { throw new Error('Authentication required. Please log in again.') } else if (response.status === 403) { throw new Error('Access forbidden. Admin privileges required.') } else if (response.status === 500) { throw new Error('Server error. Please try again later.') } else { throw new Error(`HTTP ${response.status}: ${response.statusText} - ${errorText}`) } } const data = await response.json() console.log('Health monitor API response:', data) // Transform API response to match HealthMetrics interface return { total_assets: data.total_assets || 0, total_organizations: data.total_organizations || 0, critical_alerts_24h: data.critical_alerts_24h || 0, system_status: 'healthy', // Default, API doesn't return this yet uptime_percentage: 99.9, // Default, API doesn't return this yet response_time_ms: 50, // Default, API doesn't return this yet database_connections: 0, // Default, API doesn't return this yet active_users: data.user_distribution ? Object.values(data.user_distribution).reduce((a: number, b: number) => a + b, 0) : 0, last_updated: new Date().toISOString() } } catch (error) { console.error('Failed to fetch real health metrics:', error) throw error // Don't fall back to mock data - let the caller handle it } } // Get system alerts (mocked for now - no backend endpoint) async getSystemAlerts(options?: { severity?: SystemAlert['severity'] resolved?: boolean limit?: number }): Promise { await this.delay(500) let alerts = generateMockAlerts(10) if (options?.severity) { alerts = alerts.filter(alert => alert.severity === options.severity) } if (options?.resolved !== undefined) { alerts = alerts.filter(alert => alert.resolved === options.resolved) } if (options?.limit) { alerts = alerts.slice(0, options.limit) } return alerts } // Get auth headers (for real API calls) private getAuthHeaders(): Record { const authStore = useAuthStore() const headers: Record = { 'Content-Type': 'application/json' } if (authStore.token) { headers['Authorization'] = `Bearer ${authStore.token}` } // Add geographical scope headers if (authStore.getScopeId) { headers['X-Scope-Id'] = authStore.getScopeId.toString() } if (authStore.getRegionCode) { headers['X-Region-Code'] = authStore.getRegionCode } if (authStore.getScopeLevel) { headers['X-Scope-Level'] = authStore.getScopeLevel } return headers } } // Composable export const useHealthMonitor = () => { const state = ref({ metrics: null, alerts: [], loading: false, error: null, lastUpdated: null }) const apiService = new HealthMonitorApiService() let refreshInterval: NodeJS.Timeout | null = null // Computed properties const systemStatusColor = computed(() => { if (!state.value.metrics) return 'grey' switch (state.value.metrics.system_status) { case 'healthy': return 'green' case 'degraded': return 'dark-blue' // Changed from orange to dark-blue for better contrast case 'critical': return 'red' default: return 'grey' } }) const systemStatusIcon = computed(() => { if (!state.value.metrics) return 'mdi-help-circle' switch (state.value.metrics.system_status) { case 'healthy': return 'mdi-check-circle' case 'degraded': return 'mdi-alert-circle' case 'critical': return 'mdi-alert-octagon' default: return 'mdi-help-circle' } }) const criticalAlerts = computed(() => { return state.value.alerts.filter(alert => alert.severity === 'critical' && !alert.resolved) }) const warningAlerts = computed(() => { return state.value.alerts.filter(alert => alert.severity === 'warning' && !alert.resolved) }) const formattedUptime = computed(() => { if (!state.value.metrics) return 'N/A' return `${state.value.metrics.uptime_percentage.toFixed(2)}%` }) const formattedResponseTime = computed(() => { if (!state.value.metrics) return 'N/A' return `${state.value.metrics.response_time_ms}ms` }) // Actions const fetchHealthMetrics = async () => { state.value.loading = true state.value.error = null try { const metrics = await apiService.getHealthMetrics() state.value.metrics = metrics state.value.lastUpdated = new Date() } catch (error) { state.value.error = error instanceof Error ? error.message : 'Failed to fetch health metrics' console.error('Error fetching health metrics:', error) // NO FALLBACK TO MOCK DATA - let error propagate } finally { state.value.loading = false } } const fetchSystemAlerts = async (options?: { severity?: SystemAlert['severity'] resolved?: boolean limit?: number }) => { state.value.loading = true state.value.error = null try { const alerts = await apiService.getSystemAlerts(options) state.value.alerts = alerts } catch (error) { state.value.error = error instanceof Error ? error.message : 'Failed to fetch system alerts' console.error('Error fetching system alerts:', error) // Fallback to mock data for alerts (since no real endpoint yet) state.value.alerts = generateMockAlerts(5) } finally { state.value.loading = false } } const refreshAll = async () => { await Promise.all([ fetchHealthMetrics(), fetchSystemAlerts() ]) } const markAlertAsResolved = async (alertId: string) => { // In a real implementation, this would call an API endpoint // await apiService.resolveAlert(alertId) // Update local state const alertIndex = state.value.alerts.findIndex(alert => alert.id === alertId) if (alertIndex !== -1) { state.value.alerts[alertIndex].resolved = true } } const dismissAlert = (alertId: string) => { // Remove alert from local state (frontend only) state.value.alerts = state.value.alerts.filter(alert => alert.id !== alertId) } // Start automatic refresh (30-second interval) const startPolling = (intervalMs: number = 30000) => { stopPolling() // Clear any existing interval refreshInterval = setInterval(() => { console.log('Auto-refreshing health monitor data...') refreshAll() }, intervalMs) console.log(`Health monitor polling started with ${intervalMs}ms interval`) } // Stop automatic refresh const stopPolling = () => { if (refreshInterval) { clearInterval(refreshInterval) refreshInterval = null console.log('Health monitor polling stopped') } } // Initialize with polling const initialize = (enablePolling: boolean = true) => { refreshAll() if (enablePolling) { startPolling() } } // Cleanup on unmount onUnmounted(() => { stopPolling() }) return { // State state: computed(() => state.value), metrics: computed(() => state.value.metrics), alerts: computed(() => state.value.alerts), loading: computed(() => state.value.loading), error: computed(() => state.value.error), lastUpdated: computed(() => state.value.lastUpdated), // Computed systemStatusColor, systemStatusIcon, criticalAlerts, warningAlerts, formattedUptime, formattedResponseTime, // Actions fetchHealthMetrics, fetchSystemAlerts, refreshAll, markAlertAsResolved, dismissAlert, initialize, startPolling, stopPolling, // Helper functions getAlertColor: (severity: SystemAlert['severity']) => { switch (severity) { case 'info': return 'blue' case 'warning': return 'dark-blue' // Changed from orange to dark-blue case 'critical': return 'red' default: return 'grey' } }, getAlertIcon: (severity: SystemAlert['severity']) => { switch (severity) { case 'info': return 'mdi-information' case 'warning': return 'mdi-alert' case 'critical': return 'mdi-alert-circle' default: return 'mdi-help-circle' } }, formatTimestamp: (timestamp: string) => { const date = new Date(timestamp) return date.toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' }) } } }