Files
infohliadka/pages/admin/monitoring/realtime.tsx
Lukas Davidovic 249a672cd7 transform admin panel with comprehensive professional UI
- migrate from SQLite to PostgreSQL with Drizzle ORM
- implement comprehensive AdminLayout with expandable sidebar navigation
- create professional dashboard with real-time charts and metrics
- add advanced monitoring, reporting, and export functionality
- fix menu alignment and remove non-existent pages
- eliminate duplicate headers and improve UI consistency
- add Tailwind CSS v3 for professional styling
- expand database schema from 6 to 15 tables
- implement role-based access control and API key management
- create comprehensive settings, monitoring, and system info pages
2025-09-06 15:14:20 +02:00

432 lines
14 KiB
TypeScript

import { useState, useEffect, useRef } from 'react'
import type { NextPage } from 'next'
import Head from 'next/head'
import AdminLayout from '../../../components/AdminLayout'
import {
ChartBarIcon,
ServerStackIcon,
CircleStackIcon as DatabaseIcon,
CpuChipIcon,
CircleStackIcon,
GlobeAltIcon,
ExclamationTriangleIcon,
CheckCircleIcon,
ClockIcon,
ArrowPathIcon
} from '@heroicons/react/24/outline'
import { Line, Bar } from 'react-chartjs-2'
interface RealtimeMetrics {
timestamp: string
cpu_usage: number
memory_usage: number
disk_usage: number
active_connections: number
api_requests_per_minute: number
database_queries_per_minute: number
response_time: number
error_rate: number
}
interface SystemAlert {
id: string
type: 'warning' | 'error' | 'info'
title: string
message: string
timestamp: string
resolved: boolean
}
interface ActiveUser {
id: string
name: string
email: string
last_activity: string
ip_address: string
location: string
}
const RealtimeMonitoring: NextPage = () => {
const [metrics, setMetrics] = useState<RealtimeMetrics[]>([])
const [alerts, setAlerts] = useState<SystemAlert[]>([])
const [activeUsers, setActiveUsers] = useState<ActiveUser[]>([])
const [isConnected, setIsConnected] = useState(false)
const [autoRefresh, setAutoRefresh] = useState(true)
const intervalRef = useRef<NodeJS.Timeout | null>(null)
useEffect(() => {
if (autoRefresh) {
fetchData()
intervalRef.current = setInterval(fetchData, 5000) // Update every 5 seconds
} else if (intervalRef.current) {
clearInterval(intervalRef.current)
}
return () => {
if (intervalRef.current) {
clearInterval(intervalRef.current)
}
}
}, [autoRefresh])
const fetchData = async () => {
try {
// Fetch real-time metrics
const metricsResponse = await fetch('/api/admin/monitoring/realtime')
if (metricsResponse.ok) {
const newMetric = await metricsResponse.json()
setMetrics(prev => {
const updated = [...prev, newMetric].slice(-20) // Keep last 20 data points
return updated
})
setIsConnected(true)
}
// Fetch alerts
const alertsResponse = await fetch('/api/admin/monitoring/alerts')
if (alertsResponse.ok) {
const alertsData = await alertsResponse.json()
setAlerts(alertsData)
}
// Fetch active users
const usersResponse = await fetch('/api/admin/monitoring/active-users')
if (usersResponse.ok) {
const usersData = await usersResponse.json()
setActiveUsers(usersData)
}
} catch (error) {
console.error('Failed to fetch monitoring data:', error)
setIsConnected(false)
}
}
const getLatestMetric = () => metrics[metrics.length - 1] || {}
const getStatusColor = (value: number, thresholds: { warning: number, critical: number }) => {
if (value >= thresholds.critical) return 'text-red-600 bg-red-100'
if (value >= thresholds.warning) return 'text-yellow-600 bg-yellow-100'
return 'text-green-600 bg-green-100'
}
// Chart configurations
const cpuChartData = {
labels: metrics.map((_, index) => `${index * 5}s ago`).reverse(),
datasets: [
{
label: 'CPU Usage %',
data: metrics.map(m => m.cpu_usage).reverse(),
borderColor: 'rgb(239, 68, 68)',
backgroundColor: 'rgba(239, 68, 68, 0.1)',
tension: 0.4,
fill: true,
},
],
}
const memoryChartData = {
labels: metrics.map((_, index) => `${index * 5}s ago`).reverse(),
datasets: [
{
label: 'Memory Usage %',
data: metrics.map(m => m.memory_usage).reverse(),
borderColor: 'rgb(59, 130, 246)',
backgroundColor: 'rgba(59, 130, 246, 0.1)',
tension: 0.4,
fill: true,
},
],
}
const apiRequestsData = {
labels: metrics.map((_, index) => `${index * 5}s`).reverse(),
datasets: [
{
label: 'API Requests/min',
data: metrics.map(m => m.api_requests_per_minute).reverse(),
backgroundColor: 'rgba(34, 197, 94, 0.8)',
borderColor: 'rgb(34, 197, 94)',
},
],
}
const latest = getLatestMetric()
return (
<AdminLayout title="Real-time Monitoring">
<Head>
<title>Real-time Monitoring - Hliadka.sk Admin</title>
</Head>
{/* Connection Status */}
<div className="mb-6 flex items-center justify-between">
<div className="flex items-center space-x-4">
<div className={`flex items-center space-x-2 px-3 py-1 rounded-full text-sm font-medium ${
isConnected ? 'bg-green-100 text-green-800' : 'bg-red-100 text-red-800'
}`}>
<div className={`w-2 h-2 rounded-full ${isConnected ? 'bg-green-500' : 'bg-red-500'}`} />
<span>{isConnected ? 'Pripojené' : 'Odpojené'}</span>
</div>
<button
onClick={() => setAutoRefresh(!autoRefresh)}
className={`flex items-center space-x-2 px-3 py-1 rounded-md text-sm font-medium transition-colors ${
autoRefresh
? 'bg-primary-100 text-primary-800 hover:bg-primary-200'
: 'bg-gray-100 text-gray-800 hover:bg-gray-200'
}`}
>
<ArrowPathIcon className={`w-4 h-4 ${autoRefresh ? 'animate-spin' : ''}`} />
<span>{autoRefresh ? 'Auto-refresh ON' : 'Auto-refresh OFF'}</span>
</button>
</div>
<div className="text-sm text-gray-500">
Posledná aktualizácia: {new Date().toLocaleTimeString('sk-SK')}
</div>
</div>
{/* System Status Cards */}
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-6 mb-8">
<div className="card p-6">
<div className="flex items-center justify-between">
<div>
<p className="text-sm font-medium text-gray-600">CPU Usage</p>
<p className={`text-2xl font-bold ${getStatusColor(latest.cpu_usage || 0, { warning: 70, critical: 90 })}`}>
{latest.cpu_usage || 0}%
</p>
</div>
<CpuChipIcon className="h-8 w-8 text-gray-400" />
</div>
<div className="mt-2">
<div className="w-full bg-gray-200 rounded-full h-2">
<div
className={`h-2 rounded-full ${
(latest.cpu_usage || 0) >= 90 ? 'bg-red-500' :
(latest.cpu_usage || 0) >= 70 ? 'bg-yellow-500' : 'bg-green-500'
}`}
style={{ width: `${latest.cpu_usage || 0}%` }}
/>
</div>
</div>
</div>
<div className="card p-6">
<div className="flex items-center justify-between">
<div>
<p className="text-sm font-medium text-gray-600">Memory Usage</p>
<p className={`text-2xl font-bold ${getStatusColor(latest.memory_usage || 0, { warning: 80, critical: 95 })}`}>
{latest.memory_usage || 0}%
</p>
</div>
<CircleStackIcon className="h-8 w-8 text-gray-400" />
</div>
<div className="mt-2">
<div className="w-full bg-gray-200 rounded-full h-2">
<div
className={`h-2 rounded-full ${
(latest.memory_usage || 0) >= 95 ? 'bg-red-500' :
(latest.memory_usage || 0) >= 80 ? 'bg-yellow-500' : 'bg-green-500'
}`}
style={{ width: `${latest.memory_usage || 0}%` }}
/>
</div>
</div>
</div>
<div className="card p-6">
<div className="flex items-center justify-between">
<div>
<p className="text-sm font-medium text-gray-600">Active Connections</p>
<p className="text-2xl font-bold text-primary-600">
{latest.active_connections || 0}
</p>
</div>
<GlobeAltIcon className="h-8 w-8 text-gray-400" />
</div>
<p className="text-sm text-gray-500 mt-1">Databáza + API</p>
</div>
<div className="card p-6">
<div className="flex items-center justify-between">
<div>
<p className="text-sm font-medium text-gray-600">Response Time</p>
<p className={`text-2xl font-bold ${getStatusColor(latest.response_time || 0, { warning: 500, critical: 1000 })}`}>
{latest.response_time || 0}ms
</p>
</div>
<ClockIcon className="h-8 w-8 text-gray-400" />
</div>
<p className="text-sm text-gray-500 mt-1">Priemerný API response</p>
</div>
</div>
{/* Real-time Charts */}
<div className="grid grid-cols-1 lg:grid-cols-2 gap-8 mb-8">
<div className="card p-6">
<h3 className="text-lg font-semibold text-gray-900 mb-4">CPU Usage - Posledných 100s</h3>
<Line
data={cpuChartData}
options={{
responsive: true,
maintainAspectRatio: false,
plugins: {
legend: {
display: false,
},
},
scales: {
y: {
beginAtZero: true,
max: 100,
ticks: {
callback: (value) => `${value}%`
}
},
x: {
display: false
}
},
elements: {
point: {
radius: 0
}
}
}}
height={200}
/>
</div>
<div className="card p-6">
<h3 className="text-lg font-semibold text-gray-900 mb-4">Memory Usage - Posledných 100s</h3>
<Line
data={memoryChartData}
options={{
responsive: true,
maintainAspectRatio: false,
plugins: {
legend: {
display: false,
},
},
scales: {
y: {
beginAtZero: true,
max: 100,
ticks: {
callback: (value) => `${value}%`
}
},
x: {
display: false
}
},
elements: {
point: {
radius: 0
}
}
}}
height={200}
/>
</div>
</div>
{/* API Activity */}
<div className="card p-6 mb-8">
<h3 className="text-lg font-semibold text-gray-900 mb-4">API Activity - Requests per Minute</h3>
<Bar
data={apiRequestsData}
options={{
responsive: true,
maintainAspectRatio: false,
plugins: {
legend: {
display: false,
},
},
scales: {
y: {
beginAtZero: true,
},
x: {
display: false
}
},
}}
height={150}
/>
</div>
{/* Alerts and Active Users */}
<div className="grid grid-cols-1 lg:grid-cols-2 gap-8">
{/* System Alerts */}
<div className="card p-6">
<h3 className="text-lg font-semibold text-gray-900 mb-4">Systémové upozornenia</h3>
<div className="space-y-3">
{alerts.length > 0 ? alerts.map((alert) => (
<div key={alert.id} className={`p-3 rounded-md border-l-4 ${
alert.type === 'error' ? 'bg-red-50 border-red-400' :
alert.type === 'warning' ? 'bg-yellow-50 border-yellow-400' :
'bg-blue-50 border-blue-400'
}`}>
<div className="flex items-center justify-between">
<div className="flex items-center space-x-2">
{alert.type === 'error' ? (
<ExclamationTriangleIcon className="h-5 w-5 text-red-500" />
) : alert.type === 'warning' ? (
<ExclamationTriangleIcon className="h-5 w-5 text-yellow-500" />
) : (
<CheckCircleIcon className="h-5 w-5 text-blue-500" />
)}
<span className="font-medium text-sm">{alert.title}</span>
</div>
<span className="text-xs text-gray-500">
{new Date(alert.timestamp).toLocaleTimeString('sk-SK')}
</span>
</div>
<p className="text-sm text-gray-600 mt-1">{alert.message}</p>
</div>
)) : (
<div className="text-center py-8 text-gray-500">
<CheckCircleIcon className="h-12 w-12 mx-auto mb-2 text-green-500" />
<p>Žiadne aktívne upozornenia</p>
</div>
)}
</div>
</div>
{/* Active Users */}
<div className="card p-6">
<h3 className="text-lg font-semibold text-gray-900 mb-4">Aktívni používatelia</h3>
<div className="space-y-3">
{activeUsers.map((user) => (
<div key={user.id} className="flex items-center justify-between p-3 bg-gray-50 rounded-md">
<div>
<div className="font-medium text-sm text-gray-900">{user.name}</div>
<div className="text-xs text-gray-500">{user.email}</div>
<div className="text-xs text-gray-400">{user.location}</div>
</div>
<div className="text-right">
<div className="text-xs text-gray-500">
{new Date(user.last_activity).toLocaleTimeString('sk-SK')}
</div>
<div className="text-xs text-gray-400">{user.ip_address}</div>
</div>
</div>
))}
{activeUsers.length === 0 && (
<div className="text-center py-8 text-gray-500">
<p>Žiadni aktívni používatelia</p>
</div>
)}
</div>
</div>
</div>
</AdminLayout>
)
}
export default RealtimeMonitoring