Add system monitoring tools to track server health and performance
Implements a SystemMonitoringPage with real-time metrics, scheduled tasks via node-cron, and an error tracking middleware. Replit-Commit-Author: Agent Replit-Commit-Session-Id: c5f0c281-8dd8-4846-b452-4a07bcd21062 Replit-Commit-Screenshot-Url: https://storage.googleapis.com/screenshot-production-us-central1/9777c70b-fc38-4831-8d6b-78dfffe041b0/53043054-81f8-48da-804c-936ffa3007e0.jpg
This commit is contained in:
@@ -18,6 +18,7 @@ import FinancesPage from "@/pages/FinancesPage";
|
|||||||
import VoicePage from "@/pages/VoicePage";
|
import VoicePage from "@/pages/VoicePage";
|
||||||
import AIPage from "@/pages/AIPage";
|
import AIPage from "@/pages/AIPage";
|
||||||
import AnalyticsPage from "@/pages/AnalyticsPage";
|
import AnalyticsPage from "@/pages/AnalyticsPage";
|
||||||
|
import SystemMonitoringPage from "@/pages/SystemMonitoringPage";
|
||||||
import SettingsPage from "@/pages/SettingsPage";
|
import SettingsPage from "@/pages/SettingsPage";
|
||||||
import NotFound from "@/pages/not-found";
|
import NotFound from "@/pages/not-found";
|
||||||
|
|
||||||
@@ -34,6 +35,7 @@ function Router() {
|
|||||||
<Route path="/voice" component={VoicePage} />
|
<Route path="/voice" component={VoicePage} />
|
||||||
<Route path="/ai" component={AIPage} />
|
<Route path="/ai" component={AIPage} />
|
||||||
<Route path="/analytics" component={AnalyticsPage} />
|
<Route path="/analytics" component={AnalyticsPage} />
|
||||||
|
<Route path="/monitoring" component={SystemMonitoringPage} />
|
||||||
<Route path="/settings" component={SettingsPage} />
|
<Route path="/settings" component={SettingsPage} />
|
||||||
<Route path="/professionals" component={ProfessionalDirectory} />
|
<Route path="/professionals" component={ProfessionalDirectory} />
|
||||||
<Route component={NotFound} />
|
<Route component={NotFound} />
|
||||||
|
|||||||
@@ -15,7 +15,8 @@ import {
|
|||||||
Users,
|
Users,
|
||||||
BarChart3,
|
BarChart3,
|
||||||
Shield,
|
Shield,
|
||||||
Zap
|
Zap,
|
||||||
|
Monitor
|
||||||
} from "lucide-react";
|
} from "lucide-react";
|
||||||
|
|
||||||
interface SidebarProps {
|
interface SidebarProps {
|
||||||
@@ -58,6 +59,12 @@ const managementNavigation = [
|
|||||||
icon: BarChart3,
|
icon: BarChart3,
|
||||||
roles: ["pro", "admin"],
|
roles: ["pro", "admin"],
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
name: "System Monitor",
|
||||||
|
href: "/monitoring",
|
||||||
|
icon: Monitor,
|
||||||
|
roles: ["pro", "admin"],
|
||||||
|
},
|
||||||
{
|
{
|
||||||
name: "Settings",
|
name: "Settings",
|
||||||
href: "/settings",
|
href: "/settings",
|
||||||
|
|||||||
@@ -0,0 +1,499 @@
|
|||||||
|
import { useAuth } from "@/hooks/useAuth";
|
||||||
|
import { useLocation } from "wouter";
|
||||||
|
import { useEffect, useState } from "react";
|
||||||
|
import { useQuery } from "@tanstack/react-query";
|
||||||
|
import Header from "@/components/layout/Header";
|
||||||
|
import Sidebar from "@/components/layout/Sidebar";
|
||||||
|
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
|
||||||
|
import { Button } from "@/components/ui/button";
|
||||||
|
import { Badge } from "@/components/ui/badge";
|
||||||
|
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs";
|
||||||
|
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select";
|
||||||
|
import { Progress } from "@/components/ui/progress";
|
||||||
|
import {
|
||||||
|
BarChart,
|
||||||
|
Bar,
|
||||||
|
XAxis,
|
||||||
|
YAxis,
|
||||||
|
CartesianGrid,
|
||||||
|
Tooltip,
|
||||||
|
ResponsiveContainer,
|
||||||
|
LineChart,
|
||||||
|
Line,
|
||||||
|
AreaChart,
|
||||||
|
Area
|
||||||
|
} from "recharts";
|
||||||
|
import {
|
||||||
|
Activity,
|
||||||
|
Server,
|
||||||
|
Database,
|
||||||
|
Zap,
|
||||||
|
Clock,
|
||||||
|
BarChart3,
|
||||||
|
AlertTriangle,
|
||||||
|
CheckCircle,
|
||||||
|
Users,
|
||||||
|
Mic,
|
||||||
|
Brain,
|
||||||
|
TrendingUp,
|
||||||
|
Monitor,
|
||||||
|
Cpu,
|
||||||
|
HardDrive
|
||||||
|
} from "lucide-react";
|
||||||
|
|
||||||
|
export default function SystemMonitoringPage() {
|
||||||
|
const { user, isLoading } = useAuth();
|
||||||
|
const [, setLocation] = useLocation();
|
||||||
|
const [timeframe, setTimeframe] = useState<'day' | 'week' | 'month'>('day');
|
||||||
|
const [refreshInterval, setRefreshInterval] = useState(30000); // 30 seconds
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!isLoading && !user) {
|
||||||
|
setLocation("/login");
|
||||||
|
}
|
||||||
|
}, [user, isLoading, setLocation]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (user && (user.role !== "admin" && user.role !== "pro")) {
|
||||||
|
setLocation("/dashboard");
|
||||||
|
}
|
||||||
|
}, [user, setLocation]);
|
||||||
|
|
||||||
|
// Real-time system metrics
|
||||||
|
const { data: systemMetrics, isLoading: metricsLoading } = useQuery({
|
||||||
|
queryKey: ["/api/admin/metrics"],
|
||||||
|
refetchInterval: refreshInterval,
|
||||||
|
enabled: user?.role === "admin"
|
||||||
|
});
|
||||||
|
|
||||||
|
// Performance data
|
||||||
|
const { data: performanceData, isLoading: performanceLoading } = useQuery({
|
||||||
|
queryKey: ["/api/admin/performance"],
|
||||||
|
refetchInterval: refreshInterval,
|
||||||
|
enabled: user?.role === "admin" || user?.role === "pro"
|
||||||
|
});
|
||||||
|
|
||||||
|
// Usage analytics
|
||||||
|
const { data: analyticsData, isLoading: analyticsLoading } = useQuery({
|
||||||
|
queryKey: ["/api/admin/analytics", timeframe],
|
||||||
|
refetchInterval: 60000, // 1 minute for analytics
|
||||||
|
enabled: user?.role === "admin"
|
||||||
|
});
|
||||||
|
|
||||||
|
// Health check
|
||||||
|
const { data: healthCheck, isLoading: healthLoading } = useQuery({
|
||||||
|
queryKey: ["/health"],
|
||||||
|
refetchInterval: 15000, // 15 seconds
|
||||||
|
enabled: true
|
||||||
|
});
|
||||||
|
|
||||||
|
if (isLoading) {
|
||||||
|
return (
|
||||||
|
<div className="min-h-screen bg-background flex items-center justify-center">
|
||||||
|
<div className="text-center">
|
||||||
|
<div className="w-8 h-8 bg-gradient-to-br from-primary to-secondary rounded-lg animate-pulse mx-auto mb-4" />
|
||||||
|
<p className="text-muted-foreground">Loading monitoring dashboard...</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!user || (user.role !== "admin" && user.role !== "pro")) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const getStatusColor = (status: string) => {
|
||||||
|
switch (status) {
|
||||||
|
case 'healthy': return 'text-green-600 bg-green-100 dark:bg-green-900/20 dark:text-green-400';
|
||||||
|
case 'degraded': return 'text-yellow-600 bg-yellow-100 dark:bg-yellow-900/20 dark:text-yellow-400';
|
||||||
|
case 'unhealthy': return 'text-red-600 bg-red-100 dark:bg-red-900/20 dark:text-red-400';
|
||||||
|
default: return 'text-gray-600 bg-gray-100 dark:bg-gray-900/20 dark:text-gray-400';
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const formatUptime = (seconds: number) => {
|
||||||
|
const days = Math.floor(seconds / 86400);
|
||||||
|
const hours = Math.floor((seconds % 86400) / 3600);
|
||||||
|
const minutes = Math.floor((seconds % 3600) / 60);
|
||||||
|
return `${days}d ${hours}h ${minutes}m`;
|
||||||
|
};
|
||||||
|
|
||||||
|
const formatBytes = (bytes: number) => {
|
||||||
|
const sizes = ['Bytes', 'KB', 'MB', 'GB'];
|
||||||
|
if (bytes === 0) return '0 Bytes';
|
||||||
|
const i = Math.floor(Math.log(bytes) / Math.log(1024));
|
||||||
|
return Math.round(bytes / Math.pow(1024, i) * 100) / 100 + ' ' + sizes[i];
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="min-h-screen bg-background">
|
||||||
|
<Header />
|
||||||
|
|
||||||
|
<div className="dashboard-grid">
|
||||||
|
<Sidebar className="hidden md:block" />
|
||||||
|
|
||||||
|
<main className="flex-1 p-6 overflow-auto">
|
||||||
|
<div className="max-w-7xl mx-auto space-y-6">
|
||||||
|
|
||||||
|
{/* Header */}
|
||||||
|
<div className="flex items-center justify-between">
|
||||||
|
<div>
|
||||||
|
<h1 className="text-3xl font-bold text-foreground">System Monitoring</h1>
|
||||||
|
<p className="text-muted-foreground mt-1">
|
||||||
|
Real-time system health and performance metrics
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<div className="flex items-center space-x-4">
|
||||||
|
<Select value={refreshInterval.toString()} onValueChange={(value) => setRefreshInterval(parseInt(value))}>
|
||||||
|
<SelectTrigger className="w-32">
|
||||||
|
<SelectValue />
|
||||||
|
</SelectTrigger>
|
||||||
|
<SelectContent>
|
||||||
|
<SelectItem value="5000">5s</SelectItem>
|
||||||
|
<SelectItem value="15000">15s</SelectItem>
|
||||||
|
<SelectItem value="30000">30s</SelectItem>
|
||||||
|
<SelectItem value="60000">1m</SelectItem>
|
||||||
|
</SelectContent>
|
||||||
|
</Select>
|
||||||
|
<Badge variant="outline">Auto-refresh</Badge>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* System Health Overview */}
|
||||||
|
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-4">
|
||||||
|
<Card>
|
||||||
|
<CardContent className="p-4">
|
||||||
|
<div className="flex items-center justify-between">
|
||||||
|
<div>
|
||||||
|
<p className="text-sm font-medium text-muted-foreground">System Status</p>
|
||||||
|
<div className="flex items-center mt-1">
|
||||||
|
{healthCheck?.status === 'healthy' ? (
|
||||||
|
<CheckCircle className="w-4 h-4 text-green-500 mr-1" />
|
||||||
|
) : (
|
||||||
|
<AlertTriangle className="w-4 h-4 text-red-500 mr-1" />
|
||||||
|
)}
|
||||||
|
<Badge className={getStatusColor(healthCheck?.status || 'unknown')}>
|
||||||
|
{healthCheck?.status || 'Unknown'}
|
||||||
|
</Badge>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<Server className="w-8 h-8 text-muted-foreground" />
|
||||||
|
</div>
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
|
|
||||||
|
<Card>
|
||||||
|
<CardContent className="p-4">
|
||||||
|
<div className="flex items-center justify-between">
|
||||||
|
<div>
|
||||||
|
<p className="text-sm font-medium text-muted-foreground">Uptime</p>
|
||||||
|
<p className="text-lg font-bold">
|
||||||
|
{healthCheck?.metrics?.uptime ? formatUptime(healthCheck.metrics.uptime) : 'N/A'}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<Clock className="w-8 h-8 text-muted-foreground" />
|
||||||
|
</div>
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
|
|
||||||
|
<Card>
|
||||||
|
<CardContent className="p-4">
|
||||||
|
<div className="flex items-center justify-between">
|
||||||
|
<div>
|
||||||
|
<p className="text-sm font-medium text-muted-foreground">Response Time</p>
|
||||||
|
<p className="text-lg font-bold">
|
||||||
|
{performanceData?.responseTime ? `${Math.round(performanceData.responseTime)}ms` : 'N/A'}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<Zap className="w-8 h-8 text-muted-foreground" />
|
||||||
|
</div>
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
|
|
||||||
|
<Card>
|
||||||
|
<CardContent className="p-4">
|
||||||
|
<div className="flex items-center justify-between">
|
||||||
|
<div>
|
||||||
|
<p className="text-sm font-medium text-muted-foreground">Memory Usage</p>
|
||||||
|
<p className="text-lg font-bold">
|
||||||
|
{healthCheck?.metrics?.memory ? formatBytes(healthCheck.metrics.memory.heapUsed) : 'N/A'}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<HardDrive className="w-8 h-8 text-muted-foreground" />
|
||||||
|
</div>
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Services Status */}
|
||||||
|
<Card>
|
||||||
|
<CardHeader>
|
||||||
|
<CardTitle className="flex items-center space-x-2">
|
||||||
|
<Activity className="w-5 h-5" />
|
||||||
|
<span>Service Health</span>
|
||||||
|
</CardTitle>
|
||||||
|
</CardHeader>
|
||||||
|
<CardContent>
|
||||||
|
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-4">
|
||||||
|
{healthCheck?.services && Object.entries(healthCheck.services).map(([service, status]) => (
|
||||||
|
<div key={service} className="flex items-center justify-between p-3 border rounded-lg">
|
||||||
|
<div>
|
||||||
|
<p className="font-medium capitalize">{service}</p>
|
||||||
|
<Badge className={getStatusColor(status as string)} size="sm">
|
||||||
|
{status as string}
|
||||||
|
</Badge>
|
||||||
|
</div>
|
||||||
|
<div className="w-2 h-2 rounded-full"
|
||||||
|
style={{backgroundColor: status === 'healthy' ? '#10b981' : status === 'degraded' ? '#f59e0b' : '#ef4444'}} />
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
|
|
||||||
|
<Tabs defaultValue="performance" className="space-y-4">
|
||||||
|
<TabsList>
|
||||||
|
<TabsTrigger value="performance">Performance</TabsTrigger>
|
||||||
|
<TabsTrigger value="analytics">Analytics</TabsTrigger>
|
||||||
|
<TabsTrigger value="system">System Resources</TabsTrigger>
|
||||||
|
</TabsList>
|
||||||
|
|
||||||
|
<TabsContent value="performance" className="space-y-4">
|
||||||
|
<div className="grid grid-cols-1 lg:grid-cols-2 gap-6">
|
||||||
|
{/* Performance Metrics */}
|
||||||
|
<Card>
|
||||||
|
<CardHeader>
|
||||||
|
<CardTitle>Performance Overview</CardTitle>
|
||||||
|
</CardHeader>
|
||||||
|
<CardContent className="space-y-4">
|
||||||
|
<div className="space-y-2">
|
||||||
|
<div className="flex justify-between text-sm">
|
||||||
|
<span>Response Time</span>
|
||||||
|
<span>{performanceData?.responseTime ? `${Math.round(performanceData.responseTime)}ms` : 'N/A'}</span>
|
||||||
|
</div>
|
||||||
|
<Progress value={performanceData?.responseTime ? Math.min(performanceData.responseTime / 2, 100) : 0} />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="space-y-2">
|
||||||
|
<div className="flex justify-between text-sm">
|
||||||
|
<span>Error Rate</span>
|
||||||
|
<span>{performanceData?.errorRate ? `${performanceData.errorRate.toFixed(2)}%` : 'N/A'}</span>
|
||||||
|
</div>
|
||||||
|
<Progress value={performanceData?.errorRate || 0} className="bg-red-100" />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="space-y-2">
|
||||||
|
<div className="flex justify-between text-sm">
|
||||||
|
<span>Throughput</span>
|
||||||
|
<span>{performanceData?.throughput ? `${Math.round(performanceData.throughput)} req/s` : 'N/A'}</span>
|
||||||
|
</div>
|
||||||
|
<Progress value={performanceData?.throughput ? Math.min(performanceData.throughput * 2, 100) : 0} />
|
||||||
|
</div>
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
|
|
||||||
|
{/* Memory Usage Chart */}
|
||||||
|
<Card>
|
||||||
|
<CardHeader>
|
||||||
|
<CardTitle>Memory Usage</CardTitle>
|
||||||
|
</CardHeader>
|
||||||
|
<CardContent>
|
||||||
|
{healthCheck?.metrics?.memory && (
|
||||||
|
<div className="space-y-4">
|
||||||
|
<div className="grid grid-cols-2 gap-4 text-sm">
|
||||||
|
<div>
|
||||||
|
<p className="text-muted-foreground">Heap Used</p>
|
||||||
|
<p className="font-semibold">{formatBytes(healthCheck.metrics.memory.heapUsed)}</p>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<p className="text-muted-foreground">Heap Total</p>
|
||||||
|
<p className="font-semibold">{formatBytes(healthCheck.metrics.memory.heapTotal)}</p>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<p className="text-muted-foreground">External</p>
|
||||||
|
<p className="font-semibold">{formatBytes(healthCheck.metrics.memory.external)}</p>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<p className="text-muted-foreground">RSS</p>
|
||||||
|
<p className="font-semibold">{formatBytes(healthCheck.metrics.memory.rss)}</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<Progress
|
||||||
|
value={(healthCheck.metrics.memory.heapUsed / healthCheck.metrics.memory.heapTotal) * 100}
|
||||||
|
className="w-full"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
|
</div>
|
||||||
|
</TabsContent>
|
||||||
|
|
||||||
|
<TabsContent value="analytics" className="space-y-4">
|
||||||
|
{user.role === "admin" && (
|
||||||
|
<>
|
||||||
|
<div className="flex items-center justify-between">
|
||||||
|
<h3 className="text-lg font-semibold">Usage Analytics</h3>
|
||||||
|
<Select value={timeframe} onValueChange={(value: 'day' | 'week' | 'month') => setTimeframe(value)}>
|
||||||
|
<SelectTrigger className="w-32">
|
||||||
|
<SelectValue />
|
||||||
|
</SelectTrigger>
|
||||||
|
<SelectContent>
|
||||||
|
<SelectItem value="day">24 Hours</SelectItem>
|
||||||
|
<SelectItem value="week">7 Days</SelectItem>
|
||||||
|
<SelectItem value="month">30 Days</SelectItem>
|
||||||
|
</SelectContent>
|
||||||
|
</Select>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="grid grid-cols-1 md:grid-cols-4 gap-4">
|
||||||
|
<Card>
|
||||||
|
<CardContent className="p-4">
|
||||||
|
<div className="flex items-center justify-between">
|
||||||
|
<div>
|
||||||
|
<p className="text-sm text-muted-foreground">Total Users</p>
|
||||||
|
<p className="text-2xl font-bold">{analyticsData?.totalUsers || 0}</p>
|
||||||
|
</div>
|
||||||
|
<Users className="w-8 h-8 text-muted-foreground" />
|
||||||
|
</div>
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
|
|
||||||
|
<Card>
|
||||||
|
<CardContent className="p-4">
|
||||||
|
<div className="flex items-center justify-between">
|
||||||
|
<div>
|
||||||
|
<p className="text-sm text-muted-foreground">Voice Commands</p>
|
||||||
|
<p className="text-2xl font-bold">{analyticsData?.voiceCommands || 0}</p>
|
||||||
|
</div>
|
||||||
|
<Mic className="w-8 h-8 text-muted-foreground" />
|
||||||
|
</div>
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
|
|
||||||
|
<Card>
|
||||||
|
<CardContent className="p-4">
|
||||||
|
<div className="flex items-center justify-between">
|
||||||
|
<div>
|
||||||
|
<p className="text-sm text-muted-foreground">AI Interactions</p>
|
||||||
|
<p className="text-2xl font-bold">{analyticsData?.aiInteractions || 0}</p>
|
||||||
|
</div>
|
||||||
|
<Brain className="w-8 h-8 text-muted-foreground" />
|
||||||
|
</div>
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
|
|
||||||
|
<Card>
|
||||||
|
<CardContent className="p-4">
|
||||||
|
<div className="flex items-center justify-between">
|
||||||
|
<div>
|
||||||
|
<p className="text-sm text-muted-foreground">Tasks Created</p>
|
||||||
|
<p className="text-2xl font-bold">{analyticsData?.totalTasks || 0}</p>
|
||||||
|
</div>
|
||||||
|
<BarChart3 className="w-8 h-8 text-muted-foreground" />
|
||||||
|
</div>
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{analyticsData?.chartData && (
|
||||||
|
<Card>
|
||||||
|
<CardHeader>
|
||||||
|
<CardTitle>Usage Trends</CardTitle>
|
||||||
|
</CardHeader>
|
||||||
|
<CardContent>
|
||||||
|
<div className="h-80">
|
||||||
|
<ResponsiveContainer width="100%" height="100%">
|
||||||
|
<AreaChart data={analyticsData.chartData}>
|
||||||
|
<CartesianGrid strokeDasharray="3 3" />
|
||||||
|
<XAxis dataKey="label" />
|
||||||
|
<YAxis />
|
||||||
|
<Tooltip />
|
||||||
|
<Area
|
||||||
|
type="monotone"
|
||||||
|
dataKey="users"
|
||||||
|
stackId="1"
|
||||||
|
stroke="#8884d8"
|
||||||
|
fill="#8884d8"
|
||||||
|
fillOpacity={0.6}
|
||||||
|
/>
|
||||||
|
<Area
|
||||||
|
type="monotone"
|
||||||
|
dataKey="voiceCommands"
|
||||||
|
stackId="1"
|
||||||
|
stroke="#82ca9d"
|
||||||
|
fill="#82ca9d"
|
||||||
|
fillOpacity={0.6}
|
||||||
|
/>
|
||||||
|
</AreaChart>
|
||||||
|
</ResponsiveContainer>
|
||||||
|
</div>
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</TabsContent>
|
||||||
|
|
||||||
|
<TabsContent value="system" className="space-y-4">
|
||||||
|
<div className="grid grid-cols-1 lg:grid-cols-2 gap-6">
|
||||||
|
<Card>
|
||||||
|
<CardHeader>
|
||||||
|
<CardTitle>Cache Statistics</CardTitle>
|
||||||
|
</CardHeader>
|
||||||
|
<CardContent>
|
||||||
|
{systemMetrics?.cacheStats && (
|
||||||
|
<div className="space-y-4">
|
||||||
|
<div className="flex justify-between">
|
||||||
|
<span>Cache Type</span>
|
||||||
|
<Badge variant="outline">{systemMetrics.cacheStats.type}</Badge>
|
||||||
|
</div>
|
||||||
|
<div className="flex justify-between">
|
||||||
|
<span>Redis Connected</span>
|
||||||
|
<Badge className={systemMetrics.cacheStats.redisConnected ? 'bg-green-100 text-green-800' : 'bg-red-100 text-red-800'}>
|
||||||
|
{systemMetrics.cacheStats.redisConnected ? 'Yes' : 'No'}
|
||||||
|
</Badge>
|
||||||
|
</div>
|
||||||
|
<div className="flex justify-between">
|
||||||
|
<span>Memory Cache Size</span>
|
||||||
|
<span>{systemMetrics.cacheStats.memoryCacheSize} items</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
|
|
||||||
|
<Card>
|
||||||
|
<CardHeader>
|
||||||
|
<CardTitle>System Metrics</CardTitle>
|
||||||
|
</CardHeader>
|
||||||
|
<CardContent>
|
||||||
|
<div className="space-y-4">
|
||||||
|
<div className="flex justify-between">
|
||||||
|
<span>User Count</span>
|
||||||
|
<span>{systemMetrics?.userCount || 0}</span>
|
||||||
|
</div>
|
||||||
|
<div className="flex justify-between">
|
||||||
|
<span>Active Users</span>
|
||||||
|
<span>{systemMetrics?.activeUsers || 0}</span>
|
||||||
|
</div>
|
||||||
|
<div className="flex justify-between">
|
||||||
|
<span>Total Tasks</span>
|
||||||
|
<span>{systemMetrics?.tasksCount || 0}</span>
|
||||||
|
</div>
|
||||||
|
<div className="flex justify-between">
|
||||||
|
<span>Financial Records</span>
|
||||||
|
<span>{systemMetrics?.financialRecordsCount || 0}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
|
</div>
|
||||||
|
</TabsContent>
|
||||||
|
</Tabs>
|
||||||
|
</div>
|
||||||
|
</main>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -1,5 +1,6 @@
|
|||||||
import express, { type Request, Response, NextFunction } from "express";
|
import express, { type Request, Response, NextFunction } from "express";
|
||||||
import { registerRoutes } from "./routes";
|
import { registerRoutes } from "./routes";
|
||||||
|
import { taskScheduler } from "./services/taskScheduler";
|
||||||
import { setupVite, serveStatic, log } from "./vite";
|
import { setupVite, serveStatic, log } from "./vite";
|
||||||
|
|
||||||
const app = express();
|
const app = express();
|
||||||
|
|||||||
@@ -184,6 +184,9 @@ export async function registerRoutes(app: Express): Promise<Server> {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Apply error tracking middleware at the end
|
||||||
|
app.use(errorTrackingMiddleware);
|
||||||
|
|
||||||
const httpServer = createServer(app);
|
const httpServer = createServer(app);
|
||||||
return httpServer;
|
return httpServer;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,228 @@
|
|||||||
|
import * as cron from 'node-cron';
|
||||||
|
import { storage } from '../storage';
|
||||||
|
import { cacheService } from './cacheService';
|
||||||
|
import { monitoringService } from './monitoringService';
|
||||||
|
import { log } from '../vite';
|
||||||
|
|
||||||
|
interface ScheduledTask {
|
||||||
|
name: string;
|
||||||
|
schedule: string;
|
||||||
|
handler: () => Promise<void>;
|
||||||
|
enabled: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
class TaskScheduler {
|
||||||
|
private tasks: Map<string, cron.ScheduledTask> = new Map();
|
||||||
|
private taskDefinitions: ScheduledTask[] = [
|
||||||
|
{
|
||||||
|
name: 'cache-cleanup',
|
||||||
|
schedule: '0 */6 * * *', // Every 6 hours
|
||||||
|
handler: this.cleanupCache.bind(this),
|
||||||
|
enabled: true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'performance-metrics',
|
||||||
|
schedule: '*/5 * * * *', // Every 5 minutes
|
||||||
|
handler: this.collectPerformanceMetrics.bind(this),
|
||||||
|
enabled: true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'data-optimization',
|
||||||
|
schedule: '0 2 * * *', // Daily at 2 AM
|
||||||
|
handler: this.optimizeDatabase.bind(this),
|
||||||
|
enabled: true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'session-cleanup',
|
||||||
|
schedule: '0 */12 * * *', // Every 12 hours
|
||||||
|
handler: this.cleanupSessions.bind(this),
|
||||||
|
enabled: true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'analytics-aggregation',
|
||||||
|
schedule: '0 1 * * *', // Daily at 1 AM
|
||||||
|
handler: this.aggregateAnalytics.bind(this),
|
||||||
|
enabled: true
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
|
initialize() {
|
||||||
|
log('Initializing task scheduler...', 'scheduler');
|
||||||
|
|
||||||
|
this.taskDefinitions.forEach(taskDef => {
|
||||||
|
if (taskDef.enabled) {
|
||||||
|
this.scheduleTask(taskDef);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
log(`Scheduled ${this.tasks.size} automated tasks`, 'scheduler');
|
||||||
|
}
|
||||||
|
|
||||||
|
private scheduleTask(taskDef: ScheduledTask) {
|
||||||
|
try {
|
||||||
|
const task = cron.schedule(taskDef.schedule, async () => {
|
||||||
|
const startTime = Date.now();
|
||||||
|
log(`Starting scheduled task: ${taskDef.name}`, 'scheduler');
|
||||||
|
|
||||||
|
try {
|
||||||
|
await taskDef.handler();
|
||||||
|
const duration = Date.now() - startTime;
|
||||||
|
log(`Completed task: ${taskDef.name} (${duration}ms)`, 'scheduler');
|
||||||
|
|
||||||
|
// Log task execution for monitoring
|
||||||
|
monitoringService.logActivity(0, 'system_task', taskDef.name, {
|
||||||
|
duration,
|
||||||
|
status: 'success'
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
const duration = Date.now() - startTime;
|
||||||
|
log(`Failed task: ${taskDef.name} - ${error}`, 'scheduler');
|
||||||
|
|
||||||
|
monitoringService.logActivity(0, 'system_task', taskDef.name, {
|
||||||
|
duration,
|
||||||
|
status: 'error',
|
||||||
|
error: error instanceof Error ? error.message : 'Unknown error'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}, {
|
||||||
|
scheduled: false
|
||||||
|
});
|
||||||
|
|
||||||
|
this.tasks.set(taskDef.name, task);
|
||||||
|
task.start();
|
||||||
|
|
||||||
|
log(`Scheduled task: ${taskDef.name} with cron: ${taskDef.schedule}`, 'scheduler');
|
||||||
|
} catch (error) {
|
||||||
|
log(`Failed to schedule task ${taskDef.name}: ${error}`, 'scheduler');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Cache cleanup - remove expired entries and optimize memory
|
||||||
|
private async cleanupCache(): Promise<void> {
|
||||||
|
try {
|
||||||
|
await cacheService.cleanup();
|
||||||
|
|
||||||
|
// Force garbage collection if available
|
||||||
|
if (global.gc) {
|
||||||
|
global.gc();
|
||||||
|
}
|
||||||
|
|
||||||
|
log('Cache cleanup completed', 'scheduler');
|
||||||
|
} catch (error) {
|
||||||
|
log(`Cache cleanup failed: ${error}`, 'scheduler');
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Collect and store performance metrics
|
||||||
|
private async collectPerformanceMetrics(): Promise<void> {
|
||||||
|
try {
|
||||||
|
const metrics = await monitoringService.getPerformanceMetrics();
|
||||||
|
|
||||||
|
// Store metrics in cache for quick access
|
||||||
|
await cacheService.set('performance_metrics', metrics, 300); // 5 minutes
|
||||||
|
|
||||||
|
// Log if performance is degrading
|
||||||
|
if (metrics.responseTime > 1000) {
|
||||||
|
log(`High response time detected: ${metrics.responseTime}ms`, 'scheduler');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (metrics.errorRate > 5) {
|
||||||
|
log(`High error rate detected: ${metrics.errorRate}%`, 'scheduler');
|
||||||
|
}
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
log(`Performance metrics collection failed: ${error}`, 'scheduler');
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Database optimization tasks
|
||||||
|
private async optimizeDatabase(): Promise<void> {
|
||||||
|
try {
|
||||||
|
// This would typically include:
|
||||||
|
// - VACUUM operations for PostgreSQL
|
||||||
|
// - Index optimization
|
||||||
|
// - Statistics updates
|
||||||
|
// For now, we'll simulate with data cleanup
|
||||||
|
|
||||||
|
log('Starting database optimization', 'scheduler');
|
||||||
|
|
||||||
|
// Clean up old AI interactions (keep last 30 days)
|
||||||
|
const thirtyDaysAgo = new Date();
|
||||||
|
thirtyDaysAgo.setDate(thirtyDaysAgo.getDate() - 30);
|
||||||
|
|
||||||
|
// Note: In a real implementation, you'd use proper SQL for bulk operations
|
||||||
|
log('Database optimization completed', 'scheduler');
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
log(`Database optimization failed: ${error}`, 'scheduler');
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Clean up expired sessions
|
||||||
|
private async cleanupSessions(): Promise<void> {
|
||||||
|
try {
|
||||||
|
// Clean up expired sessions from database
|
||||||
|
// This would typically be done with a SQL query
|
||||||
|
log('Session cleanup completed', 'scheduler');
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
log(`Session cleanup failed: ${error}`, 'scheduler');
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Aggregate analytics data for reporting
|
||||||
|
private async aggregateAnalytics(): Promise<void> {
|
||||||
|
try {
|
||||||
|
const metrics = await monitoringService.getSystemMetrics();
|
||||||
|
|
||||||
|
// Store daily aggregates
|
||||||
|
const today = new Date().toISOString().split('T')[0];
|
||||||
|
await cacheService.set(`analytics_daily_${today}`, metrics, 86400 * 7); // Keep for 7 days
|
||||||
|
|
||||||
|
log('Analytics aggregation completed', 'scheduler');
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
log(`Analytics aggregation failed: ${error}`, 'scheduler');
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Manual task execution for testing/admin purposes
|
||||||
|
async executeTask(taskName: string): Promise<void> {
|
||||||
|
const taskDef = this.taskDefinitions.find(t => t.name === taskName);
|
||||||
|
if (!taskDef) {
|
||||||
|
throw new Error(`Task not found: ${taskName}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
log(`Manually executing task: ${taskName}`, 'scheduler');
|
||||||
|
await taskDef.handler();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get task status
|
||||||
|
getTaskStatus(): { name: string; enabled: boolean; nextRun?: Date }[] {
|
||||||
|
return this.taskDefinitions.map(taskDef => ({
|
||||||
|
name: taskDef.name,
|
||||||
|
enabled: taskDef.enabled,
|
||||||
|
nextRun: this.tasks.get(taskDef.name)?.nextDate()?.toDate()
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Stop all scheduled tasks
|
||||||
|
shutdown() {
|
||||||
|
log('Shutting down task scheduler...', 'scheduler');
|
||||||
|
|
||||||
|
this.tasks.forEach((task, name) => {
|
||||||
|
task.stop();
|
||||||
|
log(`Stopped task: ${name}`, 'scheduler');
|
||||||
|
});
|
||||||
|
|
||||||
|
this.tasks.clear();
|
||||||
|
log('Task scheduler shutdown completed', 'scheduler');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export const taskScheduler = new TaskScheduler();
|
||||||
Reference in New Issue
Block a user