Merge pull request #60 from operacle/develop
Fix: Server history chart and date filter and Added Distributed Regional Monitoring Dockerfile
This commit is contained in:
@@ -1,4 +1,4 @@
|
|||||||

|

|
||||||
|
|
||||||
# 🚀 What is CheckCle?
|
# 🚀 What is CheckCle?
|
||||||
|
|
||||||
@@ -13,6 +13,7 @@ CheckCle is an Open Source solution for seamless, real-time monitoring of full-s
|
|||||||
- Monitor HTTP, DNS, and Ping protocols
|
- Monitor HTTP, DNS, and Ping protocols
|
||||||
- Monitor TCP-based, API services (e.g., FTP, SMTP, HTTP)
|
- Monitor TCP-based, API services (e.g., FTP, SMTP, HTTP)
|
||||||
- Track detail uptime, response times, and performance issues
|
- Track detail uptime, response times, and performance issues
|
||||||
|
- Distributed Regional Monitoring
|
||||||
- Incident History (UP/DOWN/WARNING/PAUSE)
|
- Incident History (UP/DOWN/WARNING/PAUSE)
|
||||||
- SSL & Domain Monitoring (Domain, Issuer, Expiration Date, Days Left, Status, Last Notified)
|
- SSL & Domain Monitoring (Domain, Issuer, Expiration Date, Days Left, Status, Last Notified)
|
||||||
- Infrastructure Server Monitoring, Supports Linux (🐧 Debian, Ubuntu, CentOS, Red Hat, etc.) and Windows (Beta). And Servers metrics like CPU, RAM, disk usage, and network activity) with an one-line installation angent script.
|
- Infrastructure Server Monitoring, Supports Linux (🐧 Debian, Ubuntu, CentOS, Red Hat, etc.) and Windows (Beta). And Servers metrics like CPU, RAM, disk usage, and network activity) with an one-line installation angent script.
|
||||||
@@ -76,9 +77,11 @@ services:
|
|||||||
4. Follow the Quick Start Guide at https://docs.checkcle.io
|
4. Follow the Quick Start Guide at https://docs.checkcle.io
|
||||||
|
|
||||||
###
|
###
|
||||||

|

|
||||||

|

|
||||||

|

|
||||||
|

|
||||||
|

