diff --git a/README.md b/README.md index 34ecf1f..c5acd79 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -![CheckCle Platform](https://pub-4a4062303020445f8f289a2fee84f9e8.r2.dev/images/checkcle-black.png) +![CheckCle Platform](https://pub-4a4062303020445f8f289a2fee84f9e8.r2.dev/images/instance-server-monitoring.png) # 🚀 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 TCP-based, API services (e.g., FTP, SMTP, HTTP) - Track detail uptime, response times, and performance issues +- Distributed Regional Monitoring - Incident History (UP/DOWN/WARNING/PAUSE) - 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. @@ -76,9 +77,11 @@ services: 4. Follow the Quick Start Guide at https://docs.checkcle.io ### -![checkcle-collapse-black](https://pub-4a4062303020445f8f289a2fee84f9e8.r2.dev/images/checkcle-black.png) +![checkcle-collapse-black](https://pub-4a4062303020445f8f289a2fee84f9e8.r2.dev/images/uptime-monitoring.png) ![Service Detail Page](https://pub-4a4062303020445f8f289a2fee84f9e8.r2.dev/images/uptime-service-detail.png) -![Schedule Maintenance](https://pub-4a4062303020445f8f289a2fee84f9e8.r2.dev/images/maintenance-dahboard.png) +![checkcle-server-instance](https://pub-4a4062303020445f8f289a2fee84f9e8.r2.dev/images/instance-server-monitoring.png) +![Schedule Maintenance](https://pub-4a4062303020445f8f289a2fee84f9e8.r2.dev/images/checkcle-schedule-maintenance.png) +![SSL Monitoring](https://pub-4a4062303020445f8f289a2fee84f9e8.r2.dev/images/ssl-monitoring.png) ## 📝 Development Roadmap @@ -134,6 +137,10 @@ Here are some ways you can help improve CheckCle: CheckCle is released under the MIT License. --- +## 👥 Contributors + +[![](https://contrib.rocks/image?repo=operacle/checkcle)](https://github.com/operacle/checkcle/graphs/contributors) + ## Star History diff --git a/application/src/components/dashboard/StatusCards.tsx b/application/src/components/dashboard/StatusCards.tsx index e0a4794..2ee4f5d 100644 --- a/application/src/components/dashboard/StatusCards.tsx +++ b/application/src/components/dashboard/StatusCards.tsx @@ -27,15 +27,15 @@ export const StatusCards = ({ services }: StatusCardsProps) => { } relative z-10`} style={{ background: theme === 'dark' - ? "linear-gradient(135deg, rgba(67, 160, 71, 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%, rgba(102, 187, 106, 0.6) 100%)" + : "linear-gradient(135deg, rgba(65, 59, 55, 0.8) 0%, #66bb6a 100%)" }} > {/* Grid Pattern Overlay */}
{ } relative z-10`} style={{ background: theme === 'dark' - ? "linear-gradient(135deg, rgba(229, 57, 53, 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%, rgba(239, 83, 80, 0.6) 100%)" + : "linear-gradient(135deg, rgba(65, 59, 55, 0.8) 0%, #ef5350 100%)" }} > {/* Grid Pattern Overlay */}
{ } relative z-10`} style={{ background: theme === 'dark' - ? "linear-gradient(135deg, rgba(25, 118, 210, 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%, rgba(66, 165, 245, 0.6) 100%)" + : "linear-gradient(135deg, rgba(65, 59, 55, 0.8) 0%, #42a5f5 100%)" }} > {/* Grid Pattern Overlay */}
{ } relative z-10`} style={{ background: theme === 'dark' - ? "linear-gradient(135deg, rgba(255, 152, 0, 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%, rgba(255, 183, 77, 0.6) 100%)" + : "linear-gradient(135deg, rgba(65, 59, 55, 0.8) 0%, #ffb74d 100%)" }} > {/* Grid Pattern Overlay */}
{ const { t } = useLanguage(); + const { theme } = useTheme(); const { toast } = useToast(); const [manualRefresh, setManualRefresh] = React.useState(0); - - // Combine the external refresh trigger with our internal one + const combinedRefreshTrigger = refreshTrigger + manualRefresh; - - const { - loading, - filter, - setFilter, - maintenanceData, - overviewStats, + + const { + loading, + filter, + setFilter, + maintenanceData, + overviewStats, fetchMaintenanceData, isEmpty, error, - initialized + initialized, } = useMaintenanceData({ refreshTrigger: combinedRefreshTrigger }); - // Display toast when error occurs useEffect(() => { if (error) { toast({ @@ -47,66 +43,91 @@ export const ScheduledMaintenanceTab = ({ refreshTrigger = 0 }: ScheduledMainten } }, [error, toast, t]); - // Force fetch data on initial load useEffect(() => { - console.log("ScheduledMaintenanceTab is mounted, fetching data"); fetchMaintenanceData(true); }, [fetchMaintenanceData]); - // Handle maintenance updates const handleMaintenanceUpdated = () => { - console.log("Maintenance updated, refreshing data"); - setManualRefresh(prev => prev + 1); + setManualRefresh((prev) => prev + 1); }; - // Handle tab changes const handleTabChange = (value: string) => { setFilter(value); }; - // Show full-page loading state during initial load if (loading && !initialized) { return ; } + const gradientCard = ( + title: string, + value: string | number, + icon: JSX.Element, + gradient: string + ) => ( + +
+
+
+ + {title} + + + {value} +
+ {icon} +
+
+ + ); + return ( <> - {/* Status checker for automatic updates */} - {/* Overview Cards */} -
- } - isLoading={loading} - color="blue" - /> - } - isLoading={loading} - color="amber" - /> - } - isLoading={loading} - color="green" - /> - } - isLoading={loading} - color="purple" - /> +
+ {gradientCard( + t('upcomingMaintenance'), + loading ? '...' : overviewStats.upcoming, + , + 'rgba(59,130,246,0.6)' + )} + {gradientCard( + t('ongoingMaintenance'), + loading ? '...' : overviewStats.ongoing, + , + 'rgba(251,191,36,0.6)' + )} + {gradientCard( + t('completedMaintenance'), + loading ? '...' : overviewStats.completed, + , + 'rgba(34,197,94,0.6)' + )} + {gradientCard( + t('totalScheduledHours'), + loading ? '...' : `${overviewStats.totalDuration}h`, + , + 'rgba(139,92,246,0.6)' + )}
@@ -117,10 +138,10 @@ export const ScheduledMaintenanceTab = ({ refreshTrigger = 0 }: ScheduledMainten - @@ -129,24 +150,24 @@ export const ScheduledMaintenanceTab = ({ refreshTrigger = 0 }: ScheduledMainten {t('completedMaintenance')} - - + - - - + - - - + { 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 ; } else if (osLower.includes('windows')) { return ; diff --git a/application/src/components/servers/ServerHistoryCharts.tsx b/application/src/components/servers/ServerHistoryCharts.tsx index a53371a..b9b0cce 100644 --- a/application/src/components/servers/ServerHistoryCharts.tsx +++ b/application/src/components/servers/ServerHistoryCharts.tsx @@ -1,20 +1,23 @@ import { useQuery } from "@tanstack/react-query"; import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"; 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 { Loader2, TrendingUp, Cpu, HardDrive, Wifi, MemoryStick } from "lucide-react"; import { useTheme } from "@/contexts/ThemeContext"; -import { DateRangeFilter } from "@/components/services/DateRangeFilter"; +import { TimeRangeSelector } from "./charts/TimeRangeSelector"; import { useState } from "react"; +import { formatChartData, filterMetricsByTimeRange, formatBytes } from "./charts/dataUtils"; interface ServerHistoryChartsProps { serverId: string; } +type TimeRange = '60m' | '1d' | '7d' | '1m' | '3m'; + export const ServerHistoryCharts = ({ serverId }: ServerHistoryChartsProps) => { const { theme } = useTheme(); - const [dateRange, setDateRange] = useState({ start: new Date(Date.now() - 24 * 60 * 60 * 1000), end: new Date() }); + const [timeRange, setTimeRange] = useState("1d"); console.log('ServerHistoryCharts: Rendering with serverId:', serverId); @@ -23,117 +26,135 @@ export const ServerHistoryCharts = ({ serverId }: ServerHistoryChartsProps) => { isLoading, error } = useQuery({ - queryKey: ['server-metrics-history', serverId], + queryKey: ['server-metrics-history', serverId, timeRange], queryFn: async () => { 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); return result; }, enabled: !!serverId, - refetchInterval: 60000, // Refresh every minute + refetchInterval: 60000, 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:', { metricsCount: metrics.length, isLoading, error: error?.message, firstMetric: metrics[0], - serverId + serverId, + timeRange }); - const formatBytes = (bytes: number) => { - 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 chartData = formatChartData(metrics, timeRange); const getGridColor = () => theme === 'dark' ? '#374151' : '#e5e7eb'; 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 ( +
+

{label}

+ {data?.fullTimestamp && ( +

+ {new Date(data.fullTimestamp).toLocaleString('en-US', { + weekday: 'short', + year: 'numeric', + month: 'short', + day: 'numeric', + hour: '2-digit', + minute: '2-digit', + second: '2-digit' + })} +

+ )} + {payload.map((entry: any, index: number) => { + const data = entry.payload; + return ( +
+
+
+ {entry.name}: {entry.value}% +
+ {entry.dataKey === 'cpuUsage' && ( +
+
CPU Cores: {data.cpuCores}
+
Free: {data.cpuFree}%
+
+ )} + {entry.dataKey === 'ramUsagePercent' && ( +
+
Used: {data.ramUsed}
+
Total: {data.ramTotal}
+
Free: {data.ramFree}
+
+ )} + {entry.dataKey === 'diskUsagePercent' && ( +
+
Used: {data.diskUsed}
+
Total: {data.diskTotal}
+
Free: {data.diskFree}
+
+ )} +
+ ); + })} +
+ ); + } + 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 ( +
+

{label}

+ {data?.fullTimestamp && ( +

+ {new Date(data.fullTimestamp).toLocaleString('en-US', { + weekday: 'short', + year: 'numeric', + month: 'short', + day: 'numeric', + hour: '2-digit', + minute: '2-digit', + second: '2-digit' + })} +

+ )} + {payload.map((entry: any, index: number) => ( +
+
+ + {entry.name}: {entry.dataKey.includes('Speed') ? `${entry.value} KB/s` : formatBytes(entry.value)} + +
+ ))} + {data && ( +
+
Total RX: {data.networkRx}
+
Total TX: {data.networkTx}
+
+ )} +
+ ); + } + return null; + }; + if (isLoading) { return (
@@ -143,6 +164,7 @@ export const ServerHistoryCharts = ({ serverId }: ServerHistoryChartsProps) => {

Historical Performance

+
{[1, 2, 3, 4].map((index) => ( @@ -174,7 +196,7 @@ export const ServerHistoryCharts = ({ serverId }: ServerHistoryChartsProps) => {

Historical Performance

- +
@@ -197,16 +219,16 @@ export const ServerHistoryCharts = ({ serverId }: ServerHistoryChartsProps) => {

Historical Performance

- +
-

No historical data available

-

Metrics count: {metrics.length}

+

No historical data available for {timeRange}

+

Raw metrics count: {metrics.length}

Server ID: {serverId}

- {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'}

@@ -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 (
@@ -223,20 +248,28 @@ export const ServerHistoryCharts = ({ serverId }: ServerHistoryChartsProps) => {

Historical Performance

- ({chartData.length} data points) + ({chartData.length} data points • {timeRange})
- +
- {/* CPU Usage Chart */} + {/* CPU Usage Chart - Smooth Area Chart with Blue Gradient */} - -
- + +
+
+ +
+ CPU Usage
- CPU Usage + {latestData && ( +
+
{latestData.cpuUsage}%
+
{latestData.cpuCores} cores
+
+ )}
@@ -246,10 +279,10 @@ export const ServerHistoryCharts = ({ serverId }: ServerHistoryChartsProps) => { color: theme === 'dark' ? "#3b82f6" : "#2563eb", } }} className="h-80"> - + - + @@ -266,10 +299,10 @@ export const ServerHistoryCharts = ({ serverId }: ServerHistoryChartsProps) => { label={{ value: 'CPU %', angle: -90, position: 'insideLeft' }} /> } + content={} cursor={{ stroke: getGridColor() }} /> - { fillOpacity={1} name="CPU Usage (%)" /> - + - {/* Memory Usage Chart */} + {/* Memory Usage Chart - Basis Area Chart with Green Gradient */} - -
- + +
+
+ +
+ Memory Usage
- Memory Usage + {latestData && ( +
+
{latestData.ramUsagePercent}%
+
{latestData.ramUsed} / {latestData.ramTotal}
+
+ )}
@@ -301,10 +342,10 @@ export const ServerHistoryCharts = ({ serverId }: ServerHistoryChartsProps) => { color: theme === 'dark' ? "#10b981" : "#059669", } }} className="h-80"> - + - + @@ -321,11 +362,11 @@ export const ServerHistoryCharts = ({ serverId }: ServerHistoryChartsProps) => { label={{ value: 'Memory %', angle: -90, position: 'insideLeft' }} /> } + content={} cursor={{ stroke: getGridColor() }} /> - { fillOpacity={1} name="Memory Usage (%)" /> - + - {/* Disk Usage Chart */} + {/* Disk Usage Chart - Stepped Area Chart with Orange Gradient */} - -
- + +
+
+ +
+ Disk Usage
- Disk Usage + {latestData && ( +
+
{latestData.diskUsagePercent}%
+
{latestData.diskUsed} / {latestData.diskTotal}
+
+ )}
@@ -356,10 +405,10 @@ export const ServerHistoryCharts = ({ serverId }: ServerHistoryChartsProps) => { color: theme === 'dark' ? "#f59e0b" : "#d97706", } }} className="h-80"> - + - + @@ -376,11 +425,11 @@ export const ServerHistoryCharts = ({ serverId }: ServerHistoryChartsProps) => { label={{ value: 'Disk %', angle: -90, position: 'insideLeft' }} /> } + content={} cursor={{ stroke: getGridColor() }} /> - { fillOpacity={1} name="Disk Usage (%)" /> - + - {/* Network Traffic Chart */} + {/* Network Traffic Chart - Dual Line Chart with Purple/Red Theme */} - -
- + +
+
+ +
+ Network Traffic
- Network Traffic + {latestData && ( +
+
{latestData.networkRxSpeed} KB/s ↓
+
{latestData.networkTxSpeed} KB/s ↑
+
+ )}
@@ -421,6 +478,13 @@ export const ServerHistoryCharts = ({ serverId }: ServerHistoryChartsProps) => { + + + + + + + { formatBytes(value) + '/s'} - label={{ value: 'Network Speed', angle: -90, position: 'insideLeft' }} + label={{ value: 'Network Speed (KB/s)', angle: -90, position: 'insideLeft' }} /> } + content={} cursor={{ stroke: getGridColor() }} - formatter={(value, name) => [formatBytes(Number(value)) + '/s', name]} /> { stroke={theme === 'dark' ? "#a78bfa" : "#7c3aed"} strokeWidth={3} dot={false} - fill="url(#networkGradient)" - fillOpacity={0.5} name="RX Speed" + filter="url(#glow)" + strokeDasharray="0" /> { strokeWidth={3} dot={false} name="TX Speed" + strokeDasharray="5 5" /> diff --git a/application/src/components/servers/ServerMetricsCharts.tsx b/application/src/components/servers/ServerMetricsCharts.tsx index 70dd5be..31fc7e0 100644 --- a/application/src/components/servers/ServerMetricsCharts.tsx +++ b/application/src/components/servers/ServerMetricsCharts.tsx @@ -26,7 +26,7 @@ export const ServerMetricsCharts = ({ serverId }: ServerMetricsChartsProps) => { error } = useQuery({ queryKey: ['server-metrics', serverId, timeRange], - queryFn: () => serverService.getServerMetrics(serverId), + queryFn: () => serverService.getServerMetrics(serverId, timeRange), enabled: !!serverId, refetchInterval: 30000 }); @@ -53,7 +53,7 @@ export const ServerMetricsCharts = ({ serverId }: ServerMetricsChartsProps) => { if (chartData.length === 0) { return (
-

No server metrics data available

+

No server metrics data available for {timeRange}

); } @@ -63,7 +63,7 @@ export const ServerMetricsCharts = ({ serverId }: ServerMetricsChartsProps) => {

Server Metrics

- ({chartData.length} data points) + ({chartData.length} data points • {timeRange})
diff --git a/application/src/components/servers/ServerMetricsOverview.tsx b/application/src/components/servers/ServerMetricsOverview.tsx index 840db9c..34ec57a 100644 --- a/application/src/components/servers/ServerMetricsOverview.tsx +++ b/application/src/components/servers/ServerMetricsOverview.tsx @@ -203,47 +203,6 @@ export const ServerMetricsOverview = ({ server }: ServerMetricsOverviewProps) => icon={HardDrive} color={theme === 'dark' ? "#f59e0b" : "#d97706"} /> - - {/* System Info Card */} - -
- - -
- -
- System Information -
-
- -
-
-
- Operating System: -
{server.os_type}
-
-
- Hostname: -
{server.hostname}
-
-
-
-
- IP Address: -
{server.ip_address}
-
- {server.system_info && ( -
- System Details: -
- {server.system_info} -
-
- )} -
-
-
-
); }; \ No newline at end of file diff --git a/application/src/components/servers/ServerStatsCards.tsx b/application/src/components/servers/ServerStatsCards.tsx index 3c6e035..379288b 100644 --- a/application/src/components/servers/ServerStatsCards.tsx +++ b/application/src/components/servers/ServerStatsCards.tsx @@ -17,32 +17,32 @@ export const ServerStatsCards = ({ stats }: ServerStatsCardsProps) => { value: stats.total, icon: Server, gradient: theme === 'dark' - ? "linear-gradient(135deg, rgba(139, 69, 19, 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%, rgba(160, 82, 45, 0.6) 100%)" + : "linear-gradient(135deg, rgba(65, 59, 55, 0.8) 0%, #a0522d 100%)" }, { title: "ONLINE SERVERS", value: stats.online, icon: Activity, gradient: theme === 'dark' - ? "linear-gradient(135deg, rgba(67, 160, 71, 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%, rgba(102, 187, 106, 0.6) 100%)" + : "linear-gradient(135deg, rgba(65, 59, 55, 0.8) 0%, #66bb6a 100%)" }, { title: "OFFLINE SERVERS", value: stats.offline, icon: Power, gradient: theme === 'dark' - ? "linear-gradient(135deg, rgba(229, 57, 53, 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%, rgba(239, 83, 80, 0.6) 100%)" + : "linear-gradient(135deg, rgba(65, 59, 55, 0.8) 0%, #ef5350 100%)" }, { title: "WARNING SERVERS", value: stats.warning, icon: AlertTriangle, gradient: theme === 'dark' - ? "linear-gradient(135deg, rgba(255, 152, 0, 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%, rgba(255, 183, 77, 0.6) 100%)" + : "linear-gradient(135deg, rgba(65, 59, 55, 0.8) 0%, #ffb74d 100%)" } ]; @@ -62,7 +62,7 @@ export const ServerStatsCards = ({ stats }: ServerStatsCardsProps) => {
{ @@ -11,22 +11,27 @@ export const ServerStatusBadge = ({ status }: ServerStatusBadgeProps) => { case 'up': return { label: 'Online', - className: 'bg-green-100 text-green-800 border-green-200' + className: 'bg-green-600 text-green-100 border-green-200' }; case 'down': return { label: 'Offline', - className: 'bg-red-100 text-red-800 border-red-200' + className: 'bg-red-600 text-red-100 border-red-200' }; case 'warning': return { 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: return { label: 'Unknown', - className: 'bg-gray-100 text-gray-800 border-gray-200' + className: 'bg-gray-600 text-gray-100 border-gray-200' }; } }; diff --git a/application/src/components/servers/ServerTable.tsx b/application/src/components/servers/ServerTable.tsx index 85af02b..d906b44 100644 --- a/application/src/components/servers/ServerTable.tsx +++ b/application/src/components/servers/ServerTable.tsx @@ -119,7 +119,6 @@ export const ServerTable = ({ servers, isLoading, onRefresh }: ServerTableProps) Memory Disk Uptime - Docker Last Checked Actions @@ -136,7 +135,7 @@ export const ServerTable = ({ servers, isLoading, onRefresh }: ServerTableProps)
{server.name}
-
{server.hostname}
+
@@ -161,7 +160,8 @@ export const ServerTable = ({ servers, isLoading, onRefresh }: ServerTableProps) value={cpuUsage} className="h-2" indicatorClassName={ - cpuUsage > 80 ? "bg-red-500" : + cpuUsage > 90 ? "bg-red-00" : + cpuUsage > 75 ? "bg-orange-500" : cpuUsage > 60 ? "bg-yellow-500" : "bg-green-500" } /> @@ -170,8 +170,8 @@ export const ServerTable = ({ servers, isLoading, onRefresh }: ServerTableProps)
- {serverService.formatBytes(server.ram_used)} {memoryUsage.toFixed(1)}% + {serverService.formatBytes(server.ram_total)}
- {serverService.formatBytes(server.disk_used)} {diskUsage.toFixed(1)}% + {serverService.formatBytes(server.disk_total)}
{server.uptime}
- - {server.docker === 'true' ? ( - - - Enabled - - ) : ( - - Disabled - - )} - +
{new Date(server.last_checked).toLocaleString()} diff --git a/application/src/components/servers/charts/dataUtils.ts b/application/src/components/servers/charts/dataUtils.ts index 92f10f5..5e0ef91 100644 --- a/application/src/components/servers/charts/dataUtils.ts +++ b/application/src/components/servers/charts/dataUtils.ts @@ -57,11 +57,43 @@ export const filterMetricsByTimeRange = (metrics: any[], timeRange: TimeRange): const cutoffTime = new Date(now.getTime() - (selectedRange.hours * 60 * 60 * 1000)); return metrics.filter(metric => { - const metricTime = new Date(metric.timestamp); + const metricTime = new Date(metric.created || metric.timestamp); 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) => { const filteredMetrics = filterMetricsByTimeRange(metrics, timeRange); @@ -85,11 +117,11 @@ export const formatChartData = (metrics: any[], timeRange: TimeRange) => { const networkRxSpeed = metric.network_rx_speed || 0; const networkTxSpeed = metric.network_tx_speed || 0; + const timestamp = metric.created || metric.timestamp; + return { - timestamp: new Date(metric.timestamp).toLocaleTimeString('en-US', { - hour: '2-digit', - minute: '2-digit' - }), + timestamp: formatTimestamp(timestamp, timeRange), + fullTimestamp: timestamp, cpuUsage: Math.round(cpuUsage * 100) / 100, cpuCores: parseInt(metric.cpu_cores) || 0, cpuFree: 100 - cpuUsage, diff --git a/application/src/components/ssl-domain/SSLCertificateStatusCards.tsx b/application/src/components/ssl-domain/SSLCertificateStatusCards.tsx index d449cfe..7ef844a 100644 --- a/application/src/components/ssl-domain/SSLCertificateStatusCards.tsx +++ b/application/src/components/ssl-domain/SSLCertificateStatusCards.tsx @@ -1,9 +1,9 @@ - import React from "react"; import { SSLCertificate } from "@/types/ssl.types"; 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 { useTheme } from "@/contexts/ThemeContext"; interface SSLCertificateStatusCardsProps { certificates: SSLCertificate[]; @@ -11,37 +11,113 @@ interface SSLCertificateStatusCardsProps { export const SSLCertificateStatusCards = ({ certificates }: SSLCertificateStatusCardsProps) => { const { t } = useLanguage(); - + const { theme } = useTheme(); + // Count certificates by status - const validCount = certificates.filter(cert => cert.status === 'valid').length; - const expiringCount = certificates.filter(cert => cert.status === 'expiring_soon').length; - const expiredCount = certificates.filter(cert => cert.status === 'expired').length; + const validCount = certificates.filter(cert => cert.status === "valid").length; + const expiringCount = certificates.filter(cert => cert.status === "expiring_soon").length; + const expiredCount = certificates.filter(cert => cert.status === "expired").length; return ( -
- } - color="green" - className="hover:scale-105 transition-transform duration-200" - /> +
+ {/* Valid Certificates */} + +
+
+
+ + + {t("validCertificates")} + + + + {validCount} +
+ +
+
+ - } - color="amber" - className="hover:scale-105 transition-transform duration-200" - /> + {/* Expiring Soon */} + +
+
+
+ + + {t("expiringSoon")} + + + + {expiringCount} +
+ +
+
+ - } - color="red" - className="hover:scale-105 transition-transform duration-200" - /> + {/* Expired */} + +
+
+
+ + + {t("expired")} + + + + {expiredCount} +
+ +
+
+
); -}; \ No newline at end of file +}; diff --git a/application/src/pages/ServerDetail.tsx b/application/src/pages/ServerDetail.tsx index 63ea12f..4727e84 100644 --- a/application/src/pages/ServerDetail.tsx +++ b/application/src/pages/ServerDetail.tsx @@ -10,7 +10,7 @@ import { Header } from "@/components/dashboard/Header"; import { serverService } from "@/services/serverService"; import { authService } from "@/services/authService"; 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 { ServerMetricsOverview } from "@/components/servers/ServerMetricsOverview"; import { ServerHistoryCharts } from "@/components/servers/ServerHistoryCharts"; @@ -126,7 +126,7 @@ const ServerDetail = () => {
- +

{server?.name || 'Server Detail'} @@ -135,11 +135,14 @@ const ServerDetail = () => {

Monitor server performance metrics and system health {server && ( - - {server.hostname} • {server.ip_address} • {server.os_type} + + {server.hostname} • {server.ip_address} • {server.os_type} • {server.system_info} + )}

+ +

diff --git a/application/src/services/serverService.ts b/application/src/services/serverService.ts index e95d9d7..eb2b2d7 100644 --- a/application/src/services/serverService.ts +++ b/application/src/services/serverService.ts @@ -1,4 +1,3 @@ - import { pb } from '@/lib/pocketbase'; import { Server, ServerStats } from '@/types/server.types'; @@ -23,9 +22,9 @@ export const serverService = { } }, - async getServerMetrics(serverId: string): Promise { + async getServerMetrics(serverId: string, timeRange?: string): Promise { 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 let server; @@ -36,48 +35,63 @@ export const serverService = { 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; if (server && server.server_id) { metricsServerId = server.server_id; console.log('serverService.getServerMetrics: Using server.server_id for metrics:', metricsServerId); } - // Try filtering by server_id first - let filteredRecords = await pb.collection('server_metrics').getFullList({ - filter: `server_id = "${metricsServerId}"`, + // Build filter for server_id and time range + let 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', requestKey: null }); - console.log('serverService.getServerMetrics: Filtered records by server_id:', filteredRecords.length); - - // 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; + console.log('serverService.getServerMetrics: Found', records.length, 'records with filter'); + return records; } catch (error) { console.error('Error fetching server metrics:', error); throw error; diff --git a/docker/distributed-regional-monitoring.yml b/docker/distributed-regional-monitoring.yml new file mode 100644 index 0000000..6cb24e3 --- /dev/null +++ b/docker/distributed-regional-monitoring.yml @@ -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"]