admin firs step
This commit is contained in:
590
frontend/admin/components/SystemHealthTile.vue
Normal file
590
frontend/admin/components/SystemHealthTile.vue
Normal file
@@ -0,0 +1,590 @@
|
||||
<template>
|
||||
<v-card
|
||||
color="blue-grey-darken-1"
|
||||
variant="tonal"
|
||||
class="h-100 d-flex flex-column"
|
||||
>
|
||||
<v-card-title class="d-flex align-center justify-space-between">
|
||||
<div class="d-flex align-center">
|
||||
<v-icon icon="mdi-server" class="mr-2"></v-icon>
|
||||
<span class="text-subtitle-1 font-weight-bold">System Health</span>
|
||||
</div>
|
||||
<div class="d-flex align-center">
|
||||
<v-chip size="small" :color="overallStatusColor" class="mr-2">
|
||||
<v-icon :icon="overallStatusIcon" size="small" class="mr-1"></v-icon>
|
||||
{{ overallStatusText }}
|
||||
</v-chip>
|
||||
<v-menu>
|
||||
<template v-slot:activator="{ props }">
|
||||
<v-btn
|
||||
size="x-small"
|
||||
variant="text"
|
||||
v-bind="props"
|
||||
class="text-caption"
|
||||
>
|
||||
{{ selectedEnvironment }}
|
||||
<v-icon icon="mdi-chevron-down" size="small"></v-icon>
|
||||
</v-btn>
|
||||
</template>
|
||||
<v-list density="compact">
|
||||
<v-list-item
|
||||
v-for="env in environmentOptions"
|
||||
:key="env.value"
|
||||
@click="selectedEnvironment = env.value"
|
||||
>
|
||||
<v-list-item-title>{{ env.label }}</v-list-item-title>
|
||||
</v-list-item>
|
||||
</v-list>
|
||||
</v-menu>
|
||||
</div>
|
||||
</v-card-title>
|
||||
|
||||
<v-card-text class="flex-grow-1 pa-0">
|
||||
<div class="pa-4">
|
||||
<!-- System Status Overview -->
|
||||
<div class="mb-4">
|
||||
<div class="text-subtitle-2 font-weight-medium mb-2">System Status</div>
|
||||
<v-row dense>
|
||||
<v-col v-for="component in systemComponents" :key="component.name" cols="6" sm="3">
|
||||
<v-card variant="outlined" class="pa-2">
|
||||
<div class="d-flex align-center justify-space-between">
|
||||
<div>
|
||||
<div class="d-flex align-center">
|
||||
<v-icon :icon="component.icon" size="small" :color="component.statusColor" class="mr-2"></v-icon>
|
||||
<span class="text-caption font-weight-medium">{{ component.name }}</span>
|
||||
</div>
|
||||
<div class="text-caption text-grey mt-1">{{ component.description }}</div>
|
||||
</div>
|
||||
<div class="text-right">
|
||||
<div class="text-caption" :class="`text-${component.statusColor}`">{{ component.status }}</div>
|
||||
<div class="text-caption text-grey">{{ component.uptime }}%</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Response Time Indicator -->
|
||||
<div v-if="component.responseTime" class="mt-2">
|
||||
<div class="d-flex justify-space-between">
|
||||
<span class="text-caption text-grey">Response</span>
|
||||
<span class="text-caption" :class="getResponseTimeColor(component.responseTime)">{{ component.responseTime }}ms</span>
|
||||
</div>
|
||||
<v-progress-linear
|
||||
:model-value="Math.min(component.responseTime / 10, 100)"
|
||||
height="4"
|
||||
:color="getResponseTimeColor(component.responseTime)"
|
||||
class="mt-1"
|
||||
></v-progress-linear>
|
||||
</div>
|
||||
</v-card>
|
||||
</v-col>
|
||||
</v-row>
|
||||
</div>
|
||||
|
||||
<!-- API Response Times Chart -->
|
||||
<div class="mb-4">
|
||||
<div class="text-subtitle-2 font-weight-medium mb-2">API Response Times (Last 24h)</div>
|
||||
<div class="chart-container" style="height: 150px;">
|
||||
<canvas ref="responseTimeChart"></canvas>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Database Metrics -->
|
||||
<div class="mb-4">
|
||||
<div class="text-subtitle-2 font-weight-medium mb-2">Database Metrics</div>
|
||||
<v-row dense>
|
||||
<v-col cols="6" sm="3">
|
||||
<v-card variant="outlined" class="pa-2 text-center">
|
||||
<div class="text-h6 font-weight-bold" :class="databaseMetrics.connections > 80 ? 'text-error' : 'text-success'">{{ databaseMetrics.connections }}</div>
|
||||
<div class="text-caption text-grey">Connections</div>
|
||||
<div class="text-caption">{{ databaseMetrics.activeConnections }} active</div>
|
||||
</v-card>
|
||||
</v-col>
|
||||
<v-col cols="6" sm="3">
|
||||
<v-card variant="outlined" class="pa-2 text-center">
|
||||
<div class="text-h6 font-weight-bold" :class="databaseMetrics.queryTime > 500 ? 'text-error' : databaseMetrics.queryTime > 200 ? 'text-warning' : 'text-success'">{{ databaseMetrics.queryTime }}ms</div>
|
||||
<div class="text-caption text-grey">Avg Query Time</div>
|
||||
<div class="text-caption">{{ databaseMetrics.queriesPerSecond }} qps</div>
|
||||
</v-card>
|
||||
</v-col>
|
||||
<v-col cols="6" sm="3">
|
||||
<v-card variant="outlined" class="pa-2 text-center">
|
||||
<div class="text-h6 font-weight-bold" :class="databaseMetrics.cacheHitRate < 80 ? 'text-error' : databaseMetrics.cacheHitRate < 90 ? 'text-warning' : 'text-success'">{{ databaseMetrics.cacheHitRate }}%</div>
|
||||
<div class="text-caption text-grey">Cache Hit Rate</div>
|
||||
<div class="text-caption">{{ formatBytes(databaseMetrics.cacheSize) }} cache</div>
|
||||
</v-card>
|
||||
</v-col>
|
||||
<v-col cols="6" sm="3">
|
||||
<v-card variant="outlined" class="pa-2 text-center">
|
||||
<div class="text-h6 font-weight-bold" :class="databaseMetrics.replicationLag > 1000 ? 'text-error' : databaseMetrics.replicationLag > 500 ? 'text-warning' : 'text-success'">{{ databaseMetrics.replicationLag }}ms</div>
|
||||
<div class="text-caption text-grey">Replication Lag</div>
|
||||
<div class="text-caption">{{ databaseMetrics.replicationStatus }}</div>
|
||||
</v-card>
|
||||
</v-col>
|
||||
</v-row>
|
||||
</div>
|
||||
|
||||
<!-- Server Resources -->
|
||||
<div>
|
||||
<div class="text-subtitle-2 font-weight-medium mb-2">Server Resources</div>
|
||||
<v-row dense>
|
||||
<v-col cols="12" sm="6">
|
||||
<v-card variant="outlined" class="pa-3">
|
||||
<div class="d-flex justify-space-between align-center mb-2">
|
||||
<span class="text-caption font-weight-medium">CPU Usage</span>
|
||||
<span class="text-caption" :class="serverResources.cpu > 80 ? 'text-error' : serverResources.cpu > 60 ? 'text-warning' : 'text-success'">{{ serverResources.cpu }}%</span>
|
||||
</div>
|
||||
<v-progress-linear
|
||||
:model-value="serverResources.cpu"
|
||||
height="8"
|
||||
:color="serverResources.cpu > 80 ? 'error' : serverResources.cpu > 60 ? 'warning' : 'success'"
|
||||
rounded
|
||||
></v-progress-linear>
|
||||
<div class="text-caption text-grey mt-1">{{ serverResources.cpuCores }} cores @ {{ serverResources.cpuFrequency }}GHz</div>
|
||||
</v-card>
|
||||
</v-col>
|
||||
<v-col cols="12" sm="6">
|
||||
<v-card variant="outlined" class="pa-3">
|
||||
<div class="d-flex justify-space-between align-center mb-2">
|
||||
<span class="text-caption font-weight-medium">Memory Usage</span>
|
||||
<span class="text-caption" :class="serverResources.memory > 80 ? 'text-error' : serverResources.memory > 60 ? 'text-warning' : 'text-success'">{{ serverResources.memory }}%</span>
|
||||
</div>
|
||||
<v-progress-linear
|
||||
:model-value="serverResources.memory"
|
||||
height="8"
|
||||
:color="serverResources.memory > 80 ? 'error' : serverResources.memory > 60 ? 'warning' : 'success'"
|
||||
rounded
|
||||
></v-progress-linear>
|
||||
<div class="text-caption text-grey mt-1">{{ formatBytes(serverResources.memoryUsed) }} / {{ formatBytes(serverResources.memoryTotal) }}</div>
|
||||
</v-card>
|
||||
</v-col>
|
||||
<v-col cols="12" sm="6">
|
||||
<v-card variant="outlined" class="pa-3">
|
||||
<div class="d-flex justify-space-between align-center mb-2">
|
||||
<span class="text-caption font-weight-medium">Disk I/O</span>
|
||||
<span class="text-caption" :class="serverResources.diskIO > 80 ? 'text-error' : serverResources.diskIO > 60 ? 'text-warning' : 'text-success'">{{ serverResources.diskIO }}%</span>
|
||||
</div>
|
||||
<v-progress-linear
|
||||
:model-value="serverResources.diskIO"
|
||||
height="8"
|
||||
:color="serverResources.diskIO > 80 ? 'error' : serverResources.diskIO > 60 ? 'warning' : 'success'"
|
||||
rounded
|
||||
></v-progress-linear>
|
||||
<div class="text-caption text-grey mt-1">{{ formatBytes(serverResources.diskRead) }}/s read, {{ formatBytes(serverResources.diskWrite) }}/s write</div>
|
||||
</v-card>
|
||||
</v-col>
|
||||
<v-col cols="12" sm="6">
|
||||
<v-card variant="outlined" class="pa-3">
|
||||
<div class="d-flex justify-space-between align-center mb-2">
|
||||
<span class="text-caption font-weight-medium">Network</span>
|
||||
<span class="text-caption" :class="serverResources.network > 80 ? 'text-error' : serverResources.network > 60 ? 'text-warning' : 'text-success'">{{ serverResources.network }}%</span>
|
||||
</div>
|
||||
<v-progress-linear
|
||||
:model-value="serverResources.network"
|
||||
height="8"
|
||||
:color="serverResources.network > 80 ? 'error' : serverResources.network > 60 ? 'warning' : 'success'"
|
||||
rounded
|
||||
></v-progress-linear>
|
||||
<div class="text-caption text-grey mt-1">{{ formatBytes(serverResources.networkIn) }}/s in, {{ formatBytes(serverResources.networkOut) }}/s out</div>
|
||||
</v-card>
|
||||
</v-col>
|
||||
</v-row>
|
||||
</div>
|
||||
</div>
|
||||
</v-card-text>
|
||||
|
||||
<v-card-actions class="pa-3">
|
||||
<div class="d-flex justify-space-between align-center w-100">
|
||||
<div class="text-caption">
|
||||
<v-icon icon="mdi-clock" size="small" class="mr-1"></v-icon>
|
||||
Last check: {{ lastCheckTime }}
|
||||
<v-chip size="x-small" color="green" class="ml-2">
|
||||
Auto-refresh: {{ refreshInterval }}s
|
||||
</v-chip>
|
||||
</div>
|
||||
|
||||
<div class="d-flex">
|
||||
<v-btn
|
||||
size="x-small"
|
||||
variant="text"
|
||||
@click="refreshHealth"
|
||||
:loading="isRefreshing"
|
||||
>
|
||||
<v-icon icon="mdi-refresh" size="small" class="mr-1"></v-icon>
|
||||
Refresh
|
||||
</v-btn>
|
||||
<v-btn
|
||||
size="x-small"
|
||||
variant="text"
|
||||
@click="toggleAutoRefresh"
|
||||
:color="autoRefresh ? 'primary' : 'grey'"
|
||||
class="ml-2"
|
||||
>
|
||||
<v-icon :icon="autoRefresh ? 'mdi-pause' : 'mdi-play'" size="small" class="mr-1"></v-icon>
|
||||
{{ autoRefresh ? 'Pause' : 'Resume' }}
|
||||
</v-btn>
|
||||
</div>
|
||||
</div>
|
||||
</v-card-actions>
|
||||
</v-card>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, computed, onMounted, onUnmounted, nextTick } from 'vue'
|
||||
import { Chart, registerables } from 'chart.js'
|
||||
|
||||
// Register Chart.js components
|
||||
Chart.register(...registerables)
|
||||
|
||||
// Types
|
||||
interface SystemComponent {
|
||||
name: string
|
||||
description: string
|
||||
status: 'healthy' | 'degraded' | 'down'
|
||||
statusColor: string
|
||||
icon: string
|
||||
uptime: number
|
||||
responseTime?: number
|
||||
}
|
||||
|
||||
interface DatabaseMetrics {
|
||||
connections: number
|
||||
activeConnections: number
|
||||
queryTime: number
|
||||
queriesPerSecond: number
|
||||
cacheHitRate: number
|
||||
cacheSize: number
|
||||
replicationLag: number
|
||||
replicationStatus: string
|
||||
}
|
||||
|
||||
interface ServerResources {
|
||||
cpu: number
|
||||
cpuCores: number
|
||||
cpuFrequency: number
|
||||
memory: number
|
||||
memoryUsed: number
|
||||
memoryTotal: number
|
||||
diskIO: number
|
||||
diskRead: number
|
||||
diskWrite: number
|
||||
network: number
|
||||
networkIn: number
|
||||
networkOut: number
|
||||
}
|
||||
|
||||
// State
|
||||
const selectedEnvironment = ref('production')
|
||||
const isRefreshing = ref(false)
|
||||
const autoRefresh = ref(true)
|
||||
const refreshInterval = ref(30)
|
||||
const responseTimeChart = ref<HTMLCanvasElement | null>(null)
|
||||
let chartInstance: Chart | null = null
|
||||
let refreshTimer: number | null = null
|
||||
|
||||
// Environment options
|
||||
const environmentOptions = [
|
||||
{ label: 'Production', value: 'production' },
|
||||
{ label: 'Staging', value: 'staging' },
|
||||
{ label: 'Development', value: 'development' },
|
||||
{ label: 'Testing', value: 'testing' }
|
||||
]
|
||||
|
||||
// System components data
|
||||
const systemComponents = ref<SystemComponent[]>([
|
||||
{ name: 'API Gateway', description: 'Main API endpoint', status: 'healthy', statusColor: 'success', icon: 'mdi-api', uptime: 99.9, responseTime: 45 },
|
||||
{ name: 'Database', description: 'PostgreSQL cluster', status: 'healthy', statusColor: 'success', icon: 'mdi-database', uptime: 99.95, responseTime: 120 },
|
||||
{ name: 'Cache', description: 'Redis cache layer', status: 'healthy', statusColor: 'success', icon: 'mdi-memory', uptime: 99.8, responseTime: 8 },
|
||||
{ name: 'Message Queue', description: 'RabbitMQ broker', status: 'degraded', statusColor: 'warning', icon: 'mdi-message-processing', uptime: 98.5, responseTime: 250 },
|
||||
{ name: 'File Storage', description: 'S3-compatible storage', status: 'healthy', statusColor: 'success', icon: 'mdi-file-cloud', uptime: 99.7, responseTime: 180 },
|
||||
{ name: 'Authentication', description: 'OAuth2/JWT service', status: 'healthy', statusColor: 'success', icon: 'mdi-shield-account', uptime: 99.9, responseTime: 65 },
|
||||
{ name: 'Monitoring', description: 'Prometheus/Grafana', status: 'healthy', statusColor: 'success', icon: 'mdi-chart-line', uptime: 99.8, responseTime: 95 },
|
||||
{ name: 'Load Balancer', description: 'Nginx reverse proxy', status: 'healthy', statusColor: 'success', icon: 'mdi-load-balancer', uptime: 99.99, responseTime: 12 }
|
||||
])
|
||||
|
||||
const databaseMetrics = ref<DatabaseMetrics>({
|
||||
connections: 64,
|
||||
activeConnections: 42,
|
||||
queryTime: 85,
|
||||
queriesPerSecond: 1250,
|
||||
cacheHitRate: 92,
|
||||
cacheSize: 2147483648, // 2GB
|
||||
replicationLag: 45,
|
||||
replicationStatus: 'Synced'
|
||||
})
|
||||
|
||||
const serverResources = ref<ServerResources>({
|
||||
cpu: 42,
|
||||
cpuCores: 8,
|
||||
cpuFrequency: 3.2,
|
||||
memory: 68,
|
||||
memoryUsed: 1090519040, // ~1GB
|
||||
memoryTotal: 17179869184, // 16GB
|
||||
diskIO: 28,
|
||||
diskRead: 5242880, // 5MB/s
|
||||
diskWrite: 1048576, // 1MB/s
|
||||
network: 45,
|
||||
networkIn: 2097152, // 2MB/s
|
||||
networkOut: 1048576 // 1MB/s
|
||||
})
|
||||
|
||||
// Computed properties
|
||||
const overallStatus = computed(() => {
|
||||
const healthyCount = systemComponents.value.filter(c => c.status === 'healthy').length
|
||||
const totalCount = systemComponents.value.length
|
||||
|
||||
if (healthyCount === totalCount) return 'healthy'
|
||||
if (healthyCount >= totalCount * 0.8) return 'degraded'
|
||||
return 'critical'
|
||||
})
|
||||
|
||||
const overallStatusColor = computed(() => {
|
||||
switch (overallStatus.value) {
|
||||
case 'healthy': return 'green'
|
||||
case 'degraded': return 'orange'
|
||||
case 'critical': return 'red'
|
||||
default: return 'grey'
|
||||
}
|
||||
})
|
||||
|
||||
const overallStatusIcon = computed(() => {
|
||||
switch (overallStatus.value) {
|
||||
case 'healthy': return 'mdi-check-circle'
|
||||
case 'degraded': return 'mdi-alert-circle'
|
||||
case 'critical': return 'mdi-close-circle'
|
||||
default: return 'mdi-help-circle'
|
||||
}
|
||||
})
|
||||
|
||||
const overallStatusText = computed(() => {
|
||||
switch (overallStatus.value) {
|
||||
case 'healthy': return 'All Systems Normal'
|
||||
case 'degraded': return 'Minor Issues'
|
||||
case case 'critical': return 'Critical Issues'
|
||||
default: return 'Unknown'
|
||||
}
|
||||
})
|
||||
|
||||
const lastCheckTime = computed(() => {
|
||||
const now = new Date()
|
||||
return now.toLocaleTimeString([], { hour: '2-digit', minute: '2-digit', second: '2-digit' })
|
||||
})
|
||||
|
||||
// Helper functions
|
||||
const getResponseTimeColor = (responseTime: number) => {
|
||||
if (responseTime < 100) return 'success'
|
||||
if (responseTime < 300) return 'warning'
|
||||
return 'error'
|
||||
}
|
||||
|
||||
const formatBytes = (bytes: number) => {
|
||||
if (bytes >= 1073741824) {
|
||||
return `${(bytes / 1073741824).toFixed(1)} GB`
|
||||
} else if (bytes >= 1048576) {
|
||||
return `${(bytes / 1048576).toFixed(1)} MB`
|
||||
} else if (bytes >= 1024) {
|
||||
return `${(bytes / 1024).toFixed(1)} KB`
|
||||
}
|
||||
return `${bytes} B`
|
||||
}
|
||||
|
||||
// Chart functions
|
||||
const initChart = () => {
|
||||
if (!responseTimeChart.value) return
|
||||
|
||||
// Destroy existing chart
|
||||
if (chartInstance) {
|
||||
chartInstance.destroy()
|
||||
}
|
||||
|
||||
const ctx = responseTimeChart.value.getContext('2d')
|
||||
if (!ctx) return
|
||||
|
||||
// Generate mock response time data for last 24 hours
|
||||
const labels = Array.from({ length: 24 }, (_, i) => {
|
||||
const hour = new Date(Date.now() - (23 - i) * 3600000)
|
||||
return hour.getHours().toString().padStart(2, '0') + ':00'
|
||||
})
|
||||
|
||||
const data = labels.map(() => {
|
||||
const base = 50
|
||||
const spike = Math.random() > 0.9 ? 300 : 0
|
||||
const variance = Math.random() * 40
|
||||
return Math.round(base + variance + spike)
|
||||
})
|
||||
|
||||
chartInstance = new Chart(ctx, {
|
||||
type: 'line',
|
||||
data: {
|
||||
labels,
|
||||
datasets: [{
|
||||
label: 'API Response Time (ms)',
|
||||
data,
|
||||
borderColor: '#2196F3',
|
||||
backgroundColor: 'rgba(33, 150, 243, 0.1)',
|
||||
tension: 0.4,
|
||||
fill: true,
|
||||
pointBackgroundColor: (context) => {
|
||||
const value = context.dataset.data[context.dataIndex] as number
|
||||
return value > 200 ? '#F44336' : value > 100 ? '#FF9800' : '#4CAF50'
|
||||
},
|
||||
pointBorderColor: '#FFFFFF',
|
||||
pointBorderWidth: 2
|
||||
}]
|
||||
},
|
||||
options: {
|
||||
responsive: true,
|
||||
maintainAspectRatio: false,
|
||||
plugins: {
|
||||
legend: {
|
||||
display: false
|
||||
},
|
||||
tooltip: {
|
||||
callbacks: {
|
||||
label: (context) => {
|
||||
return `Response Time: ${context.raw}ms`
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
scales: {
|
||||
x: {
|
||||
grid: {
|
||||
display: false
|
||||
},
|
||||
ticks: {
|
||||
maxRotation: 0,
|
||||
callback: (value, index) => {
|
||||
// Show only every 3rd hour label
|
||||
return index % 3 === 0 ? labels[index] : ''
|
||||
}
|
||||
}
|
||||
},
|
||||
y: {
|
||||
beginAtZero: true,
|
||||
title: {
|
||||
display: true,
|
||||
text: 'Milliseconds (ms)'
|
||||
},
|
||||
ticks: {
|
||||
callback: (value) => `${value}ms`
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// Auto-refresh management
|
||||
const startAutoRefresh = () => {
|
||||
if (refreshTimer) clearInterval(refreshTimer)
|
||||
refreshTimer = setInterval(() => {
|
||||
refreshHealth()
|
||||
}, refreshInterval.value * 1000) as unknown as number
|
||||
}
|
||||
|
||||
const stopAutoRefresh = () => {
|
||||
if (refreshTimer) {
|
||||
clearInterval(refreshTimer)
|
||||
refreshTimer = null
|
||||
}
|
||||
}
|
||||
|
||||
const toggleAutoRefresh = () => {
|
||||
autoRefresh.value = !autoRefresh.value
|
||||
if (autoRefresh.value) {
|
||||
startAutoRefresh()
|
||||
} else {
|
||||
stopAutoRefresh()
|
||||
}
|
||||
}
|
||||
|
||||
// Actions
|
||||
const refreshHealth = () => {
|
||||
if (isRefreshing.value) return
|
||||
|
||||
isRefreshing.value = true
|
||||
|
||||
// Simulate API call
|
||||
setTimeout(() => {
|
||||
// Update system components with random variations
|
||||
systemComponents.value.forEach(component => {
|
||||
// Random status changes (rare)
|
||||
if (Math.random() > 0.95) {
|
||||
component.status = Math.random() > 0.7 ? 'degraded' : 'healthy'
|
||||
component.statusColor = component.status === 'healthy' ? 'success' : 'warning'
|
||||
}
|
||||
|
||||
// Update response times
|
||||
if (component.responseTime) {
|
||||
const variation = Math.random() * 40 - 20
|
||||
component.responseTime = Math.max(10, Math.round(component.responseTime + variation))
|
||||
}
|
||||
|
||||
// Update uptime (slight variations)
|
||||
component.uptime = Math.min(99.99, component.uptime + (Math.random() * 0.1 - 0.05))
|
||||
})
|
||||
|
||||
// Update database metrics
|
||||
databaseMetrics.value.connections = Math.round(64 + Math.random() * 20 - 10)
|
||||
databaseMetrics.value.activeConnections = Math.round(databaseMetrics.value.connections * 0.7)
|
||||
databaseMetrics.value.queryTime = Math.round(85 + Math.random() * 30 - 15)
|
||||
databaseMetrics.value.queriesPerSecond = Math.round(1250 + Math.random() * 200 - 100)
|
||||
databaseMetrics.value.cacheHitRate = Math.min(99, Math.round(92 + Math.random() * 4 - 2))
|
||||
databaseMetrics.value.replicationLag = Math.round(45 + Math.random() * 20 - 10)
|
||||
|
||||
// Update server resources
|
||||
serverResources.value.cpu = Math.round(42 + Math.random() * 20 - 10)
|
||||
serverResources.value.memory = Math.round(68 + Math.random() * 10 - 5)
|
||||
serverResources.value.diskIO = Math.round(28 + Math.random() * 15 - 7)
|
||||
serverResources.value.network = Math.round(45 + Math.random() * 20 - 10)
|
||||
|
||||
// Update chart
|
||||
initChart()
|
||||
|
||||
isRefreshing.value = false
|
||||
}, 800)
|
||||
}
|
||||
|
||||
// Lifecycle hooks
|
||||
onMounted(() => {
|
||||
nextTick(() => {
|
||||
initChart()
|
||||
})
|
||||
|
||||
// Start auto-refresh if enabled
|
||||
if (autoRefresh.value) {
|
||||
startAutoRefresh()
|
||||
}
|
||||
})
|
||||
|
||||
onUnmounted(() => {
|
||||
if (chartInstance) {
|
||||
chartInstance.destroy()
|
||||
}
|
||||
|
||||
stopAutoRefresh()
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.chart-container {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.h-100 {
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.v-progress-linear {
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
.v-card {
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
.v-card:hover {
|
||||
transform: translateY(-2px);
|
||||
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
</style>
|
||||
Reference in New Issue
Block a user