|
||||||
|
|
||||||
## 📝 Development Roadmap
|
## 📝 Development Roadmap
|
||||||
|
|
||||||
@@ -134,6 +137,10 @@ Here are some ways you can help improve CheckCle:
|
|||||||
CheckCle is released under the MIT License.
|
CheckCle is released under the MIT License.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
## 👥 Contributors
|
||||||
|
|
||||||
|
[](https://github.com/operacle/checkcle/graphs/contributors)
|
||||||
|
|
||||||
|
|
||||||
## Star History
|
## Star History
|
||||||
|
|
||||||
|
|||||||
@@ -27,15 +27,15 @@ export const StatusCards = ({ services }: StatusCardsProps) => {
|
|||||||
} relative z-10`}
|
} relative z-10`}
|
||||||
style={{
|
style={{
|
||||||
background: theme === 'dark'
|
background: theme === 'dark'
|
||||||
? "linear-gradient(135deg, rgba(67, 160, 71, 0.8) 0%, rgba(102, 187, 106, 0.6) 100%)"
|
? "linear-gradient(135deg, rgba(65, 59, 55, 0.8) 0%, rgba(102, 187, 106, 0.6) 100%)"
|
||||||
: "linear-gradient(135deg, #43a047 0%, #66bb6a 100%)"
|
: "linear-gradient(135deg, rgba(65, 59, 55, 0.8) 0%, #66bb6a 100%)"
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{/* Grid Pattern Overlay */}
|
{/* Grid Pattern Overlay */}
|
||||||
<div className="absolute inset-0 z-0 opacity-10">
|
<div className="absolute inset-0 z-0 opacity-10">
|
||||||
<div className="w-full h-full"
|
<div className="w-full h-full"
|
||||||
style={{
|
style={{
|
||||||
backgroundImage: `linear-gradient(#fff 1px, transparent 1px),
|
backgroundImage: `linear-gradient(#000 1px, transparent 1px),
|
||||||
linear-gradient(90deg, #fff 1px, transparent 1px)`,
|
linear-gradient(90deg, #fff 1px, transparent 1px)`,
|
||||||
backgroundSize: '20px 20px'
|
backgroundSize: '20px 20px'
|
||||||
}}
|
}}
|
||||||
@@ -59,15 +59,15 @@ export const StatusCards = ({ services }: StatusCardsProps) => {
|
|||||||
} relative z-10`}
|
} relative z-10`}
|
||||||
style={{
|
style={{
|
||||||
background: theme === 'dark'
|
background: theme === 'dark'
|
||||||
? "linear-gradient(135deg, rgba(229, 57, 53, 0.8) 0%, rgba(239, 83, 80, 0.6) 100%)"
|
? "linear-gradient(135deg, rgba(65, 59, 55, 0.8) 0%, rgba(239, 83, 80, 0.6) 100%)"
|
||||||
: "linear-gradient(135deg, #e53935 0%, #ef5350 100%)"
|
: "linear-gradient(135deg, rgba(65, 59, 55, 0.8) 0%, #ef5350 100%)"
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{/* Grid Pattern Overlay */}
|
{/* Grid Pattern Overlay */}
|
||||||
<div className="absolute inset-0 z-0 opacity-10">
|
<div className="absolute inset-0 z-0 opacity-10">
|
||||||
<div className="w-full h-full"
|
<div className="w-full h-full"
|
||||||
style={{
|
style={{
|
||||||
backgroundImage: `linear-gradient(#fff 1px, transparent 1px),
|
backgroundImage: `linear-gradient(#000 1px, transparent 1px),
|
||||||
linear-gradient(90deg, #fff 1px, transparent 1px)`,
|
linear-gradient(90deg, #fff 1px, transparent 1px)`,
|
||||||
backgroundSize: '20px 20px'
|
backgroundSize: '20px 20px'
|
||||||
}}
|
}}
|
||||||
@@ -91,15 +91,15 @@ export const StatusCards = ({ services }: StatusCardsProps) => {
|
|||||||
} relative z-10`}
|
} relative z-10`}
|
||||||
style={{
|
style={{
|
||||||
background: theme === 'dark'
|
background: theme === 'dark'
|
||||||
? "linear-gradient(135deg, rgba(25, 118, 210, 0.8) 0%, rgba(66, 165, 245, 0.6) 100%)"
|
? "linear-gradient(135deg, rgba(65, 59, 55, 0.8) 0%, rgba(66, 165, 245, 0.6) 100%)"
|
||||||
: "linear-gradient(135deg, #1976d2 0%, #42a5f5 100%)"
|
: "linear-gradient(135deg, rgba(65, 59, 55, 0.8) 0%, #42a5f5 100%)"
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{/* Grid Pattern Overlay */}
|
{/* Grid Pattern Overlay */}
|
||||||
<div className="absolute inset-0 z-0 opacity-10">
|
<div className="absolute inset-0 z-0 opacity-10">
|
||||||
<div className="w-full h-full"
|
<div className="w-full h-full"
|
||||||
style={{
|
style={{
|
||||||
backgroundImage: `linear-gradient(#fff 1px, transparent 1px),
|
backgroundImage: `linear-gradient(#000 1px, transparent 1px),
|
||||||
linear-gradient(90deg, #fff 1px, transparent 1px)`,
|
linear-gradient(90deg, #fff 1px, transparent 1px)`,
|
||||||
backgroundSize: '20px 20px'
|
backgroundSize: '20px 20px'
|
||||||
}}
|
}}
|
||||||
@@ -123,15 +123,15 @@ export const StatusCards = ({ services }: StatusCardsProps) => {
|
|||||||
} relative z-10`}
|
} relative z-10`}
|
||||||
style={{
|
style={{
|
||||||
background: theme === 'dark'
|
background: theme === 'dark'
|
||||||
? "linear-gradient(135deg, rgba(255, 152, 0, 0.8) 0%, rgba(255, 183, 77, 0.6) 100%)"
|
? "linear-gradient(135deg, rgba(65, 59, 55, 0.8) 0%, rgba(255, 183, 77, 0.6) 100%)"
|
||||||
: "linear-gradient(135deg, #ff9800 0%, #ffb74d 100%)"
|
: "linear-gradient(135deg, rgba(65, 59, 55, 0.8) 0%, #ffb74d 100%)"
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{/* Grid Pattern Overlay */}
|
{/* Grid Pattern Overlay */}
|
||||||
<div className="absolute inset-0 z-0 opacity-10">
|
<div className="absolute inset-0 z-0 opacity-10">
|
||||||
<div className="w-full h-full"
|
<div className="w-full h-full"
|
||||||
style={{
|
style={{
|
||||||
backgroundImage: `linear-gradient(#fff 1px, transparent 1px),
|
backgroundImage: `linear-gradient(#000 1px, transparent 1px),
|
||||||
linear-gradient(90deg, #fff 1px, transparent 1px)`,
|
linear-gradient(90deg, #fff 1px, transparent 1px)`,
|
||||||
backgroundSize: '20px 20px'
|
backgroundSize: '20px 20px'
|
||||||
}}
|
}}
|
||||||
|
|||||||
@@ -1,16 +1,13 @@
|
|||||||
|
|
||||||
import React, { useEffect } from 'react';
|
import React, { useEffect } from 'react';
|
||||||
import { useLanguage } from '@/contexts/LanguageContext';
|
import { useLanguage } from '@/contexts/LanguageContext';
|
||||||
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card';
|
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card';
|
||||||
import { Tabs, TabsList, TabsTrigger, TabsContent } from '@/components/ui/tabs';
|
import { Tabs, TabsList, TabsTrigger, TabsContent } from '@/components/ui/tabs';
|
||||||
import { MaintenanceTable, MaintenanceStatusChecker } from './maintenance';
|
import { MaintenanceTable, MaintenanceStatusChecker } from './maintenance';
|
||||||
import { EmptyMaintenanceState } from './maintenance/EmptyMaintenanceState';
|
import { LoadingState } from '@/components/services/LoadingState';
|
||||||
import { OverviewCard } from './common/OverviewCard';
|
|
||||||
import { Calendar, Clock, CheckCircle } from 'lucide-react';
|
import { Calendar, Clock, CheckCircle } from 'lucide-react';
|
||||||
import { useMaintenanceData } from './hooks/useMaintenanceData';
|
import { useMaintenanceData } from './hooks/useMaintenanceData';
|
||||||
import { Skeleton } from '@/components/ui/skeleton';
|
|
||||||
import { useToast } from '@/hooks/use-toast';
|
import { useToast } from '@/hooks/use-toast';
|
||||||
import { LoadingState } from '@/components/services/LoadingState';
|
import { useTheme } from '@/contexts/ThemeContext';
|
||||||
|
|
||||||
interface ScheduledMaintenanceTabProps {
|
interface ScheduledMaintenanceTabProps {
|
||||||
refreshTrigger?: number;
|
refreshTrigger?: number;
|
||||||
@@ -18,25 +15,24 @@ interface ScheduledMaintenanceTabProps {
|
|||||||
|
|
||||||
export const ScheduledMaintenanceTab = ({ refreshTrigger = 0 }: ScheduledMaintenanceTabProps) => {
|
export const ScheduledMaintenanceTab = ({ refreshTrigger = 0 }: ScheduledMaintenanceTabProps) => {
|
||||||
const { t } = useLanguage();
|
const { t } = useLanguage();
|
||||||
|
const { theme } = useTheme();
|
||||||
const { toast } = useToast();
|
const { toast } = useToast();
|
||||||
const [manualRefresh, setManualRefresh] = React.useState(0);
|
const [manualRefresh, setManualRefresh] = React.useState(0);
|
||||||
|
|
||||||
// Combine the external refresh trigger with our internal one
|
|
||||||
const combinedRefreshTrigger = refreshTrigger + manualRefresh;
|
const combinedRefreshTrigger = refreshTrigger + manualRefresh;
|
||||||
|
|
||||||
const {
|
const {
|
||||||
loading,
|
loading,
|
||||||
filter,
|
filter,
|
||||||
setFilter,
|
setFilter,
|
||||||
maintenanceData,
|
maintenanceData,
|
||||||
overviewStats,
|
overviewStats,
|
||||||
fetchMaintenanceData,
|
fetchMaintenanceData,
|
||||||
isEmpty,
|
isEmpty,
|
||||||
error,
|
error,
|
||||||
initialized
|
initialized,
|
||||||
} = useMaintenanceData({ refreshTrigger: combinedRefreshTrigger });
|
} = useMaintenanceData({ refreshTrigger: combinedRefreshTrigger });
|
||||||
|
|
||||||
// Display toast when error occurs
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (error) {
|
if (error) {
|
||||||
toast({
|
toast({
|
||||||
@@ -47,66 +43,91 @@ export const ScheduledMaintenanceTab = ({ refreshTrigger = 0 }: ScheduledMainten
|
|||||||
}
|
}
|
||||||
}, [error, toast, t]);
|
}, [error, toast, t]);
|
||||||
|
|
||||||
// Force fetch data on initial load
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
console.log("ScheduledMaintenanceTab is mounted, fetching data");
|
|
||||||
fetchMaintenanceData(true);
|
fetchMaintenanceData(true);
|
||||||
}, [fetchMaintenanceData]);
|
}, [fetchMaintenanceData]);
|
||||||
|
|
||||||
// Handle maintenance updates
|
|
||||||
const handleMaintenanceUpdated = () => {
|
const handleMaintenanceUpdated = () => {
|
||||||
console.log("Maintenance updated, refreshing data");
|
setManualRefresh((prev) => prev + 1);
|
||||||
setManualRefresh(prev => prev + 1);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
// Handle tab changes
|
|
||||||
const handleTabChange = (value: string) => {
|
const handleTabChange = (value: string) => {
|
||||||
setFilter(value);
|
setFilter(value);
|
||||||
};
|
};
|
||||||
|
|
||||||
// Show full-page loading state during initial load
|
|
||||||
if (loading && !initialized) {
|
if (loading && !initialized) {
|
||||||
return <LoadingState />;
|
return <LoadingState />;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const gradientCard = (
|
||||||
|
title: string,
|
||||||
|
value: string | number,
|
||||||
|
icon: JSX.Element,
|
||||||
|
gradient: string
|
||||||
|
) => (
|
||||||
|
<Card
|
||||||
|
className="border-none rounded-xl shadow-lg overflow-hidden transition-all duration-300 hover:shadow-xl transform hover:-translate-y-1 relative z-10"
|
||||||
|
style={{
|
||||||
|
background: theme === 'dark'
|
||||||
|
? `linear-gradient(135deg, rgba(65,59,55,0.8) 0%, ${gradient})`
|
||||||
|
: `linear-gradient(135deg, rgba(65,59,55,0.8) 0%, ${gradient})`,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<div className="absolute inset-0 z-0 opacity-10">
|
||||||
|
<div
|
||||||
|
className="w-full h-full"
|
||||||
|
style={{
|
||||||
|
backgroundImage: `linear-gradient(#000 1px, transparent 1px),
|
||||||
|
linear-gradient(90deg, #fff 1px, transparent 1px)`,
|
||||||
|
backgroundSize: '20px 20px',
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<CardHeader className="pb-2 relative z-10">
|
||||||
|
<CardTitle className="text-sm font-medium text-white">{title}</CardTitle>
|
||||||
|
</CardHeader>
|
||||||
|
<CardContent className="flex items-center justify-between relative z-10">
|
||||||
|
<span className="text-4xl font-bold text-white">{value}</span>
|
||||||
|
<div className="rounded-full p-3 bg-white/25 backdrop-blur-sm">
|
||||||
|
{icon}
|
||||||
|
</div>
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
{/* Status checker for automatic updates */}
|
<MaintenanceStatusChecker
|
||||||
<MaintenanceStatusChecker
|
|
||||||
maintenanceData={maintenanceData}
|
maintenanceData={maintenanceData}
|
||||||
onStatusUpdated={handleMaintenanceUpdated}
|
onStatusUpdated={handleMaintenanceUpdated}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
{/* Overview Cards */}
|
{/* Overview Cards */}
|
||||||
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-4 mb-6">
|
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 gap-6 mb-8 w-full">
|
||||||
<OverviewCard
|
{gradientCard(
|
||||||
title={t('upcomingMaintenance')}
|
t('upcomingMaintenance'),
|
||||||
value={loading ? '...' : overviewStats.upcoming}
|
loading ? '...' : overviewStats.upcoming,
|
||||||
icon={<Calendar className="h-5 w-5 text-white" />}
|
<Calendar className="h-6 w-6 text-white" />,
|
||||||
isLoading={loading}
|
'rgba(59,130,246,0.6)'
|
||||||
color="blue"
|
)}
|
||||||
/>
|
{gradientCard(
|
||||||
<OverviewCard
|
t('ongoingMaintenance'),
|
||||||
title={t('ongoingMaintenance')}
|
loading ? '...' : overviewStats.ongoing,
|
||||||
value={loading ? '...' : overviewStats.ongoing}
|
<Clock className="h-6 w-6 text-white" />,
|
||||||
icon={<Clock className="h-5 w-5 text-white" />}
|
'rgba(251,191,36,0.6)'
|
||||||
isLoading={loading}
|
)}
|
||||||
color="amber"
|
{gradientCard(
|
||||||
/>
|
t('completedMaintenance'),
|
||||||
<OverviewCard
|
loading ? '...' : overviewStats.completed,
|
||||||
title={t('completedMaintenance')}
|
<CheckCircle className="h-6 w-6 text-white" />,
|
||||||
value={loading ? '...' : overviewStats.completed}
|
'rgba(34,197,94,0.6)'
|
||||||
icon={<CheckCircle className="h-5 w-5 text-white" />}
|
)}
|
||||||
isLoading={loading}
|
{gradientCard(
|
||||||
color="green"
|
t('totalScheduledHours'),
|
||||||
/>
|
loading ? '...' : `${overviewStats.totalDuration}h`,
|
||||||
<OverviewCard
|
<Clock className="h-6 w-6 text-white" />,
|
||||||
title={t('totalScheduledHours')}
|
'rgba(139,92,246,0.6)'
|
||||||
value={loading ? '...' : `${overviewStats.totalDuration}h`}
|
)}
|
||||||
icon={<Clock className="h-5 w-5 text-white" />}
|
|
||||||
isLoading={loading}
|
|
||||||
color="purple"
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<Card>
|
<Card>
|
||||||
@@ -117,10 +138,10 @@ export const ScheduledMaintenanceTab = ({ refreshTrigger = 0 }: ScheduledMainten
|
|||||||
</CardDescription>
|
</CardDescription>
|
||||||
</CardHeader>
|
</CardHeader>
|
||||||
<CardContent>
|
<CardContent>
|
||||||
<Tabs
|
<Tabs
|
||||||
defaultValue="upcoming"
|
defaultValue="upcoming"
|
||||||
value={filter}
|
value={filter}
|
||||||
className="w-full"
|
className="w-full"
|
||||||
onValueChange={handleTabChange}
|
onValueChange={handleTabChange}
|
||||||
>
|
>
|
||||||
<TabsList className="mb-6">
|
<TabsList className="mb-6">
|
||||||
@@ -129,24 +150,24 @@ export const ScheduledMaintenanceTab = ({ refreshTrigger = 0 }: ScheduledMainten
|
|||||||
<TabsTrigger value="completed">{t('completedMaintenance')}</TabsTrigger>
|
<TabsTrigger value="completed">{t('completedMaintenance')}</TabsTrigger>
|
||||||
</TabsList>
|
</TabsList>
|
||||||
|
|
||||||
<TabsContent value="upcoming" className="space-y-4">
|
<TabsContent value="upcoming">
|
||||||
<MaintenanceTable
|
<MaintenanceTable
|
||||||
data={maintenanceData}
|
data={maintenanceData}
|
||||||
isLoading={loading && initialized}
|
isLoading={loading && initialized}
|
||||||
onMaintenanceUpdated={handleMaintenanceUpdated}
|
onMaintenanceUpdated={handleMaintenanceUpdated}
|
||||||
/>
|
/>
|
||||||
</TabsContent>
|
</TabsContent>
|
||||||
|
|
||||||
<TabsContent value="ongoing" className="space-y-4">
|
<TabsContent value="ongoing">
|
||||||
<MaintenanceTable
|
<MaintenanceTable
|
||||||
data={maintenanceData}
|
data={maintenanceData}
|
||||||
isLoading={loading && initialized}
|
isLoading={loading && initialized}
|
||||||
onMaintenanceUpdated={handleMaintenanceUpdated}
|
onMaintenanceUpdated={handleMaintenanceUpdated}
|
||||||
/>
|
/>
|
||||||
</TabsContent>
|
</TabsContent>
|
||||||
|
|
||||||
<TabsContent value="completed" className="space-y-4">
|
<TabsContent value="completed">
|
||||||
<MaintenanceTable
|
<MaintenanceTable
|
||||||
data={maintenanceData}
|
data={maintenanceData}
|
||||||
isLoading={loading && initialized}
|
isLoading={loading && initialized}
|
||||||
onMaintenanceUpdated={handleMaintenanceUpdated}
|
onMaintenanceUpdated={handleMaintenanceUpdated}
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ export const OSTypeIcon = ({ osType, className = "h-4 w-4" }: OSTypeIconProps) =
|
|||||||
const getOSIcon = (os: string) => {
|
const getOSIcon = (os: string) => {
|
||||||
const osLower = os.toLowerCase();
|
const osLower = os.toLowerCase();
|
||||||
|
|
||||||
if (osLower.includes('linux') || osLower.includes('ubuntu') || osLower.includes('centos')) {
|
if (osLower.includes('linux') || osLower.includes('ubuntu') || osLower.includes('centos') || osLower.includes('debian')) {
|
||||||
return <Server className={className} />;
|
return <Server className={className} />;
|
||||||
} else if (osLower.includes('windows')) {
|
} else if (osLower.includes('windows')) {
|
||||||
return <Monitor className={className} />;
|
return <Monitor className={className} />;
|
||||||
|
|||||||
@@ -1,20 +1,23 @@
|
|||||||
import { useQuery } from "@tanstack/react-query";
|
import { useQuery } from "@tanstack/react-query";
|
||||||
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
|
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
|
||||||
import { ChartContainer, ChartTooltip, ChartTooltipContent } from "@/components/ui/chart";
|
import { ChartContainer, ChartTooltip, ChartTooltipContent } from "@/components/ui/chart";
|
||||||
import { LineChart, Line, XAxis, YAxis, CartesianGrid, ResponsiveContainer } from "recharts";
|
import { LineChart, Line, AreaChart, Area, XAxis, YAxis, CartesianGrid, ResponsiveContainer } from "recharts";
|
||||||
import { serverService } from "@/services/serverService";
|
import { serverService } from "@/services/serverService";
|
||||||
import { Loader2, TrendingUp, Cpu, HardDrive, Wifi, MemoryStick } from "lucide-react";
|
import { Loader2, TrendingUp, Cpu, HardDrive, Wifi, MemoryStick } from "lucide-react";
|
||||||
import { useTheme } from "@/contexts/ThemeContext";
|
import { useTheme } from "@/contexts/ThemeContext";
|
||||||
import { DateRangeFilter } from "@/components/services/DateRangeFilter";
|
import { TimeRangeSelector } from "./charts/TimeRangeSelector";
|
||||||
import { useState } from "react";
|
import { useState } from "react";
|
||||||
|
import { formatChartData, filterMetricsByTimeRange, formatBytes } from "./charts/dataUtils";
|
||||||
|
|
||||||
interface ServerHistoryChartsProps {
|
interface ServerHistoryChartsProps {
|
||||||
serverId: string;
|
serverId: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type TimeRange = '60m' | '1d' | '7d' | '1m' | '3m';
|
||||||
|
|
||||||
export const ServerHistoryCharts = ({ serverId }: ServerHistoryChartsProps) => {
|
export const ServerHistoryCharts = ({ serverId }: ServerHistoryChartsProps) => {
|
||||||
const { theme } = useTheme();
|
const { theme } = useTheme();
|
||||||
const [dateRange, setDateRange] = useState({ start: new Date(Date.now() - 24 * 60 * 60 * 1000), end: new Date() });
|
const [timeRange, setTimeRange] = useState<TimeRange>("1d");
|
||||||
|
|
||||||
console.log('ServerHistoryCharts: Rendering with serverId:', serverId);
|
console.log('ServerHistoryCharts: Rendering with serverId:', serverId);
|
||||||
|
|
||||||
@@ -23,117 +26,135 @@ export const ServerHistoryCharts = ({ serverId }: ServerHistoryChartsProps) => {
|
|||||||
isLoading,
|
isLoading,
|
||||||
error
|
error
|
||||||
} = useQuery({
|
} = useQuery({
|
||||||
queryKey: ['server-metrics-history', serverId],
|
queryKey: ['server-metrics-history', serverId, timeRange],
|
||||||
queryFn: async () => {
|
queryFn: async () => {
|
||||||
console.log('ServerHistoryCharts: Fetching metrics for serverId:', serverId);
|
console.log('ServerHistoryCharts: Fetching metrics for serverId:', serverId);
|
||||||
const result = await serverService.getServerMetrics(serverId);
|
const result = await serverService.getServerMetrics(serverId, timeRange);
|
||||||
console.log('ServerHistoryCharts: Raw metrics result:', result);
|
console.log('ServerHistoryCharts: Raw metrics result:', result);
|
||||||
return result;
|
return result;
|
||||||
},
|
},
|
||||||
enabled: !!serverId,
|
enabled: !!serverId,
|
||||||
refetchInterval: 60000, // Refresh every minute
|
refetchInterval: 60000,
|
||||||
retry: 1
|
retry: 1
|
||||||
});
|
});
|
||||||
|
|
||||||
const handleDateRangeChange = (startDate: Date, endDate: Date, option: any) => {
|
|
||||||
setDateRange({ start: startDate, end: endDate });
|
|
||||||
console.log('Date range changed:', { startDate, endDate, option });
|
|
||||||
};
|
|
||||||
|
|
||||||
console.log('ServerHistoryCharts: Query state:', {
|
console.log('ServerHistoryCharts: Query state:', {
|
||||||
metricsCount: metrics.length,
|
metricsCount: metrics.length,
|
||||||
isLoading,
|
isLoading,
|
||||||
error: error?.message,
|
error: error?.message,
|
||||||
firstMetric: metrics[0],
|
firstMetric: metrics[0],
|
||||||
serverId
|
serverId,
|
||||||
|
timeRange
|
||||||
});
|
});
|
||||||
|
|
||||||
const formatBytes = (bytes: number) => {
|
const chartData = formatChartData(metrics, timeRange);
|
||||||
if (bytes === 0) return '0 B';
|
|
||||||
const k = 1024;
|
|
||||||
const sizes = ['B', 'KB', 'MB', 'GB', 'TB'];
|
|
||||||
const i = Math.floor(Math.log(bytes) / Math.log(k));
|
|
||||||
return parseFloat((bytes / Math.pow(k, i)).toFixed(1)) + ' ' + sizes[i];
|
|
||||||
};
|
|
||||||
|
|
||||||
const parseValueWithUnit = (value: string | number) => {
|
|
||||||
if (typeof value === 'number') return value;
|
|
||||||
const match = value.toString().match(/^([\d.]+)/);
|
|
||||||
return match ? parseFloat(match[1]) : 0;
|
|
||||||
};
|
|
||||||
|
|
||||||
const convertToBytes = (value: string | number): number => {
|
|
||||||
if (typeof value === 'number') return value;
|
|
||||||
|
|
||||||
const str = value.toString();
|
|
||||||
const numMatch = str.match(/^([\d.]+)/);
|
|
||||||
const unitMatch = str.match(/([A-Za-z]+)$/);
|
|
||||||
|
|
||||||
if (!numMatch) return 0;
|
|
||||||
|
|
||||||
const num = parseFloat(numMatch[1]);
|
|
||||||
const unit = unitMatch ? unitMatch[1].toUpperCase() : 'B';
|
|
||||||
|
|
||||||
const multipliers: { [key: string]: number } = {
|
|
||||||
'B': 1,
|
|
||||||
'KB': 1024,
|
|
||||||
'MB': 1024 * 1024,
|
|
||||||
'GB': 1024 * 1024 * 1024,
|
|
||||||
'TB': 1024 * 1024 * 1024 * 1024
|
|
||||||
};
|
|
||||||
|
|
||||||
return num * (multipliers[unit] || 1);
|
|
||||||
};
|
|
||||||
|
|
||||||
const formatChartData = (metrics: any[]) => {
|
|
||||||
console.log('ServerHistoryCharts: Formatting chart data for', metrics.length, 'metrics');
|
|
||||||
|
|
||||||
if (!Array.isArray(metrics) || metrics.length === 0) {
|
|
||||||
console.log('ServerHistoryCharts: No metrics to format');
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
|
|
||||||
const formattedData = metrics.slice(-50).reverse().map((metric, index) => {
|
|
||||||
console.log(`ServerHistoryCharts: Processing metric ${index}:`, metric);
|
|
||||||
|
|
||||||
const cpuUsage = parseValueWithUnit(metric.cpu_usage || 0);
|
|
||||||
const ramUsedBytes = convertToBytes(metric.ram_used || 0);
|
|
||||||
const ramTotalBytes = convertToBytes(metric.ram_total || 0);
|
|
||||||
const diskUsedBytes = convertToBytes(metric.disk_used || 0);
|
|
||||||
const diskTotalBytes = convertToBytes(metric.disk_total || 0);
|
|
||||||
|
|
||||||
const ramUsagePercent = ramTotalBytes > 0 ? (ramUsedBytes / ramTotalBytes) * 100 : 0;
|
|
||||||
const diskUsagePercent = diskTotalBytes > 0 ? (diskUsedBytes / diskTotalBytes) * 100 : 0;
|
|
||||||
|
|
||||||
return {
|
|
||||||
timestamp: new Date(metric.timestamp || metric.created).toLocaleTimeString('en-US', {
|
|
||||||
hour: '2-digit',
|
|
||||||
minute: '2-digit'
|
|
||||||
}),
|
|
||||||
fullTimestamp: new Date(metric.timestamp || metric.created).toLocaleString(),
|
|
||||||
cpuUsage: Math.round(cpuUsage * 100) / 100,
|
|
||||||
ramUsagePercent: Math.round(ramUsagePercent * 100) / 100,
|
|
||||||
diskUsagePercent: Math.round(diskUsagePercent * 100) / 100,
|
|
||||||
ramUsedBytes,
|
|
||||||
ramTotalBytes,
|
|
||||||
diskUsedBytes,
|
|
||||||
diskTotalBytes,
|
|
||||||
networkRxBytes: metric.network_rx_bytes || 0,
|
|
||||||
networkTxBytes: metric.network_tx_bytes || 0,
|
|
||||||
networkRxSpeed: parseValueWithUnit(metric.network_rx_speed || 0),
|
|
||||||
networkTxSpeed: parseValueWithUnit(metric.network_tx_speed || 0),
|
|
||||||
};
|
|
||||||
});
|
|
||||||
|
|
||||||
console.log('ServerHistoryCharts: Formatted chart data:', formattedData);
|
|
||||||
return formattedData;
|
|
||||||
};
|
|
||||||
|
|
||||||
const chartData = formatChartData(metrics);
|
|
||||||
|
|
||||||
const getGridColor = () => theme === 'dark' ? '#374151' : '#e5e7eb';
|
const getGridColor = () => theme === 'dark' ? '#374151' : '#e5e7eb';
|
||||||
const getAxisColor = () => theme === 'dark' ? '#9ca3af' : '#6b7280';
|
const getAxisColor = () => theme === 'dark' ? '#9ca3af' : '#6b7280';
|
||||||
|
|
||||||
|
// Custom tooltip content with detailed information and full date/time
|
||||||
|
const DetailedTooltipContent = ({ active, payload, label }: any) => {
|
||||||
|
if (active && payload && payload.length) {
|
||||||
|
const data = payload[0]?.payload;
|
||||||
|
return (
|
||||||
|
<div className="bg-popover/95 border border-border backdrop-blur-sm rounded-lg p-3 shadow-lg">
|
||||||
|
<p className="font-medium text-popover-foreground mb-2">{label}</p>
|
||||||
|
{data?.fullTimestamp && (
|
||||||
|
<p className="text-xs text-muted-foreground mb-2">
|
||||||
|
{new Date(data.fullTimestamp).toLocaleString('en-US', {
|
||||||
|
weekday: 'short',
|
||||||
|
year: 'numeric',
|
||||||
|
month: 'short',
|
||||||
|
day: 'numeric',
|
||||||
|
hour: '2-digit',
|
||||||
|
minute: '2-digit',
|
||||||
|
second: '2-digit'
|
||||||
|
})}
|
||||||
|
</p>
|
||||||
|
)}
|
||||||
|
{payload.map((entry: any, index: number) => {
|
||||||
|
const data = entry.payload;
|
||||||
|
return (
|
||||||
|
<div key={index} className="space-y-1">
|
||||||
|
<div className="flex items-center gap-2">
|
||||||
|
<div
|
||||||
|
className="w-3 h-3 rounded-full"
|
||||||
|
style={{ backgroundColor: entry.color }}
|
||||||
|
/>
|
||||||
|
<span className="text-sm font-medium">{entry.name}: {entry.value}%</span>
|
||||||
|
</div>
|
||||||
|
{entry.dataKey === 'cpuUsage' && (
|
||||||
|
<div className="text-xs text-muted-foreground ml-5">
|
||||||
|
<div>CPU Cores: {data.cpuCores}</div>
|
||||||
|
<div>Free: {data.cpuFree}%</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
{entry.dataKey === 'ramUsagePercent' && (
|
||||||
|
<div className="text-xs text-muted-foreground ml-5">
|
||||||
|
<div>Used: {data.ramUsed}</div>
|
||||||
|
<div>Total: {data.ramTotal}</div>
|
||||||
|
<div>Free: {data.ramFree}</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
{entry.dataKey === 'diskUsagePercent' && (
|
||||||
|
<div className="text-xs text-muted-foreground ml-5">
|
||||||
|
<div>Used: {data.diskUsed}</div>
|
||||||
|
<div>Total: {data.diskTotal}</div>
|
||||||
|
<div>Free: {data.diskFree}</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Network tooltip with detailed info and full date/time
|
||||||
|
const NetworkTooltipContent = ({ active, payload, label }: any) => {
|
||||||
|
if (active && payload && payload.length) {
|
||||||
|
const data = payload[0]?.payload;
|
||||||
|
return (
|
||||||
|
<div className="bg-popover/95 border border-border backdrop-blur-sm rounded-lg p-3 shadow-lg">
|
||||||
|
<p className="font-medium text-popover-foreground mb-2">{label}</p>
|
||||||
|
{data?.fullTimestamp && (
|
||||||
|
<p className="text-xs text-muted-foreground mb-2">
|
||||||
|
{new Date(data.fullTimestamp).toLocaleString('en-US', {
|
||||||
|
weekday: 'short',
|
||||||
|
year: 'numeric',
|
||||||
|
month: 'short',
|
||||||
|
day: 'numeric',
|
||||||
|
hour: '2-digit',
|
||||||
|
minute: '2-digit',
|
||||||
|
second: '2-digit'
|
||||||
|
})}
|
||||||
|
</p>
|
||||||
|
)}
|
||||||
|
{payload.map((entry: any, index: number) => (
|
||||||
|
<div key={index} className="flex items-center gap-2 mb-1">
|
||||||
|
<div
|
||||||
|
className="w-3 h-3 rounded-full"
|
||||||
|
style={{ backgroundColor: entry.color }}
|
||||||
|
/>
|
||||||
|
<span className="text-sm font-medium">
|
||||||
|
{entry.name}: {entry.dataKey.includes('Speed') ? `${entry.value} KB/s` : formatBytes(entry.value)}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
{data && (
|
||||||
|
<div className="text-xs text-muted-foreground mt-2 pt-2 border-t border-border">
|
||||||
|
<div>Total RX: {data.networkRx}</div>
|
||||||
|
<div>Total TX: {data.networkTx}</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
};
|
||||||
|
|
||||||
if (isLoading) {
|
if (isLoading) {
|
||||||
return (
|
return (
|
||||||
<div className="space-y-6">
|
<div className="space-y-6">
|
||||||
@@ -143,6 +164,7 @@ export const ServerHistoryCharts = ({ serverId }: ServerHistoryChartsProps) => {
|
|||||||
<h2 className="text-lg font-medium">Historical Performance</h2>
|
<h2 className="text-lg font-medium">Historical Performance</h2>
|
||||||
<Loader2 className="h-4 w-4 animate-spin ml-2" />
|
<Loader2 className="h-4 w-4 animate-spin ml-2" />
|
||||||
</div>
|
</div>
|
||||||
|
<TimeRangeSelector value={timeRange} onChange={setTimeRange} />
|
||||||
</div>
|
</div>
|
||||||
<div className="grid grid-cols-1 lg:grid-cols-2 gap-6">
|
<div className="grid grid-cols-1 lg:grid-cols-2 gap-6">
|
||||||
{[1, 2, 3, 4].map((index) => (
|
{[1, 2, 3, 4].map((index) => (
|
||||||
@@ -174,7 +196,7 @@ export const ServerHistoryCharts = ({ serverId }: ServerHistoryChartsProps) => {
|
|||||||
<TrendingUp className="h-5 w-5" />
|
<TrendingUp className="h-5 w-5" />
|
||||||
<h2 className="text-lg font-medium">Historical Performance</h2>
|
<h2 className="text-lg font-medium">Historical Performance</h2>
|
||||||
</div>
|
</div>
|
||||||
<DateRangeFilter onRangeChange={handleDateRangeChange} />
|
<TimeRangeSelector value={timeRange} onChange={setTimeRange} />
|
||||||
</div>
|
</div>
|
||||||
<Card>
|
<Card>
|
||||||
<CardContent className="flex items-center justify-center py-12">
|
<CardContent className="flex items-center justify-center py-12">
|
||||||
@@ -197,16 +219,16 @@ export const ServerHistoryCharts = ({ serverId }: ServerHistoryChartsProps) => {
|
|||||||
<TrendingUp className="h-5 w-5" />
|
<TrendingUp className="h-5 w-5" />
|
||||||
<h2 className="text-lg font-medium">Historical Performance</h2>
|
<h2 className="text-lg font-medium">Historical Performance</h2>
|
||||||
</div>
|
</div>
|
||||||
<DateRangeFilter onRangeChange={handleDateRangeChange} />
|
<TimeRangeSelector value={timeRange} onChange={setTimeRange} />
|
||||||
</div>
|
</div>
|
||||||
<Card>
|
<Card>
|
||||||
<CardContent className="flex items-center justify-center py-12">
|
<CardContent className="flex items-center justify-center py-12">
|
||||||
<div className="text-center">
|
<div className="text-center">
|
||||||
<p className="text-muted-foreground">No historical data available</p>
|
<p className="text-muted-foreground">No historical data available for {timeRange}</p>
|
||||||
<p className="text-xs mt-2">Metrics count: {metrics.length}</p>
|
<p className="text-xs mt-2">Raw metrics count: {metrics.length}</p>
|
||||||
<p className="text-xs mt-1">Server ID: {serverId}</p>
|
<p className="text-xs mt-1">Server ID: {serverId}</p>
|
||||||
<p className="text-xs mt-1 text-muted-foreground">
|
<p className="text-xs mt-1 text-muted-foreground">
|
||||||
{metrics.length > 0 ? 'Data exists but failed to format' : 'No metrics data found'}
|
{metrics.length > 0 ? 'Data exists but filtered out by time range' : 'No metrics data found'}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</CardContent>
|
</CardContent>
|
||||||
@@ -215,7 +237,10 @@ export const ServerHistoryCharts = ({ serverId }: ServerHistoryChartsProps) => {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
console.log('ServerHistoryCharts: Rendering individual charts with', chartData.length, 'data points');
|
console.log('ServerHistoryCharts: Rendering charts with', chartData.length, 'data points for time range:', timeRange);
|
||||||
|
|
||||||
|
// Calculate summary stats from latest data point
|
||||||
|
const latestData = chartData[chartData.length - 1];
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="space-y-6">
|
<div className="space-y-6">
|
||||||
@@ -223,20 +248,28 @@ export const ServerHistoryCharts = ({ serverId }: ServerHistoryChartsProps) => {
|
|||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
<TrendingUp className="h-5 w-5" />
|
<TrendingUp className="h-5 w-5" />
|
||||||
<h2 className="text-lg font-medium">Historical Performance</h2>
|
<h2 className="text-lg font-medium">Historical Performance</h2>
|
||||||
<span className="text-xs text-muted-foreground">({chartData.length} data points)</span>
|
<span className="text-xs text-muted-foreground">({chartData.length} data points • {timeRange})</span>
|
||||||
</div>
|
</div>
|
||||||
<DateRangeFilter onRangeChange={handleDateRangeChange} />
|
<TimeRangeSelector value={timeRange} onChange={setTimeRange} />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="grid grid-cols-1 lg:grid-cols-2 gap-6">
|
<div className="grid grid-cols-1 lg:grid-cols-2 gap-6">
|
||||||
{/* CPU Usage Chart */}
|
{/* CPU Usage Chart - Smooth Area Chart with Blue Gradient */}
|
||||||
<Card className="bg-gradient-to-br from-background to-muted/20 border-border/50 shadow-lg hover:shadow-xl transition-all duration-300 backdrop-blur-sm">
|
<Card className="bg-gradient-to-br from-background to-muted/20 border-border/50 shadow-lg hover:shadow-xl transition-all duration-300 backdrop-blur-sm">
|
||||||
<CardHeader className="pb-2">
|
<CardHeader className="pb-2">
|
||||||
<CardTitle className="flex items-center gap-2 text-foreground">
|
<CardTitle className="flex items-center justify-between text-foreground">
|
||||||
<div className="p-2 rounded-lg bg-blue-500/15">
|
<div className="flex items-center gap-2">
|
||||||
<Cpu className="h-5 w-5 text-blue-500" />
|
<div className="p-2 rounded-lg bg-blue-500/15">
|
||||||
|
<Cpu className="h-5 w-5 text-blue-500" />
|
||||||
|
</div>
|
||||||
|
CPU Usage
|
||||||
</div>
|
</div>
|
||||||
CPU Usage
|
{latestData && (
|
||||||
|
<div className="text-right text-sm">
|
||||||
|
<div className="text-blue-500 font-semibold">{latestData.cpuUsage}%</div>
|
||||||
|
<div className="text-xs text-muted-foreground">{latestData.cpuCores} cores</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
</CardTitle>
|
</CardTitle>
|
||||||
</CardHeader>
|
</CardHeader>
|
||||||
<CardContent className="pt-2">
|
<CardContent className="pt-2">
|
||||||
@@ -246,10 +279,10 @@ export const ServerHistoryCharts = ({ serverId }: ServerHistoryChartsProps) => {
|
|||||||
color: theme === 'dark' ? "#3b82f6" : "#2563eb",
|
color: theme === 'dark' ? "#3b82f6" : "#2563eb",
|
||||||
}
|
}
|
||||||
}} className="h-80">
|
}} className="h-80">
|
||||||
<LineChart data={chartData}>
|
<AreaChart data={chartData}>
|
||||||
<defs>
|
<defs>
|
||||||
<linearGradient id="cpuGradient" x1="0" y1="0" x2="0" y2="1">
|
<linearGradient id="cpuGradient" x1="0" y1="0" x2="0" y2="1">
|
||||||
<stop offset="5%" stopColor={theme === 'dark' ? "#3b82f6" : "#2563eb"} stopOpacity={0.3}/>
|
<stop offset="5%" stopColor={theme === 'dark' ? "#3b82f6" : "#2563eb"} stopOpacity={0.4}/>
|
||||||
<stop offset="95%" stopColor={theme === 'dark' ? "#1e40af" : "#1d4ed8"} stopOpacity={0.1}/>
|
<stop offset="95%" stopColor={theme === 'dark' ? "#1e40af" : "#1d4ed8"} stopOpacity={0.1}/>
|
||||||
</linearGradient>
|
</linearGradient>
|
||||||
</defs>
|
</defs>
|
||||||
@@ -266,10 +299,10 @@ export const ServerHistoryCharts = ({ serverId }: ServerHistoryChartsProps) => {
|
|||||||
label={{ value: 'CPU %', angle: -90, position: 'insideLeft' }}
|
label={{ value: 'CPU %', angle: -90, position: 'insideLeft' }}
|
||||||
/>
|
/>
|
||||||
<ChartTooltip
|
<ChartTooltip
|
||||||
content={<ChartTooltipContent className="bg-popover border-border" />}
|
content={<DetailedTooltipContent />}
|
||||||
cursor={{ stroke: getGridColor() }}
|
cursor={{ stroke: getGridColor() }}
|
||||||
/>
|
/>
|
||||||
<Line
|
<Area
|
||||||
type="monotone"
|
type="monotone"
|
||||||
dataKey="cpuUsage"
|
dataKey="cpuUsage"
|
||||||
stroke={theme === 'dark' ? "#60a5fa" : "#2563eb"}
|
stroke={theme === 'dark' ? "#60a5fa" : "#2563eb"}
|
||||||
@@ -279,19 +312,27 @@ export const ServerHistoryCharts = ({ serverId }: ServerHistoryChartsProps) => {
|
|||||||
fillOpacity={1}
|
fillOpacity={1}
|
||||||
name="CPU Usage (%)"
|
name="CPU Usage (%)"
|
||||||
/>
|
/>
|
||||||
</LineChart>
|
</AreaChart>
|
||||||
</ChartContainer>
|
</ChartContainer>
|
||||||
</CardContent>
|
</CardContent>
|
||||||
</Card>
|
</Card>
|
||||||
|
|
||||||
{/* Memory Usage Chart */}
|
{/* Memory Usage Chart - Basis Area Chart with Green Gradient */}
|
||||||
<Card className="bg-gradient-to-br from-background to-muted/20 border-border/50 shadow-lg hover:shadow-xl transition-all duration-300 backdrop-blur-sm">
|
<Card className="bg-gradient-to-br from-background to-muted/20 border-border/50 shadow-lg hover:shadow-xl transition-all duration-300 backdrop-blur-sm">
|
||||||
<CardHeader className="pb-2">
|
<CardHeader className="pb-2">
|
||||||
<CardTitle className="flex items-center gap-2 text-foreground">
|
<CardTitle className="flex items-center justify-between text-foreground">
|
||||||
<div className="p-2 rounded-lg bg-green-500/15">
|
<div className="flex items-center gap-2">
|
||||||
<MemoryStick className="h-5 w-5 text-green-500" />
|
<div className="p-2 rounded-lg bg-green-500/15">
|
||||||
|
<MemoryStick className="h-5 w-5 text-green-500" />
|
||||||
|
</div>
|
||||||
|
Memory Usage
|
||||||
</div>
|
</div>
|
||||||
Memory Usage
|
{latestData && (
|
||||||
|
<div className="text-right text-sm">
|
||||||
|
<div className="text-green-500 font-semibold">{latestData.ramUsagePercent}%</div>
|
||||||
|
<div className="text-xs text-muted-foreground">{latestData.ramUsed} / {latestData.ramTotal}</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
</CardTitle>
|
</CardTitle>
|
||||||
</CardHeader>
|
</CardHeader>
|
||||||
<CardContent className="pt-2">
|
<CardContent className="pt-2">
|
||||||
@@ -301,10 +342,10 @@ export const ServerHistoryCharts = ({ serverId }: ServerHistoryChartsProps) => {
|
|||||||
color: theme === 'dark' ? "#10b981" : "#059669",
|
color: theme === 'dark' ? "#10b981" : "#059669",
|
||||||
}
|
}
|
||||||
}} className="h-80">
|
}} className="h-80">
|
||||||
<LineChart data={chartData}>
|
<AreaChart data={chartData}>
|
||||||
<defs>
|
<defs>
|
||||||
<linearGradient id="memoryGradient" x1="0" y1="0" x2="0" y2="1">
|
<linearGradient id="memoryGradient" x1="0" y1="0" x2="0" y2="1">
|
||||||
<stop offset="5%" stopColor={theme === 'dark' ? "#10b981" : "#059669"} stopOpacity={0.3}/>
|
<stop offset="5%" stopColor={theme === 'dark' ? "#10b981" : "#059669"} stopOpacity={0.5}/>
|
||||||
<stop offset="95%" stopColor={theme === 'dark' ? "#047857" : "#065f46"} stopOpacity={0.1}/>
|
<stop offset="95%" stopColor={theme === 'dark' ? "#047857" : "#065f46"} stopOpacity={0.1}/>
|
||||||
</linearGradient>
|
</linearGradient>
|
||||||
</defs>
|
</defs>
|
||||||
@@ -321,11 +362,11 @@ export const ServerHistoryCharts = ({ serverId }: ServerHistoryChartsProps) => {
|
|||||||
label={{ value: 'Memory %', angle: -90, position: 'insideLeft' }}
|
label={{ value: 'Memory %', angle: -90, position: 'insideLeft' }}
|
||||||
/>
|
/>
|
||||||
<ChartTooltip
|
<ChartTooltip
|
||||||
content={<ChartTooltipContent className="bg-popover border-border" />}
|
content={<DetailedTooltipContent />}
|
||||||
cursor={{ stroke: getGridColor() }}
|
cursor={{ stroke: getGridColor() }}
|
||||||
/>
|
/>
|
||||||
<Line
|
<Area
|
||||||
type="monotone"
|
type="basis"
|
||||||
dataKey="ramUsagePercent"
|
dataKey="ramUsagePercent"
|
||||||
stroke={theme === 'dark' ? "#34d399" : "#059669"}
|
stroke={theme === 'dark' ? "#34d399" : "#059669"}
|
||||||
strokeWidth={3}
|
strokeWidth={3}
|
||||||
@@ -334,19 +375,27 @@ export const ServerHistoryCharts = ({ serverId }: ServerHistoryChartsProps) => {
|
|||||||
fillOpacity={1}
|
fillOpacity={1}
|
||||||
name="Memory Usage (%)"
|
name="Memory Usage (%)"
|
||||||
/>
|
/>
|
||||||
</LineChart>
|
</AreaChart>
|
||||||
</ChartContainer>
|
</ChartContainer>
|
||||||
</CardContent>
|
</CardContent>
|
||||||
</Card>
|
</Card>
|
||||||
|
|
||||||
{/* Disk Usage Chart */}
|
{/* Disk Usage Chart - Stepped Area Chart with Orange Gradient */}
|
||||||
<Card className="bg-gradient-to-br from-background to-muted/20 border-border/50 shadow-lg hover:shadow-xl transition-all duration-300 backdrop-blur-sm">
|
<Card className="bg-gradient-to-br from-background to-muted/20 border-border/50 shadow-lg hover:shadow-xl transition-all duration-300 backdrop-blur-sm">
|
||||||
<CardHeader className="pb-2">
|
<CardHeader className="pb-2">
|
||||||
<CardTitle className="flex items-center gap-2 text-foreground">
|
<CardTitle className="flex items-center justify-between text-foreground">
|
||||||
<div className="p-2 rounded-lg bg-amber-500/15">
|
<div className="flex items-center gap-2">
|
||||||
<HardDrive className="h-5 w-5 text-amber-500" />
|
<div className="p-2 rounded-lg bg-amber-500/15">
|
||||||
|
<HardDrive className="h-5 w-5 text-amber-500" />
|
||||||
|
</div>
|
||||||
|
Disk Usage
|
||||||
</div>
|
</div>
|
||||||
Disk Usage
|
{latestData && (
|
||||||
|
<div className="text-right text-sm">
|
||||||
|
<div className="text-amber-500 font-semibold">{latestData.diskUsagePercent}%</div>
|
||||||
|
<div className="text-xs text-muted-foreground">{latestData.diskUsed} / {latestData.diskTotal}</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
</CardTitle>
|
</CardTitle>
|
||||||
</CardHeader>
|
</CardHeader>
|
||||||
<CardContent className="pt-2">
|
<CardContent className="pt-2">
|
||||||
@@ -356,10 +405,10 @@ export const ServerHistoryCharts = ({ serverId }: ServerHistoryChartsProps) => {
|
|||||||
color: theme === 'dark' ? "#f59e0b" : "#d97706",
|
color: theme === 'dark' ? "#f59e0b" : "#d97706",
|
||||||
}
|
}
|
||||||
}} className="h-80">
|
}} className="h-80">
|
||||||
<LineChart data={chartData}>
|
<AreaChart data={chartData}>
|
||||||
<defs>
|
<defs>
|
||||||
<linearGradient id="diskGradient" x1="0" y1="0" x2="0" y2="1">
|
<linearGradient id="diskGradient" x1="0" y1="0" x2="0" y2="1">
|
||||||
<stop offset="5%" stopColor={theme === 'dark' ? "#f59e0b" : "#d97706"} stopOpacity={0.3}/>
|
<stop offset="5%" stopColor={theme === 'dark' ? "#f59e0b" : "#d97706"} stopOpacity={0.6}/>
|
||||||
<stop offset="95%" stopColor={theme === 'dark' ? "#d97706" : "#b45309"} stopOpacity={0.1}/>
|
<stop offset="95%" stopColor={theme === 'dark' ? "#d97706" : "#b45309"} stopOpacity={0.1}/>
|
||||||
</linearGradient>
|
</linearGradient>
|
||||||
</defs>
|
</defs>
|
||||||
@@ -376,11 +425,11 @@ export const ServerHistoryCharts = ({ serverId }: ServerHistoryChartsProps) => {
|
|||||||
label={{ value: 'Disk %', angle: -90, position: 'insideLeft' }}
|
label={{ value: 'Disk %', angle: -90, position: 'insideLeft' }}
|
||||||
/>
|
/>
|
||||||
<ChartTooltip
|
<ChartTooltip
|
||||||
content={<ChartTooltipContent className="bg-popover border-border" />}
|
content={<DetailedTooltipContent />}
|
||||||
cursor={{ stroke: getGridColor() }}
|
cursor={{ stroke: getGridColor() }}
|
||||||
/>
|
/>
|
||||||
<Line
|
<Area
|
||||||
type="monotone"
|
type="step"
|
||||||
dataKey="diskUsagePercent"
|
dataKey="diskUsagePercent"
|
||||||
stroke={theme === 'dark' ? "#fbbf24" : "#d97706"}
|
stroke={theme === 'dark' ? "#fbbf24" : "#d97706"}
|
||||||
strokeWidth={3}
|
strokeWidth={3}
|
||||||
@@ -389,19 +438,27 @@ export const ServerHistoryCharts = ({ serverId }: ServerHistoryChartsProps) => {
|
|||||||
fillOpacity={1}
|
fillOpacity={1}
|
||||||
name="Disk Usage (%)"
|
name="Disk Usage (%)"
|
||||||
/>
|
/>
|
||||||
</LineChart>
|
</AreaChart>
|
||||||
</ChartContainer>
|
</ChartContainer>
|
||||||
</CardContent>
|
</CardContent>
|
||||||
</Card>
|
</Card>
|
||||||
|
|
||||||
{/* Network Traffic Chart */}
|
{/* Network Traffic Chart - Dual Line Chart with Purple/Red Theme */}
|
||||||
<Card className="bg-gradient-to-br from-background to-muted/20 border-border/50 shadow-lg hover:shadow-xl transition-all duration-300 backdrop-blur-sm">
|
<Card className="bg-gradient-to-br from-background to-muted/20 border-border/50 shadow-lg hover:shadow-xl transition-all duration-300 backdrop-blur-sm">
|
||||||
<CardHeader className="pb-2">
|
<CardHeader className="pb-2">
|
||||||
<CardTitle className="flex items-center gap-2 text-foreground">
|
<CardTitle className="flex items-center justify-between text-foreground">
|
||||||
<div className="p-2 rounded-lg bg-purple-500/15">
|
<div className="flex items-center gap-2">
|
||||||
<Wifi className="h-5 w-5 text-purple-500" />
|
<div className="p-2 rounded-lg bg-purple-500/15">
|
||||||
|
<Wifi className="h-5 w-5 text-purple-500" />
|
||||||
|
</div>
|
||||||
|
Network Traffic
|
||||||
</div>
|
</div>
|
||||||
Network Traffic
|
{latestData && (
|
||||||
|
<div className="text-right text-sm">
|
||||||
|
<div className="text-purple-500 font-semibold">{latestData.networkRxSpeed} KB/s ↓</div>
|
||||||
|
<div className="text-red-500 font-semibold">{latestData.networkTxSpeed} KB/s ↑</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
</CardTitle>
|
</CardTitle>
|
||||||
</CardHeader>
|
</CardHeader>
|
||||||
<CardContent className="pt-2">
|
<CardContent className="pt-2">
|
||||||
@@ -421,6 +478,13 @@ export const ServerHistoryCharts = ({ serverId }: ServerHistoryChartsProps) => {
|
|||||||
<stop offset="5%" stopColor={theme === 'dark' ? "#8b5cf6" : "#7c3aed"} stopOpacity={0.2}/>
|
<stop offset="5%" stopColor={theme === 'dark' ? "#8b5cf6" : "#7c3aed"} stopOpacity={0.2}/>
|
||||||
<stop offset="95%" stopColor={theme === 'dark' ? "#7c3aed" : "#6d28d9"} stopOpacity={0.05}/>
|
<stop offset="95%" stopColor={theme === 'dark' ? "#7c3aed" : "#6d28d9"} stopOpacity={0.05}/>
|
||||||
</linearGradient>
|
</linearGradient>
|
||||||
|
<filter id="glow">
|
||||||
|
<feGaussianBlur stdDeviation="2" result="coloredBlur"/>
|
||||||
|
<feMerge>
|
||||||
|
<feMergeNode in="coloredBlur"/>
|
||||||
|
<feMergeNode in="SourceGraphic"/>
|
||||||
|
</feMerge>
|
||||||
|
</filter>
|
||||||
</defs>
|
</defs>
|
||||||
<CartesianGrid strokeDasharray="3 3" stroke={getGridColor()} opacity={0.3} />
|
<CartesianGrid strokeDasharray="3 3" stroke={getGridColor()} opacity={0.3} />
|
||||||
<XAxis
|
<XAxis
|
||||||
@@ -431,13 +495,11 @@ export const ServerHistoryCharts = ({ serverId }: ServerHistoryChartsProps) => {
|
|||||||
<YAxis
|
<YAxis
|
||||||
tick={{ fontSize: 10, fill: getAxisColor() }}
|
tick={{ fontSize: 10, fill: getAxisColor() }}
|
||||||
axisLine={{ stroke: getGridColor() }}
|
axisLine={{ stroke: getGridColor() }}
|
||||||
tickFormatter={(value) => formatBytes(value) + '/s'}
|
label={{ value: 'Network Speed (KB/s)', angle: -90, position: 'insideLeft' }}
|
||||||
label={{ value: 'Network Speed', angle: -90, position: 'insideLeft' }}
|
|
||||||
/>
|
/>
|
||||||
<ChartTooltip
|
<ChartTooltip
|
||||||
content={<ChartTooltipContent className="bg-popover border-border" />}
|
content={<NetworkTooltipContent />}
|
||||||
cursor={{ stroke: getGridColor() }}
|
cursor={{ stroke: getGridColor() }}
|
||||||
formatter={(value, name) => [formatBytes(Number(value)) + '/s', name]}
|
|
||||||
/>
|
/>
|
||||||
<Line
|
<Line
|
||||||
type="monotone"
|
type="monotone"
|
||||||
@@ -445,9 +507,9 @@ export const ServerHistoryCharts = ({ serverId }: ServerHistoryChartsProps) => {
|
|||||||
stroke={theme === 'dark' ? "#a78bfa" : "#7c3aed"}
|
stroke={theme === 'dark' ? "#a78bfa" : "#7c3aed"}
|
||||||
strokeWidth={3}
|
strokeWidth={3}
|
||||||
dot={false}
|
dot={false}
|
||||||
fill="url(#networkGradient)"
|
|
||||||
fillOpacity={0.5}
|
|
||||||
name="RX Speed"
|
name="RX Speed"
|
||||||
|
filter="url(#glow)"
|
||||||
|
strokeDasharray="0"
|
||||||
/>
|
/>
|
||||||
<Line
|
<Line
|
||||||
type="monotone"
|
type="monotone"
|
||||||
@@ -456,6 +518,7 @@ export const ServerHistoryCharts = ({ serverId }: ServerHistoryChartsProps) => {
|
|||||||
strokeWidth={3}
|
strokeWidth={3}
|
||||||
dot={false}
|
dot={false}
|
||||||
name="TX Speed"
|
name="TX Speed"
|
||||||
|
strokeDasharray="5 5"
|
||||||
/>
|
/>
|
||||||
</LineChart>
|
</LineChart>
|
||||||
</ChartContainer>
|
</ChartContainer>
|
||||||
|
|||||||
@@ -26,7 +26,7 @@ export const ServerMetricsCharts = ({ serverId }: ServerMetricsChartsProps) => {
|
|||||||
error
|
error
|
||||||
} = useQuery({
|
} = useQuery({
|
||||||
queryKey: ['server-metrics', serverId, timeRange],
|
queryKey: ['server-metrics', serverId, timeRange],
|
||||||
queryFn: () => serverService.getServerMetrics(serverId),
|
queryFn: () => serverService.getServerMetrics(serverId, timeRange),
|
||||||
enabled: !!serverId,
|
enabled: !!serverId,
|
||||||
refetchInterval: 30000
|
refetchInterval: 30000
|
||||||
});
|
});
|
||||||
@@ -53,7 +53,7 @@ export const ServerMetricsCharts = ({ serverId }: ServerMetricsChartsProps) => {
|
|||||||
if (chartData.length === 0) {
|
if (chartData.length === 0) {
|
||||||
return (
|
return (
|
||||||
<div className="flex items-center justify-center h-96 text-muted-foreground">
|
<div className="flex items-center justify-center h-96 text-muted-foreground">
|
||||||
<p>No server metrics data available</p>
|
<p>No server metrics data available for {timeRange}</p>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -63,7 +63,7 @@ export const ServerMetricsCharts = ({ serverId }: ServerMetricsChartsProps) => {
|
|||||||
<div className="flex items-center justify-between">
|
<div className="flex items-center justify-between">
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
<h2 className="text-lg font-medium">Server Metrics</h2>
|
<h2 className="text-lg font-medium">Server Metrics</h2>
|
||||||
<span className="text-xs text-muted-foreground">({chartData.length} data points)</span>
|
<span className="text-xs text-muted-foreground">({chartData.length} data points • {timeRange})</span>
|
||||||
</div>
|
</div>
|
||||||
<TimeRangeSelector value={timeRange} onChange={setTimeRange} />
|
<TimeRangeSelector value={timeRange} onChange={setTimeRange} />
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -203,47 +203,6 @@ export const ServerMetricsOverview = ({ server }: ServerMetricsOverviewProps) =>
|
|||||||
icon={HardDrive}
|
icon={HardDrive}
|
||||||
color={theme === 'dark' ? "#f59e0b" : "#d97706"}
|
color={theme === 'dark' ? "#f59e0b" : "#d97706"}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
{/* System Info Card */}
|
|
||||||
<Card className="relative overflow-hidden bg-gradient-to-br from-card/80 via-card to-card/60 border border-border/60 shadow-lg hover:shadow-xl hover:border-border/80 transition-all duration-300 backdrop-blur-sm group xl:col-span-2">
|
|
||||||
<div className="absolute inset-0 bg-gradient-to-br from-transparent via-white/[0.02] to-white/[0.05] opacity-0 group-hover:opacity-100 transition-opacity duration-300" />
|
|
||||||
<CardHeader className="pb-3 relative">
|
|
||||||
<CardTitle className="flex items-center gap-3 text-sm font-semibold">
|
|
||||||
<div className="p-2.5 rounded-xl bg-purple-500/15 shadow-sm ring-1 ring-white/10 transition-all duration-300 group-hover:scale-110" style={{ boxShadow: '0 4px 12px rgba(168, 85, 247, 0.2)' }}>
|
|
||||||
<Info className="h-4 w-4 text-purple-500" />
|
|
||||||
</div>
|
|
||||||
<span className="text-foreground/90">System Information</span>
|
|
||||||
</CardTitle>
|
|
||||||
</CardHeader>
|
|
||||||
<CardContent className="space-y-3 relative">
|
|
||||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-4 text-xs">
|
|
||||||
<div className="space-y-3">
|
|
||||||
<div className="bg-muted/20 rounded-lg p-3">
|
|
||||||
<span className="text-muted-foreground block mb-1">Operating System:</span>
|
|
||||||
<div className="font-semibold text-foreground">{server.os_type}</div>
|
|
||||||
</div>
|
|
||||||
<div className="bg-muted/20 rounded-lg p-3">
|
|
||||||
<span className="text-muted-foreground block mb-1">Hostname:</span>
|
|
||||||
<div className="font-mono text-xs text-foreground break-all">{server.hostname}</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div className="space-y-3">
|
|
||||||
<div className="bg-muted/20 rounded-lg p-3">
|
|
||||||
<span className="text-muted-foreground block mb-1">IP Address:</span>
|
|
||||||
<div className="font-mono text-xs text-foreground">{server.ip_address}</div>
|
|
||||||
</div>
|
|
||||||
{server.system_info && (
|
|
||||||
<div className="bg-muted/20 rounded-lg p-3">
|
|
||||||
<span className="text-muted-foreground block mb-1">System Details:</span>
|
|
||||||
<div className="font-mono text-[10px] text-foreground/80 line-clamp-3 leading-relaxed" title={server.system_info}>
|
|
||||||
{server.system_info}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</CardContent>
|
|
||||||
</Card>
|
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
@@ -17,32 +17,32 @@ export const ServerStatsCards = ({ stats }: ServerStatsCardsProps) => {
|
|||||||
value: stats.total,
|
value: stats.total,
|
||||||
icon: Server,
|
icon: Server,
|
||||||
gradient: theme === 'dark'
|
gradient: theme === 'dark'
|
||||||
? "linear-gradient(135deg, rgba(139, 69, 19, 0.8) 0%, rgba(160, 82, 45, 0.6) 100%)"
|
? "linear-gradient(135deg, rgba(65, 59, 55, 0.8) 0%, rgba(160, 82, 45, 0.6) 100%)"
|
||||||
: "linear-gradient(135deg, #8b4513 0%, #a0522d 100%)"
|
: "linear-gradient(135deg, rgba(65, 59, 55, 0.8) 0%, #a0522d 100%)"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: "ONLINE SERVERS",
|
title: "ONLINE SERVERS",
|
||||||
value: stats.online,
|
value: stats.online,
|
||||||
icon: Activity,
|
icon: Activity,
|
||||||
gradient: theme === 'dark'
|
gradient: theme === 'dark'
|
||||||
? "linear-gradient(135deg, rgba(67, 160, 71, 0.8) 0%, rgba(102, 187, 106, 0.6) 100%)"
|
? "linear-gradient(135deg, rgba(65, 59, 55, 0.8) 0%, rgba(102, 187, 106, 0.6) 100%)"
|
||||||
: "linear-gradient(135deg, #43a047 0%, #66bb6a 100%)"
|
: "linear-gradient(135deg, rgba(65, 59, 55, 0.8) 0%, #66bb6a 100%)"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: "OFFLINE SERVERS",
|
title: "OFFLINE SERVERS",
|
||||||
value: stats.offline,
|
value: stats.offline,
|
||||||
icon: Power,
|
icon: Power,
|
||||||
gradient: theme === 'dark'
|
gradient: theme === 'dark'
|
||||||
? "linear-gradient(135deg, rgba(229, 57, 53, 0.8) 0%, rgba(239, 83, 80, 0.6) 100%)"
|
? "linear-gradient(135deg, rgba(65, 59, 55, 0.8) 0%, rgba(239, 83, 80, 0.6) 100%)"
|
||||||
: "linear-gradient(135deg, #e53935 0%, #ef5350 100%)"
|
: "linear-gradient(135deg, rgba(65, 59, 55, 0.8) 0%, #ef5350 100%)"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: "WARNING SERVERS",
|
title: "WARNING SERVERS",
|
||||||
value: stats.warning,
|
value: stats.warning,
|
||||||
icon: AlertTriangle,
|
icon: AlertTriangle,
|
||||||
gradient: theme === 'dark'
|
gradient: theme === 'dark'
|
||||||
? "linear-gradient(135deg, rgba(255, 152, 0, 0.8) 0%, rgba(255, 183, 77, 0.6) 100%)"
|
? "linear-gradient(135deg, rgba(65, 59, 55, 0.8) 0%, rgba(255, 183, 77, 0.6) 100%)"
|
||||||
: "linear-gradient(135deg, #ff9800 0%, #ffb74d 100%)"
|
: "linear-gradient(135deg, rgba(65, 59, 55, 0.8) 0%, #ffb74d 100%)"
|
||||||
}
|
}
|
||||||
];
|
];
|
||||||
|
|
||||||
@@ -62,7 +62,7 @@ export const ServerStatsCards = ({ stats }: ServerStatsCardsProps) => {
|
|||||||
<div className="absolute inset-0 z-0 opacity-10">
|
<div className="absolute inset-0 z-0 opacity-10">
|
||||||
<div className="w-full h-full"
|
<div className="w-full h-full"
|
||||||
style={{
|
style={{
|
||||||
backgroundImage: `linear-gradient(#fff 1px, transparent 1px),
|
backgroundImage: `linear-gradient(#000 1px, transparent 1px),
|
||||||
linear-gradient(90deg, #fff 1px, transparent 1px)`,
|
linear-gradient(90deg, #fff 1px, transparent 1px)`,
|
||||||
backgroundSize: '20px 20px'
|
backgroundSize: '20px 20px'
|
||||||
}}
|
}}
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
import { Badge } from "@/components/ui/badge";
|
import { Badge } from "@/components/ui/badge";
|
||||||
|
|
||||||
interface ServerStatusBadgeProps {
|
interface ServerStatusBadgeProps {
|
||||||
status: 'up' | 'down' | 'warning';
|
status: 'up' | 'down' | 'warning' | 'paused';
|
||||||
}
|
}
|
||||||
|
|
||||||
export const ServerStatusBadge = ({ status }: ServerStatusBadgeProps) => {
|
export const ServerStatusBadge = ({ status }: ServerStatusBadgeProps) => {
|
||||||
@@ -11,22 +11,27 @@ export const ServerStatusBadge = ({ status }: ServerStatusBadgeProps) => {
|
|||||||
case 'up':
|
case 'up':
|
||||||
return {
|
return {
|
||||||
label: 'Online',
|
label: 'Online',
|
||||||
className: 'bg-green-100 text-green-800 border-green-200'
|
className: 'bg-green-600 text-green-100 border-green-200'
|
||||||
};
|
};
|
||||||
case 'down':
|
case 'down':
|
||||||
return {
|
return {
|
||||||
label: 'Offline',
|
label: 'Offline',
|
||||||
className: 'bg-red-100 text-red-800 border-red-200'
|
className: 'bg-red-600 text-red-100 border-red-200'
|
||||||
};
|
};
|
||||||
case 'warning':
|
case 'warning':
|
||||||
return {
|
return {
|
||||||
label: 'Warning',
|
label: 'Warning',
|
||||||
className: 'bg-yellow-100 text-yellow-800 border-yellow-200'
|
className: 'bg-yellow-600 text-yellow-800 border-yellow-200'
|
||||||
|
};
|
||||||
|
case 'paused':
|
||||||
|
return {
|
||||||
|
label: 'Paused',
|
||||||
|
className: 'bg-gray-600 text-gray-100 border-gray-200'
|
||||||
};
|
};
|
||||||
default:
|
default:
|
||||||
return {
|
return {
|
||||||
label: 'Unknown',
|
label: 'Unknown',
|
||||||
className: 'bg-gray-100 text-gray-800 border-gray-200'
|
className: 'bg-gray-600 text-gray-100 border-gray-200'
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -119,7 +119,6 @@ export const ServerTable = ({ servers, isLoading, onRefresh }: ServerTableProps)
|
|||||||
<TableHead>Memory</TableHead>
|
<TableHead>Memory</TableHead>
|
||||||
<TableHead>Disk</TableHead>
|
<TableHead>Disk</TableHead>
|
||||||
<TableHead>Uptime</TableHead>
|
<TableHead>Uptime</TableHead>
|
||||||
<TableHead>Docker</TableHead>
|
|
||||||
<TableHead>Last Checked</TableHead>
|
<TableHead>Last Checked</TableHead>
|
||||||
<TableHead className="text-right">Actions</TableHead>
|
<TableHead className="text-right">Actions</TableHead>
|
||||||
</TableRow>
|
</TableRow>
|
||||||
@@ -136,7 +135,7 @@ export const ServerTable = ({ servers, isLoading, onRefresh }: ServerTableProps)
|
|||||||
<TableCell>
|
<TableCell>
|
||||||
<div>
|
<div>
|
||||||
<div className="font-medium">{server.name}</div>
|
<div className="font-medium">{server.name}</div>
|
||||||
<div className="text-sm text-muted-foreground">{server.hostname}</div>
|
|
||||||
</div>
|
</div>
|
||||||
</TableCell>
|
</TableCell>
|
||||||
<TableCell>
|
<TableCell>
|
||||||
@@ -161,7 +160,8 @@ export const ServerTable = ({ servers, isLoading, onRefresh }: ServerTableProps)
|
|||||||
value={cpuUsage}
|
value={cpuUsage}
|
||||||
className="h-2"
|
className="h-2"
|
||||||
indicatorClassName={
|
indicatorClassName={
|
||||||
cpuUsage > 80 ? "bg-red-500" :
|
cpuUsage > 90 ? "bg-red-00" :
|
||||||
|
cpuUsage > 75 ? "bg-orange-500" :
|
||||||
cpuUsage > 60 ? "bg-yellow-500" : "bg-green-500"
|
cpuUsage > 60 ? "bg-yellow-500" : "bg-green-500"
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
@@ -170,8 +170,8 @@ export const ServerTable = ({ servers, isLoading, onRefresh }: ServerTableProps)
|
|||||||
<TableCell>
|
<TableCell>
|
||||||
<div className="space-y-1 min-w-[120px]">
|
<div className="space-y-1 min-w-[120px]">
|
||||||
<div className="flex justify-between text-sm">
|
<div className="flex justify-between text-sm">
|
||||||
<span>{serverService.formatBytes(server.ram_used)}</span>
|
|
||||||
<span className="text-muted-foreground">{memoryUsage.toFixed(1)}%</span>
|
<span className="text-muted-foreground">{memoryUsage.toFixed(1)}%</span>
|
||||||
|
<span>{serverService.formatBytes(server.ram_total)}</span>
|
||||||
</div>
|
</div>
|
||||||
<Progress
|
<Progress
|
||||||
value={memoryUsage}
|
value={memoryUsage}
|
||||||
@@ -186,8 +186,8 @@ export const ServerTable = ({ servers, isLoading, onRefresh }: ServerTableProps)
|
|||||||
<TableCell>
|
<TableCell>
|
||||||
<div className="space-y-1 min-w-[120px]">
|
<div className="space-y-1 min-w-[120px]">
|
||||||
<div className="flex justify-between text-sm">
|
<div className="flex justify-between text-sm">
|
||||||
<span>{serverService.formatBytes(server.disk_used)}</span>
|
|
||||||
<span className="text-muted-foreground">{diskUsage.toFixed(1)}%</span>
|
<span className="text-muted-foreground">{diskUsage.toFixed(1)}%</span>
|
||||||
|
<span>{serverService.formatBytes(server.disk_total)}</span>
|
||||||
</div>
|
</div>
|
||||||
<Progress
|
<Progress
|
||||||
value={diskUsage}
|
value={diskUsage}
|
||||||
@@ -202,18 +202,7 @@ export const ServerTable = ({ servers, isLoading, onRefresh }: ServerTableProps)
|
|||||||
<TableCell>
|
<TableCell>
|
||||||
<div className="text-sm">{server.uptime}</div>
|
<div className="text-sm">{server.uptime}</div>
|
||||||
</TableCell>
|
</TableCell>
|
||||||
<TableCell>
|
|
||||||
{server.docker === 'true' ? (
|
|
||||||
<Badge variant="secondary" className="text-xs">
|
|
||||||
<Activity className="h-3 w-3 mr-1" />
|
|
||||||
Enabled
|
|
||||||
</Badge>
|
|
||||||
) : (
|
|
||||||
<Badge variant="outline" className="text-xs">
|
|
||||||
Disabled
|
|
||||||
</Badge>
|
|
||||||
)}
|
|
||||||
</TableCell>
|
|
||||||
<TableCell>
|
<TableCell>
|
||||||
<div className="text-sm text-muted-foreground">
|
<div className="text-sm text-muted-foreground">
|
||||||
{new Date(server.last_checked).toLocaleString()}
|
{new Date(server.last_checked).toLocaleString()}
|
||||||
|
|||||||
@@ -57,11 +57,43 @@ export const filterMetricsByTimeRange = (metrics: any[], timeRange: TimeRange):
|
|||||||
const cutoffTime = new Date(now.getTime() - (selectedRange.hours * 60 * 60 * 1000));
|
const cutoffTime = new Date(now.getTime() - (selectedRange.hours * 60 * 60 * 1000));
|
||||||
|
|
||||||
return metrics.filter(metric => {
|
return metrics.filter(metric => {
|
||||||
const metricTime = new Date(metric.timestamp);
|
const metricTime = new Date(metric.created || metric.timestamp);
|
||||||
return metricTime >= cutoffTime;
|
return metricTime >= cutoffTime;
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const formatTimestamp = (timestamp: string, timeRange: TimeRange): string => {
|
||||||
|
const date = new Date(timestamp);
|
||||||
|
|
||||||
|
if (timeRange === '60m') {
|
||||||
|
// For 60 minutes, show time with seconds
|
||||||
|
return date.toLocaleString('en-US', {
|
||||||
|
month: 'short',
|
||||||
|
day: 'numeric',
|
||||||
|
hour: '2-digit',
|
||||||
|
minute: '2-digit',
|
||||||
|
second: '2-digit'
|
||||||
|
});
|
||||||
|
} else if (timeRange === '1d') {
|
||||||
|
// For 1 day, show date and time
|
||||||
|
return date.toLocaleString('en-US', {
|
||||||
|
month: 'short',
|
||||||
|
day: 'numeric',
|
||||||
|
hour: '2-digit',
|
||||||
|
minute: '2-digit'
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
// For longer periods, show date and time
|
||||||
|
return date.toLocaleString('en-US', {
|
||||||
|
month: 'short',
|
||||||
|
day: 'numeric',
|
||||||
|
year: 'numeric',
|
||||||
|
hour: '2-digit',
|
||||||
|
minute: '2-digit'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
export const formatChartData = (metrics: any[], timeRange: TimeRange) => {
|
export const formatChartData = (metrics: any[], timeRange: TimeRange) => {
|
||||||
const filteredMetrics = filterMetricsByTimeRange(metrics, timeRange);
|
const filteredMetrics = filterMetricsByTimeRange(metrics, timeRange);
|
||||||
|
|
||||||
@@ -85,11 +117,11 @@ export const formatChartData = (metrics: any[], timeRange: TimeRange) => {
|
|||||||
const networkRxSpeed = metric.network_rx_speed || 0;
|
const networkRxSpeed = metric.network_rx_speed || 0;
|
||||||
const networkTxSpeed = metric.network_tx_speed || 0;
|
const networkTxSpeed = metric.network_tx_speed || 0;
|
||||||
|
|
||||||
|
const timestamp = metric.created || metric.timestamp;
|
||||||
|
|
||||||
return {
|
return {
|
||||||
timestamp: new Date(metric.timestamp).toLocaleTimeString('en-US', {
|
timestamp: formatTimestamp(timestamp, timeRange),
|
||||||
hour: '2-digit',
|
fullTimestamp: timestamp,
|
||||||
minute: '2-digit'
|
|
||||||
}),
|
|
||||||
cpuUsage: Math.round(cpuUsage * 100) / 100,
|
cpuUsage: Math.round(cpuUsage * 100) / 100,
|
||||||
cpuCores: parseInt(metric.cpu_cores) || 0,
|
cpuCores: parseInt(metric.cpu_cores) || 0,
|
||||||
cpuFree: 100 - cpuUsage,
|
cpuFree: 100 - cpuUsage,
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
|
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import { SSLCertificate } from "@/types/ssl.types";
|
import { SSLCertificate } from "@/types/ssl.types";
|
||||||
import { useLanguage } from "@/contexts/LanguageContext";
|
import { useLanguage } from "@/contexts/LanguageContext";
|
||||||
import { OverviewCard } from "@/components/schedule-incident/common/OverviewCard";
|
import { Card, CardHeader, CardTitle, CardContent } from "@/components/ui/card";
|
||||||
import { Shield, ShieldAlert, ShieldX } from "lucide-react";
|
import { Shield, ShieldAlert, ShieldX } from "lucide-react";
|
||||||
|
import { useTheme } from "@/contexts/ThemeContext";
|
||||||
|
|
||||||
interface SSLCertificateStatusCardsProps {
|
interface SSLCertificateStatusCardsProps {
|
||||||
certificates: SSLCertificate[];
|
certificates: SSLCertificate[];
|
||||||
@@ -11,37 +11,113 @@ interface SSLCertificateStatusCardsProps {
|
|||||||
|
|
||||||
export const SSLCertificateStatusCards = ({ certificates }: SSLCertificateStatusCardsProps) => {
|
export const SSLCertificateStatusCards = ({ certificates }: SSLCertificateStatusCardsProps) => {
|
||||||
const { t } = useLanguage();
|
const { t } = useLanguage();
|
||||||
|
const { theme } = useTheme();
|
||||||
|
|
||||||
// Count certificates by status
|
// Count certificates by status
|
||||||
const validCount = certificates.filter(cert => cert.status === 'valid').length;
|
const validCount = certificates.filter(cert => cert.status === "valid").length;
|
||||||
const expiringCount = certificates.filter(cert => cert.status === 'expiring_soon').length;
|
const expiringCount = certificates.filter(cert => cert.status === "expiring_soon").length;
|
||||||
const expiredCount = certificates.filter(cert => cert.status === 'expired').length;
|
const expiredCount = certificates.filter(cert => cert.status === "expired").length;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="grid grid-cols-1 md:grid-cols-3 gap-6">
|
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-6 mb-8 w-full">
|
||||||
<OverviewCard
|
{/* Valid Certificates */}
|
||||||
title={t('validCertificates')}
|
<Card
|
||||||
value={validCount}
|
className="border-none rounded-xl shadow-lg overflow-hidden transition-all duration-300 hover:shadow-xl transform hover:-translate-y-1 relative z-10"
|
||||||
icon={<Shield className="h-6 w-6 text-white" />}
|
style={{
|
||||||
color="green"
|
background:
|
||||||
className="hover:scale-105 transition-transform duration-200"
|
theme === "dark"
|
||||||
/>
|
? "linear-gradient(135deg, rgba(65,59,55,0.8) 0%, rgba(34,197,94,0.6) 100%)"
|
||||||
|
: "linear-gradient(135deg, rgba(65,59,55,0.8) 0%, #22c55e 100%)",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<div className="absolute inset-0 z-0 opacity-10">
|
||||||
|
<div
|
||||||
|
className="w-full h-full"
|
||||||
|
style={{
|
||||||
|
backgroundImage: `linear-gradient(#000 1px, transparent 1px),
|
||||||
|
linear-gradient(90deg, #fff 1px, transparent 1px)`,
|
||||||
|
backgroundSize: "20px 20px",
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<CardHeader className="pb-2 relative z-10">
|
||||||
|
<CardTitle className="text-sm font-medium text-white">
|
||||||
|
{t("validCertificates")}
|
||||||
|
</CardTitle>
|
||||||
|
</CardHeader>
|
||||||
|
<CardContent className="flex items-center justify-between relative z-10">
|
||||||
|
<span className="text-5xl font-bold text-white">{validCount}</span>
|
||||||
|
<div className="rounded-full p-3 bg-white/25 backdrop-blur-sm">
|
||||||
|
<Shield className="h-6 w-6 text-white" />
|
||||||
|
</div>
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
|
|
||||||
<OverviewCard
|
{/* Expiring Soon */}
|
||||||
title={t('expiringSoon')}
|
<Card
|
||||||
value={expiringCount}
|
className="border-none rounded-xl shadow-lg overflow-hidden transition-all duration-300 hover:shadow-xl transform hover:-translate-y-1 relative z-10"
|
||||||
icon={<ShieldAlert className="h-6 w-6 text-white" />}
|
style={{
|
||||||
color="amber"
|
background:
|
||||||
className="hover:scale-105 transition-transform duration-200"
|
theme === "dark"
|
||||||
/>
|
? "linear-gradient(135deg, rgba(65,59,55,0.8) 0%, rgba(251,191,36,0.6) 100%)"
|
||||||
|
: "linear-gradient(135deg, rgba(65,59,55,0.8) 0%, #fbbf24 100%)",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<div className="absolute inset-0 z-0 opacity-10">
|
||||||
|
<div
|
||||||
|
className="w-full h-full"
|
||||||
|
style={{
|
||||||
|
backgroundImage: `linear-gradient(#000 1px, transparent 1px),
|
||||||
|
linear-gradient(90deg, #fff 1px, transparent 1px)`,
|
||||||
|
backgroundSize: "20px 20px",
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<CardHeader className="pb-2 relative z-10">
|
||||||
|
<CardTitle className="text-sm font-medium text-white">
|
||||||
|
{t("expiringSoon")}
|
||||||
|
</CardTitle>
|
||||||
|
</CardHeader>
|
||||||
|
<CardContent className="flex items-center justify-between relative z-10">
|
||||||
|
<span className="text-5xl font-bold text-white">{expiringCount}</span>
|
||||||
|
<div className="rounded-full p-3 bg-white/25 backdrop-blur-sm">
|
||||||
|
<ShieldAlert className="h-6 w-6 text-white" />
|
||||||
|
</div>
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
|
|
||||||
<OverviewCard
|
{/* Expired */}
|
||||||
title={t('expired')}
|
<Card
|
||||||
value={expiredCount}
|
className="border-none rounded-xl shadow-lg overflow-hidden transition-all duration-300 hover:shadow-xl transform hover:-translate-y-1 relative z-10"
|
||||||
icon={<ShieldX className="h-6 w-6 text-white" />}
|
style={{
|
||||||
color="red"
|
background:
|
||||||
className="hover:scale-105 transition-transform duration-200"
|
theme === "dark"
|
||||||
/>
|
? "linear-gradient(135deg, rgba(65,59,55,0.8) 0%, rgba(239,68,68,0.6) 100%)"
|
||||||
|
: "linear-gradient(135deg, rgba(65,59,55,0.8) 0%, #ef4444 100%)",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<div className="absolute inset-0 z-0 opacity-10">
|
||||||
|
<div
|
||||||
|
className="w-full h-full"
|
||||||
|
style={{
|
||||||
|
backgroundImage: `linear-gradient(#000 1px, transparent 1px),
|
||||||
|
linear-gradient(90deg, #fff 1px, transparent 1px)`,
|
||||||
|
backgroundSize: "20px 20px",
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<CardHeader className="pb-2 relative z-10">
|
||||||
|
<CardTitle className="text-sm font-medium text-white">
|
||||||
|
{t("expired")}
|
||||||
|
</CardTitle>
|
||||||
|
</CardHeader>
|
||||||
|
<CardContent className="flex items-center justify-between relative z-10">
|
||||||
|
<span className="text-5xl font-bold text-white">{expiredCount}</span>
|
||||||
|
<div className="rounded-full p-3 bg-white/25 backdrop-blur-sm">
|
||||||
|
<ShieldX className="h-6 w-6 text-white" />
|
||||||
|
</div>
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ import { Header } from "@/components/dashboard/Header";
|
|||||||
import { serverService } from "@/services/serverService";
|
import { serverService } from "@/services/serverService";
|
||||||
import { authService } from "@/services/authService";
|
import { authService } from "@/services/authService";
|
||||||
import { Button } from "@/components/ui/button";
|
import { Button } from "@/components/ui/button";
|
||||||
import { ArrowLeft, Server } from "lucide-react";
|
import { ArrowLeft, Server, Database } from "lucide-react";
|
||||||
import { ServerMetricsCharts } from "@/components/servers/ServerMetricsCharts";
|
import { ServerMetricsCharts } from "@/components/servers/ServerMetricsCharts";
|
||||||
import { ServerMetricsOverview } from "@/components/servers/ServerMetricsOverview";
|
import { ServerMetricsOverview } from "@/components/servers/ServerMetricsOverview";
|
||||||
import { ServerHistoryCharts } from "@/components/servers/ServerHistoryCharts";
|
import { ServerHistoryCharts } from "@/components/servers/ServerHistoryCharts";
|
||||||
@@ -126,7 +126,7 @@ const ServerDetail = () => {
|
|||||||
</div>
|
</div>
|
||||||
<div className="flex items-center gap-3 mb-2">
|
<div className="flex items-center gap-3 mb-2">
|
||||||
<div className="h-8 w-8 rounded bg-primary/10 flex items-center justify-center">
|
<div className="h-8 w-8 rounded bg-primary/10 flex items-center justify-center">
|
||||||
<Server className="h-4 w-4 text-primary" />
|
<Database className="h-4 w-4 text-primary" />
|
||||||
</div>
|
</div>
|
||||||
<h1 className="text-2xl font-bold text-foreground">
|
<h1 className="text-2xl font-bold text-foreground">
|
||||||
{server?.name || 'Server Detail'}
|
{server?.name || 'Server Detail'}
|
||||||
@@ -135,11 +135,14 @@ const ServerDetail = () => {
|
|||||||
<p className={`text-muted-foreground mt-1 sm:mt-2 transition-all duration-300 ${sidebarCollapsed ? 'text-base sm:text-lg' : 'text-sm sm:text-base'}`}>
|
<p className={`text-muted-foreground mt-1 sm:mt-2 transition-all duration-300 ${sidebarCollapsed ? 'text-base sm:text-lg' : 'text-sm sm:text-base'}`}>
|
||||||
Monitor server performance metrics and system health
|
Monitor server performance metrics and system health
|
||||||
{server && (
|
{server && (
|
||||||
<span className="block text-xs text-muted-foreground/70 mt-1">
|
<span className="block text-xs text-foreground/80 mt-1">
|
||||||
{server.hostname} • {server.ip_address} • {server.os_type}
|
{server.hostname} • {server.ip_address} • {server.os_type} • {server.system_info}
|
||||||
</span>
|
</span>
|
||||||
|
|
||||||
)}
|
)}
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
|
|
||||||
import { pb } from '@/lib/pocketbase';
|
import { pb } from '@/lib/pocketbase';
|
||||||
import { Server, ServerStats } from '@/types/server.types';
|
import { Server, ServerStats } from '@/types/server.types';
|
||||||
|
|
||||||
@@ -23,9 +22,9 @@ export const serverService = {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
async getServerMetrics(serverId: string): Promise<any[]> {
|
async getServerMetrics(serverId: string, timeRange?: string): Promise<any[]> {
|
||||||
try {
|
try {
|
||||||
console.log('serverService.getServerMetrics: Fetching metrics for serverId:', serverId);
|
console.log('serverService.getServerMetrics: Fetching metrics for serverId:', serverId, 'timeRange:', timeRange);
|
||||||
|
|
||||||
// First, get the server to find the correct server_id for metrics
|
// First, get the server to find the correct server_id for metrics
|
||||||
let server;
|
let server;
|
||||||
@@ -36,48 +35,63 @@ export const serverService = {
|
|||||||
console.log('serverService.getServerMetrics: Could not fetch server details:', error);
|
console.log('serverService.getServerMetrics: Could not fetch server details:', error);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Try to get metrics using the server's server_id field if available
|
// Use the server's server_id field if available, otherwise use the serverId
|
||||||
let metricsServerId = serverId;
|
let metricsServerId = serverId;
|
||||||
if (server && server.server_id) {
|
if (server && server.server_id) {
|
||||||
metricsServerId = server.server_id;
|
metricsServerId = server.server_id;
|
||||||
console.log('serverService.getServerMetrics: Using server.server_id for metrics:', metricsServerId);
|
console.log('serverService.getServerMetrics: Using server.server_id for metrics:', metricsServerId);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Try filtering by server_id first
|
// Build filter for server_id and time range
|
||||||
let filteredRecords = await pb.collection('server_metrics').getFullList({
|
let filter = `server_id = "${metricsServerId}"`;
|
||||||
filter: `server_id = "${metricsServerId}"`,
|
|
||||||
|
// Add agent_id filter if available in server data
|
||||||
|
if (server && server.agent_id) {
|
||||||
|
filter += ` && agent_id = "${server.agent_id}"`;
|
||||||
|
console.log('serverService.getServerMetrics: Added agent_id filter:', server.agent_id);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add time range filter
|
||||||
|
if (timeRange) {
|
||||||
|
const now = new Date();
|
||||||
|
let cutoffTime;
|
||||||
|
|
||||||
|
switch (timeRange) {
|
||||||
|
case '60m':
|
||||||
|
cutoffTime = new Date(now.getTime() - (60 * 60 * 1000));
|
||||||
|
break;
|
||||||
|
case '1d':
|
||||||
|
cutoffTime = new Date(now.getTime() - (24 * 60 * 60 * 1000));
|
||||||
|
break;
|
||||||
|
case '7d':
|
||||||
|
cutoffTime = new Date(now.getTime() - (7 * 24 * 60 * 60 * 1000));
|
||||||
|
break;
|
||||||
|
case '1m':
|
||||||
|
cutoffTime = new Date(now.getTime() - (30 * 24 * 60 * 60 * 1000));
|
||||||
|
break;
|
||||||
|
case '3m':
|
||||||
|
cutoffTime = new Date(now.getTime() - (90 * 24 * 60 * 60 * 1000));
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
cutoffTime = new Date(now.getTime() - (24 * 60 * 60 * 1000));
|
||||||
|
}
|
||||||
|
|
||||||
|
const cutoffISO = cutoffTime.toISOString();
|
||||||
|
filter += ` && created >= "${cutoffISO}"`;
|
||||||
|
console.log('serverService.getServerMetrics: Using time filter:', cutoffISO);
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log('serverService.getServerMetrics: Final filter:', filter);
|
||||||
|
|
||||||
|
// Fetch filtered records with proper sorting
|
||||||
|
const records = await pb.collection('server_metrics').getFullList({
|
||||||
|
filter: filter,
|
||||||
sort: '-created',
|
sort: '-created',
|
||||||
requestKey: null
|
requestKey: null
|
||||||
});
|
});
|
||||||
|
|
||||||
console.log('serverService.getServerMetrics: Filtered records by server_id:', filteredRecords.length);
|
console.log('serverService.getServerMetrics: Found', records.length, 'records with filter');
|
||||||
|
return records;
|
||||||
// If no records found with server_id, try alternative approaches
|
|
||||||
if (filteredRecords.length === 0) {
|
|
||||||
console.log('serverService.getServerMetrics: No records found with server_id filter, trying alternatives...');
|
|
||||||
|
|
||||||
// Get all records to see what's available
|
|
||||||
const allRecords = await pb.collection('server_metrics').getFullList({
|
|
||||||
sort: '-created',
|
|
||||||
requestKey: null
|
|
||||||
});
|
|
||||||
|
|
||||||
console.log('serverService.getServerMetrics: Total server_metrics records:', allRecords.length);
|
|
||||||
if (allRecords.length > 0) {
|
|
||||||
console.log('serverService.getServerMetrics: Sample record fields:', Object.keys(allRecords[0]));
|
|
||||||
console.log('serverService.getServerMetrics: Sample server_id values:', allRecords.slice(0, 5).map(r => r.server_id));
|
|
||||||
}
|
|
||||||
|
|
||||||
// For now, return some sample data from available records if server matches pattern
|
|
||||||
// This is temporary until the correct server_id mapping is established
|
|
||||||
if (allRecords.length > 0) {
|
|
||||||
console.log('serverService.getServerMetrics: Using available records as fallback');
|
|
||||||
filteredRecords = allRecords.slice(0, 50); // Get recent 50 records
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
console.log('serverService.getServerMetrics: Returning', filteredRecords.length, 'records');
|
|
||||||
return filteredRecords;
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Error fetching server metrics:', error);
|
console.error('Error fetching server metrics:', error);
|
||||||
throw error;
|
throw error;
|
||||||
|
|||||||
@@ -0,0 +1,21 @@
|
|||||||
|
# CheckCle Distributed Regional Monitoring Agent - System metrics collection (HTTP, PING, TCP, DNS)
|
||||||
|
# This Dockerfile for build with this repository: https://github.com/operacle/Distributed-Regional-Monitoring
|
||||||
|
|
||||||
|
FROM golang:1.21-alpine AS builder
|
||||||
|
|
||||||
|
WORKDIR /app
|
||||||
|
COPY go.mod go.sum ./
|
||||||
|
RUN go mod download
|
||||||
|
|
||||||
|
COPY . .
|
||||||
|
RUN CGO_ENABLED=0 GOOS=linux go build -a -installsuffix cgo -o service-operation .
|
||||||
|
|
||||||
|
FROM alpine:latest
|
||||||
|
RUN apk --no-cache add ca-certificates
|
||||||
|
WORKDIR /root/
|
||||||
|
|
||||||
|
COPY --from=builder /app/service-operation .
|
||||||
|
|
||||||
|
EXPOSE 8091
|
||||||
|
|
||||||
|
CMD ["./service-operation"]
|
||||||
Reference in New Issue
Block a user