2026.06.04 frontend építés közben
This commit is contained in:
@@ -0,0 +1,390 @@
|
||||
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<HealthMetrics> {
|
||||
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<SystemAlert[]> {
|
||||
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<string, string> {
|
||||
const authStore = useAuthStore()
|
||||
const headers: Record<string, string> = {
|
||||
'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<HealthMonitorState>({
|
||||
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' })
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user