335 lines
9.6 KiB
TypeScript
335 lines
9.6 KiB
TypeScript
import { ref, computed } 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
|
|
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 = 'http://localhost:8000/api/v1/admin' // Should come from environment config
|
|
private delay = (ms: number) => new Promise(resolve => setTimeout(resolve, ms))
|
|
|
|
// Get health metrics
|
|
async getHealthMetrics(): Promise<HealthMetrics> {
|
|
// In a real implementation, this would call the actual API
|
|
// const response = await fetch(`${this.baseUrl}/health-monitor`, {
|
|
// headers: this.getAuthHeaders()
|
|
// })
|
|
//
|
|
// if (!response.ok) {
|
|
// throw new Error(`HTTP ${response.status}: ${response.statusText}`)
|
|
// }
|
|
//
|
|
// return await response.json()
|
|
|
|
await this.delay(800) // Simulate network delay
|
|
|
|
// For now, return mock data
|
|
return generateMockMetrics()
|
|
}
|
|
|
|
// Get system alerts
|
|
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()
|
|
|
|
// Computed properties
|
|
const systemStatusColor = computed(() => {
|
|
if (!state.value.metrics) return 'grey'
|
|
|
|
switch (state.value.metrics.system_status) {
|
|
case 'healthy': return 'green'
|
|
case 'degraded': return 'orange'
|
|
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)
|
|
|
|
// Fallback to mock data
|
|
state.value.metrics = generateMockMetrics()
|
|
} 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
|
|
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)
|
|
}
|
|
|
|
// Initialize
|
|
const initialize = () => {
|
|
refreshAll()
|
|
}
|
|
|
|
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,
|
|
|
|
// Helper functions
|
|
getAlertColor: (severity: SystemAlert['severity']) => {
|
|
switch (severity) {
|
|
case 'info': return 'blue'
|
|
case 'warning': return 'orange'
|
|
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' })
|
|
}
|
|
}
|
|
}
|
|
|
|
export default useHealthMonitor |