From b4a12a46df39d3001eccd8f4625583a22f5d6c2e Mon Sep 17 00:00:00 2001 From: Tola Leng Date: Tue, 15 Jul 2025 22:27:20 +0700 Subject: [PATCH 01/21] Added config file blank issues --- .github/ISSUE_TEMPLATE/config.yml | 1 + 1 file changed, 1 insertion(+) create mode 100644 .github/ISSUE_TEMPLATE/config.yml diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml new file mode 100644 index 0000000..ec4bb38 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/config.yml @@ -0,0 +1 @@ +blank_issues_enabled: false \ No newline at end of file From 2de3a179676bb4ad55ded1a2d787f274f5bef069 Mon Sep 17 00:00:00 2001 From: Tola Leng Date: Tue, 15 Jul 2025 22:32:02 +0700 Subject: [PATCH 02/21] Added issue template icon --- .github/ISSUE_TEMPLATE/ask_for_help.yml | 2 +- .github/ISSUE_TEMPLATE/bug_report.yml | 2 +- .github/ISSUE_TEMPLATE/documentation.yml | 2 +- .github/ISSUE_TEMPLATE/feature_request.yml | 2 +- .github/ISSUE_TEMPLATE/security_issue.yml | 2 +- .github/ISSUE_TEMPLATE/translation_request.yml | 2 +- 6 files changed, 6 insertions(+), 6 deletions(-) diff --git a/.github/ISSUE_TEMPLATE/ask_for_help.yml b/.github/ISSUE_TEMPLATE/ask_for_help.yml index 0b058a2..54e72f7 100644 --- a/.github/ISSUE_TEMPLATE/ask_for_help.yml +++ b/.github/ISSUE_TEMPLATE/ask_for_help.yml @@ -1,4 +1,4 @@ -name: Ask for Help +name: ❓ Ask for Help description: Ask a question or request guidance about this project. title: "[Question]: " labels: [question] diff --git a/.github/ISSUE_TEMPLATE/bug_report.yml b/.github/ISSUE_TEMPLATE/bug_report.yml index ae7046d..bb41a83 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.yml +++ b/.github/ISSUE_TEMPLATE/bug_report.yml @@ -1,4 +1,4 @@ -name: Bug Report +name: 🐛 Bug Report description: Report a reproducible bug to help us improve. title: "[Bug]: " labels: [bug] diff --git a/.github/ISSUE_TEMPLATE/documentation.yml b/.github/ISSUE_TEMPLATE/documentation.yml index e4c64a7..735acc2 100644 --- a/.github/ISSUE_TEMPLATE/documentation.yml +++ b/.github/ISSUE_TEMPLATE/documentation.yml @@ -1,4 +1,4 @@ -name: Documentation Improvement +name: 📝 Documentation Improvement description: Suggest improvements or report issues in documentation. title: "[Docs]: " labels: [documentation] diff --git a/.github/ISSUE_TEMPLATE/feature_request.yml b/.github/ISSUE_TEMPLATE/feature_request.yml index 87cd7ca..90423a9 100644 --- a/.github/ISSUE_TEMPLATE/feature_request.yml +++ b/.github/ISSUE_TEMPLATE/feature_request.yml @@ -1,4 +1,4 @@ -name: Feature Request +name: 🚀 Feature Request description: Suggest an idea to improve this project. title: "[Feature]: " labels: [enhancement] diff --git a/.github/ISSUE_TEMPLATE/security_issue.yml b/.github/ISSUE_TEMPLATE/security_issue.yml index ee44028..31e140c 100644 --- a/.github/ISSUE_TEMPLATE/security_issue.yml +++ b/.github/ISSUE_TEMPLATE/security_issue.yml @@ -1,4 +1,4 @@ -name: Security Issue +name: 🛡️ Security Issue description: Report a potential security vulnerability. title: "[Security]: " labels: [security] diff --git a/.github/ISSUE_TEMPLATE/translation_request.yml b/.github/ISSUE_TEMPLATE/translation_request.yml index 5e2b226..a156fca 100644 --- a/.github/ISSUE_TEMPLATE/translation_request.yml +++ b/.github/ISSUE_TEMPLATE/translation_request.yml @@ -1,4 +1,4 @@ -name: Translation Request +name: 🌎 Translation Request description: Request a translation or report translation issues. title: "[Translation]: " labels: [translation] From 88877a9fc03c073d4c7744fd7f260df9d97b6073 Mon Sep 17 00:00:00 2001 From: aa123456 Date: Thu, 17 Jul 2025 16:36:00 +0700 Subject: [PATCH 03/21] Updated Incident Management Overview Cards linear-gradient --- .../schedule-incident/common/OverviewCard.tsx | 5 +++ .../incident-management/OverviewCards.tsx | 32 ++++++++++++++++--- 2 files changed, 32 insertions(+), 5 deletions(-) diff --git a/application/src/components/schedule-incident/common/OverviewCard.tsx b/application/src/components/schedule-incident/common/OverviewCard.tsx index 6c96010..ccbeb45 100644 --- a/application/src/components/schedule-incident/common/OverviewCard.tsx +++ b/application/src/components/schedule-incident/common/OverviewCard.tsx @@ -18,6 +18,7 @@ interface OverviewCardProps { valueClassName?: string; isLoading?: boolean; color?: string; + gradient?: string; } export const OverviewCard = ({ @@ -30,11 +31,15 @@ export const OverviewCard = ({ valueClassName, isLoading = false, color = "blue", + gradient, }: OverviewCardProps) => { const { theme } = useTheme(); // Map color prop to gradient colors const getGradientBackground = () => { + if (gradient) { + return gradient; + } const colors = { blue: theme === 'dark' ? "linear-gradient(135deg, rgba(25, 118, 210, 0.8) 0%, rgba(66, 165, 245, 0.6) 100%)" diff --git a/application/src/components/schedule-incident/incident-management/OverviewCards.tsx b/application/src/components/schedule-incident/incident-management/OverviewCards.tsx index 454c21d..73566b9 100644 --- a/application/src/components/schedule-incident/incident-management/OverviewCards.tsx +++ b/application/src/components/schedule-incident/incident-management/OverviewCards.tsx @@ -3,6 +3,7 @@ import React from 'react'; import { AlertCircle, CheckCircle, Clock, AlertTriangle, Flag } from 'lucide-react'; import { useLanguage } from '@/contexts/LanguageContext'; import { OverviewCard } from '../common/OverviewCard'; +import { useTheme } from '@/contexts/ThemeContext'; interface OverviewStatsProps { unresolved: number; @@ -24,6 +25,7 @@ export const OverviewCards: React.FC = ({ initialized }) => { const { t } = useLanguage(); + const { theme } = useTheme(); return (
@@ -32,35 +34,55 @@ export const OverviewCards: React.FC = ({ value={overviewStats.unresolved.toString()} icon={} isLoading={loading && initialized} - color="red" + gradient={ + theme === "dark" + ? "linear-gradient(135deg, #4b3b37 0%, rgba(239, 83, 80, 0.6) 100%)" + : "linear-gradient(135deg, #4b3b37 0%, rgba(239, 83, 80, 0.6) 100%)" + } /> } isLoading={loading && initialized} - color="amber" + gradient={ + theme === "dark" + ? "linear-gradient(135deg, #4b3b37 0%, rgba(255, 183, 77, 0.6) 100%)" + : "linear-gradient(135deg, #4b3b37 0%, rgba(255, 183, 77, 0.6) 100%)" + } /> } isLoading={loading && initialized} - color="orange" + gradient={ + theme === "dark" + ? "linear-gradient(135deg, #4b3b37 0%, rgba(255, 109, 0, 0.6) 100%)" + : "linear-gradient(135deg, #4b3b37 0%, rgba(255, 109, 0, 0.6) 100%)" + } /> } isLoading={loading && initialized} - color="green" + gradient={ + theme === "dark" + ? "linear-gradient(135deg, #4b3b37 0%, rgba(102, 187, 106, 0.6) 100%)" + : "linear-gradient(135deg, #4b3b37 0%, rgba(102, 187, 106, 0.6) 100%)" + } /> } isLoading={loading && initialized} - color="blue" + gradient={ + theme === "dark" + ? "linear-gradient(135deg, #4b3b37 0%, rgba(66, 165, 245, 0.6) 100%)" + : "linear-gradient(135deg, #4b3b37 0%, rgba(66, 165, 245, 0.6) 100%)" + } />
); From 6906a659527cd1c542da965baedd850907ce9b19 Mon Sep 17 00:00:00 2001 From: Tola Leng Date: Thu, 17 Jul 2025 17:26:17 +0700 Subject: [PATCH 04/21] Fix: Improve uptime monitoring dashboard performance - Reduce refetch intervals, increase stale times, and optimize retry behavior for uptime service and server metrics. --- .../servers/ServerMetricsCharts.tsx | 5 +- .../charts/hooks/useServerHistoryData.ts | 18 +- .../hooks/useRealTimeUpdates.tsx | 136 ++++++++---- .../hooks/useServiceData.tsx | 167 ++++++++------- .../src/components/services/StatusBadge.tsx | 115 +++++------ .../src/components/services/UptimeBar.tsx | 195 +++++++----------- .../services/hooks/useUptimeData.ts | 6 +- application/src/pages/Dashboard.tsx | 31 ++- .../service-status/startMonitoring.ts | 24 ++- 9 files changed, 367 insertions(+), 330 deletions(-) diff --git a/application/src/components/servers/ServerMetricsCharts.tsx b/application/src/components/servers/ServerMetricsCharts.tsx index a3fa10c..1a60497 100644 --- a/application/src/components/servers/ServerMetricsCharts.tsx +++ b/application/src/components/servers/ServerMetricsCharts.tsx @@ -28,7 +28,10 @@ export const ServerMetricsCharts = ({ serverId }: ServerMetricsChartsProps) => { queryKey: ['server-metrics', serverId, timeRange], queryFn: () => serverService.getServerMetrics(serverId, timeRange), enabled: !!serverId, - refetchInterval: 30000 + refetchInterval: timeRange === '60m' ? 60000 : timeRange === '1d' ? 120000 : 300000, // Increased intervals + staleTime: timeRange === '60m' ? 30000 : timeRange === '1d' ? 60000 : 120000, // Increased stale time + gcTime: 10 * 60 * 1000, // 10 minutes cache + refetchOnWindowFocus: false, // Prevent refetch on window focus }); const chartData = formatChartData(metrics, timeRange); diff --git a/application/src/components/servers/charts/hooks/useServerHistoryData.ts b/application/src/components/servers/charts/hooks/useServerHistoryData.ts index d08a437..954f1b2 100644 --- a/application/src/components/servers/charts/hooks/useServerHistoryData.ts +++ b/application/src/components/servers/charts/hooks/useServerHistoryData.ts @@ -1,5 +1,5 @@ -import { useState } from "react"; +import { useState, useMemo } from "react"; import { useQuery } from "@tanstack/react-query"; import { serverService } from "@/services/serverService"; import { formatChartData } from "../dataUtils"; @@ -23,15 +23,19 @@ export const useServerHistoryData = (serverId: string) => { return result || []; }, enabled: !!serverId, - refetchInterval: timeRange === '60m' ? 30000 : timeRange === '1d' ? 60000 : 120000, // Reduced frequency - staleTime: timeRange === '60m' ? 15000 : timeRange === '1d' ? 30000 : 60000, // Increased stale time - retry: 2, // Reduced retries - retryDelay: (attemptIndex) => Math.min(1000 * 2 ** attemptIndex, 3000), // Faster retry - gcTime: 5 * 60 * 1000, // 5 minutes cache + refetchInterval: timeRange === '60m' ? 60000 : timeRange === '1d' ? 120000 : 300000, // Significantly increased intervals + staleTime: timeRange === '60m' ? 30000 : timeRange === '1d' ? 60000 : 120000, // Increased stale time + retry: 1, // Reduced retries to prevent excessive requests + retryDelay: 2000, // Slower retry + gcTime: 10 * 60 * 1000, // 10 minutes cache + refetchOnWindowFocus: false, // Prevent refetch on window focus + refetchOnMount: false, // Prevent refetch on mount if data exists }); // Memoize chart data formatting to prevent unnecessary recalculations - const chartData = formatChartData(metrics, timeRange); + const chartData = useMemo(() => { + return formatChartData(metrics, timeRange); + }, [metrics, timeRange]); return { timeRange, diff --git a/application/src/components/services/ServiceDetailContainer/hooks/useRealTimeUpdates.tsx b/application/src/components/services/ServiceDetailContainer/hooks/useRealTimeUpdates.tsx index 0bc9cc5..b4e6d82 100644 --- a/application/src/components/services/ServiceDetailContainer/hooks/useRealTimeUpdates.tsx +++ b/application/src/components/services/ServiceDetailContainer/hooks/useRealTimeUpdates.tsx @@ -1,5 +1,5 @@ -import { useEffect } from "react"; +import { useEffect, useRef } from "react"; import { pb } from "@/lib/pocketbase"; import { Service, UptimeData } from "@/types/service.types"; @@ -18,71 +18,117 @@ export const useRealTimeUpdates = ({ setService, setUptimeData }: UseRealTimeUpdatesProps) => { - // Listen for real-time updates to this service + const subscriptionsRef = useRef<{ + service?: () => void; + uptime?: () => void; + }>({}); + + const lastUpdateRef = useRef(0); + const updateThrottleMs = 30000; // Throttle to once per 30 seconds for better performance + + // Listen for real-time updates to this service with throttling useEffect(() => { if (!serviceId) return; - // console.log(`Setting up real-time updates for service: ${serviceId}`); + // console.log(`Setting up real-time updates for service: ${serviceId}`); - try { - // Subscribe to the service record for real-time updates - const subscription = pb.collection('services').subscribe(serviceId, function(e) { - // console.log("Service updated:", e.record); - - // Update our local state with the new data - if (e.record) { - setService(prev => { - if (!prev) return null; - return { - ...prev, - status: e.record.status || prev.status, - responseTime: e.record.response_time || e.record.responseTime || prev.responseTime, - uptime: e.record.uptime || prev.uptime, - lastChecked: e.record.last_checked || e.record.lastChecked || prev.lastChecked, - }; - }); + const setupSubscriptions = async () => { + try { + // Clean up existing subscriptions first + if (subscriptionsRef.current.service) { + subscriptionsRef.current.service(); + } + if (subscriptionsRef.current.uptime) { + subscriptionsRef.current.uptime(); } - }); - // Subscribe to uptime data updates - const uptimeSubscription = pb.collection('uptime_data').subscribe('*', function(e) { - if (e.record && e.record.service_id === serviceId) { - // console.log("New uptime data:", e.record); + // Subscribe to the service record with throttling + const serviceUnsubscribe = await pb.collection('services').subscribe(serviceId, function(e) { + const now = Date.now(); + if (now - lastUpdateRef.current < updateThrottleMs) { + // console.log("Service update throttled"); + return; + } + lastUpdateRef.current = now; + + // console.log("Service updated (throttled):", e.record); + + // Update our local state with the new data + if (e.record) { + setService(prev => { + if (!prev) return null; + return { + ...prev, + status: e.record.status || prev.status, + responseTime: e.record.response_time || e.record.responseTime || prev.responseTime, + uptime: e.record.uptime || prev.uptime, + lastChecked: e.record.last_checked || e.record.lastChecked || prev.lastChecked, + }; + }); + } + }); + + subscriptionsRef.current.service = serviceUnsubscribe; + + // Subscribe to uptime data updates with throttling + const uptimeUnsubscribe = await pb.collection('uptime_data').subscribe('*', function(e) { + if (!e.record || e.record.service_id !== serviceId) return; + + const now = Date.now(); + if (now - lastUpdateRef.current < updateThrottleMs) { + // console.log("Uptime data update throttled"); + return; + } + lastUpdateRef.current = now; + + // console.log("New uptime data (throttled):", e.record); // Add the new uptime data to our list if it's within the selected date range const timestamp = new Date(e.record.timestamp); if (timestamp >= startDate && timestamp <= endDate) { setUptimeData(prev => { + // Limit the array size to prevent memory issues + const maxRecords = 100; const newData: UptimeData = { id: e.record.id, - service_id: e.record.service_id, // Include service_id - serviceId: e.record.service_id, // Keep for backward compatibility + service_id: e.record.service_id, + serviceId: e.record.service_id, timestamp: e.record.timestamp, status: e.record.status, responseTime: e.record.response_time || 0, - date: e.record.timestamp, // Adding required date property - uptime: e.record.uptime || 0 // Adding required uptime property + date: e.record.timestamp, + uptime: e.record.uptime || 0 }; - // Add at the beginning of the array to maintain newest first sorting - return [newData, ...prev]; + // Add at the beginning and limit array size + const updatedData = [newData, ...prev]; + return updatedData.slice(0, maxRecords); }); } - } - }); + }); - // Clean up the subscriptions - return () => { - // console.log(`Cleaning up subscriptions for service: ${serviceId}`); - try { - pb.collection('services').unsubscribe(serviceId); - pb.collection('uptime_data').unsubscribe('*'); - } catch (error) { - // console.error("Error cleaning up subscriptions:", error); + subscriptionsRef.current.uptime = uptimeUnsubscribe; + + } catch (error) { + // console.error("Error setting up real-time updates:", error); + } + }; + + setupSubscriptions(); + + // Return cleanup function + return () => { + // console.log(`Cleaning up subscriptions for service: ${serviceId}`); + try { + if (subscriptionsRef.current.service) { + subscriptionsRef.current.service(); } - }; - } catch (error) { - // console.error("Error setting up real-time updates:", error); - } + if (subscriptionsRef.current.uptime) { + subscriptionsRef.current.uptime(); + } + } catch (error) { + // console.error("Error cleaning up subscriptions:", error); + } + }; }, [serviceId, startDate, endDate, setService, setUptimeData]); }; \ No newline at end of file diff --git a/application/src/components/services/ServiceDetailContainer/hooks/useServiceData.tsx b/application/src/components/services/ServiceDetailContainer/hooks/useServiceData.tsx index ccd6259..fa42951 100644 --- a/application/src/components/services/ServiceDetailContainer/hooks/useServiceData.tsx +++ b/application/src/components/services/ServiceDetailContainer/hooks/useServiceData.tsx @@ -1,4 +1,5 @@ -import { useState, useEffect } from "react"; + +import { useState, useEffect, useCallback, useMemo } from "react"; import { pb } from "@/lib/pocketbase"; import { Service, UptimeData } from "@/types/service.types"; import { useToast } from "@/hooks/use-toast"; @@ -16,14 +17,17 @@ export const useServiceData = (serviceId: string | undefined, startDate: Date, e const { toast } = useToast(); const navigate = useNavigate(); - // Get regional agents for "all" monitoring + // Get regional agents for "all" monitoring with optimized caching const { data: regionalAgents = [] } = useQuery({ queryKey: ['regional-services'], queryFn: regionalService.getRegionalServices, - enabled: selectedRegionalAgent === "all" + enabled: selectedRegionalAgent === "all", + staleTime: 5 * 60 * 1000, // Cache for 5 minutes + gcTime: 10 * 60 * 1000, // Keep in cache for 10 minutes + refetchOnWindowFocus: false, }); - const handleStatusChange = async (newStatus: "up" | "down" | "paused" | "warning") => { + const handleStatusChange = useCallback(async (newStatus: "up" | "down" | "paused" | "warning") => { if (!service || !serviceId) return; try { @@ -38,7 +42,7 @@ export const useServiceData = (serviceId: string | undefined, startDate: Date, e description: `Service status changed to ${newStatus}`, }); } catch (error) { - // console.error("Failed to update service status:", error); + // console.error("Failed to update service status:", error); setService(prevService => prevService); toast({ @@ -47,9 +51,9 @@ export const useServiceData = (serviceId: string | undefined, startDate: Date, e description: "Could not update service status. Please try again.", }); } - }; + }, [service, serviceId, toast]); - const fetchUptimeData = async (serviceId: string, start: Date, end: Date, selectedRange?: DateRangeOption | string, regionalAgent?: string) => { + const fetchUptimeData = useCallback(async (serviceId: string, start: Date, end: Date, selectedRange?: DateRangeOption | string, regionalAgent?: string) => { try { if (!service) { // console.log('No service data available for uptime fetch'); @@ -80,7 +84,7 @@ export const useServiceData = (serviceId: string | undefined, startDate: Date, e // Fetch default monitoring data const defaultData = await uptimeService.getUptimeHistory(serviceId, limit, start, end, service.type); - // console.log(`Retrieved ${defaultData.length} default monitoring records`); + // console.log(`Retrieved ${defaultData.length} default monitoring records`); // Mark default data with source identifier const markedDefaultData = defaultData.map(record => ({ @@ -100,7 +104,7 @@ export const useServiceData = (serviceId: string | undefined, startDate: Date, e const regionalData = await uptimeService.getUptimeHistoryByRegionalAgent( serviceId, limit, start, end, service.type, agent.region_name, agent.agent_id ); - // console.log(`Retrieved ${regionalData.length} records from ${agent.region_name}`); + // console.log(`Retrieved ${regionalData.length} records from ${agent.region_name}`); // Mark regional data with source identifier const markedRegionalData = regionalData.map(record => ({ @@ -112,18 +116,18 @@ export const useServiceData = (serviceId: string | undefined, startDate: Date, e history = [...history, ...markedRegionalData]; } catch (error) { - // console.error(`Error fetching data from ${agent.region_name}:`, error); + // console.error(`Error fetching data from ${agent.region_name}:`, error); } } - // console.log(`Total combined records: ${history.length}`); + // console.log(`Total combined records: ${history.length}`); } else { // Fetch regional agent specific data const [regionName, agentId] = currentAgent.split("|"); - // console.log(`Fetching regional agent data for region: ${regionName}, agent: ${agentId} from ${service.type} collection`); + console.log(`Fetching regional agent data for region: ${regionName}, agent: ${agentId} from ${service.type} collection`); history = await uptimeService.getUptimeHistoryByRegionalAgent(serviceId, limit, start, end, service.type, regionName, agentId); - // console.log(`Retrieved ${history.length} regional monitoring records`); + // console.log(`Retrieved ${history.length} regional monitoring records`); } // Sort by timestamp (newest first) @@ -131,7 +135,7 @@ export const useServiceData = (serviceId: string | undefined, startDate: Date, e new Date(b.timestamp).getTime() - new Date(a.timestamp).getTime() ); - //console.log(`Final dataset: ${filteredHistory.length} records for ${currentAgent === "all" ? "all sources" : "regional"} monitoring`); + // console.log(`Final dataset: ${filteredHistory.length} records for ${currentAgent === "all" ? "all sources" : "regional"} monitoring`); setUptimeData(filteredHistory); return filteredHistory; } catch (error) { @@ -143,9 +147,9 @@ export const useServiceData = (serviceId: string | undefined, startDate: Date, e }); return []; } - }; + }, [service, selectedRegionalAgent, regionalAgents, toast]); - const handleRegionalAgentChange = (agent: string) => { + const handleRegionalAgentChange = useCallback((agent: string) => { // console.log(`Regional agent changed from ${selectedRegionalAgent} to: ${agent}`); // Clear data immediately when switching @@ -154,78 +158,83 @@ export const useServiceData = (serviceId: string | undefined, startDate: Date, e // Refetch data with new agent selection if (serviceId && !isLoading && service) { - // console.log(`Refetching data for new agent: ${agent}`); + // console.log(`Refetching data for new agent: ${agent}`); fetchUptimeData(serviceId, startDate, endDate, '24h', agent); } - }; + }, [selectedRegionalAgent, serviceId, isLoading, service, fetchUptimeData, startDate, endDate]); + + // Memoize the service data fetching to prevent unnecessary re-runs + const fetchServiceData = useCallback(async () => { + try { + if (!serviceId) { + setIsLoading(false); + return; + } + + setIsLoading(true); + + const timeoutPromise = new Promise((_, reject) => { + setTimeout(() => reject(new Error("Request timed out")), 10000); + }); + + const fetchPromise = pb.collection('services').getOne(serviceId); + const serviceData = await Promise.race([fetchPromise, timeoutPromise]) as any; + + const formattedService: Service = { + id: serviceData.id, + name: serviceData.name, + url: serviceData.url || "", + host: serviceData.host || "", + port: serviceData.port || undefined, + domain: serviceData.domain || "", + type: serviceData.service_type || serviceData.type || "HTTP", + status: serviceData.status || "paused", + responseTime: serviceData.response_time || serviceData.responseTime || 0, + uptime: serviceData.uptime || 0, + lastChecked: serviceData.last_checked || serviceData.lastChecked || new Date().toLocaleString(), + interval: serviceData.heartbeat_interval || serviceData.interval || 60, + retries: serviceData.max_retries || serviceData.retries || 3, + notificationChannel: serviceData.notification_id, + alertTemplate: serviceData.template_id, + alerts: serviceData.alerts || "unmuted" + }; + + // console.log(`Loaded service: ${formattedService.name} (${formattedService.type})`); + setService(formattedService); + + // Small delay to ensure state is updated before fetching uptime data + await new Promise(resolve => setTimeout(resolve, 100)); + } catch (error) { + // console.error("Error fetching service:", error); + toast({ + variant: "destructive", + title: "Error", + description: "Failed to load service data. Please try again.", + }); + navigate("/dashboard"); + } finally { + setIsLoading(false); + } + }, [serviceId, navigate, toast]); // Initial data loading useEffect(() => { - const fetchServiceData = async () => { - try { - if (!serviceId) { - setIsLoading(false); - return; - } - - setIsLoading(true); - - const timeoutPromise = new Promise((_, reject) => { - setTimeout(() => reject(new Error("Request timed out")), 10000); - }); - - const fetchPromise = pb.collection('services').getOne(serviceId); - const serviceData = await Promise.race([fetchPromise, timeoutPromise]) as any; - - const formattedService: Service = { - id: serviceData.id, - name: serviceData.name, - url: serviceData.url || "", - host: serviceData.host || "", - port: serviceData.port || undefined, - domain: serviceData.domain || "", - type: serviceData.service_type || serviceData.type || "HTTP", - status: serviceData.status || "paused", - responseTime: serviceData.response_time || serviceData.responseTime || 0, - uptime: serviceData.uptime || 0, - lastChecked: serviceData.last_checked || serviceData.lastChecked || new Date().toLocaleString(), - interval: serviceData.heartbeat_interval || serviceData.interval || 60, - retries: serviceData.max_retries || serviceData.retries || 3, - notificationChannel: serviceData.notification_id, - alertTemplate: serviceData.template_id, - alerts: serviceData.alerts || "unmuted" - }; - - // console.log(`Loaded service: ${formattedService.name} (${formattedService.type})`); - setService(formattedService); - - // Small delay to ensure state is updated before fetching uptime data - await new Promise(resolve => setTimeout(resolve, 100)); - } catch (error) { - // console.error("Error fetching service:", error); - toast({ - variant: "destructive", - title: "Error", - description: "Failed to load service data. Please try again.", - }); - navigate("/dashboard"); - } finally { - setIsLoading(false); - } - }; - fetchServiceData(); - }, [serviceId, navigate, toast]); + }, [fetchServiceData]); - // Update data when date range changes or when service is loaded + // Update data when date range changes or when service is loaded - with debouncing useEffect(() => { if (serviceId && !isLoading && service) { - // console.log(`Date range changed or service loaded, refetching data for ${serviceId}: ${startDate.toISOString()} to ${endDate.toISOString()}`); - fetchUptimeData(serviceId, startDate, endDate, '24h', selectedRegionalAgent); - } - }, [startDate, endDate, serviceId, isLoading, service, selectedRegionalAgent, regionalAgents]); + const timeoutId = setTimeout(() => { + // console.log(`Date range changed or service loaded, refetching data for ${serviceId}: ${startDate.toISOString()} to ${endDate.toISOString()}`); + fetchUptimeData(serviceId, startDate, endDate, '24h', selectedRegionalAgent); + }, 500); // Debounce API calls by 500ms - return { + return () => clearTimeout(timeoutId); + } + }, [startDate, endDate, serviceId, isLoading, service, selectedRegionalAgent, regionalAgents, fetchUptimeData]); + + return useMemo(() => ({ service, setService, uptimeData, @@ -235,5 +244,5 @@ export const useServiceData = (serviceId: string | undefined, startDate: Date, e fetchUptimeData, selectedRegionalAgent, handleRegionalAgentChange - }; + }), [service, uptimeData, isLoading, handleStatusChange, fetchUptimeData, selectedRegionalAgent, handleRegionalAgentChange]); }; \ No newline at end of file diff --git a/application/src/components/services/StatusBadge.tsx b/application/src/components/services/StatusBadge.tsx index 2621b0a..df8b8de 100644 --- a/application/src/components/services/StatusBadge.tsx +++ b/application/src/components/services/StatusBadge.tsx @@ -1,71 +1,70 @@ -import React from "react"; -import { Check, X, Pause, AlertTriangle } from "lucide-react"; +import React, { memo } from "react"; +import { Badge } from "@/components/ui/badge"; +import { Check } from "lucide-react"; -export interface StatusBadgeProps { - status: string; +interface StatusBadgeProps { + status: "up" | "down" | "paused" | "warning"; size?: "sm" | "md" | "lg"; } -export const StatusBadge = ({ status, size = "sm" }: StatusBadgeProps) => { - // Determine the sizing classes based on the size prop - const getSizeClasses = () => { - switch (size) { - case "lg": - return "px-3 py-1.5 text-sm gap-1.5"; - case "md": - return "px-2.5 py-1 text-sm gap-1.5"; - case "sm": +const StatusBadgeComponent = ({ status, size = "sm" }: StatusBadgeProps) => { + const getStatusConfig = (status: string) => { + switch (status) { + case "up": + return { + variant: "default" as const, + className: "bg-emerald-700 text-emerald-100 border-emerald-200 hover:bg-emerald-200", + label: + + Up + + + }; + case "down": + return { + variant: "destructive" as const, + className: "bg-red-700 text-red-100 border-red-200 hover:bg-red-200", + label: "Down" + }; + case "warning": + return { + variant: "destructive" as const, + className: "bg-amber-700 text-amber-100 border-amber-200 hover:bg-amber-200", + label: "Warning" + }; + case "paused": + return { + variant: "secondary" as const, + className: "bg-gray-700 text-gray-100 border-gray-200 hover:bg-gray-200", + label: "Paused" + }; default: - return "px-2 py-0.5 text-xs gap-0.5"; + return { + variant: "outline" as const, + className: "bg-gray-700 text-gray-100 border-gray-200", + label: "Unknown" + }; } }; - const getIconSize = () => { - switch (size) { - case "lg": - return "h-4 w-4"; - case "md": - return "h-4 w-4"; - case "sm": - default: - return "h-3 w-3"; - } + const sizeClasses = { + sm: "text-xs px-2 py-1", + md: "text-sm px-3 py-1.5", + lg: "text-base px-4 py-2" }; - const sizeClasses = getSizeClasses(); - const iconSize = getIconSize(); + const config = getStatusConfig(status); - switch (status) { - case "up": - return ( -
- - Up -
- ); - case "down": - return ( -
- - Down -
- ); - case "warning": - return ( -
- - Warning -
- ); - case "paused": - return ( -
- - Paused -
- ); - default: - return null; - } + return ( + + {config.label} + + ); }; + +// Memoize the component to prevent unnecessary re-renders +export const StatusBadge = memo(StatusBadgeComponent); \ No newline at end of file diff --git a/application/src/components/services/UptimeBar.tsx b/application/src/components/services/UptimeBar.tsx index 1a827fd..642e35e 100644 --- a/application/src/components/services/UptimeBar.tsx +++ b/application/src/components/services/UptimeBar.tsx @@ -1,9 +1,10 @@ -import React from "react"; -import { useConsolidatedUptimeData } from "./hooks/useConsolidatedUptimeData"; -import { TooltipProvider, Tooltip, TooltipTrigger, TooltipContent } from "@/components/ui/tooltip"; -import { UptimeSummary } from "./uptime/UptimeSummary"; -import { formatRelative } from "date-fns"; +import React, { memo, useMemo } from 'react'; +import { useQuery } from '@tanstack/react-query'; +import { TooltipProvider } from '@/components/ui/tooltip'; +import { UptimeStatusItem } from './uptime/UptimeStatusItem'; +import { uptimeService } from '@/services/uptimeService'; +import { UptimeData } from '@/types/service.types'; interface UptimeBarProps { uptime: number; @@ -13,127 +14,81 @@ interface UptimeBarProps { serviceType?: string; } -export const UptimeBar = ({ uptime, status, serviceId, interval, serviceType }: UptimeBarProps) => { - // Use consolidated hook to get properly merged data - const { consolidatedItems, isLoading } = useConsolidatedUptimeData({ - serviceId, - serviceType, - status, - interval +const UptimeBarComponent = ({ uptime, status, serviceId, interval, serviceType = "HTTP" }: UptimeBarProps) => { + // Calculate date range for last 20 checks with much more aggressive caching + const endDate = useMemo(() => new Date(), []); + const startDate = useMemo(() => { + const start = new Date(endDate); + start.setHours(start.getHours() - Math.max(interval * 20 / 3600, 24)); // At least 24 hours + return start; + }, [endDate, interval]); + + // Fetch uptime data with very aggressive caching to reduce API calls + const { data: uptimeData = [] } = useQuery({ + queryKey: ['uptime-bar', serviceId, serviceType], + queryFn: () => uptimeService.getUptimeHistory(serviceId, 20, startDate, endDate, serviceType), + enabled: !!serviceId, + staleTime: 30000, // Data is fresh for 30 seconds + gcTime: 600000, // Keep in cache for 10 minutes + refetchInterval: 60000, // 1 minute polling + refetchOnWindowFocus: false, + refetchOnMount: false, + refetchOnReconnect: false, + retry: 3, + retryDelay: (attemptIndex) => Math.min(1000 * 2 ** attemptIndex, 10000), }); - const getStatusColor = (itemStatus: string, hasData: boolean = true) => { - if (!hasData) { - return "bg-gray-400"; // No data color - grey + // Memoize the uptime calculation to prevent unnecessary recalculations + const { uptimePercentage, displayData } = useMemo(() => { + if (!uptimeData || uptimeData.length === 0) { + return { + uptimePercentage: uptime || 0, + displayData: [] + }; } - - switch (itemStatus) { - case "up": - return "bg-emerald-500"; - case "down": - return "bg-red-500"; - case "warning": - return "bg-yellow-500"; - case "paused": - return "bg-gray-400"; // Paused status - grey - case "unknown": - case "NA": - default: - return "bg-gray-400"; // Unknown/NA/default - grey - } - }; - if (isLoading) { - return ( -
-
- {Array(20).fill(null).map((_, index) => ( -
- ))} -
- -
- ); - } + // Calculate uptime percentage from actual data + const upCount = uptimeData.filter(item => item.status === 'up').length; + const totalCount = uptimeData.length; + const calculatedUptime = totalCount > 0 ? (upCount / totalCount) * 100 : 0; - // console.log('UptimeBar rendering consolidated items:', consolidatedItems.length); + // Limit display to last 20 items for performance + const limitedData = uptimeData.slice(0, 20); + + return { + uptimePercentage: Math.round(calculatedUptime * 100) / 100, + displayData: limitedData + }; + }, [uptimeData, uptime]); + + // Memoize the status items to prevent unnecessary re-renders + const statusItems = useMemo(() => + displayData.map((item, index) => ( + + )), + [displayData] + ); return ( -
- -
- {consolidatedItems.map((slot, index) => { - // Check if we have actual monitoring data with valid status - const hasValidData = slot.items.length > 0 && slot.items.some(item => - item.status && ['up', 'down', 'warning', 'paused'].includes(item.status) - ); - - // Determine the primary status for bar color (prioritize worst status) - let primaryStatus = 'unknown'; - if (hasValidData) { - const statuses = slot.items.map(item => item.status); - primaryStatus = statuses.includes('down') ? 'down' : - statuses.includes('warning') ? 'warning' : - statuses.includes('up') ? 'up' : - statuses.includes('paused') ? 'paused' : 'unknown'; - } - - // console.log(`Bar ${index} - Timestamp: ${slot.timestamp}, Items: ${slot.items.length}, Primary Status: ${primaryStatus}, Has Valid Data: ${hasValidData}`); - slot.items.forEach((item, itemIndex) => { - // console.log(` Item ${itemIndex}: Source=${item.source}, Status=${item.status}, ResponseTime=${item.responseTime}, IsDefault=${item.isDefault}`); - }); - - return ( - - -
- - -
-
- {formatRelative(new Date(slot.timestamp), new Date())} -
- {hasValidData ? ( -
- {slot.items.map((item, itemIndex) => ( -
-
-
- {item.source} -
-
- {item.status === 'paused' ? 'Paused' : - item.responseTime && item.responseTime > 0 ? `${item.responseTime}ms` : - 'No response'} -
-
- ))} - {slot.items.length > 1 && ( -
- {slot.items.length} monitoring sources -
- )} -
- ) : ( -
- No monitoring data available -
- )} -
- - - ); - })} + +
+
+ {statusItems.length > 0 ? statusItems : ( + // Fallback display when no data +
+ )}
- - -
+ + {uptimePercentage.toFixed(1)}% + +
+ + + Last 20 checks + +
); -}; \ No newline at end of file +}; + +// Memoize the component to prevent unnecessary re-renders +export const UptimeBar = memo(UptimeBarComponent); \ No newline at end of file diff --git a/application/src/components/services/hooks/useUptimeData.ts b/application/src/components/services/hooks/useUptimeData.ts index 0bfe235..96a20d1 100644 --- a/application/src/components/services/hooks/useUptimeData.ts +++ b/application/src/components/services/hooks/useUptimeData.ts @@ -14,7 +14,7 @@ interface UseUptimeDataProps { export const useUptimeData = ({ serviceId, serviceType, status, interval }: UseUptimeDataProps) => { const [historyItems, setHistoryItems] = useState([]); - // Fetch ALL uptime history data including regional monitoring data + // Fetch ALL uptime history data including regional monitoring data with 1-minute polling const { data: uptimeData, isLoading, error, isFetching, refetch } = useQuery({ queryKey: ['allUptimeHistory', serviceId, serviceType], queryFn: async () => { @@ -34,8 +34,8 @@ export const useUptimeData = ({ serviceId, serviceType, status, interval }: UseU return allMonitoringData; }, enabled: !!serviceId, - refetchInterval: 30000, - staleTime: 15000, + refetchInterval: 60000, // 1 minute polling + staleTime: 30000, // Data is fresh for 30 seconds placeholderData: (previousData) => previousData, retry: 3, retryDelay: (attemptIndex) => Math.min(1000 * 2 ** attemptIndex, 10000), diff --git a/application/src/pages/Dashboard.tsx b/application/src/pages/Dashboard.tsx index a61dd8e..2ab2c52 100644 --- a/application/src/pages/Dashboard.tsx +++ b/application/src/pages/Dashboard.tsx @@ -20,7 +20,7 @@ const Dashboard = () => { // For debugging user data useEffect(() => { - // console.log("Current user data:", currentUser); + // console.log("Current user data:", currentUser); }, [currentUser]); // Handle logout @@ -29,22 +29,39 @@ const Dashboard = () => { navigate("/login"); }; - // Fetch all services + // Fetch all services with 1-minute polling for real-time updates const { data: services = [], isLoading, error } = useQuery({ queryKey: ['services'], queryFn: serviceService.getServices, - refetchInterval: 10000, // Refresh data every 10 seconds + refetchInterval: 60000, // 1 minute as requested + staleTime: 30000, // Data is fresh for 30 seconds + gcTime: 120000, // Keep in cache for 2 minutes + refetchOnWindowFocus: true, // Refetch when window gains focus + refetchOnMount: true, // Refetch on mount + refetchOnReconnect: true, // Refetch on reconnect + retry: 2, + retryDelay: 3000, }); - // Start monitoring all active services when the dashboard loads + // Start monitoring all active services when the dashboard loads - only once useEffect(() => { + let hasStarted = false; + const startActiveServices = async () => { + if (hasStarted) return; + hasStarted = true; + await serviceService.startAllActiveServices(); - // console.log("Active services monitoring started"); + // console.log("Active services monitoring started"); }; - startActiveServices(); - }, []); + // Only start once and add a delay to prevent immediate execution + const timeoutId = setTimeout(startActiveServices, 2000); + + return () => { + clearTimeout(timeoutId); + }; + }, []); // Remove services dependency to prevent re-runs // Show the loading state while fetching data if (isLoading) { diff --git a/application/src/services/monitoring/service-status/startMonitoring.ts b/application/src/services/monitoring/service-status/startMonitoring.ts index a1d7a11..5cfb9e8 100644 --- a/application/src/services/monitoring/service-status/startMonitoring.ts +++ b/application/src/services/monitoring/service-status/startMonitoring.ts @@ -3,13 +3,13 @@ import { pb } from '@/lib/pocketbase'; import { monitoringIntervals } from '../monitoringIntervals'; /** - * Start monitoring for a specific service + * Start monitoring for a specific service with optimized performance */ export async function startMonitoringService(serviceId: string): Promise { try { // First check if the service is already being monitored if (monitoringIntervals.has(serviceId)) { - // console.log(`Service ${serviceId} is already being monitored`); + // console.log(`Service ${serviceId} is already being monitored`); return; } @@ -22,7 +22,7 @@ export async function startMonitoringService(serviceId: string): Promise { return; } - // console.log(`Starting monitoring for service ${serviceId} (${service.name})`); + // console.log(`Starting optimized monitoring for service ${serviceId} (${service.name})`); // Update the service status to active/up in the database await pb.collection('services').update(serviceId, { @@ -30,20 +30,24 @@ export async function startMonitoringService(serviceId: string): Promise { }); // The actual service checking is now handled by the Go microservice - // This frontend service just tracks the monitoring state - const intervalMs = (service.heartbeat_interval || 60) * 1000; - // console.log(`Service ${service.name} monitoring delegated to backend service`); + // This frontend service just tracks the monitoring state with much less frequency + const intervalMs = Math.max((service.heartbeat_interval || 60) * 1000, 60000); // Minimum 1 minute + // console.log(`Service ${service.name} monitoring delegated to backend service with interval ${intervalMs}ms`); // Store a placeholder interval to track that this service is being monitored + // Significantly reduce frequency to prevent excessive logging and CPU usage const intervalId = window.setInterval(() => { - // console.log(`Monitoring active for service ${service.name} (handled by backend)`); - }, intervalMs); + // Only log every 5 minutes to reduce console spam + if (Date.now() % (5 * 60 * 1000) < intervalMs) { + // console.log(`Monitoring active for service ${service.name} (handled by backend)`); + } + }, Math.max(intervalMs, 300000)); // Minimum 5 minutes for logging // Store the interval ID for this service monitoringIntervals.set(serviceId, intervalId); - // console.log(`Monitoring registered for service ${serviceId}`); + // console.log(`Optimized monitoring registered for service ${serviceId}`); } catch (error) { - // console.error("Error starting service monitoring:", error); + // console.error("Error starting service monitoring:", error); } } \ No newline at end of file From 0cad3746164a186afdbeb0555c66b8804b76d10a Mon Sep 17 00:00:00 2001 From: Tola Leng Date: Thu, 17 Jul 2025 23:03:40 +0700 Subject: [PATCH 05/21] Fix server delete function and Implement pause/resume functionality - Implement the server delete functionality in the server table and dropdown menu. - Implement pause and resume functionality for server monitoring in the server table dropdown menu. --- .../src/components/servers/ServerTable.tsx | 498 +++++++++++------- application/src/types/server.types.ts | 2 +- 2 files changed, 310 insertions(+), 190 deletions(-) diff --git a/application/src/components/servers/ServerTable.tsx b/application/src/components/servers/ServerTable.tsx index c73172f..8664c30 100644 --- a/application/src/components/servers/ServerTable.tsx +++ b/application/src/components/servers/ServerTable.tsx @@ -8,11 +8,14 @@ import { Input } from "@/components/ui/input"; import { Badge } from "@/components/ui/badge"; import { Progress } from "@/components/ui/progress"; import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuSeparator, DropdownMenuTrigger } from "@/components/ui/dropdown-menu"; +import { AlertDialog, AlertDialogAction, AlertDialogCancel, AlertDialogContent, AlertDialogDescription, AlertDialogFooter, AlertDialogHeader, AlertDialogTitle } from "@/components/ui/alert-dialog"; import { RefreshCw, Search, Eye, Activity, MoreHorizontal, Pause, Play, Edit, Trash2 } from "lucide-react"; import { Server } from "@/types/server.types"; import { ServerStatusBadge } from "./ServerStatusBadge"; import { OSTypeIcon } from "./OSTypeIcon"; import { serverService } from "@/services/serverService"; +import { useToast } from "@/hooks/use-toast"; +import { pb } from "@/lib/pocketbase"; interface ServerTableProps { servers: Server[]; @@ -22,8 +25,12 @@ interface ServerTableProps { export const ServerTable = ({ servers, isLoading, onRefresh }: ServerTableProps) => { const [searchTerm, setSearchTerm] = useState(""); - const [pausedServers, setPausedServers] = useState>(new Set()); + const [deleteDialogOpen, setDeleteDialogOpen] = useState(false); + const [selectedServer, setSelectedServer] = useState(null); + const [isDeleting, setIsDeleting] = useState(false); + const [pausingServers, setPausingServers] = useState>(new Set()); const navigate = useNavigate(); + const { toast } = useToast(); const filteredServers = servers.filter(server => server.name.toLowerCase().includes(searchTerm.toLowerCase()) || @@ -39,29 +46,105 @@ export const ServerTable = ({ servers, isLoading, onRefresh }: ServerTableProps) navigate(`/container-monitoring/${serverId}`); }; - const handlePauseResume = (serverId: string) => { - const isPaused = pausedServers.has(serverId); - if (isPaused) { - setPausedServers(prev => { + const handlePauseResume = async (server: Server) => { + const serverId = server.id; + const isPaused = server.status === "paused"; + + if (pausingServers.has(serverId)) { + return; // Already processing this server + } + + try { + setPausingServers(prev => new Set(prev).add(serverId)); + + if (isPaused) { + // Resume monitoring + await pb.collection('servers').update(serverId, { + status: "up", + last_checked: new Date().toISOString() + }); + + toast({ + title: "Server resumed", + description: `Monitoring resumed for ${server.name}`, + }); + + console.log(`Resume server monitoring: ${serverId}`); + } else { + // Pause monitoring + await pb.collection('servers').update(serverId, { + status: "paused", + last_checked: new Date().toISOString() + }); + + toast({ + title: "Server paused", + description: `Monitoring paused for ${server.name}`, + }); + + console.log(`Pause server monitoring: ${serverId}`); + } + + // Refresh the server list to show updated status + onRefresh(); + + } catch (error) { + console.error('Error updating server status:', error); + toast({ + variant: "destructive", + title: "Error", + description: `Failed to ${isPaused ? 'resume' : 'pause'} server monitoring. Please try again.`, + }); + } finally { + setPausingServers(prev => { const newSet = new Set(prev); newSet.delete(serverId); return newSet; }); - // console.log('Resume server monitoring:', serverId); - } else { - setPausedServers(prev => new Set(prev).add(serverId)); - // console.log('Pause server monitoring:', serverId); } }; const handleEdit = (serverId: string) => { // TODO: Implement edit functionality - // console.log('Edit server:', serverId); + console.log('Edit server:', serverId); }; - const handleDelete = (serverId: string) => { - // TODO: Implement delete functionality - // console.log('Delete server:', serverId); + const handleDelete = (server: Server) => { + setSelectedServer(server); + setDeleteDialogOpen(true); + }; + + const confirmDelete = async () => { + if (!selectedServer || isDeleting) return; + + try { + setIsDeleting(true); + + // Delete the server from the database + await pb.collection('servers').delete(selectedServer.id); + + toast({ + title: "Server deleted", + description: `${selectedServer.name} has been deleted successfully.`, + }); + + // Refresh the server list + onRefresh(); + + // Close the dialog + setDeleteDialogOpen(false); + setSelectedServer(null); + + } catch (error) { + console.error('Error deleting server:', error); + toast({ + variant: "destructive", + title: "Error", + description: "Failed to delete server. Please try again.", + }); + } finally { + setIsDeleting(false); + } }; if (isLoading) { @@ -81,189 +164,226 @@ export const ServerTable = ({ servers, isLoading, onRefresh }: ServerTableProps) } return ( - - -
- Servers -
-
- - setSearchTerm(e.target.value)} - className="pl-8" - /> + <> + + +
+ Servers +
+
+ + setSearchTerm(e.target.value)} + className="pl-8" + /> +
+
-
-
- - - {filteredServers.length === 0 ? ( -
-

No servers found

-
- ) : ( -
- - - - Name - Status - OS - IP Address - CPU - Memory - Disk - Uptime - Last Checked - Actions - - - - {filteredServers.map((server) => { - const cpuUsage = server.cpu_usage || 0; - const memoryUsage = server.ram_total > 0 ? (server.ram_used / server.ram_total) * 100 : 0; - const diskUsage = server.disk_total > 0 ? (server.disk_used / server.disk_total) * 100 : 0; - const isPaused = pausedServers.has(server.id); + + + {filteredServers.length === 0 ? ( +
+

No servers found

+
+ ) : ( +
+
+ + + Name + Status + OS + IP Address + CPU + Memory + Disk + Uptime + Last Checked + Actions + + + + {filteredServers.map((server) => { + const cpuUsage = server.cpu_usage || 0; + const memoryUsage = server.ram_total > 0 ? (server.ram_used / server.ram_total) * 100 : 0; + const diskUsage = server.disk_total > 0 ? (server.disk_used / server.disk_total) * 100 : 0; + const isPaused = server.status === "paused"; + const isProcessing = pausingServers.has(server.id); - return ( - - -
-
{server.name}
- -
-
- - - - -
- - {server.os_type} -
-
- - {server.ip_address} - - -
-
- {cpuUsage.toFixed(1)}% - {server.cpu_cores} cores + return ( + + +
+
{server.name}
- 90 ? "bg-red-00" : - cpuUsage > 75 ? "bg-orange-500" : - cpuUsage > 60 ? "bg-yellow-500" : "bg-green-500" - } - /> -
- - -
-
- {memoryUsage.toFixed(1)}% - {serverService.formatBytes(server.ram_total)} + + + + + +
+ + {server.os_type}
- 90 ? "bg-red-500" : - memoryUsage > 75 ? "bg-yellow-500" : "bg-blue-500" - } - /> -
- - -
-
- {diskUsage.toFixed(1)}% - {serverService.formatBytes(server.disk_total)} + + + {server.ip_address} + + +
+
+ {cpuUsage.toFixed(1)}% + {server.cpu_cores} cores +
+ 90 ? "bg-red-500" : + cpuUsage > 75 ? "bg-orange-500" : + cpuUsage > 60 ? "bg-yellow-500" : "bg-green-500" + } + />
- 95 ? "bg-red-500" : - diskUsage > 85 ? "bg-yellow-500" : "bg-orange-500" - } - /> -
- - -
{server.uptime}
-
- - -
- {new Date(server.last_checked).toLocaleString()} -
-
- - - - - - - handleViewDetails(server.id)}> - - View Server Detail - - {server.docker === 'true' && ( - handleViewContainers(server.id)}> - - Container Monitoring + + +
+
+ {memoryUsage.toFixed(1)}% + {serverService.formatBytes(server.ram_total)} +
+ 90 ? "bg-red-500" : + memoryUsage > 75 ? "bg-yellow-500" : "bg-blue-500" + } + /> +
+
+ +
+
+ {diskUsage.toFixed(1)}% + {serverService.formatBytes(server.disk_total)} +
+ 95 ? "bg-red-500" : + diskUsage > 85 ? "bg-yellow-500" : "bg-orange-500" + } + /> +
+
+ +
{server.uptime}
+
+ + +
+ {new Date(server.last_checked).toLocaleString()} +
+
+ + + + + + + handleViewDetails(server.id)}> + + View Server Detail - )} - - handlePauseResume(server.id)}> - {isPaused ? ( - <> - - Resume Monitoring - - ) : ( - <> - - Pause Monitoring - + {server.docker === 'true' && ( + handleViewContainers(server.id)}> + + Container Monitoring + )} - - - handleEdit(server.id)}> - - Edit Server - - handleDelete(server.id)} - className="text-red-600 focus:text-red-600" - > - - Delete Server - - - - - - ); - })} - -
-
- )} -
- + + handlePauseResume(server)} + disabled={isProcessing} + > + {isPaused ? ( + <> + + Resume Monitoring + + ) : ( + <> + + Pause Monitoring + + )} + + + handleEdit(server.id)}> + + Edit Server + + handleDelete(server)} + className="text-red-600 focus:text-red-600" + > + + Delete Server + + + + + + ); + })} + + +
+ )} + + + + {/* Delete Confirmation Dialog */} + + + + Are you sure you want to delete this server? + + This action cannot be undone. This will permanently delete{' '} + + {selectedServer?.name} + {' '} + and all of its monitoring data. + + + + + Cancel + + + {isDeleting ? "Deleting..." : "Delete"} + + + + + ); }; \ No newline at end of file diff --git a/application/src/types/server.types.ts b/application/src/types/server.types.ts index de0c42d..c346d01 100644 --- a/application/src/types/server.types.ts +++ b/application/src/types/server.types.ts @@ -8,7 +8,7 @@ export interface Server { hostname: string; ip_address: string; os_type: string; - status: 'up' | 'down' | 'warning'; + status: 'up' | 'down' | 'warning' | 'paused'; uptime: string; ram_total: number; ram_used: number; From 6c227f496e498b660aa71d33bb87554ad4e70d8e Mon Sep 17 00:00:00 2001 From: Tola Leng Date: Fri, 18 Jul 2025 14:03:32 +0700 Subject: [PATCH 06/21] Remove individual toast notifications - During SSL certificate refresh and display a single summary notification after the "Refresh All" action. --- .../ssl-domain/SSLDomainContent.tsx | 37 ++++++++----------- 1 file changed, 15 insertions(+), 22 deletions(-) diff --git a/application/src/components/ssl-domain/SSLDomainContent.tsx b/application/src/components/ssl-domain/SSLDomainContent.tsx index 6769462..bb38980 100644 --- a/application/src/components/ssl-domain/SSLDomainContent.tsx +++ b/application/src/components/ssl-domain/SSLDomainContent.tsx @@ -30,12 +30,12 @@ export const SSLDomainContent = () => { queryKey: ['ssl-certificates'], queryFn: async () => { try { - // console.log("Fetching SSL certificates from SSLDomainContent..."); + console.log("Fetching SSL certificates from SSLDomainContent..."); const result = await fetchSSLCertificates(); - // console.log("Received SSL certificates:", result); + console.log("Received SSL certificates:", result); return result; } catch (error) { - // console.error("Error fetching certificates:", error); + console.error("Error fetching certificates:", error); toast.error(t('failedToLoadCertificates')); throw error; } @@ -53,7 +53,7 @@ export const SSLDomainContent = () => { toast.success(t('sslCertificateAdded')); }, onError: (error) => { - // console.error("Error adding SSL certificate:", error); + console.error("Error adding SSL certificate:", error); toast.error(error instanceof Error ? error.message : t('failedToAddCertificate')); } }); @@ -61,7 +61,7 @@ export const SSLDomainContent = () => { // Edit certificate mutation - Updated to ensure thresholds are properly updated const editMutation = useMutation({ mutationFn: async (certificate: SSLCertificate) => { - // console.log("Updating certificate with data:", certificate); + console.log("Updating certificate with data:", certificate); // Create the update data object const updateData = { @@ -70,12 +70,12 @@ export const SSLDomainContent = () => { notification_channel: certificate.notification_channel, }; - // console.log("Update data to be sent:", updateData); + console.log("Update data to be sent:", updateData); // Update certificate in the database using PocketBase directly const updated = await pb.collection('ssl_certificates').update(certificate.id, updateData); - // console.log("PocketBase update response:", updated); + console.log("PocketBase update response:", updated); // After updating the settings, refresh the certificate to ensure it's up to date // This will also check if notification needs to be sent based on updated thresholds @@ -90,7 +90,7 @@ export const SSLDomainContent = () => { toast.success(t('sslCertificateUpdated')); }, onError: (error) => { - // console.error("Error updating SSL certificate:", error); + console.error("Error updating SSL certificate:", error); toast.error(error instanceof Error ? error.message : t('failedToUpdateCertificate')); } }); @@ -103,33 +103,26 @@ export const SSLDomainContent = () => { toast.success(t('sslCertificateDeleted')); }, onError: (error) => { - // console.error("Error deleting SSL certificate:", error); + console.error("Error deleting SSL certificate:", error); toast.error(error instanceof Error ? error.message : t('failedToDeleteCertificate')); } }); - // Refresh certificate mutation - Updated to ensure notifications are sent + // Refresh certificate mutation - Updated to remove individual toast notifications const refreshMutation = useMutation({ mutationFn: checkAndUpdateCertificate, onSuccess: (data) => { queryClient.invalidateQueries({ queryKey: ['ssl-certificates'] }); setRefreshingId(null); - toast.success(t('sslCertificateRefreshed').replace('{domain}', data.domain)); + // Removed individual success toast notification }, onError: (error) => { - // console.error("Error refreshing SSL certificate:", error); - - let errorMessage = t('failedToCheckCertificate'); - - if (error instanceof Error) { - errorMessage = error.message; - } - - toast.error(errorMessage); + console.error("Error refreshing SSL certificate:", error); setRefreshingId(null); // Still refresh the data to show any partial information that was saved queryClient.invalidateQueries({ queryKey: ['ssl-certificates'] }); + // Removed individual error toast notification } }); @@ -149,7 +142,7 @@ export const SSLDomainContent = () => { } }, onError: (error) => { - // console.error("Error refreshing all certificates:", error); + console.error("Error refreshing all certificates:", error); toast.error(t('failedToCheckCertificate')); setIsRefreshingAll(false); @@ -174,7 +167,7 @@ export const SSLDomainContent = () => { }; const handleUpdateCertificate = (certificate: SSLCertificate) => { - // console.log("Handling certificate update with data:", certificate); + console.log("Handling certificate update with data:", certificate); editMutation.mutate(certificate); }; From e388912ceede8334a095ceba7c3a4702cd2e4a96 Mon Sep 17 00:00:00 2001 From: Tola Leng Date: Fri, 18 Jul 2025 21:51:25 +0700 Subject: [PATCH 07/21] Feat: added bash script for one click server agent montioring --- scripts/server-agent.sh | 460 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 460 insertions(+) create mode 100644 scripts/server-agent.sh diff --git a/scripts/server-agent.sh b/scripts/server-agent.sh new file mode 100644 index 0000000..3435b3e --- /dev/null +++ b/scripts/server-agent.sh @@ -0,0 +1,460 @@ + +#!/bin/bash + +# CheckCle Server Monitoring Agent - One-Click Installation Script +# This script provides fully automated installation using environment variables + +set -e + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +CONFIG_FILE="/etc/monitoring-agent/monitoring-agent.env" + +# GitHub release base URL +GITHUB_BASE_URL="https://github.com/operacle/checke-server-agent/releases/download/v1.0.0" + +# Colors for output +RED='\033[0;31m' +GREEN='\033[0;32m' +YELLOW='\033[1;33m' +BLUE='\033[0;34m' +NC='\033[0m' # No Color + +# Helper functions +log_info() { + echo -e "${BLUE}[INFO]${NC} $1" +} + +log_success() { + echo -e "${GREEN}[SUCCESS]${NC} $1" +} + +log_warning() { + echo -e "${YELLOW}[WARNING]${NC} $1" +} + +log_error() { + echo -e "${RED}[ERROR]${NC} $1" +} + +# Check if running as root +check_root() { + if [[ $EUID -ne 0 ]]; then + log_error "This script must be run as root (use sudo)" + exit 1 + fi +} + +# Detect system architecture and package format +detect_system() { + log_info "Detecting system architecture and package format..." + + # Detect architecture + ARCH=$(uname -m) + case $ARCH in + x86_64) + PACKAGE_ARCH="amd64" + ;; + aarch64|arm64) + PACKAGE_ARCH="arm64" + ;; + *) + log_error "Unsupported architecture: $ARCH" + log_info "Supported architectures: x86_64 (amd64), aarch64/arm64" + exit 1 + ;; + esac + + # Detect package format preference + if command -v dpkg >/dev/null 2>&1; then + PACKAGE_FORMAT="deb" + PACKAGE_MANAGER="dpkg" + elif command -v rpm >/dev/null 2>&1; then + PACKAGE_FORMAT="rpm" + if command -v yum >/dev/null 2>&1; then + PACKAGE_MANAGER="yum" + elif command -v dnf >/dev/null 2>&1; then + PACKAGE_MANAGER="dnf" + else + PACKAGE_MANAGER="rpm" + fi + else + log_error "No supported package manager found (dpkg or rpm required)" + log_info "This script supports Debian/Ubuntu (.deb) and RHEL/CentOS/Fedora (.rpm) systems" + exit 1 + fi + + # Construct package filename and URL + PACKAGE_FILENAME="monitoring-agent_1.0.0_${PACKAGE_ARCH}.${PACKAGE_FORMAT}" + PACKAGE_URL="${GITHUB_BASE_URL}/${PACKAGE_FILENAME}" + + log_success "System detection complete:" + log_info " Architecture: $ARCH -> $PACKAGE_ARCH" + log_info " Package format: $PACKAGE_FORMAT" + log_info " Package manager: $PACKAGE_MANAGER" + log_info " Package URL: $PACKAGE_URL" +} + +# Validate required environment variables - check both current and sudo environments +validate_environment() { + # Check if SERVER_TOKEN is available in current environment or passed as argument + if [[ -z "$SERVER_TOKEN" ]]; then + log_error "SERVER_TOKEN environment variable is required" + log_info "Usage examples:" + log_info " SERVER_TOKEN=your-token sudo -E bash $0" + log_info " sudo SERVER_TOKEN=your-token bash $0" + log_info " curl -L script-url | SERVER_TOKEN=your-token sudo bash" + log_info "" + log_info "Required: SERVER_TOKEN" + log_info "Optional: POCKETBASE_URL, SERVER_NAME, AGENT_ID, HEALTH_CHECK_PORT" + exit 1 + fi + + log_success "Environment validation passed" + log_info "SERVER_TOKEN: ${SERVER_TOKEN:0:8}..." + [[ -n "$POCKETBASE_URL" ]] && log_info "POCKETBASE_URL: $POCKETBASE_URL" + [[ -n "$SERVER_NAME" ]] && log_info "SERVER_NAME: $SERVER_NAME" + [[ -n "$AGENT_ID" ]] && log_info "AGENT_ID: $AGENT_ID" +} + +# Download package based on detected system +download_package() { + local temp_dir="/tmp/monitoring-agent-install" + mkdir -p "$temp_dir" + + log_info "Downloading monitoring agent package..." + log_info "URL: $PACKAGE_URL" + + if curl -L -f -o "$temp_dir/$PACKAGE_FILENAME" "$PACKAGE_URL"; then + DOWNLOADED_PACKAGE="$temp_dir/$PACKAGE_FILENAME" + log_success "Package downloaded successfully: $PACKAGE_FILENAME" + else + log_error "Failed to download package from: $PACKAGE_URL" + log_info "Please check:" + log_info " 1. Internet connectivity" + log_info " 2. Package availability for your architecture ($PACKAGE_ARCH)" + log_info " 3. GitHub repository access" + exit 1 + fi +} + +# Install package based on detected package manager +install_package() { + log_info "Installing monitoring agent package using $PACKAGE_MANAGER..." + + case $PACKAGE_MANAGER in + dpkg) + # Update package lists + apt-get update -qq + + # Install the package + if dpkg -i "$DOWNLOADED_PACKAGE" 2>/dev/null; then + log_success "DEB package installed successfully" + else + log_warning "Package installation had dependency issues, fixing..." + apt-get install -f -y + log_success "Dependencies resolved and package installed" + fi + ;; + + rpm) + # Install the package directly + if rpm -ivh "$DOWNLOADED_PACKAGE" 2>/dev/null; then + log_success "RPM package installed successfully" + else + log_error "RPM package installation failed" + log_info "Try installing manually: sudo rpm -ivh $DOWNLOADED_PACKAGE" + exit 1 + fi + ;; + + yum) + if yum localinstall -y "$DOWNLOADED_PACKAGE"; then + log_success "Package installed successfully via YUM" + else + log_error "YUM package installation failed" + exit 1 + fi + ;; + + dnf) + if dnf localinstall -y "$DOWNLOADED_PACKAGE"; then + log_success "Package installed successfully via DNF" + else + log_error "DNF package installation failed" + exit 1 + fi + ;; + + *) + log_error "Unsupported package manager: $PACKAGE_MANAGER" + exit 1 + ;; + esac +} + +# Auto-detect system information +detect_system_info() { + log_info "Auto-detecting system information..." + + # Detect hostname + DETECTED_HOSTNAME=$(hostname) + log_info "Detected hostname: $DETECTED_HOSTNAME" + + # Detect IP address + DETECTED_IP=$(ip route get 8.8.8.8 2>/dev/null | head -1 | awk '{print $7}' | head -1) + if [[ -z "$DETECTED_IP" ]]; then + DETECTED_IP=$(hostname -I | awk '{print $1}') + fi + log_info "Detected IP address: $DETECTED_IP" + + # Detect OS + if [[ -f /etc/os-release ]]; then + DETECTED_OS=$(grep "^ID=" /etc/os-release | cut -d'=' -f2 | tr -d '"') + else + DETECTED_OS="linux" + fi + log_info "Detected OS: $DETECTED_OS" +} + +# Configure agent using environment variables +configure_agent() { + log_info "Configuring agent with provided settings..." + + # Use environment variables or auto-detected defaults + SERVER_NAME="${SERVER_NAME:-$DETECTED_HOSTNAME}" + POCKETBASE_URL="${POCKETBASE_URL:-http://localhost:8090}" + IP_ADDRESS="${IP_ADDRESS:-$DETECTED_IP}" + HOSTNAME="${HOSTNAME:-$DETECTED_HOSTNAME}" + OS_TYPE="${OS_TYPE:-$DETECTED_OS}" + AGENT_ID="${AGENT_ID:-monitoring-agent-$(hostname -s)}" + HEALTH_CHECK_PORT="${HEALTH_CHECK_PORT:-8081}" + + log_info "Final configuration:" + log_info " Server Name: $SERVER_NAME" + log_info " Agent ID: $AGENT_ID" + log_info " PocketBase URL: $POCKETBASE_URL" + log_info " IP Address: $IP_ADDRESS" + log_info " OS Type: $OS_TYPE" + log_info " Health Check Port: $HEALTH_CHECK_PORT" + + write_config +} + +# Write configuration to file +write_config() { + log_info "Writing configuration to $CONFIG_FILE..." + + # Create directory if it doesn't exist + mkdir -p "$(dirname "$CONFIG_FILE")" + + cat > "$CONFIG_FILE" << EOF +# CheckCle Server Monitoring Agent Configuration +# Generated on $(date) + +# Basic Configuration +AGENT_ID=$AGENT_ID +CHECK_INTERVAL=30s +HEALTH_CHECK_PORT=$HEALTH_CHECK_PORT + +# PocketBase Configuration +POCKETBASE_ENABLED=true +POCKETBASE_URL=$POCKETBASE_URL + +# Server Configuration +SERVER_NAME=$SERVER_NAME +HOSTNAME=$HOSTNAME +IP_ADDRESS=$IP_ADDRESS +OS_TYPE=$OS_TYPE +SERVER_TOKEN=$SERVER_TOKEN + +# Remote Control +REMOTE_CONTROL_ENABLED=true +COMMAND_CHECK_INTERVAL=10s + +# Monitoring Settings +REPORT_INTERVAL=5m +MAX_RETRIES=3 +REQUEST_TIMEOUT=10s +EOF + + # Set proper permissions + chown root:monitoring-agent "$CONFIG_FILE" 2>/dev/null || chown root:root "$CONFIG_FILE" + chmod 640 "$CONFIG_FILE" + + log_success "Configuration written successfully" +} + +# Start and enable service +start_service() { + log_info "Starting monitoring agent service..." + + # Reload systemd + systemctl daemon-reload + + # Enable service + systemctl enable monitoring-agent + log_success "Service enabled for auto-start" + + # Start service + if systemctl start monitoring-agent; then + log_success "Service started successfully" + else + log_error "Failed to start service" + log_info "Check logs with: journalctl -u monitoring-agent -f" + return 1 + fi + + # Check service status + sleep 2 + if systemctl is-active --quiet monitoring-agent; then + log_success "Service is running" + else + log_warning "Service may have issues, checking status..." + systemctl status monitoring-agent --no-pager + return 1 + fi +} + +# Test installation +test_installation() { + log_info "Testing installation..." + + # Test health endpoint + local health_port=${HEALTH_CHECK_PORT:-8081} + log_info "Testing health endpoint at http://localhost:$health_port/health" + + # Wait a moment for service to fully start + sleep 3 + + if curl -s "http://localhost:$health_port/health" > /dev/null; then + log_success "Health endpoint is responding" + else + log_warning "Health endpoint not responding yet (service may still be starting)" + fi + + # Show recent logs + log_info "Recent service logs:" + journalctl -u monitoring-agent --no-pager -n 5 +} + +# Show post-installation information +show_post_install_info() { + echo + echo "=============================================" + echo " Installation Complete!" + echo "=============================================" + echo + log_success "CheckCle Monitoring Agent installed and configured successfully" + echo + echo "System Information:" + echo " Architecture: $ARCH ($PACKAGE_ARCH)" + echo " Package: $PACKAGE_FILENAME" + echo " Package Manager: $PACKAGE_MANAGER" + echo + echo "Configuration: $CONFIG_FILE" + echo "Service status: systemctl status monitoring-agent" + echo "Service logs: journalctl -u monitoring-agent -f" + echo "Health check: curl http://localhost:${HEALTH_CHECK_PORT:-8081}/health" + echo + echo "The monitoring agent is now running and will appear in your dashboard." + echo +} + +# Clean up temporary files +cleanup() { + if [[ -n "$DOWNLOADED_PACKAGE" && -f "$DOWNLOADED_PACKAGE" ]]; then + rm -f "$DOWNLOADED_PACKAGE" + rm -rf "$(dirname "$DOWNLOADED_PACKAGE")" + fi +} + +# Main installation function +main() { + echo "=============================================" + echo " CheckCle Server Monitoring Agent" + echo " One-Click Installation" + echo "=============================================" + echo + + check_root + validate_environment + detect_system + detect_system_info + + log_info "Starting automated installation..." + + # Set up cleanup trap + trap cleanup EXIT + + # Download and install package + download_package + install_package + + # Configure and start service + configure_agent + + if start_service; then + test_installation + show_post_install_info + else + log_error "Service failed to start properly" + log_info "Check configuration: sudo nano $CONFIG_FILE" + log_info "Restart service: sudo systemctl restart monitoring-agent" + exit 1 + fi +} + +# Handle script arguments +case "${1:-}" in + --help|-h) + echo "CheckCle Server Monitoring Agent - One-Click Installer" + echo + echo "Usage: SERVER_TOKEN=your-token [OPTIONS] sudo bash $0" + echo " or: sudo SERVER_TOKEN=your-token [OPTIONS] bash $0" + echo " or: curl -L script-url | SERVER_TOKEN=your-token [OPTIONS] sudo bash" + echo + echo "System Requirements:" + echo " - Linux with systemd" + echo " - Supported architectures: x86_64 (amd64), aarch64/arm64" + echo " - Supported distributions: Debian/Ubuntu (.deb), RHEL/CentOS/Fedora (.rpm)" + echo + echo "Required Environment Variables:" + echo " SERVER_TOKEN Server authentication token" + echo + echo "Optional Environment Variables:" + echo " POCKETBASE_URL PocketBase URL (default: http://localhost:8090)" + echo " SERVER_NAME Server name (default: hostname)" + echo " AGENT_ID Agent identifier (default: monitoring-agent-hostname)" + echo " HEALTH_CHECK_PORT Health check port (default: 8081)" + echo + echo "Examples:" + echo " SERVER_TOKEN=abc123 sudo -E bash $0" + echo " sudo SERVER_TOKEN=abc123 POCKETBASE_URL=https://pb.example.com bash $0" + echo + echo "Options:" + echo " --help, -h Show this help message" + echo " --uninstall Uninstall the monitoring agent" + echo + exit 0 + ;; + --uninstall) + check_root + log_info "Uninstalling monitoring agent..." + systemctl stop monitoring-agent 2>/dev/null || true + systemctl disable monitoring-agent 2>/dev/null || true + + # Remove based on detected package manager + if command -v dpkg >/dev/null 2>&1; then + dpkg -r monitoring-agent 2>/dev/null || true + elif command -v rpm >/dev/null 2>&1; then + rpm -e monitoring-agent 2>/dev/null || true + fi + + rm -rf /etc/monitoring-agent 2>/dev/null || true + log_success "Monitoring agent uninstalled" + exit 0 + ;; + *) + main "$@" + ;; +esac \ No newline at end of file From c1c94115e0f4846699cb11083eb46603e04ac8e1 Mon Sep 17 00:00:00 2001 From: Tola Leng Date: Fri, 18 Jul 2025 21:52:16 +0700 Subject: [PATCH 08/21] Feat: Added Operating System Logo for Server Agent --- application/public/upload/os/centos.png | Bin 0 -> 42745 bytes application/public/upload/os/debian.png | Bin 0 -> 39614 bytes application/public/upload/os/linux.png | Bin 0 -> 59587 bytes application/public/upload/os/rhel.png | Bin 0 -> 13576 bytes application/public/upload/os/ubuntu.png | Bin 0 -> 48541 bytes application/public/upload/os/windows.png | Bin 0 -> 6140 bytes 6 files changed, 0 insertions(+), 0 deletions(-) create mode 100644 application/public/upload/os/centos.png create mode 100644 application/public/upload/os/debian.png create mode 100644 application/public/upload/os/linux.png create mode 100644 application/public/upload/os/rhel.png create mode 100644 application/public/upload/os/ubuntu.png create mode 100644 application/public/upload/os/windows.png diff --git a/application/public/upload/os/centos.png b/application/public/upload/os/centos.png new file mode 100644 index 0000000000000000000000000000000000000000..a946ebc6eafb2d8eabde67eef1e8de7001b984bd GIT binary patch literal 42745 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 UX9zt6M;O`6jjtJ18hAeXAEuzRY5)KL literal 0 HcmV?d00001 diff --git a/application/public/upload/os/debian.png b/application/public/upload/os/debian.png new file mode 100644 index 0000000000000000000000000000000000000000..9b025dc46c8aa631ec4c9b86e5b7ecadb8770d32 GIT binary patch literal 39614 zcmbrkbyQu=wl7F<2tk4e4I12?jk^X52^!cq1l_n>aCdhncyJHy?oNL6c52#vIvuf&Z&bd~|MMOH;yabq)c88>@lH8&-7u$v{A&xlr72vyL9A0Y6} z7-B%~^3BTHf!_s0`wv}y;QznVEVRHPdm|HmRSBtoF9Aj%S~Cd5mY;>i+1Z)dnSqnBcAx|(ppySXL&DerY;SH0F}JZM|4Y-r(8du0 zqNPP8|2Jt-2*lpp@ITb89heRNnQ{EPg&AO)#lXmcg^ii@@3qLu1^>04U)0Fq@9`Yz^!kjMZIjjX|`k#tt@)_F&_GDE!^@ZzAIM#s&~$BO!KHb}lAX z9ws(kL6-mJ(cd%vYlEZVWkB!~fgx7?R zlZ(~xKZO3Z<$scqG6bk_v2(F=bMdipvhnb7@v{DB!#@xI=X!M;N3hvnR)pC9v+Td- z{#h%?@>jZ6=Km7PKZAcuu7KFj|muoS&XfWzZqLY z{wj&>-%2t9^Fz!bR>uFOB2g>I|5OoRd;C@g)}|m@7bYWP69Y#p2(6H`y@8QAK#N=! z4A_gJfi1a`fs?tZ!9V6BYHLfbU}I`S&O{D~;s3$zzfTZk`EQ5+A24hC-%S3mQU2q3 zf6Wni0C39jZ%%;`zo;X`%*I|w-Nwp*T*27dRPgVb|9ckjgMaS&za??NQU4?I|8WTy z1Ka=R6TmY5e{9*tUfssVN=V$m+R1?YgQ~H$k+Hq8Jsr7`4HzULDh)9+uxI6yH6?d+ zFt!J2h)Qc(eN&W?ay2D4v^TIe0*U>dv9vTX6mu42`Cp0tzcTVK-he;>gbbGd1QuZM zpU?!{0YqyL1Rj$*Pi`2Pglt&}QFWK}!&TR0U5%8Jvy+x>1?52#cyMyqHm4`g+D8CZ=#9&VBLIi9?f$G4 z;{hhVzhZy@Llc6k|F2=;h~OB45&M@t9NJv5Ly}iY>VX8(8yQ>hmBr)*&eg~tii6!c zOUPLs-%%*RYeb22A;QAI3YKnZ^rB>khQfw^yf5Ofbiw_2w#6mUhS8R9*iqyhBOHV~ z_()9%e)hv{W)t7XI9_GWI4H)_`Egr}mm`(%&X-=29rX)V$=e*MXswBN@%#brveA{_ zC|Rnof5<_7FNTV&B1W#BfU_kYjrm@JdIx@Q)*Wf>^_njdewjtMDrO99?o%<253Ct! zKE}T5W*Ppz)V@Z(+5}uEBvh(RZX(Dc3mH^=Ne*F{%2RUF8M+z$)6AuGY_pOa^9yw~ z`Bi>-Hs5kOHS^qS6_U3z{&SS7CwYx}{@H6Nc?f;k{D=m1^bsOVAM<1)kiWc2y1cPR z#;GGpPzqGS;{UjWx#Z)N{bpap6G;YXBGMwr*&rymeteka0PmAo&AK1kuONWwppqu1 z63Rq=3K}1!kC4_}Yzd=u@;p%@k$1j`*(z<^rE&Z`HEYgTX0%mg2~XJ%SPdAE4$bw0 zua(_;qSqA7^VoIVS1boVb9r`WiDYT;%|PI#oB3 z^(1%8FHZCCt0m`!H9%v1XC~puIpHJ2y;=M#f_{mC1xIk=K;wqa%J~%SwkW}N_||3X zn(gdL2wK$_kIew#*aD3us3v?PI%>`Gx4^!24IU9O5@bb$pvi!oW;2i_aq~6hz~>J> zPt`-#d)tazW-$xj$Ro2dhm{}V#w-+K%O^&IFQ{ys$8h`Mne_su15saVByY-TuD>ac z6r&2lby-vmmVio_AuYA_xf5HHR3Uy=WBgsuye0kV)I_3MjEAz@Q->)0^*qU+^vqXo z^xal+9K-sLLtjsVPz%{)FedK0=h4+Q{oQ3`L8S9D4V?^*4qNo*NkMHaFhPKj*gCMw}vLxyqa<3iMo9d2#mRFJgi_oF5 zd_qz3bH9ChvCKvWxw#`dTnLNg6E*Th7lN!QC$6H(w_kr)W(SpXqmk4k`LZeOq6csJ zba`kp#*;gSP*>xB_BV-G_^C_5)vR%Jy~p3!D(DOemZ#1bM-vV$Ej@NRIR3PWmtKxH zi>`id_dXcEiX9r<*Gk0zQFDsN^6uI}f2`kv1Fhel0p?5a0sJvij2DkrGiEx!?q@L@uDAh7sk8ga zlkW}F8p8NE%gR(5gqh=fp=lZ|WmHG}w^o_@Ifor14lQk-8#gOH77-Cofc>td%&Xi*z!A33Wx0Qk^f(wILwx2gwWl?g6Y%VS}bRD+uUyASK5!g;UcA-*3myvG4k#$rO(dz0<-px z;H?m2+cHOdnxZUMZ@)efqlE6gKW3Z&ixe7Ea)9+_Ue&qm+$cSfybHSFxzn~{a^)aD zl(5vR#bcH;Ct!wcBj|@BU-(on;5MJ5x^e{dqOP`DFzMjj#*b)h zqGGxFldI7ku~}Erg3*<+7LF2rqCY8BM?dUPc+Dh1KB#>;gttOp+}7EsDcJ}XaUtt2 z*r0jEcj#y|B0@)B=bF%;aeh^t+-8a;T%&!q8@&*+@Tj5PlrA>G!S57^QG7~DYYiLS zrzhNpmiReGE#U#-If^9!gXya=8}wsg>mtfxYpx@sa*O)qtM||F%*j4&CgFr~r3Wn< zO}dZ`w!n1g8^_MK^j)Pth zekjAppAKrMdSUqIoHSvJF~&C&jd-#dB?rPx-DeBoj^LrN`#mgYlFT&UReGFM)CQW2 zp?Cmne{C}+9jI568VK!c1Z%_2P-P4gT%Zb;X7AsUNasxP0odqZI(|+vl=51 zsaaZ5@D7DAWB;}fpj-IUogBI`>rQ)q5xjBFI`E7EptIgFKtw#_4}= zUYuEGOW17(TaxcU2-X8(WGLM!N3Btf{jeFTe$ z&ju%Ik|8u^hD!tMh?Wm&C_9RzYZO~!xMni^acm~VW@M*0RdV3ApTeyzLaoE^)aR@YPujY?RY_f*3x}$eWq2fy?}H=thW{85==4SB}FD%w%l{ znmP&gH}1FcS<_J4`UyDfT13V%P9`ijQwze?a>*8JXDbd`1w*SbXj%DVfrTCDQxpU;{0 z(5*miwIls%hXBE1I9TWceVdk26^yZu5ExSpeUf*01 zi>SIKyl(hK)+=YXzY?b$`xIa4=E&oA)`E{`#g=Msyj^L`82rSG<%yKLUdztYM`eWA zT_pKv3Y(CZ<}c4Ki^R?{S}`#^G0K<}Sh6J;&0wqZ#wc#g2yim_Tt@0Do4 z#@69 zVgI3u=yshs(8Eu8kB1d`bVRx?kRej1eoQ+1M6jXZe*z5U)b8Ye;O(7-Y-jkb+>BL^N zH}-_A_%!PLT~|o#vM+swsNTUSw6KIzS;Mt~6pecivmC9b%F?afcHqrxSASH(*7ry# zNvY9JtM@Ff&OH{UxoBSv2gCswE~MeUTk`giF4Ue`nv8w|@K8)HX+oOBjy$R|7w(T4 zBtL-IA|T$}tqPIH{&Zd<4xYZ`4yh(ZjSiUmpu(*qr-vu58Zj~$B31dqP}>6O3j6N; zzb2{B`|*iyV9$4boMFYHc3t0}6$+D3!RL9V5M743c2os8Lb9ghZ$%MAV}#R|fN6Ek z7BTNPsnf%|XQ zl)ELN3D7?=QaT02Jag^v=- zn$Uy#g!xxsAC6I@bOLq%Ss(`&hVuB6NOf3AmS3TuN=z7@XiEOT6I1__7H8kzOMDl3 z$EzkGJWb3fzz@*qG--Ol9Ziw9$Lx4L%d%u&(MP~hhr?8QaP$@_%PLg!n-ae=U#*xo z0#SF^6unO9K*P5@TyVIol({(mldsE@mD=U>H`zTp%}@I*U|l!a{rJ}smqGOirG)IT z!9Mh@D-_8TG8^Q!!48=Ge&64zPWNL)5=q*b5tY_K zrsQMHUW`t2idWANY$nLTIO3Uw_n!O4?n`u3Bu}I34+hf9*biIIsfzPv) z9Zwf2G^bBNm>y(ImgCLIxeU@cTXjWEbDnBC7v;Bc^D)!Z_FIo&6BqgUW-}}1)cwd6 z`gvq1A3NaY-d3Uf&WH~B2 z%^57@qA;-U+)snNb0{^;Ri(pwogvr_&Uuo{2-9fIN2MM#s$Ns7Xf5kj1?UUbmPqEZ z`W{q0`Zz1qvZidm)L086FJrI=NO|H?Un}KXoC)eMJ4~F!l%k1h>>5A6rcPy<>Nw`x z8WVa~;Q_aqF9j%*Dh(E4eN}qJ&1dWSNn-g%Wo1#SwF7%y;%^o5_xk#YO^E(0fXReqh4h|YTPuQ{~0wOqdHPWI)Ilz4gX1E*NB zuN1lvfOUxa6vds-oJp}&A?bpDI%KK_)!UAs+h>(Tx3q8Fe`pQaG#`g6cmo}aCs-oSp(3WS$r(=^1x~Lov zv5l^#UhTY&XQaPX#8+_>=x2==XQdS+pXU6F|u~_XomjLr4>b z3at(PTNgiYxR1z}W|d|KYX>uG_p`x$Bp{sBh8XWH!uxq)X@gNG5H)B=b1cquN|n+B z%SNg1t*1SFyAxM$EA%+^CBRX zJH1r8=dsG0sLALqBq^vgz0Q8>oxfd8iBV9IypnqVNOpnRVD--xAUO@c*HW(n+nq}t zUN!4Ov!CSTNAFP&SUkZcm zet$w$J`sCBmF?%il?`*blZqdE-Uh&1`j8`yvc8ksAtG!_Ao0WVG(`maVm z#Hol|mcA$GOOsCmrM-e_58nOv%bH$_W?0^^DH@nQVs_aeqg~~KT}4_|M=k7>CN<=~ z*`!al96^!~VG;t%AFLK7pF@}~AMa()%NjNW+~Xom=)c$Jo`<*wYIh_-VyVIh#B7w9 zGD5cFI<m|qx0X;$odwLP}GQ?e!S@WFar`lWeqzv&MK znVe!gSdjtyGEt0y`#is*;5vY(1P-qv5xs?J{x8mVhn~aCjgx4ktKTGTwyKmB_4H7gUzykVA=(0Tf^Bif0;B~FJ14={q#u|f!6WivDx}v!F5{2;v}r&- z6fdQoV^i@ZlkRtz4fdwP*6(28o~Yzv$Corxx_5}BN-5&J_2`33$fMC=pfg|6+;0z1 zojVwk^`@Z49}GY#=5f_8^qlg&>4xdB#k{9HNYD`V$s|!;jUZz?z#G4xq7zYIl%bU0 zoI7G(M`kwVLcXkOQKI2@$(H+l$hFhNS%&-m?VRpyWM;>GKb5TvO&|+S7tL}64oRuK zKMQ=8PUjF-_5MgO-Q`tD!G*-FCKhw&&`PV{h@Uy9*S?s=8ib;&*dJem%~(t{3~RE& zY3b!e8G1YJjM6J(IkS9E;JbXY)~HJEu=>ntADlV+_K zf=cXubXDF=0YP34RZitBmOj_?k5X}cThV`F#dE&4-}v6L@Bk%SXneFSD4+XWfdcF5 zqO_TqKaEyupu5kcBayD8`T9A7;+{-|8>&#)Dno!7{-elgkdD)1!(*zJhMHs$t-A3? z*pbaM&pqi%Gz5o(*DyW{^*HY-aK%CT;wk?EE!6<@p*ZJtMqe0KMeoqcV^b$qcxmB5 zJRhf?!~3S`J#n*!IcbWdVbzyJjzQsO6sGXtoVgep5#ISSJ(BO9`sBHkP!WIQg^I{I zphn>}DcP2%7y%HA4jZ7M zl;Uz_SZdHJ2IMo|eG%{Ij_$g>L?vz&M}5~0`|mNc+%ro2X_wK!0PNVszL|JB+JNnY zK~hhu_Sz^jXMrWn4@arak18_sfx;91ctJaXs{`*9q-=|Yu8waMjF==!tE0GVZ!jX>uAoQ;HU-?yklc>AJp$h%!)Krp%e1t=nSa=A&*ylPZ{TdQoU8>Tz%LkBeAEE> zFR`ct8BIQYW$O~#T=+|QX1kTxwvK!?VJ6X;d{{>e6E_1=I^4a*!{gKf-tP`TlVjY= z{cVP_N-=|-(`1l~dc70v@1tW@ze*?O&nw~`dZRX7q86+PDYZW8D;^aTvF)BDsXDCL z)eoh)YEY5HX7c%t+y#knSr(E-4dmF5Z(C-ArUL9Xo4 zL$>ZlqD-A(CW2Nb_VV$*I85?V3#&wtKa7x{Iq7}+gimnxlO&Mgu^qJSO}bnm810f6 zFtnxoq7<9>C~mR|nd0c%wkK4Llg3seRlIJ@lOf}K6=4{ve8M~$&Vws1H_k@vJF)F= zc64QTQMHigyr9zy4o zd9}i$$$9s&`n&r`AgkvBOE$N-lNDICNCUPtaF=JB z3W0^L@M>s@C0Uyj4*@cG3cqF-a(ztp8?90=RpJI1lM(F{a*PFfoL;y4DPYdVUrgFm zL}{bS2DviPw@>iE4@ctCW{L5lw#*7q#8-^qvtDPd%U&l_$}_kXs5LIpQyIstk4(gM zVkudXx0oSTw5I~@nB$4N`i3$@Jo$$nw}(ioR(oke%Z7ra{%A#|cILFlOHxK-!|Pai zAQ|=X3jK1@48O5hs%$bxchUUp?p z(IXpweKqJBcjB2sD_m4wKI<(n6`I{5j%r4ihN+z+pP$NXRaR;kfM|*7Y7CB+%i?O->vxYGvu~uox9{88=ArBY0Qe4>yd?U7 zhjcvPPP7vzEt%b2O9U5bJb10~Eoa!6w;y{cM& zY$jXgj7pr$c&3EOF;;$Iq@>i;c{9sMc%}Pt3Xs8>Jv~MNAi)P!-^JuLNW^=1L1?6a zG6t0yjt3L5%kQCjAh?j=K6)mL%)C}e0f=vN@YcodxaFth%z-K#=cPX(hN&)|H@eo%m%Q8oHWWUqS`{{r|-T{Y$Q zuVnhWS#2}jGfFqFwz*G9WoUXDEY|cZPSR7N=+ctyWpj;TH94<^_K3+abNHcxCbqH! z6SlKNgf})xQQ_K$)%`cpzsbyVE_RXjo447vo^+9tY8Z4;nwyE+%P`g_{iPDBH#97U z2uktB^X?sAm+(8hDjCd^Z@9@IN^Duf&0LfG^HVtgA+q1fu}N-Yd(ZEK^PyE+2?+Y3 z@>6e{yOwGaZ-NU7MHbvLo@1lKPmSS zYL_gl?gtKcIMLD)e)3%E@S^SA)6&12Ec(E_`l%A47ji?IeG&s4@kp-Q08+SoT${Rm zeEkq3PoVyo&{8%WBjCu~o)@&m(K6YpTav_vA2wpQPicA|I9_2SKIMx3MH2VEe;RBl z@n}q8pF$3F=-B*w_yDkGKh|e1x}NI&bs%>{UV5A9bw4jCzU=5caowvy`9Nq=d3$Yr z9wn?eUn;%d<_)rQ*MvHGV3IFF%V3BUh#zyTnz50XYe~OB)FPu zR_I=?JWwG$Uyv?!oI9K=3&P>DbF>kg;i!3-@PKa0a~fS~h4ec$3Q&kF8LY!~Ro^eh znTWIHzLc1LQ)m-J`N$-9;Lm>2^~Y0w(PmBcIydYZ+}nBFRO2X%&~l-dJ^B9jb^Dm>v1~`de>{g?|m-NY~7qyk$^Gv^1Jra zZ@3ZywS~Gk;Bkmk10@uvL^cA6$yNIpfF01aOdY{=$1Y*rXBB1H2%K3Fll1NCKb{U> zgwyoqDj)m5Ff*SjQh3Pq&~(4ysi+T4eXX2(mWLmw%h^WJFaDy_+7UOFMw-3-y=A}o z3hSQKTJ#|d0CiDbSI$61fy29P(T$e-{q?Gd=Qqozx@`g2?~vbF#6~&}P{mPjydc+M z#^_(5Nl;$W1)bv-Ph{UlV(dk_KQaV58Ve|WnhSi6nK@LUut(^&r@JWMoweioV!jPI&7oMZB)?(jK{!^^$SdfsRD(o)!Q$ zKvJPOLPC}_9GwXwT#uveaQZXBIBSWXLe3X}rc(C|*p)i~&q|B!OFTx*DDaWEx29O{ z{yI+NurW{!goHl_$q$YQ9n6ouFiI(M% zYsW1C_p^*Pi62OM3m>SV6O;hp4pc76)s zjT^oMn|B=h39pd>D#%j6sJ{g?`QRolx?Yfr)4NzNLaEUHR^Hnkk9zD542MH2?rvlj zIX7!RFG0;ATaiy@?ZJ4VW^7`w*>LivluYfc5cFt(jucR-6Fx-^%PW6W?__delDK@n zm(>!!fIC+Pr9p@ts$lnd3`1V_{2s@Ltk*AV1oJACn954tKK(?drOD_;!^E1ZrDcP@ zJdrF0PBRpaaOr3NNenW3xn9M|Tno9r%)1ja*SAx{0*qJ4f&%=hwXN>{E5hM;g(m{P zrjQJPKNL%|iNHaatD*N%<4nX2a-UT`k*yA&Zy!ke@x3Uf>&T{fZLn7AO?>{9Z{r~Nu&%l)u%&SptEOYH}AH@uMj4vtJ;v)4>(zb z{GzC!g*b034lTyzkXg#;8A{QUwVAb_`VpxDu}T+vLlqPA)q+Q`FW3@N)PK3!BX~JE z$#EJi!L7E`YORc$Q-1N8!xg4K3fBlf#JgwB_C#p*ZhD6TTdI_)d)=^3okqb0hPC_i z>ORYZ8_K(@ECG@>8%brj3{SMFX|@_eH@*s88`|k9S;Gj8YoPi^a0ly-=wRW8x%{|C zjd#75pqcJW3{U^#Wp5ixD-3DWHjHur@&A;c%bi-eQDb-KeVdiEMl*caLn$qy5W3p??J)ZCd*-PhxMf z%p%S`@bb9(qwoQ2Q*e!!dy4puMUBGa!NVY@q9Xs$up0H+ykx)giOI`LbKuroOe+SKq(KLnFd#ly&`q+n>M6~*YRr9+aXjIT zAEn5Pz^C0`Qb)0BKwInslz?TN0jP+UI-XxIJ>Pt*k6lKd@!TU@xY)G$xX@Z#PAed% z@m(b;Oa8b#aL2R3vS;$bC$82YU6vmueh}B~H9kVXxt-ndRs5MD&+bH%%53q0fSh*^< zvh-z+OhgJ*R7})f>9=oGPrsv1zgoyHT^Iu-%if{ajxJgWsGJCzICkSQwF?=#TnidK z$&Pp)LyxmSaWo*3l~Uru(!k{M3}5GVU8dqzVdmZ`OX zHc0%`#8VqKAJT^~S%lqF<6ULrL(_G+qncjhFT0bO=;viwg+QhOZ8Cl1iP13eG`&9k z6xJVd<48j?9{=iyB{B2nI&)-&uCZBXDn?HgfGeQlZ(q0ZD`w10eg_rniHewAcMHs# zO`36lhOE_|ob>lvLc5Q*5C91-MqXPYZQUCGtMP_1^!4;_H0e(J6U14w|Bn4 z3ETcfCY1JwAG6dEIasMkRG+?Y4~z)sGFsR6|Hyzm{}yew_Dp_UGCj)xIU#`cNCLa( zKy}_v`0(3ODo^*|A^W47gATsbrGla<*Xbv~zF;WA%BJ1NXvSA%iG`}glE}T_wsutz zIgUmx*;e-JnePbX!Br^Hb9z5pK~f~x?<9)pXP zq!%Vw!Q6PwreZ#EFX(<^Fuo+t!6%Q-zkhecGA@$~s%&XMoBuLsx_A*|Q*)y4EoQLo zRQGi|#AL(FI|hztReAnfRQ#SsmBU|z1&Rq-S~hCJ9&gqVI1JYu8wGuK#7PGqsJN#U z$0^V}mz)i$eM=}XZ>TVyQMP#1{s*daWeYX3Y{^d%1w#L|w;ir$l$FHrDO>edD^LWP zhuRd|$ZIOwJ)kc?PI|D7Zk|*5<4b7LEo1}U%EZ0*H7(~y$06eG5OHtg4U9|sjpYu* z`2_vr4(i?B2vBkhM&qy}q4-gfo zbY!#t-o26|SV0I6n<6<~q9xcPET@rs^ssnS$*_14a`N^kDZ$cq8Wt0fB`#IA_&2r* zKtFiDC(uy*m9Ur48%Og!UbAh+UD)6Q2ES2Yd*n_)3@KT5QAk9Frl6*-Zq6En7A*WJGx3vvMTwAe_NhFM z^*7lM2pPQjk|VHJCU{IG8A)0SPVI`z1ce;b;&o#FKrK>HSU+Goe4gNeKAt@RUvW@L zBn79Q4oOlO7x*?ovKMbzYNidk6<1JZPVFUC>){dNbgmdfXMPk(|Q ztChfdpXNs~rW_wm;v(%>_hgibl&_llsWN)v!&M=&@+Nm1S&W3aVmFy=ynKW)A>wv| z94=I7fCR;^QeGoIeQs}w8cQQH8kg>nwEL4ODxAqy`ModmNxw0rJ5g6wnoowcKJvgJ zs!L;ebb>t^m$tuDD(w5#SAV_zn`&4MliYOBLO;9zIM+-+iK>$dkjJ&YP3G zt0l6xM+?Hm{p`E8e=GUrByX)xX?Zd_KBlHu6-0VHtyPovHGU}7pRSG1;D zyoKM5=O3d6w%V<3X1atXvhkaXv*7P}karqhvRyS+_O%YyoM@ma{x@dED{G^^HtUK& zO^kVD(Xe<}c-TtO!@ya)86qQ7wdqUldSLLx4)^O*Zc zY&KCo&WzF4xJar*G_sxCMXOMQrK2$MS>QweX+|6I?OGdT#TLJp`t}blIW1Q0EUE`nPYEydc9`nhLxtyIiUL4us%hp|%j)g9YJdFD4wQyA?;*Zbrbi z^cyoghXr;D#?E1}jm<9pfo$C78b7=a6Tw{k;05%O1SweQC*w-WrDajzb*TaMc06+I+M(yw zR^@n-;_tNQbz;75GYET6>L875phJ$v#l9%bsT|1iMeJABD(v^p@`$hMNri%?a;Aij zcMg|cdtEw{uSrIJqVAy6gb1I%n@nTwIWdqkawZR1&Jro8X*B)JL3Rt}Q1?Dn6|;2c zZ41xFSnqL*RX!0ZWWN{CU=Aqm=hQr@x6Ty1*QAv@!=fhQe?9#JPfSlfr0DABTTH*D z7f0N=87YtHR5|>SZ%GSJl~WIu9eqMmC1iqH#&MyC?c9jocYZ~q?U1h?j+bzj} zv8wF)4gRtm1kHA2^8xKq^&5QwerT~e{LDMP~=`x}Z z*7otuX@@0lgD#}(AGm!fp_YDR5fK1klr zK0#iNyyF%<6_6ah9exZQY3JU;_;y_`yMAfn5jU9MUBjf!;&{<%MDyCWkq zfMa3aC;U0Kz-%1uUdg&wG7#s0zjAp_js^1Ey0X8bpPTTuV4$vk@@h6KZplL=inNZc z|L7h0*@)6%o^EE{UKZagOE(UJy#+aH+?1mrS;6WzXuilun4%rDS(UEV)nd$RLvJ#9 z4qZj?#Zal3P+hC%0pB!vr@rolV&V`|`a~D#*_yUyvm(EB_M_kqO_2d^5!5d$^m8>1 z{}a|Z_Geavvb)RTWlY=e3(VVe_g(|f`nkL4D>FA`Z_a|UDw+S6R;j6Fb46p?H*eBnDKPDh+ZBC zo3>=a3c=j!9h5)HYINDP(PLfgiBdrQD)oN`O)d4X3p+m>q__wj9lMI~us#v2>^rt- znWIdTyFF=@zn?omrB)+EJaO|~vso)DTKrk9EXTf#5$EODK|31wX)NbH@hV`BC3{CZ zEBiQ3GppL8dKsVcY`_veNul5B!=@0ykI(W5hHHO3CZ;K&nMe?0RyA_R)eBUxq9ujL znc+zDTO*qI*x%dB`_BjCL#6ZyiAcXhZkfVuVJP2q7e{=Z(=eEkV$IE2;r~XbEUsR4 z=!>qZjqY+G;LSg&HtZ}c`*jfQ;Q3SzHmuP1?7G7}Wgyz48aKfaS(mlrc6?S&oE4!< zY+BC!es1S(jFx&O4dMIvAE%#h-}()-ByDGLR8!5SsHS1q9#o$69EG^9)$hJBNFkUt z7`dPBYNiiR!R-l#cUTD+7pN##hQ31&yqhsP7PopAKFklRqHxYwe*uNbu*jB|^w`hJ@(T@8-+ z8)!jVIR*rI`yfL*R&8A)t-R@U9W~q;yJi<~IhNNnVn^D+AYG{~KQ6qg=w!3J{M>RF z7PsaWy#K)cRndCD`0LEjBlSc+qgMw_NSxys*v>QlZ=g? z!+Z!_CZcuE&KNlc8#$3u*8U+#A(6Is?d=~FLM{3cG%^MrVEE*cX*XGnzMZzF;H3Ys z{4A8fFi3jX3Eih|w5Uhl-;rY_95gikJpTSH|GJ{+l`g>w@^{iqO-#zSRCQjj0MV>* zAIjaoT%aI&U#B-5Ss<|eDMhZx6yNW@KxWR8{5lEoP$L@mtZyO^`^qhW!Zkt*@Wh@- z(Q*S1{ShSdZAtnd5M+dr!8mG~gk{`fR8Q6?v?Td;5Hh~ZLo}Y=G7R1C6>^E~+=BL; ztmiwoCQ%!Z^OC8psyV&#WO=V%5ZL}PYV;+QQ1dS*ZuP0<5YqhFqcy=wQ&5Wf{nzY_)Wdff(5jC~#n=_2V zx-A@JMqoO$Stv7r4BiFIjZs$FrWPG71qnc(c0GCf9d$1z4QB#Ebn3%PZ!(q5Ms++m zXkFU#RWV<+n=1n5hSj_wNO7Vi1%$JnV^6{dmm9O23aa!-p_+%~n2@?XK2q2VT8F;i zih(18{dZJVW~u1=#_|epgD+@43vh2oGuCsB!!~0cuw(M4C?Hf9a=z8#>Yr{s8gha@ zbW!GW9}y*`WlN=y16^u*utY2#UY3cjT>jTFhCU=@S#uke%#_VCXWR z58LpzPdlXV3$0|vwb07(D}cw}+}sBvcE-}=j0k%S@5ut#QTJ7C168{1%TsS)XG=pjSCirx~Owg3L3 ze&DQX*!0>TsHx=|W|b21fp6}7rXb3`yZAh}%%BBCq>?~NBYNZf%dJU+%aXNiuT|>H zF{RNpF<^ig+jUn5<~OPwZ~Pcc)n4xYgy=NG*D|wTs?>5smoP4_0%&!?Uy^hk3xcpa z*9)eI7St4ngC4)&i<5icQSA34-(hXSeDcKgO{fjgX-%H!4XaAE#1g_@4@a5t*jM$F zH<`F;3kq57K;e?Q{%V9#w+*@8X@5<+>xTJ?3^4By;hLRrZ)-B45O1f~&x_cOA}pGI zeB>r4=qoGDtQTduHJGk%Cv^K8KN2*nTTq_??QXy<1ICwE#6HP0#V}}YWC?_{3y{uq zwf0DS5Sg7VGkYAq*~}8a+9X^1&a)$TC{7!)`a}^T-4F{}hGY$!^nAfek8Gz9V7oV> z$arXpC=e8G$7?p*T!eFT$6!zBO8vYCHMPh*_tj#E9*2>9s&PN()a-6l@X-^l#DVqL zQ>1Augm)*9w3fj02GEQBv-ui(>8}~jN7Q%EH=_hxn+6e&@!PQ;*7#2^&mRVhoPPT? z9)&BI@DzN6GFtP8Efe+gO)5V6YMv?D!2n-iCNW`-$6rk3;XLqU`wZgV$;@LxgW3$2+y^rn}NI z*I$wBv-drfVS0|;17{eTB8s{+YR8-OyVusyD;U3QQvYyix~I*(rn6=#&B%pwtG&9n zSDOeB^3h}S;7CQ$bbkRplk8q)tPGfa9cf$oMqkx@^| zFLbR~gCwwVc{W|^bzIFH&rqFeDR^h~13mx?uXuKJ?m8E-PZA08Yl9*ip1QeC6%#P= z6Eem5Fv?hIe(Y^svM4@w=OS&xV9>dV>9Utp*x({vav$!q;zD1kg zDYG?GV_+_9v%x|H^6o2yPmU8(@tC(XJeYbN*;J#$zVOv8`rUkA?pk7pBUDHVpI^_b zUBnEJdFH4#>caILyM{zYc?SUw+;jBfvF0axOySKk#>A6z2f5pHAbiqT`2d&WgAvds z@?jS9>i&aIq2OQ9abLL-_ektbcD<>@`ewupl%iEZHG%++JstG(t`5_RW`E;M_c1j? zBX#d@64r(D=s@eb-Z->lqih2T9^zkX5ROF2lDvB0{`it&IqsS<#BYR=X>?ko%yAlL zYP;kvRp?Lo;YyVOpr6a57d*}7<9S&A&@vpm%!N#7(@v(LS!uFCs~opFuXMBijAoEx z`gA-f!GB|l@SAo*>ic^%xSl}dpCljllkyw0DsmFD;oN#z|Kv$9>%kV@=vy?n`vB#b zVHSbAs8LYv$m`kh%2WBpPmq31FYPQ0FByU5>dN6{*rNUp?DuEfu~FNa4Hr?AOn}WTQW8nL@;q9&RYB^(B zn-LeX5y9ENr>&OKtR$Zl?UF7co@bG8N=5Vw*cv0?A&n@Ma_V>s}$77OdkMH{w@ zaAwom()(e(!CGvbxrmS$N}$Se%a|Gd;dZnF_ATHDUaP{Z-5m*%F%ncf%uIsb-GlNQ zh-dbm8APr{ke-A+a2n>*z!~jdm}Wd~ryiLT*`3kX=GLdrssAsIt~oHSuZcElY$uJK z^cUNQ4-Y|t2u*|@Q7e=pzP-Dcn3cW36zIcM&b57gB{9Bma>iE@4_Hp^h~wXNh6v6QmrhG&m1 z-fl*GKpEW1KDuvQyBy0NC;&b&Dw-6OP+xyA7`9IU%C^P87FZ>!G zw0?1>_FAvuL2UwAm)P9Ycg-uMp^JU1^N|)wP`@pr8 z#PcB{42$Al2M&s!%}b_}0q{JEqi5w5WvSD)pV$Zc-X+GSnp=`x{Pvl#U{5S^@h>e zL${QP#O21codz(!FLR8ZDBAL`_~i}nMIO23JHKN=j5u2uEqm*V?-(5~TiiKNPcZgl zbU#d2e|>NW9LVX^!i!s#(5W)>8kPLEtlniwe>-dQRCSbAh-v}wUFP{d(3ad`>3J^O zxhe}ZRB9P8nvE^a0Ny*o?O>7q&p(n0l@Ds_7gYOh8YUV$j#Dk?RZ!)gvBJ$6bI>Sn zk&%%6)gck6i3bGVEpgEMsBYl?#>3iEeE6~z7a#29Y5G;U`S>t=ng0()jAl;=e&)f3 zXKZGxCFe`tk71dBmi^qu#PTk?>e5plqR#$vZwV5$>u`Gu#ObU>(~!wThv}4?waZUS zou^*cn|KL`2kT9ioW~!mULCK=-E+nX-nWHVHEfHnZT z+s2yq+N0}p1`iKXS$0x)Zqq`!3&up3O8&Ccm05}{rEszNMkHG5Ok@*+A$bVZu4-Mc z%A2PJ`5sgbN|kkmRin&Z&v&2o>d#b`yLoB~|Cdo&HliNZbuJjbwjj_eFe@vwj56A{ zghPS#JF|*p#55MI82P}vfQtZ*1$_#Ci(dF3=n>RR1B(Gf{Msz%(0{YPxf((*c&?y| zKr0HskS*||v`xVtzWkJ}*0H6hR)vbR-1brjP<`}%oE_-x3>C=1IAm}r$AY%%e_{DL z^xRVr?&m&bEXH}wu5(+e!vL+%P1Vrcj&M*97&K%z%X2kEkkX_?VsNcE@2$C8vKA@~ z?b9xPgl8NWD_iX%x zkFkFj7ZO7rqI#4l?2T+bE9a8I&@V=T=aR~fm3o7dU>bpg6zZ=@zY7i@aI@P)Q{{_f z_`8tzTWgqvsOk7?JuEmh*fw(o!=j5Fz_7P7fBzcO&-wfd^s&cVNCt~4e41xJv9l^1 z6m%NR3e(5kFY?>I4}yCvZ@0MBDV1R3i%s-9C1owQ6dU|Hw9;?@7sydgY-3H4VIbw& zQ&iW3!CV!i>HW%KT^%E2@l=xn#L<9e9N~gwL?Bv{o1|U!=zEw?+o5$}8Oegc za+!0|`i~0Ln*LM#o1*GV`Sj1g*3W6uRzxo-x+C%bBT4RZA8}! z8nbg^mN+@X6|mPL)DYa|^&~O)70x7mChB%H}yd<_3FY!-R4l4&tvP?AVn>)EABlco-!wh zGFuQYdwxi$Fj28?y`p+J)=}t`ib|vu6&Z z+Dh{rX>XZr^Kh*#Zh!1+KW@LDd>#PJb^hiq4$r~P<| zl9vz$GNEe#a$qTFsVO~UTg>j<^ZXj>+ltwu#AFlgDoOcoEUaq^^}Xig$&kd!36 zV{z*Gse0MkXUrkAECtfQqoo%vSI37!cC+!gh;^W>%W>_{b(YOp@z#{&mKHupZy<~O6`AdnhLF-M5-49Xa%O*B;;hSamDPK}n1^_%nLn4I zre|fY0R1|^MSh#?%`)~xFFxgdhlmBV^ZIc!H`cr3KWYT_ujB+*1EELu-SdCIK0B1| z4;VJqZ%Og~_o$z~VN;;25;8+^gFp$n4@dc!h#+Mj$&fy;?>wQPJn9H^oW5^9*%t~{ z0pykwtLxlQLY@Q}(#J~;b(3*u>oHR)1+sKNe}mzPKBYlkoivKo^P@^KdpkW**5c;u z&at1X6MvKG%$rWlN|mWOamf*jC(pel&QUOQxRO@aPwfm1vOvOBh7j9OzHNo(S@>vm zE0p!Pa>DaWyTnD9gek;)D{DGh);#K2u?@kT59#HBE=S(tR{9@ zeC-X~zb@d($4@T|=^(336Hhb*4La&PjdLc_pc3lW%1ueu zw{YoCj?{UPY7w>PM@s*(Beaf*_f{tu;mzY_i zD+K1bkRO>zu3CPRz#)d9qkAM^=@VLk%2`oYh%aE6$EUV98d5P)oUU@R_*2E29 zNj!X=z@H8s7Kq-vgw3uy+V+F-9BcVa=%0}l2-FLG!3;qj`l4hamI1NgIKjs0?XLityE(C6X{ z1v}dBnfew)@mDU78W8q7jLJ_~qj>vlAp}lK-UpA`S_F9N$6wD<%Kt{I1MSCjn8#m) z+5T>0dRe%7aO_clpd=k%i&C$3d0$x+7U!;Dys$Y@^bs>_J|=n$1x^?qYaZ%r?S=(c;q&rTLc=nWYz#{rS_tj)^G0J?FLZ1LAu+hr;3FOq~`Q410Vo*ZA2Cqi4G)h7G}7O zMcNA?E}n}*bS#R?_?z}(|Hrw#6bkiZhO9PW(%0e+xPuaQmYb5+%TbT-xDGEnz{#qWhTfT{AZ+y6u#hu*G(Irt;m%V|!TK`1|^;c_xd zeM;tYsZGF*4XmU0wlUmlAmstQ&2|JG@FNhRza4}%UKO#rsoaA*fqeMK?ihkAZmuRQ za9%yBa-t-Lu9qFI+lFpor=!BSOYA76)9kJ}P_^x8|M5l8$}}QhS*#0w&#^=X?n*|u zFoi0Gku~prfd6RZVfSvpiK2pE8}pcj@RoS4ZZLC3_~6Aey{XS%C+QNa9QV$clv|F6Kgihb)N=Hf4ti`WsKu zgpTC>57t@|vfCrUxkCioQ8$;0#Y;((ypD7yEw2#6k#*WS?T1&oI4{;Nb3LUU*D+e$ zspE-^XM@VGlCJ4fFxPd|$nO#g)U1@bD-srqml!wlm3WX)J@HvKVe42l=(5P2U_}Nc zdIRmXU9JP4>2>LI{>;U)(W=T`u4;UWD$KFykERX&D+t6;$S1$FuuYk}6ft+0HpCZ* z$7_fxAj*XWS1xssQZYPND+}wQWM;@0K5Cgc#}4;3J#tA^>q*h7mKC0pICM`C9^BWQKzmse zaDLO3i9%WNULRq^dV79e!~T-5EhX_r*h;O`ekK~E>99wRRGuazP^^ntg9fToJ5;Vz z7-HqkW$c!vZk9R343Bf?gX}dCw~94$_=_MjZdHKxC$i#r5kp@Pv9dUpA2eW3fr+om zbV7DJx{h(HTFES$AND056~7EdHt!e0Qcih{AUdsGNX@LA7tLe;T?b@4+!I`NhTK+! zE%y*uD$g<#H^dx6pDis+s59f=41T%-2b{l&DcQ3z>?N#`#NbVdII2i*Zvgo_0BIgPGUVh`X%*% zAVBf>t#U|Uj$Yf4;)o2YT^*zRrxmtbNF-tYvF^e82=Os_=c}^e;*(_)nm#8~;qUgP zWjHn@p9bACg1);;Eb-F3^xK3>3{rCxJQu@}Nd4)FF z>DnV5^|6TA8TT;2FhAKqAN^-R=}A+IqvBn{!}zhM0RyC90arCst6wu`kPmfNxxzqG zK9-`TP{dG+=PZrw7+T&K|CR%B?OO??SlV5=@&Ga4tWE2>YV03C1l~0$?K45crCepCV0{9|;b2GeGtaLy~Ru`j*n>eS-|5%7}{!;hJlK2q3 zWuDf=el|X*wzX^H1x=M_@OkeXmByb;fFQeXLOv$uNiVvN5+Z-hT68v7Nx}zunUu1h zx>T45<~!&=uZMTf=!=Wi?R3fkjJ253-9ZDZhV-&QbIllt-+3tt2q~z1JQ1ArAuQ4Q zL^WprN+&VE&l0@vJ3_-}Hd5v%9Y!~ej#Bh^PVKJ4G$G8&qCnvarFTPKvz~@nv!OS| z^f!}Rr{JX^C;-G}(}fVZRkmtnN%j!`p1F?N$rE1L^OiF)9>%3$A{YN#OKzHH2gSof zw^O7LhfuPUe8xZ9DMqv*Q`o%p>UEYn)#bdUt^3KnR_CVwCN_3Dv)Yc1=EZCDb+%%y zLeWxXzTbX`Upuay(ZvU^cTn6k?aoxL#pG}QEC3-qAm$LfuwZ?Q%L*sOo@HTax$BIpDR7biL5~h#0z3Y@)m^5U%%^Ts!6ym|c&gZeGGO5n#by?~eN|0C11M>vYqR$R%MU6mrNaxJeH@Je9 zs?tLS5^ATVdVV%rv7`A{fSf469Qxk33u#!DUnqQ#D1<#a-thT;39ehhVg5&)SK+JD zM1JULYS>VeabW^?BNiv`Cj=>*+g9tZj`r~*nN7P{V&H(uxE_57i{+z+?Rv58d z1rkR|O6k}An!?Q}E{t@cmo)l)Ga*%bpV_cU9OfOUZwJl?(m;kyKb8?T3Y*Fpl^r~d z!F-;NYD>=b#A?AzmDv%fEPEg0c-m(T8hfHe_V+_{&21+D{3K|7-0p9ZgvctLu%Zkh z!316$(nwG!lz2-lNag&rySlbx<9hXbpuq|!a0w^ukf*lD5 z)r7JuT*$+xR95Y;oYq2ao;8r`4vE9rd)WlPGu7kJ8EeS1I2n2H>L2LbsTANv?oPDO*Hb(kYx%>hK>f)%wqU>C$iu0@5ICR~saVvu1x1=j(}*-6VS#tHuU<*LRn3 z0!e$RH5pK=iEm~3FjGdE9rkDK1s{HXBLE2Zyvs>@vR~hI5Ba!JV6-MIdWHOwkFpj` z#Kk<%E&PL{_Y;S2eDBGstQ1;1S&{`aOOSIZ3{V%Qim_^fHq+2&MuVZB#g(SVfnvbE z_9h}OT>GAqxWXnq$>o>BP)C2Qt;~h|p>F~^_F{aPNi8E47GhHlhi%j7Enrxq5<&Mq z6$;*~+f%6X-Gz(%A58&m2rHA|ocPJKY7bQhRX>tqESp zMpN?d-YrRoUUqA2KdJLt`*fqnK9~r=GTC(haUpguu=DU5zV@I59^=mtN^f zPB*rw{#A`!_t}fM(#2Cvr_09^&vOpY#4E1JE<0&;_H`?KhMxek!s(aejC(dQ!T_r%6A^Evq@- zE9(j&A1f0kQPoYCo0<2*A?MkX@|Ij?RYgbh=q5+`e|TX>fA(iy+`MR0R7H^xrXHyo zVuDWcQ5>kSvO@YD9bJU)L+TA?YKcg1uMCfb+==M7<9#fK_=0OyZ{9ChE;8o)H@$p3 z)%Ad^0H{L7b76j&HKsCm)>JCmC0Bf<+}X~NoK zm~+K@PM{VYU>YOGw0hZB=&7n4t@Pa}@(TK6Nr42lFFx72YGMocMGRX~r`oT>H=Yp` zElDu_Y9$lC{(v`VEoLh5m^>#;bHSvR3<7I-} zf;6_8Hkg&nzQ_Rlgi;QN(FMyuQ*{8|7J2=5mB;`nD#COCH0$Ro2c4{A0$fapIr)oV zJS*#&nAcV)05bTy$Mm&J@#I;c51xP_C2&~mK%J^AR znQegT#^v547YgLZyE?`yK<#1EyOQ%Ghwdtm3DKPB;c7xp8T;3GyTd6T%d$}P+1me} z^+?bW8%Ur|8-XPRfMrF3Xg}$a9f;~Mz~`8?qFox?rs+i_eZ1m6Y#`PZxPP1Vx-{ik z2}*KCSg3b zM8tSg{Yc39KMs;Etc}K!pwVnlgl|U_kkEzSQHiO*E&sJ(6<8G)2Z$~Gc<|oXz9rir zzV>S0NU+t&W!cZAfldp8!(I`y-HFJHtFT$vnfQk@e4!H_5Vym|=ge2aVV|RjZ%x7< z;Xln@QKKE`T_1O|_nLmIVlC){52)##Jg;Z6s}p|p)ih{fzlHo|87)1CL#&LO=FqkI zga6fM03QXsB$4YE-{-``*FDIP{hcT@v}(RSsSnxHNgVBV-n?wD^`ChW@0)cKXV_2- zsL0wHZoO^oKk8@SJDkq$_2P?$BMU>&B{xo6K>I*zjgvX7CzBJPhXaO+8*S7l>rv8Jd07Xa{&Twu&HigNCp{q0Wd*R9nhl9=wv=|72EJ zK^7PLlhfCe?nypwmJ+X>-%Uj&^FSSaYtPz(xcfv?LgtL$Ar=rt(9uN*IZ6Wk7odA_ zant#?*(r9Uw5dn--D3VwC{t|3kWuQ$ft-1Sis`^F6K{weP|{ie!vDso5T z=iT{5J^`@3wb-D&ugdggMX*2vbd0?<#pd-s=29zkWgRu{U)UsbFvU6mjX1i!@fD1^ zVvbPtUsf_cZ>kVKpY))3b@`$CZg}H)`-25dYhuERf{Zpco4XPx2RvbKCJC->LeP%U z#uz&6m&;TT8fqc|;DfsuOU6tNVP=#)SK_;Vqu@crVZ;){Ejri=fw z)bOEk7jV_mfVbfkz0b2+is}J})zJDzti7h3VaoElEZ%2H?g0C50{KnX;p+|%ffBRv zb?iWA7p?lFTFC1j+X1BU(x@qY;n~v`mG>boG|xp=ARL)1`aruiT2@*86t&a0T7VGu zoz2cje&2XeQcj=NbDzk`-Ue9bSKAGuR;x^5Uzi8!9w3!!07k<|01|0_qZknd-A(sHgYtHNqAI7i^LIk3^6{jCltKmtOZr@b`)7l(M3 z$yxDoP0eyCpz;6%2KRzD2&qR%8gh~D;uN#{?9 z(b54QSZ$Ka|25X<)WQ}!#{XHwTF^u0?fA&^*GfC!whr#cT#*rMAuJuwy6OO$28YM5 zA0|cs|HFL+gmxU)D9cRf2c!6=GFSitqisLXaCo^zP0!t z=fab{X_8*8$X2Fc102fW`!&P|y5EeUmDswpZS;LO_Cof>61VS)->ZEkhI+px1Xz03 zO(UV$c~z;GKn0igSA)N>N?rfqb}%DbzhBT3ba_xL^gp?$BLA1l;HY?;SLVH3mKZF$(TS2iFi+nwz0u)B8x2JPVZT6ThfA93=I3tmpJy)QgE7?E3 zkN5cYe7J-e;Y8)T;Tt}7w$1+TPeS&~%xhEa!txsdKn&JVFJQJg?ee{8w_(hEBpr%5%A?qNX`rH_3qgo8Z2+axT?RjecowudkZr;C4Wrj@kaP)cs6pU5x`dd zfm+LYutvvY7EUQj!-d*`)hd73S0t$GLJ%W;(L5vhbK@6gjF-Gk?)}Zojim5=<$m^Y zNN9|x`%7@fRU-=-;?bgeYqiq8ci%6_C(Ch|Ced)$-(#D1@(iew8sYzr+8#=93_Zu; zp5ian&-Cqle(XarX#tjDK;czqm3!D4&`vr=7eu7^LfU9S+_TH3q65nlFHXfuYJLX7 zz1y)a{s~z~pf8muZ|<<{C1bm+1k|SdXZyD-zyoqqrqAGxU;OZX9$zh{spH*uA$hRi zlPg>Wi-Qcd(bWOSW&l8lR_hkq&`CO#lt@#+OnSoIauqh#a^4gcT^L5KgwA?}>D zffQ)(1eFjY=ftzpfWiurq@Kak=+O^r3#6pCp^(RB1k?9`jn zYRz1uc2hPyewv5?>2Ib1rwu`&-*~2`oA=9vevwaH+fW2FeWdkk3?ONT2a*VhSqvp` zMXh|-g;8cUCiL0$o&a@}iu`QivkY9i@Q1RBx!Io5$=u>?8hduPGlZ_-4gvOjz<$jNj!Ob$ z^9f}_84y2-rrf_I$3 z(t<6Bz_5gnGh|m=(#0ew++Na|u~o^cpR0|G%Uo!5_L?=dUvN6ucL(bZ(2t*2Mw&sS z4K2u}g^s11nO7t?Eg~7bun;y2LD_#R{G%ye{ z;G>9p)5D4$CQO$?D8a|bYFaqMNuL4eqpiVot6ki7N=Pl z@Ks~wEbblaVV!3$>QbCP{s9;gU;%Wm-%^0w0<1JZ=0^qQ@+(-+b~P~O4~yHiboie9 zcYYI=fVjFTTr-}BN^vkr_3Kgx$eqkFt1LWZPCDDkqB8KY;!BM5HifKh{${MfvKH|X zjq48OF>M2Q9B<~mfB79qClP0J6cVqz_ z`b_v^j3?qJr>TM!X@?`-^oO9s16$dKvoQt_%nwA2E)R=`oy1ZUxqqQ*c*a{=!2B!4 z>wMeCp}>%-x`!CLsx?*8YyVkrwS(V)01c$ z24J3!RazI>x-_w0os-N&gHsEz`UW^*>K*A4Dm5|MzN1SjP~OkQmvS}9q^$h20JjPR z0~9q0gU$K)Y5aM_<{eFZ-Ok~-osK$n*2;-ny8}{hz--FjkPT5DMn$Py#>vVUHM?B7 zJQK&g8WScb><&5K7T4Fb5ECC_5#M~7s^gcce}i)Zvz23VFX zS)cDBaFEv}QxCW;PFKzwQb!z%qtxbhk+0uz`Clf4aQqN22R&VieKE zVuU^F=X2reYmS_Up-S(%1(Xh@enAf?=I) z4x!+Q#yC#(lli{$_D!PQhXv)9IX}t_3ZFpcW7280j>Q%Nn24lq+kBzl3n-66Tza`) zbLHx-(?o5hU~gCe`x_%*vX`v7DO%E200@4dedi?4Ew#$joWPQ$GwBHbg#e=e zk3O$b_#fKTXWmff;)Xc41V2}yGdR#I^4Cnsok;BPJpX<{wORJ+uXKuZ<#+A)Dh>pL z1&~i$Jz!Sa=1*Q=r((6phr-%Tmt?nPgC!I@cRSUgsyK|U?_`0GNpepanI%@cilm(Q zqWw6|T0OQ{o#WbMhJez}Le}ug48SL4e6U&W=G|a~P$PaRo0c?f>ksPuTSRoh90%pu zMo0PZ{SZEp<+Y6k;pc=mf&JFkyIaePT1Eg_%5dlN#Jdf3!mFnIM8PR zEFHN^&r9MLI?Pn~W;tMJM5ePNZ49)Xt|1~E572t@SRC6$z@Ksq`R50Xnse?~TefRs zuj&eaiKRTJ9;hh@aFc>9g9edXcwxV}3NwIFNnkmo`I}SJ{9S3Fg#4_<-T&HTmZTuU zFwuu&uf%FW_`<3ZNi9whU;?9H-|jmB#!d%vA%vG)tE#^$4nU=f-b{CwjOz4yiIcPJ z3JhAZ`4(C^GgQmgj1K7eh^46H*bl$qf2a{h6)E3R< zC04pTn7?F%7Zb-iC4^u)D`QR4QS+gm(t*Lo#J|C{sa3u}4kll$0{l{pOP1d=OEdz~ z;zyI9NkK3!3~Y~{$p`lp0=74|e%prX+Mo&Shczs3fMCdhTS+6UuggTG$~}o$$yLVw~Mrpy$Ghvr|yf?>nHzk zHch82c_#nuWJ;^@ZQvB0ceMcqU5L{`Z@kvP3jYVsLid;4pmL~^Q@}Tfk)Xq7Dqd6x z9LEAQU2rWzD)o243>KK3od(?ps(c zW-Vnkb>{_L5(D3T5&?T;C!*MFW8G15&}j+Lol85BEZWrm;JGN>bR0ABUAwKV%ZPR) zz@sFryOmL%(tv>C7GXQwqb{?&$gV(W3+YB!M&$e$u}@K$_~k_--3kVkHrXKZu1SR) zdkN|k>E*tmEc#wZ_x*(qP`5xWmB21Q!Qz&!6QiBhjl7e?oNDNWX+j-X0EXjZi1C$@ ze7s;sO5Huy-M-KNsIWJP&8nW-%%pqs#vGKYp1RWAG(i}VCJu!W)BPYih)wCg1@8W0K zeZD?PoGP3<>C_KaoLl4jvYmxjp19Ewcg9CT#jBcp4Q)uCfmJBv!{X}i(fnUD+n_p8 z;C{~VQO2s^*UFHv>P3rO*;;ECp>^FJO~P^P1AjA$P9eXF15?-- z!7i4;evuSEk~&72yT{G;!Cx>>cf+1E^{|epNQ)fuJ_z8rsq5|1;g)l{r@tds+Zir@ z=I6S6ArgNna}BBsqBlov*Xgw7hV;3{m>}L%YR9(jkcpoW*4U)umCw9iNP3<5VMO{` zw8M>*$H>2K4&mLF;s!N{=|!`o$ca#xH?;Q`_ARm5XdBUGIsIuUtAXW2*>{!a_c{O6 zDWs$DzHOT~;7>Vs-@uWl>^%+Xu)sTkrf*p3+Iph0WL!WQ43F^(`*EscJB3D$@ z`zCq*-cG5*w@r#2&ND{qi;{hX(zms!6E`qRNze#{!~OWqorxaOeZfo>C&p65ftawr zOpT>%&HEzZ!Rw`(I8mnrk^Aa~f$Y`Xr3BF|@Nkr(O9dGG>XA_?lv8?Fl}{SC_!?`< zk;fS`v-*KGB>ZE)F;BmDdDx4qIhU~BwaXnt;S4%AQn z0qco{cejErur$i@zg_>$$de<0Wvt3N+TdFa6n2al7#dq(C?| ziONGv?}VzsY_j17Q*QC|gw*BQfOq@Wk z2y1Guu@(70S`VMA@{P{^>-{(XO_sy;|hVhVX3oY?V=)teMC=cVWSzvn{r$g}60yRmxJ1 zL<4ZlML-h=GjOj{_U~H-s6RhXNmI4do~V~H{}vr!=H*azH`C!GQ?&a%zzHzq(yr$L zCm+7E55B&dL&p?Ga@DX5VkIqFViD8bn#=hm&yRO;v{R>VOYD4!w<_-Hq9Yo|9HnAHl1v(FE zI-V4AiA%5V(8U_moRO2F<7X@~mYTr4y1>$vIO|FrCW6BNi|7L>z z*%T&bMjc<(!SDuzJR#Ojf1cU`fvrYWTkmOq3c7i#Oe&&MM_bInZYZvSCxXa1>R1NT zF?UVu858+E@toRzBt1-8*}_;-_h=fi8T?l*Fa2*R51Yy4=X{+ zRc9<28X2Z+l}5EWRW_P}CCD~pJ`vJ2ANnpZ$$#2e+NNU;*DD%r>$r(hsnY~{*mbZ6 z|5Y&|xy5Z0LEbtpnug_`|55$x8`7UFscI?DIC^a_15IfuU-au7?{G=2Oou&dbCQy}(C8{5%)8I@&eCU5DRgPX251uC6|AehoCGZ#VObv%DlD#QweI0RCaRLQvy*<0d>@|KN4^V_^)mOGm z)tv}BRf8iJF01!CmOcWF$C(<)tIf(2cx>4SwC>@p1`4Z~&lNfKo=w*oJGC8GaLvh9 z{Zl@HU9h0S?dII14ZmMyreKw(xvuN4`U>r3+dx#ysdMGM@DDF0516svSspOyrrIC_ z@r6p-q4oPLB^E7{LHVyQw4rvrA0KNju$M#*v7O@vGl-b$-G0-bYDamROgZJf1;Zt)L{PbBE0xCPg+#8HDT{ zCLMtNiKfZ;su%IpzEmtn)g!_CdBz%{>shvLXlb&!1ekGKL5;d^*YqVDnmj|q`rg_- z`FBE$W0!gwJ3T=YynDibi`tH6kszm!+uU7*MMR8pm#c#twGKnMa0ShDBWJeo>>z|} zeJQY|mvm|T>GMoHGhUjQzO0>0syLA@4Q%bUeK({|Vbd{bcdQ%2z^gwJUN(5{EKFLd zvpBW&Vkz(8U=&QKS~ISbohjhqzsfrZDFAG7-=joXIPG@8eg zPvl(8yt3b5q>>h_clPN|W6p6;@lOwmLkjJIjR!A95P#XC=uNflT1e zoTD_a$b0Z{lv*K+1?45}{ihaA+%x(p-+kcAEe}40@1T2XhvfDfL`Fnt)gL$49bZji zxZa|Aa*q_@T_*??ZCn#zpjOU2?r-Hsx}9%CmRJSLBk5$LmFu^z$?Jh z?SR%MKyvR)n>`a`8-A#R^K6@gLR6g&pTm*-xFPmG3judfF_@zh7Y)94@U+mM=^Co( ztULKn*NawmOYYr~v+9aZ6i06aq;F!M2}$0KC;*5X*WR;E2k~xM_+@%w#J*fUU)HBi_0weaixY{ep2ut+^sus5!mV=lxy91Yhm7>N>ck z#88szLHSUtg9J4SW5*(J0R!5(_X&R(HoHuBR@@RmLczwpwEfsjbHOaG($q3tzFeD| zH3d1JEThY{$%ajV(;OME7)HC+%$8tze~+u!+-2sKY4XQU zwSgvy+gx4+{F};BK15X9Z`;mCX~~w2V=y^3=YY+R(i}LJStds;a@9(CO5=9N{ z5Q!jd*tUePS0UbUi5vB~J9CnWNkcfNpK##69Jb9)y?trB6F3m=q6K1|UJ+;B+n`{N zfeV{cy&q9z0Qza%#M{MS%1K_F8mQuYqBfM?EORYqGPca?|&L``2Q2@`OH2^%vd-Pi#ANGwUI?ktsA?KoN+1Bph-qhFDFx|vi z8mwx}?8?b_y9^W)@h)4`%qC?niz4MlCH1aVb#CYR7=A2wo}uaj$XCt<2ZM@TW?86T zXk}4gYD_kb-ryYIv{MDUK4r+f>yQ$ONwJ#dbCq9+8*7jf-xfEr+l=yz{627__fktkU(BVBi zVl~4VBV}ny=#3R~t7&J^Q0x@?+6ox%tE}U|shAoGCJ$WGOF4m9qT&1gf(CHxN) zhF6p4RJ=0^)^}Jzb+i&k2M)!>bpyyit0onp=YN3r75B0LRkSBh#cj^68j@Or!JRm(MCQDQ zG;>|D{ahdN94n&k*nK^izLtc2z_M>Ul_TbtBd`gx}Hrp*WGllO{9KQ~S6%|+H<*14!_G=Z_L zHY~d`i0DvpEp^X{{>Ar}lSqg9uH!$k?uaOOTvz`!JXUz<{TLhG5f{u7UexqT7lUDn zeD?j*XnQ>0vw8{K{PeMu!RKeC=lhewWRwlYpA%(47Ym#+pUw7i2MSBJMXU_!;zlXx>s7mKZamMk zOK+h-$TQ!RkM>JDaF5(x3gee|ko54RZZGzL-hYG*6$mZ?<|fBoHU?@q2`vxNeVz)LDR0%SCsz$iA#fc12efMLz&4}yAgtXE`5OE#``$-ut zpr-6O_BDy3!Rg;B+J#|TGd(9Gqb7vSAT;VPCXq-^Clh?#2p<)^6XYX}UugAhpNdun zKNlXCdCuwwskn+8_&%6+c4Mw5%EyC*h-9cPbDCFX(lM?Olmeb-dR}^OH#Qd?7uD1? z@DG6BFGXK(ICTx-4YFJRi|D=3pkzPje!nR>pZNWY>r8{I49!Tk^XEg)S;ul{0!i^9 zFS4s};cr`bk_K(*7sfQx(;#wx0DNi{9&k!@lPmhU`gz#QuaQ@d3LD`xIoZ~?!6v*y zMg_7sE}GE?pW~B0k7-`%-34o5Bn@sIvC_Q$5a20JSVr7OCB~Wk8$b!^<1W-ym^Q}K zQ1)KvO!tFN*rf>kU=+==w;PM#1XjE6+*=*3X^p~10iOO7*H7Cwg2hIXPn$l0@?%sP zvYRQr6Mbg6OR^q$P%>1~injdaDHop-;iSj2rB6ezWhS!}o%Y1_eLru^OTAL<$Mc%9 zKK}Rok%9$vrgF>Mv$C!k-1`m29Lkr4VINk$i$Ij}i4dYsVD*Dav$prV8_aT7M7nIb zHs4T!20vq9D!p06LB|5s!FnP~Vedqz$Rb-5c+sr;W4-ewbU)Um=2Q}X^5d&DWAXp` zJkR8|m!#xhDcfAunOT8yW8UbbP*99&Qu|w8WFj0)t!4t?3zv93aEks4v5ywX$YJTf zqn{3aQX$6jL?if^@Pi!xtW|-p2%>}pGR`LWIZCUWg(Ca6n?@Ho#>-Cjfs}@CqN)Z^ z4eQGkYGo#l(GJkeA$sXP);PF^Y z%FABo!}`fo6zAXwkSd*NEciNv#~a^ihsEZ zEj8(lRBuF7%Ao>hPW)+mLMmTUoAQwc0;w^)v=5&R(mc>8@635T^k~`sO;=Jp59%e@ z-*SS+%Pn}0&4WJc)|-oFh+(?U@G1+IZ+v&<9!(xk!D;JnPoHksZ=f&%749+qrKA(* zXfXRr+&&Nb>wecu$aqT~^_iFKw|?lSMbAi^#Q%JQPOc8H@^loNox@o^nTXF#%K>Ha z6}6hC`x*@Z^OOXHVOS?Mf?|7B8F}$GYqt4zV-c#|B>OeaH5WU*7ta)X#-Zq#|KCtv z2KA5W9w_jvuqIxs92ujfOwitTYi?fWzLC9m!IVll%tglT99FDR7D;$|lfT*YrUkuA z?8Y1kgxf%*+VkuVS_32zUgVO%4B{$Q^P#KAHKTYv9Acm&@D?h>@ixIvBQsG`{dxo5Z2_3dAgZ`(~`;#d-s#tuH@Iguwl zPDq}13Zt&NfN<|o^3U&m6U?%3qV7x6X$; zMMRoK>|{1G3?fgibvIrA^#FapSPLQ~rZj>RBnXZ>++A&&hAZAeX2A?{8+W07Su`9m zj|Po-BkChrpGqg*vmpQ75qm3Z#0EOR&4sqy)xm;wPINZH>DbD5GmOIh$LRg(YP#=y zl;~IwuAyddyPLQdKEH9er{+#&)Cc~6puUE}{$o&xAo7D}NEtIiManiLcwh6tk9Nde zOPoe@paU%Pt=-oq=CnmB^PMma`<|F#=6ePhc%q%2<&QD2W((Szg}@2quii)xGh#?3&G!Yuio#!y6fRY7>PWhlq}J z6CLfw_vdW(ZmQWqRX&Z%f3*}=4#Ztd+$_*vW@GY-_dBPb0hxt;Z%hm%5s;idfy}wH z$ecHu)ZA%$Z{KdVuI~7tzrkqK;mCS3g=9k?Vy_ z^*M^_O(=W`HnuKgB^qiI|&bV5FY3xJkUXOsB`eY?e8Gme+=K* zhuZUaQ)U$ULGMKQx0?KzEi<{@o+-V+0{vBNu3uVP$cEPw@Q(tvN8OYiNhL^39!Fwo zBZE_dLk+GjgR4s;H7Q&sg`|_O7*E3p-_c89UkBm7 zql9~oP}p^t!j41aw;iCc^#D;X;w7Omr z0VEs}1Zb{6bflYb|1k=Ck5SlufWo$e_-sDzFV&1diiW8E3YE_hxF=h(E35i!^OIrw zu3tmK2gg+OtBR%-KI-gyWy;)(J>`$5MeJaJCkDg&#SnLHaQmEd5D2*6{j={Guv_pZ z7K%oSK8o;VRe5^8h54(p8T#vJ%e>dA`dO;c^&;{Yg4lL8V~i;+MfE9#&$__f-!x#` z(_V`O)az)@{>VGRZ6f*=;Lf<~F~*pYL;s0msq59Lf>oLZd5nwl-&k3w- ziB?*e9|wDeegaw(mj&X2^ASD+TpV{j#u!tj4&nVL1oz#h!YbQVahxpBPf&a63Q_!9 z5&1Wex5ZtLF~(GxZq)w^p4*zkyRGrT39vvvNo}bm7a{st;LX^h3uBBahF(-Y(VSoT z^|%YEL>5FRsX4#$K~!!4egoKP7sePfG~E{FLt``aA!c$xJ>eMS6MD_Cm zE1L^-m^di(qxw^nuX^O^`Gr-v zxNE9L_KbHJU7eiYE64^xJ5+RmAPsTXWQ;K-)&;CkkuQpKYv<){HT!U}8Tv5WT)$Kb zVK@e_R|s5>$n3alGRBw^Ig08Xin~cO!M1tb%cHmps%94GC9slA8 z?wX7-W|$pB^;UAhZEWK0eqnw>pxXs$_b>(Fbg?&7#+WMP z)$awlQI!9jTR8LZSiTx}Nh5*x_*qmEo&&x~90zF2pwM!a^ zqJI+PYLM}9*JF$^CrLd#lu9O+&hA})H11+XDm!g0MlJm-dy={7pF!RAz$U=QU6V1! zoS*^k`T9U^!X*~w@v<3unP^Kcd8e1?MknrEARTu-#u)PkJQ^K9-HpJvnhST?E%umM zpqG)h%zK+q|2fc$fzff-V~jDcA&b|I;^Y%Jd7x=vWf*rs#sa+=mUcPoH)OJ?!dh^GcOYs=w)a#*Ch(cy?-uFuLjP<`UMzc;^%>#DBt%g z-xw&2-@S<2tXWJMu|O|l8#7m4;Jp5$ie4;Wec+8Tamj*z7Ui2la92})<*~R6GOv_~ zy8!b#Jdb+VgL171R{}HQF3A`(;v5CNUsb;;5aq74b)xQ$t?TFOGU6(PY;;Hv+RrrrUxbHxI;r5HT(N?in z5DWAQwJ~#PMngB-IX);N9|D;YcTL8a%B66ZhI|#_nR(%=p1A8VC(Z)Bf~{t$t4-w_ zMcu^+{~O_KY{8r{<^*{4olfMliu;kvg}a-$#}>X-C=2u|&>mco(FC(l{vP4|*q(J` z%<)hjj>LZ|fv#=J-P0R)Ic8`q(5pmi?Gh&;b$H&53qOMdXJG%n#+X;()prT*3xfP- zn9xHD`d3-|)+%5x_*JGYSmL5|Vp0%>|BmPfu_JPgF)yR&!z%g-C$d^1mut$ejJqUL zBo^pZtUa~la__^hOAt)P`oSABl6mzU1-)6E-qM`A+xC@LAq(^>w@xl^OeEw+=lCgdt<7Z0y$ta zs<(><-)$B%QD6cohlp?bPeji>jHoG+<|+oLCgqZ zfj&aDW|oXo@BfFu`w)4Pz<6v$yfKw33#^M&?{nUNwy8+Z>e9s3zE zrraC_9#Z8#A^53?9B9fdkGm#Q2JFS!yh2<0%E_wH1pmUe{)>bA{yYSt@eA2=7;TmxFZNbr~}P6o8E=KUB~CifbPT8+#UU zTikV-GG&2o-p~e?B8ezP;Aet+v#5Shg+H)hH)dEwz-CqM0Qoui&HeeXYZ3Qaa~Siw zEYQty+c0XWxV+D3#1upq3;Gd+#bPVsjVT5X>{R93PTk#+M7sv^$?iqA4b2=U3v_eB zp5sp?NM=)tKgEf>UEpsJ&clM;n3ELk7Y%MjgCBT__9gSReY3g4X5{9CSfHDewwB9D zCnbrxQ6jv zk>G_h$+Z*J$H9F|aKCQKudo)aW>{FDn_;)1ZYkpZR2PMR=EYs<;8b8Nw(ZSSrwHgm zb-$`Uf^*+3Bw2H2@2Xtfg_-JRfo@7>153p@9V5>F5rO^*a3(MrXuxLert)Qh11MY6 z>o1(TdwQesn-_7LZ96k1VS#R9u`zXJt@HjYFIV^6R}ylsWi%Agk31>MY$K3 zc&I72{Ak=|nUTi=-Nb4g9|}^*o*9T-i10RGo+`6M7=z8)O}SF~5q1Due8dxykOw-m z!P>nM4w1*iB_3_c z+HBmE2Mcslc9a|B`SkizBaO~+xO0I8h+hCQ1Dmy*l2Qc@fwZE2J;<{v!P5cR=jY_^ zE7%LDDJS;xXv*3;K9o!a{bvvq79hbwlyekafG|}M`}sA)K~=R&pcQnjBF_qLeWcRf zU2xrtS#F)&Oj)uQL{kyk8m@qdIttA8_&MTa9?H1_XCX2fo4K2zQPlT>Jg34MRbA&i zn>=jJhjQd>?uz0r(3m%3fo>|!^9>(xzL0OwD4Y&56DRWp&Idggm<$rcU8AWC9(tX! z7UfCAKLcz9+2u3IBlEhKhjG_u%!#l-Hx+Fym%CKjH8_}rMw1cOjPfS0EOw5WKqBrM zO_}i=MD!^{o`}?~b?APHIFN{Hj?Cr`n~9r>Wr1$0#s-#(OZyr$8mC?w#c_t9Z$|aa zURdPBW#X>U#3GOCX5dLg9w%Z0qK74LNB3tJbYH}c_DtMV1q*ajwbrMuNCoQaQLXn* z#s(6ckLm>qixp-$v7i5Hq^PQLSa7R>he4hcqTMJxi1Z+2n{%ry#7)()7c?^>Y+$J* zGErJR8IP}3FQ+=;EeLM~Js)A3AhxisGAXJ(pld)LRWDB<(uUBDlJ%O%Wy3MiB5sSj zPGg3Z1-cn=)^a%sCoXUVI1SWGMjWRD7l``BUgS+8GEMCHd-+m2z1OFlb5DAaM|>2n zO*r~ciPTG!jB2CV+!1$`#*~x=x-q{;K{lo?pBAXT3FqGGRo~)}g$QYW`#WR5JHty~ z`mBVY+fkkb|F}r%vC)2e>*R2a9eQHS%h-#OF~?yu*QE-{z0Kgy9^Cux&H|l-P|M&< zK6u%O-MT+M%HUC>FMfoeIaR+Gm1jleSrvKKYhnW-9rIak0dCCeus}EFBq%qqJ-xXm z=d-7&`ne+7gfJImF2W=r10)#?`{c_$Mq1b_5CVl)d?=lYe_oO2o!93*uH7Zr)|6e< zA-|oW8)J@>1-dasq`>-`%SR&CkiyGFxDgEL|mQw-M~-0^jS$DuX>CB#m|nx-)m72`R$((P(@V`RZtl` zctk-wXao`gp5MI<{$g0aBDlRqSy4Jvf@4ngmkiX4cLgSjy1T!eL8Go}j~q@tct_;D;vG^@Auq^_x55i5EYOWH#+WK#fo_a3##8|dbYqM$rV3b~8)J+yRlow>7-Nj70v71T7-LKo ous}D)7-On{1-dcD7*hrQKLj|ljNfS6umAu607*qoM6N<$f=ptg=>Px# literal 0 HcmV?d00001 diff --git a/application/public/upload/os/linux.png b/application/public/upload/os/linux.png new file mode 100644 index 0000000000000000000000000000000000000000..8a69099456464af44d5fd18b76c84713273b36a7 GIT binary patch literal 59587 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 pDsShN~|AF@D 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 literal 0 HcmV?d00001 diff --git a/application/public/upload/os/rhel.png b/application/public/upload/os/rhel.png new file mode 100644 index 0000000000000000000000000000000000000000..5fd5e8b98af031729ff46df0a85c4074633f8c7e GIT binary patch literal 13576 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 NxpdW_M$aYoe*m!0r^x^S literal 0 HcmV?d00001 diff --git a/application/public/upload/os/ubuntu.png b/application/public/upload/os/ubuntu.png new file mode 100644 index 0000000000000000000000000000000000000000..fe086e29b765ac8044d0aa6099ff6f85b2bdc998 GIT binary patch literal 48541 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 literal 0 HcmV?d00001 diff --git a/application/public/upload/os/windows.png b/application/public/upload/os/windows.png new file mode 100644 index 0000000000000000000000000000000000000000..cefd4ae04028b82a3e0fe8521f49c661955b7ba2 GIT binary patch literal 6140 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 literal 0 HcmV?d00001 From 868832f5ba2d747e077b2706bb1d53eee847a491 Mon Sep 17 00:00:00 2001 From: Tola Leng Date: Fri, 18 Jul 2025 21:57:56 +0700 Subject: [PATCH 09/21] feat: Add create instance monitoring button - Adds a button to the Instance Monitoring dashboard to create server monitoring instances - Include the one-click install command in the Instance Monitoring form - Display OS list for select with images and names. --- .../servers/AddServerAgentDialog.tsx | 167 +++++++++++++++++ .../components/servers/ManualInstallTab.tsx | 125 +++++++++++++ .../src/components/servers/OSSelector.tsx | 51 ++++++ .../components/servers/OneClickInstallTab.tsx | 101 ++++++++++ .../servers/ServerAgentConfigForm.tsx | 172 ++++++++++++++++++ application/src/pages/InstanceMonitoring.tsx | 18 ++ application/src/utils/copyUtils.ts | 60 ++++++ 7 files changed, 694 insertions(+) create mode 100644 application/src/components/servers/AddServerAgentDialog.tsx create mode 100644 application/src/components/servers/ManualInstallTab.tsx create mode 100644 application/src/components/servers/OSSelector.tsx create mode 100644 application/src/components/servers/OneClickInstallTab.tsx create mode 100644 application/src/components/servers/ServerAgentConfigForm.tsx create mode 100644 application/src/utils/copyUtils.ts diff --git a/application/src/components/servers/AddServerAgentDialog.tsx b/application/src/components/servers/AddServerAgentDialog.tsx new file mode 100644 index 0000000..755bb3d --- /dev/null +++ b/application/src/components/servers/AddServerAgentDialog.tsx @@ -0,0 +1,167 @@ + +import React, { useState } from "react"; +import { useQueryClient } from "@tanstack/react-query"; +import { + Dialog, + DialogContent, + DialogDescription, + DialogHeader, + DialogTitle, +} from "@/components/ui/dialog"; +import { useToast } from "@/hooks/use-toast"; +import { Server } from "lucide-react"; +import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs"; +import { getCurrentEndpoint } from "@/lib/pocketbase"; +import { ServerAgentConfigForm } from "./ServerAgentConfigForm"; +import { OneClickInstallTab } from "./OneClickInstallTab"; +import { ManualInstallTab } from "./ManualInstallTab"; + +interface AddServerAgentDialogProps { + open: boolean; + onOpenChange: (open: boolean) => void; + onAgentAdded: () => void; +} + +export const AddServerAgentDialog: React.FC = ({ + open, + onOpenChange, + onAgentAdded, +}) => { + const { toast } = useToast(); + const queryClient = useQueryClient(); + const [isSubmitting, setIsSubmitting] = useState(false); + const [activeTab, setActiveTab] = useState("configure"); + + // Get current PocketBase URL + const currentPocketBaseUrl = getCurrentEndpoint(); + + // Form state + const [formData, setFormData] = useState({ + serverName: "", + description: "", + osType: "", + checkInterval: "60", + retryAttempt: "3", + dockerEnabled: false, + notificationEnabled: true, + }); + + // Generated server token and agent ID + const [serverToken] = useState(() => + `srv_${Math.random().toString(36).substring(2, 15)}${Math.random().toString(36).substring(2, 15)}` + ); + + const [serverId] = useState(() => + `agent_${Math.random().toString(36).substring(2, 15)}${Math.random().toString(36).substring(2, 15)}` + ); + + const handleSubmit = async (e: React.FormEvent) => { + e.preventDefault(); + if (isSubmitting) return; + + if (!formData.serverName || !formData.osType) { + toast({ + title: "Validation Error", + description: "Please fill in all required fields.", + variant: "destructive", + }); + return; + } + + setIsSubmitting(true); + + try { + // Here you would typically create the server monitoring configuration + // For now, we'll simulate the process + await new Promise(resolve => setTimeout(resolve, 1500)); + + toast({ + title: "Server Agent Created", + description: `${formData.serverName} monitoring agent has been configured successfully.`, + }); + + // Switch to one-click install tab after successful creation + setActiveTab("one-click"); + onAgentAdded(); + } catch (error) { + toast({ + title: "Error", + description: "Failed to create server monitoring agent.", + variant: "destructive", + }); + } finally { + setIsSubmitting(false); + } + }; + + const handleDialogClose = () => { + // Reset form and tab when dialog closes + setActiveTab("configure"); + setFormData({ + serverName: "", + description: "", + osType: "", + checkInterval: "60", + retryAttempt: "3", + dockerEnabled: false, + notificationEnabled: true, + }); + onOpenChange(false); + }; + + return ( + + + + + + Add Server Monitoring Agent + + + Configure a new server monitoring agent to track system metrics and performance. + + + + + + Configure Agent + One-Click Install + Manual Installation + + + + + + + + + + + + + + + + + ); +}; \ No newline at end of file diff --git a/application/src/components/servers/ManualInstallTab.tsx b/application/src/components/servers/ManualInstallTab.tsx new file mode 100644 index 0000000..9d289a8 --- /dev/null +++ b/application/src/components/servers/ManualInstallTab.tsx @@ -0,0 +1,125 @@ + +import React from "react"; +import { Button } from "@/components/ui/button"; +import { Copy, Terminal } from "lucide-react"; +import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card"; +import { copyToClipboard } from "@/utils/copyUtils"; + +interface ManualInstallTabProps { + serverToken: string; + currentPocketBaseUrl: string; + formData: { + serverName: string; + osType: string; + checkInterval: string; + }; + serverId: string; + onDialogClose: () => void; +} + +export const ManualInstallTab: React.FC = ({ + serverToken, + currentPocketBaseUrl, + formData, + serverId, + onDialogClose, +}) => { + const getManualInstallSteps = () => { + const scriptUrl = "https://raw.githubusercontent.com/operacle/checkcle/refs/heads/main/scripts/server-agent.sh"; + + return [ + { + title: "Download the installation script", + command: `curl -L -o server-agent.sh "${scriptUrl}"` + }, + { + title: "Make the script executable", + command: `chmod +x server-agent.sh` + }, + { + title: "Run the installation with your configuration", + command: `SERVER_TOKEN="${serverToken}" POCKETBASE_URL="${currentPocketBaseUrl}" SERVER_NAME="${formData.serverName}" AGENT_ID="${serverId}" sudo bash server-agent.sh` + } + ]; + }; + + return ( + + + + + Manual Installation Steps + + + Step-by-step installation process + + + +
+
+
+ Server Name: {formData.serverName} +
+
+ Agent ID: {serverId} +
+
+ OS Type: {formData.osType} +
+
+ Check Interval: {formData.checkInterval}s +
+
+
+ +
+ {getManualInstallSteps().map((step, index) => ( +
+
+ + {index + 1} + + {step.title} +
+
+
+                  {step.command}
+                
+ +
+
+ ))} +
+ +
+

Prerequisites:

+
    +
  • Ensure you have root/sudo access on the target server
  • +
  • Make sure curl is installed for downloading files
  • +
  • Internet connection required for downloading script
  • +
+ +

After Installation:

+

+ The agent will start automatically and appear in your dashboard within a few minutes. +

+
+ +
+ +
+
+
+ ); +}; \ No newline at end of file diff --git a/application/src/components/servers/OSSelector.tsx b/application/src/components/servers/OSSelector.tsx new file mode 100644 index 0000000..6147997 --- /dev/null +++ b/application/src/components/servers/OSSelector.tsx @@ -0,0 +1,51 @@ +import React from "react"; +import { Check } from "lucide-react"; +import { cn } from "@/lib/utils"; + +interface OSOption { + value: string; + name: string; + logo: string; // now it's a path to image, e.g., /logos/ubuntu.svg +} + +const osOptions: OSOption[] = [ + { value: "ubuntu", name: "Ubuntu", logo: "/upload/os/ubuntu.png" }, + { value: "debian", name: "Debian", logo: "/upload/os/debian.png" }, + { value: "centos", name: "CentOS", logo: "/upload/os/centos.png" }, + { value: "rhel", name: "Red Hat Enterprise Linux", logo: "/upload/os/rhel.png" }, + { value: "linux", name: "Linux (Generic)", logo: "/upload/os/linux.png" }, + { value: "windows", name: "Windows Server", logo: "/upload/os/windows.png" }, +]; + +interface OSSelectorProps { + value: string; + onValueChange: (value: string) => void; +} + +export const OSSelector: React.FC = ({ value, onValueChange }) => { + return ( +
+ {osOptions.map((os) => ( + + ))} +
+ ); +}; \ No newline at end of file diff --git a/application/src/components/servers/OneClickInstallTab.tsx b/application/src/components/servers/OneClickInstallTab.tsx new file mode 100644 index 0000000..767e73e --- /dev/null +++ b/application/src/components/servers/OneClickInstallTab.tsx @@ -0,0 +1,101 @@ + +import React from "react"; +import { Button } from "@/components/ui/button"; +import { Label } from "@/components/ui/label"; +import { Copy, Download } from "lucide-react"; +import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card"; +import { copyToClipboard } from "@/utils/copyUtils"; + +interface OneClickInstallTabProps { + serverToken: string; + currentPocketBaseUrl: string; + formData: { + serverName: string; + osType: string; + checkInterval: string; + retryAttempt: string; + }; + serverId: string; + onDialogClose: () => void; +} + +export const OneClickInstallTab: React.FC = ({ + serverToken, + currentPocketBaseUrl, + formData, + serverId, + onDialogClose, +}) => { + const getOneClickInstallCommand = () => { + const scriptUrl = "https://raw.githubusercontent.com/operacle/checke-server-agent/refs/heads/main/server-agent.sh"; + + return `curl -L -o server-agent.sh "${scriptUrl}" +chmod +x server-agent.sh +SERVER_TOKEN="${serverToken}" \\ +POCKETBASE_URL="${currentPocketBaseUrl}" \\ +SERVER_NAME="${formData.serverName}" \\ +AGENT_ID="${serverId}" \\ +OS_TYPE="${formData.osType}" \\ +CHECK_INTERVAL="${formData.checkInterval}" \\ +RETRY_ATTEMPTS="${formData.retryAttempt}" \\ +sudo -E bash ./server-agent.sh`; + }; + + const handleCopyCommand = async (e: React.MouseEvent) => { + e.preventDefault(); + e.stopPropagation(); + console.log('Copy button clicked'); // Debug log + const command = getOneClickInstallCommand(); + console.log('Copying command:', command); // Debug log + await copyToClipboard(command); + }; + + return ( + + + + + One-Click Install + + + Copy and paste this single command to install the monitoring agent instantly + + + +
+ +
+
+              {getOneClickInstallCommand()}
+            
+ +
+
+ +
+

Simply run this command on your server:

+
    +
  1. SSH into your target server
  2. +
  3. Paste and run the command above
  4. +
  5. The agent will be installed and started automatically
  6. +
+
+ +
+ +
+
+
+ ); +}; \ No newline at end of file diff --git a/application/src/components/servers/ServerAgentConfigForm.tsx b/application/src/components/servers/ServerAgentConfigForm.tsx new file mode 100644 index 0000000..da2e858 --- /dev/null +++ b/application/src/components/servers/ServerAgentConfigForm.tsx @@ -0,0 +1,172 @@ + +import React from "react"; +import { Button } from "@/components/ui/button"; +import { Input } from "@/components/ui/input"; +import { Label } from "@/components/ui/label"; +import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select"; +import { Copy } from "lucide-react"; +import { copyToClipboard } from "@/utils/copyUtils"; +import { OSSelector } from "./OSSelector"; + +interface ServerAgentConfigFormProps { + formData: { + serverName: string; + description: string; + osType: string; + checkInterval: string; + retryAttempt: string; + dockerEnabled: boolean; + notificationEnabled: boolean; + }; + setFormData: React.Dispatch>; + serverId: string; + serverToken: string; + currentPocketBaseUrl: string; + isSubmitting: boolean; + onSubmit: (e: React.FormEvent) => void; +} + +export const ServerAgentConfigForm: React.FC = ({ + formData, + setFormData, + serverId, + serverToken, + currentPocketBaseUrl, + isSubmitting, + onSubmit, +}) => { + return ( +
+
+
+ + setFormData(prev => ({ ...prev, serverName: e.target.value }))} + required + /> +

What is the name or label used as the identifier

+
+ +
+ +
+ + +
+

Auto-generated unique identifier

+
+
+ +
+
+ + setFormData(prev => ({ ...prev, osType: value }))} + /> +
+ +
+
+ + +

How often to check the server and metric status

+
+ +
+ + +

Number of retry attempts before marking as down

+
+ +
+ +
+ + +
+

Auto-generated authentication token

+
+ +
+ +
+ + +
+

Current system API URL

+
+
+
+ +
+ +
+
+ ); +}; \ No newline at end of file diff --git a/application/src/pages/InstanceMonitoring.tsx b/application/src/pages/InstanceMonitoring.tsx index 7ad1fc4..1a9c628 100644 --- a/application/src/pages/InstanceMonitoring.tsx +++ b/application/src/pages/InstanceMonitoring.tsx @@ -7,11 +7,14 @@ import { Sidebar } from "@/components/dashboard/Sidebar"; import { Header } from "@/components/dashboard/Header"; import { ServerStatsCards } from "@/components/servers/ServerStatsCards"; import { ServerTable } from "@/components/servers/ServerTable"; +import { AddServerAgentDialog } from "@/components/servers/AddServerAgentDialog"; import { serverService } from "@/services/serverService"; import { Server, ServerStats } from "@/types/server.types"; import { useSidebar } from "@/contexts/SidebarContext"; import { authService } from "@/services/authService"; import { useNavigate } from "react-router-dom"; +import { Button } from "@/components/ui/button"; +import { Plus } from "lucide-react"; const InstanceMonitoring = () => { const { theme } = useTheme(); @@ -27,6 +30,7 @@ const InstanceMonitoring = () => { }); const [currentUser, setCurrentUser] = useState(authService.getCurrentUser()); + const [addDialogOpen, setAddDialogOpen] = useState(false); const { data: servers = [], isLoading, error, refetch } = useQuery({ queryKey: ['servers'], @@ -48,6 +52,10 @@ const InstanceMonitoring = () => { authService.logout(); navigate('/login'); }; + + const handleAgentAdded = () => { + refetch(); + }; if (error) { return ( @@ -102,6 +110,10 @@ const InstanceMonitoring = () => { Monitor and manage your server instances in real-time

+
@@ -117,6 +129,12 @@ const InstanceMonitoring = () => {
+ +
); }; diff --git a/application/src/utils/copyUtils.ts b/application/src/utils/copyUtils.ts new file mode 100644 index 0000000..24483d4 --- /dev/null +++ b/application/src/utils/copyUtils.ts @@ -0,0 +1,60 @@ + +import { toast } from "@/hooks/use-toast"; + +export const copyToClipboard = async (text: string) => { + console.log('copyToClipboard called with text:', text); // Debug log + + try { + // Try modern clipboard API first + if (navigator.clipboard && window.isSecureContext) { + console.log('Using modern clipboard API'); // Debug log + await navigator.clipboard.writeText(text); + toast({ + title: "Copied!", + description: "Content copied to clipboard successfully.", + }); + return; + } + + console.log('Using fallback clipboard method'); // Debug log + + // Fallback for older browsers or non-secure contexts + const textArea = document.createElement("textarea"); + textArea.value = text; + textArea.style.position = "fixed"; + textArea.style.left = "-9999px"; + textArea.style.top = "-9999px"; + textArea.style.opacity = "0"; + textArea.style.pointerEvents = "none"; + textArea.style.zIndex = "-1"; + document.body.appendChild(textArea); + + // Focus and select the text + textArea.focus(); + textArea.select(); + textArea.setSelectionRange(0, textArea.value.length); + + // Use execCommand as fallback + const successful = document.execCommand('copy'); + document.body.removeChild(textArea); + + if (successful) { + console.log('Copy successful with execCommand'); // Debug log + toast({ + title: "Copied!", + description: "Content copied to clipboard successfully.", + }); + } else { + throw new Error('Copy command failed'); + } + } catch (error) { + console.error('Failed to copy to clipboard:', error); + + // Show error toast + toast({ + title: "Copy Failed", + description: "Unable to copy automatically. Please select and copy the text manually.", + variant: "destructive", + }); + } +}; \ No newline at end of file From 63d63a908ab98042f850f2fec96144a6cac6ec92 Mon Sep 17 00:00:00 2001 From: Tola Leng Date: Fri, 18 Jul 2025 22:01:26 +0700 Subject: [PATCH 10/21] Refactor: Make instance monitoring page responsive - Make the Instance Monitoring page and Server Table List fully responsive across the whole page. --- .../src/components/servers/ServerTable.tsx | 331 +++++++++--------- application/src/pages/InstanceMonitoring.tsx | 20 +- 2 files changed, 179 insertions(+), 172 deletions(-) diff --git a/application/src/components/servers/ServerTable.tsx b/application/src/components/servers/ServerTable.tsx index 8664c30..dd1c43b 100644 --- a/application/src/components/servers/ServerTable.tsx +++ b/application/src/components/servers/ServerTable.tsx @@ -149,11 +149,11 @@ export const ServerTable = ({ servers, isLoading, onRefresh }: ServerTableProps) if (isLoading) { return ( - - + + Servers - +
Loading servers... @@ -165,8 +165,8 @@ export const ServerTable = ({ servers, isLoading, onRefresh }: ServerTableProps) return ( <> - - + +
Servers
@@ -185,173 +185,180 @@ export const ServerTable = ({ servers, isLoading, onRefresh }: ServerTableProps)
- + {filteredServers.length === 0 ? ( -
+

No servers found

) : ( -
- - - - Name - Status - OS - IP Address - CPU - Memory - Disk - Uptime - Last Checked - Actions - - - - {filteredServers.map((server) => { - const cpuUsage = server.cpu_usage || 0; - const memoryUsage = server.ram_total > 0 ? (server.ram_used / server.ram_total) * 100 : 0; - const diskUsage = server.disk_total > 0 ? (server.disk_used / server.disk_total) * 100 : 0; - const isPaused = server.status === "paused"; - const isProcessing = pausingServers.has(server.id); +
+
+
+ + + Name + Status + OS + IP Address + CPU + Memory + Disk + Uptime + Last Checked + Actions + + + + {filteredServers.map((server) => { + const cpuUsage = server.cpu_usage || 0; + const memoryUsage = server.ram_total > 0 ? (server.ram_used / server.ram_total) * 100 : 0; + const diskUsage = server.disk_total > 0 ? (server.disk_used / server.disk_total) * 100 : 0; + const isPaused = server.status === "paused"; + const isProcessing = pausingServers.has(server.id); - return ( - - -
-
{server.name}
-
-
- - - - -
- - {server.os_type} -
-
- - {server.ip_address} - - -
-
- {cpuUsage.toFixed(1)}% - {server.cpu_cores} cores + return ( + + +
+ {server.name}
- 90 ? "bg-red-500" : - cpuUsage > 75 ? "bg-orange-500" : - cpuUsage > 60 ? "bg-yellow-500" : "bg-green-500" - } - /> -
- - -
-
- {memoryUsage.toFixed(1)}% - {serverService.formatBytes(server.ram_total)} + + + + + +
+ + + {server.os_type} +
- 90 ? "bg-red-500" : - memoryUsage > 75 ? "bg-yellow-500" : "bg-blue-500" - } - /> -
- - -
-
- {diskUsage.toFixed(1)}% - {serverService.formatBytes(server.disk_total)} + + + + {server.ip_address} + + + +
+
+ {cpuUsage.toFixed(1)}% + {server.cpu_cores} cores +
+ 90 ? "bg-red-500" : + cpuUsage > 75 ? "bg-orange-500" : + cpuUsage > 60 ? "bg-yellow-500" : "bg-green-500" + } + />
- 95 ? "bg-red-500" : - diskUsage > 85 ? "bg-yellow-500" : "bg-orange-500" - } - /> -
- - -
{server.uptime}
-
- - -
- {new Date(server.last_checked).toLocaleString()} -
-
- - - - - - - handleViewDetails(server.id)}> - - View Server Detail - - {server.docker === 'true' && ( - handleViewContainers(server.id)}> - - Container Monitoring + + +
+
+ {memoryUsage.toFixed(1)}% + {serverService.formatBytes(server.ram_total)} +
+ 90 ? "bg-red-500" : + memoryUsage > 75 ? "bg-yellow-500" : "bg-blue-500" + } + /> +
+
+ +
+
+ {diskUsage.toFixed(1)}% + {serverService.formatBytes(server.disk_total)} +
+ 95 ? "bg-red-500" : + diskUsage > 85 ? "bg-yellow-500" : "bg-orange-500" + } + /> +
+
+ +
+ {server.uptime} +
+
+ +
+ {new Date(server.last_checked).toLocaleString()} +
+
+ + + + + + + handleViewDetails(server.id)}> + + View Server Detail - )} - - handlePauseResume(server)} - disabled={isProcessing} - > - {isPaused ? ( - <> - - Resume Monitoring - - ) : ( - <> - - Pause Monitoring - + {server.docker === 'true' && ( + handleViewContainers(server.id)}> + + Container Monitoring + )} - - - handleEdit(server.id)}> - - Edit Server - - handleDelete(server)} - className="text-red-600 focus:text-red-600" - > - - Delete Server - - - - - - ); - })} - -
+ + handlePauseResume(server)} + disabled={isProcessing} + > + {isPaused ? ( + <> + + Resume Monitoring + + ) : ( + <> + + Pause Monitoring + + )} + + + handleEdit(server.id)}> + + Edit Server + + handleDelete(server)} + className="text-red-600 focus:text-red-600" + > + + Delete Server + + + + + + ); + })} + + +
)}
diff --git a/application/src/pages/InstanceMonitoring.tsx b/application/src/pages/InstanceMonitoring.tsx index 1a9c628..6d9f7f0 100644 --- a/application/src/pages/InstanceMonitoring.tsx +++ b/application/src/pages/InstanceMonitoring.tsx @@ -61,7 +61,7 @@ const InstanceMonitoring = () => { return (
-
+
{ return (
-
+
{ toggleSidebar={toggleSidebar} />
-
+
{/* Header Section */} -
+
-

+

Instance Monitoring

-

+

Monitor and manage your server instances in real-time

- @@ -118,12 +118,12 @@ const InstanceMonitoring = () => {
{/* Stats Cards Section */} -
+
- {/* Server Table Section */} -
+ {/* Server Table Section - This will take remaining space */} +
From d40d5898c0745ca03fc575622f1d38e4be5ff473 Mon Sep 17 00:00:00 2001 From: Tola Leng Date: Sat, 19 Jul 2025 15:14:04 +0700 Subject: [PATCH 11/21] Fix SSL frontend notifications - The frontend no longer sends notifications. The backend is now responsible for sending SSL notifications. --- .../ssl/notification/sslCheckNotifier.ts | 139 +++++------------- 1 file changed, 36 insertions(+), 103 deletions(-) diff --git a/application/src/services/ssl/notification/sslCheckNotifier.ts b/application/src/services/ssl/notification/sslCheckNotifier.ts index 5657118..e35a494 100644 --- a/application/src/services/ssl/notification/sslCheckNotifier.ts +++ b/application/src/services/ssl/notification/sslCheckNotifier.ts @@ -1,120 +1,53 @@ import { pb } from "@/lib/pocketbase"; -import { SSLCertificate } from "../types"; -import { determineSSLStatus } from "../sslStatusUtils"; -import { sendSSLNotification } from "./sslNotificationSender"; -import { toast } from "sonner"; +import { SSLCertificate } from "@/types/ssl.types"; /** - * Checks all SSL certificates and sends notifications for expiring ones - * This should be called once per day + * Check a single SSL certificate - notifications are now handled by the backend */ -export async function checkAllCertificatesAndNotify(): Promise { - // console.log("Starting daily SSL certificates check..."); - +export const checkCertificateAndNotify = async (certificate: SSLCertificate): Promise => { try { - // Fetch all SSL certificates from database - const response = await pb.collection('ssl_certificates').getList(1, 100, {}); - // Properly cast the items as SSLCertificate - const certificates = response.items as unknown as SSLCertificate[]; + // console.log(`Checking certificate for ${certificate.domain}...`); - // console.log(`Found ${certificates.length} certificates to check`); + // The actual SSL checking and notifications are now handled by the Go service + // We just need to trigger a check by updating the check_at timestamp - // Check each certificate - for (const cert of certificates) { - await checkCertificateAndNotify(cert); - } + const now = new Date(); - // console.log("Daily SSL certificates check completed"); - } catch (error) { - // console.error("Error during SSL certificates daily check:", error); - } -} - -/** - * Checks a specific SSL certificate and sends notification if needed - * This respects the Warning and Expiry Thresholds set on the certificate - * Note: SSL checking is now handled by the Go service, this function focuses on notifications - */ -export async function checkCertificateAndNotify(certificate: SSLCertificate): Promise { -// console.log(`Checking certificate for ${certificate.domain}...`); - - try { - // Use the current certificate data (updated by Go service) - const daysLeft = certificate.days_left || 0; - - // Get threshold values (ensure they are numbers) - const warningThreshold = Number(certificate.warning_threshold) || 30; - const expiryThreshold = Number(certificate.expiry_threshold) || 7; - - // console.log(`Certificate ${certificate.domain} thresholds: warning=${warningThreshold}, expiry=${expiryThreshold}, days left=${daysLeft}`); - - // Update status based on thresholds - const status = determineSSLStatus(daysLeft, warningThreshold, expiryThreshold); - - // Check if we should send a notification based on thresholds - let shouldNotify = false; - let isCritical = false; - - // Critical notifications - when days left is less than or equal to expiry threshold - if (daysLeft <= expiryThreshold) { - shouldNotify = true; - isCritical = true; - } - // Warning notifications - when days left is less than or equal to warning threshold but greater than expiry threshold - else if (daysLeft <= warningThreshold) { - shouldNotify = true; - isCritical = false; - } - - // console.log(`${certificate.domain}: ${daysLeft} days left, status: ${status}, should notify: ${shouldNotify}, critical: ${isCritical}`); - - // Update certificate status in database + // Update check_at to trigger backend check await pb.collection('ssl_certificates').update(certificate.id, { - status: status + check_at: now.toISOString() }); - - // Send notification if needed - if (shouldNotify && certificate.notification_channel) { - // console.log(`Sending notification for ${certificate.domain}`); - - // Different message based on expiry threshold - const message = isCritical - ? `🚨 CRITICAL: SSL Certificate for ${certificate.domain} will expire in ${daysLeft} days!` - : `⚠️ WARNING: SSL Certificate for ${certificate.domain} will expire in ${daysLeft} days.`; - - // Send the notification using our specialized SSL notification sender - const notificationSent = await sendSSLNotification(certificate, message, isCritical); - - if (notificationSent) { - // Update last_notified timestamp - await pb.collection('ssl_certificates').update(certificate.id, { - last_notified: new Date().toISOString() - }); - // console.log(`Notification sent for ${certificate.domain}`); - // Show toast for manual checks - toast.success(`Notification sent for ${certificate.domain}`); - return true; - } else { - // console.error(`Failed to send notification for ${certificate.domain}`); - // Show error toast for manual checks - toast.error(`Failed to send notification for ${certificate.domain}`); - return false; + } catch (error) { + throw error; + } +}; + +/** + * Check all SSL certificates - backend handles the actual checking and notifications + */ +export const checkAllCertificatesAndNotify = async (): Promise<{ success: number; failed: number }> => { + try { + + const response = await pb.collection('ssl_certificates').getList(1, 100); + const certificates = response.items as unknown as SSLCertificate[]; + + let success = 0; + let failed = 0; + + for (const cert of certificates) { + try { + await checkCertificateAndNotify(cert); + success++; + } catch (error) { + failed++; } - } else if (shouldNotify && !certificate.notification_channel) { - // console.log(`No notification channel set for ${certificate.domain}, skipping notification`); - toast.info(`No notification channel set for ${certificate.domain}, skipping notification`); - } else { - // console.log(`No notification needed for ${certificate.domain} (${daysLeft} days left)`); - // For manual checks, inform the user that thresholds weren't met - toast.info(`Certificate for ${certificate.domain} is valid (${daysLeft} days left)`); } - return true; + return { success, failed }; + } catch (error) { - // console.error(`Error checking certificate for ${certificate.domain}:`, error); - toast.error(`Error checking certificate: ${error instanceof Error ? error.message : "Unknown error"}`); - return false; + throw error; } -} \ No newline at end of file +}; \ No newline at end of file From dc586dc15fef2d925796696baef3d9e814de7b27 Mon Sep 17 00:00:00 2001 From: Tola Leng Date: Sat, 19 Jul 2025 15:23:28 +0700 Subject: [PATCH 12/21] Fix: Server Historical Performance chart improvement Fix: Improve sidebar collapse/expand performance --- .../src/components/dashboard/Sidebar.tsx | 8 +- .../components/dashboard/sidebar/MenuItem.tsx | 16 +- .../docker/DockerContainersTable.tsx | 2 +- .../components/docker/DockerStatsCards.tsx | 55 +- .../components/servers/EditServerDialog.tsx | 680 ++++++++++++++++++ .../components/servers/OneClickInstallTab.tsx | 2 +- .../servers/ServerHistoryCharts.tsx | 34 +- .../src/components/servers/ServerTable.tsx | 349 ++++----- .../components/servers/charts/CPUChart.tsx | 93 ++- .../components/servers/charts/DiskChart.tsx | 93 ++- .../components/servers/charts/MemoryChart.tsx | 93 ++- .../servers/charts/NetworkChart.tsx | 127 ++-- .../components/servers/charts/dataUtils.ts | 113 ++- application/src/pages/InstanceMonitoring.tsx | 10 +- application/src/pages/ServerDetail.tsx | 104 ++- application/src/services/serverService.ts | 271 +++---- .../src/services/serverThresholdService.ts | 85 +++ application/src/services/templateService.ts | 41 +- application/src/types/server.types.ts | 1 + 19 files changed, 1534 insertions(+), 643 deletions(-) create mode 100644 application/src/components/servers/EditServerDialog.tsx create mode 100644 application/src/services/serverThresholdService.ts diff --git a/application/src/components/dashboard/Sidebar.tsx b/application/src/components/dashboard/Sidebar.tsx index 4d15f9d..21ba733 100644 --- a/application/src/components/dashboard/Sidebar.tsx +++ b/application/src/components/dashboard/Sidebar.tsx @@ -13,7 +13,13 @@ export const Sidebar = ({ collapsed }: SidebarProps) => { const { theme } = useTheme(); return ( -
+
diff --git a/application/src/components/dashboard/sidebar/MenuItem.tsx b/application/src/components/dashboard/sidebar/MenuItem.tsx index 019d5ad..ec9f8cd 100644 --- a/application/src/components/dashboard/sidebar/MenuItem.tsx +++ b/application/src/components/dashboard/sidebar/MenuItem.tsx @@ -34,7 +34,6 @@ export const MenuItem: React.FC = ({ e.stopPropagation(); if (hasNavigation && path) { - // Use navigate instead of window.location to prevent full page reload navigate(path, { replace: false }); } }; @@ -44,11 +43,22 @@ export const MenuItem: React.FC = ({ return (
- {!collapsed && {t(translationKey)}} + {!collapsed && ( + + {t(translationKey)} + + )}
); }; \ No newline at end of file diff --git a/application/src/components/docker/DockerContainersTable.tsx b/application/src/components/docker/DockerContainersTable.tsx index 5ec906c..9e39429 100644 --- a/application/src/components/docker/DockerContainersTable.tsx +++ b/application/src/components/docker/DockerContainersTable.tsx @@ -62,7 +62,7 @@ export const DockerContainersTable = ({ containers, isLoading, onRefresh }: Dock
-
+
diff --git a/application/src/components/docker/DockerStatsCards.tsx b/application/src/components/docker/DockerStatsCards.tsx index 6376b6d..cf43d56 100644 --- a/application/src/components/docker/DockerStatsCards.tsx +++ b/application/src/components/docker/DockerStatsCards.tsx @@ -3,44 +3,51 @@ import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"; import { Badge } from "@/components/ui/badge"; import { Container, Play, Square, AlertTriangle } from "lucide-react"; import { DockerStats } from "@/types/docker.types"; +import { useTheme } from "@/contexts/ThemeContext"; interface DockerStatsCardsProps { stats: DockerStats; } export const DockerStatsCards = ({ stats }: DockerStatsCardsProps) => { + const { theme } = useTheme(); + const cards = [ { title: "Total Containers", value: stats.total, icon: Container, color: "text-blue-600", - bgColor: "bg-blue-50", - borderColor: "border-blue-200", + gradient: theme === 'dark' + ? "linear-gradient(135deg, rgba(65, 59, 55, 0.8) 0%, rgba(59, 130, 246, 0.6) 100%)" + : "linear-gradient(135deg, rgba(65, 59, 55, 0.8) 0%, #3b82f6 100%)" }, { title: "Running", value: stats.running, icon: Play, color: "text-green-600", - bgColor: "bg-green-50", - borderColor: "border-green-200", + gradient: theme === 'dark' + ? "linear-gradient(135deg, rgba(65, 59, 55, 0.8) 0%, rgba(16, 185, 129, 0.6) 100%)" + : "linear-gradient(135deg, rgba(65, 59, 55, 0.8) 0%, #10b981 100%)" }, { title: "Stopped", value: stats.stopped, icon: Square, color: "text-gray-600", - bgColor: "bg-gray-50", - borderColor: "border-gray-200", + gradient: theme === 'dark' + ? "linear-gradient(135deg, rgba(65, 59, 55, 0.8) 0%, rgba(107, 114, 128, 0.6) 100%)" + : "linear-gradient(135deg, rgba(65, 59, 55, 0.8) 0%, #6b7280 100%)" }, { title: "Warning", value: stats.warning, icon: AlertTriangle, color: "text-amber-600", - bgColor: "bg-amber-50", - borderColor: "border-amber-200", + gradient: theme === 'dark' + ? "linear-gradient(135deg, rgba(65, 59, 55, 0.8) 0%, rgba(245, 158, 11, 0.6) 100%)" + : "linear-gradient(135deg, rgba(65, 59, 55, 0.8) 0%, #f59e0b 100%)" }, ]; @@ -49,23 +56,39 @@ export const DockerStatsCards = ({ stats }: DockerStatsCardsProps) => { {cards.map((card) => { const IconComponent = card.icon; return ( - - - + + {/* Grid Pattern Overlay */} +
+
+
+ + + {card.title} -
- +
+
- +
-
+
{card.value}
Containers diff --git a/application/src/components/servers/EditServerDialog.tsx b/application/src/components/servers/EditServerDialog.tsx new file mode 100644 index 0000000..9c6b618 --- /dev/null +++ b/application/src/components/servers/EditServerDialog.tsx @@ -0,0 +1,680 @@ +import React, { useState, useEffect } from "react"; +import { Dialog, DialogContent, DialogHeader, DialogTitle } from "@/components/ui/dialog"; +import { Button } from "@/components/ui/button"; +import { Input } from "@/components/ui/input"; +import { Label } from "@/components/ui/label"; +import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select"; +import { Switch } from "@/components/ui/switch"; +import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"; +import { Checkbox } from "@/components/ui/checkbox"; +import { useToast } from "@/hooks/use-toast"; +import { pb } from "@/lib/pocketbase"; +import { Server } from "@/types/server.types"; +import { RefreshCw, X } from "lucide-react"; +import { alertConfigService, AlertConfiguration } from "@/services/alertConfigService"; +import { templateService, NotificationTemplate } from "@/services/templateService"; +import { serverThresholdService, ServerThreshold } from "@/services/serverThresholdService"; + +interface EditServerDialogProps { + server: Server | null; + open: boolean; + onOpenChange: (open: boolean) => void; + onServerUpdated: () => void; +} + +interface ServerFormData { + name: string; + check_interval: number; + retry_attempts: number; + docker_monitoring: boolean; + notification_enabled: boolean; + notification_channels: string[]; // Changed to array for multiple selections + threshold_id: string; + template_id: string; +} + +interface ThresholdFormData { + cpu_threshold: number; + ram_threshold: number; + disk_threshold: number; + network_threshold: number; +} + +export const EditServerDialog: React.FC = ({ + server, + open, + onOpenChange, + onServerUpdated, +}) => { + const [formData, setFormData] = useState({ + name: "", + check_interval: 60, + retry_attempts: 3, + docker_monitoring: false, + notification_enabled: false, + notification_channels: [], // Changed to array + threshold_id: "none", + template_id: "none", + }); + + const [thresholdFormData, setThresholdFormData] = useState({ + cpu_threshold: 80, + ram_threshold: 80, + disk_threshold: 80, + network_threshold: 80, + }); + + const [isSubmitting, setIsSubmitting] = useState(false); + const [alertConfigs, setAlertConfigs] = useState([]); + const [templates, setTemplates] = useState([]); + const [thresholds, setThresholds] = useState([]); + const [selectedTemplate, setSelectedTemplate] = useState(null); + const [selectedThreshold, setSelectedThreshold] = useState(null); + const [loadingAlertConfigs, setLoadingAlertConfigs] = useState(false); + const [loadingTemplates, setLoadingTemplates] = useState(false); + const [loadingThresholds, setLoadingThresholds] = useState(false); + const { toast } = useToast(); + + // Initialize form data when server changes + useEffect(() => { + if (server) { + console.log("Setting form data for server:", server); + // Parse comma-separated notification_id into array + const notificationChannels = server.notification_id + ? server.notification_id.split(',').map(id => id.trim()).filter(id => id) + : []; + + setFormData({ + name: server.name || "", + check_interval: server.check_interval || 60, + retry_attempts: 3, + docker_monitoring: server.docker === "true", + notification_enabled: notificationChannels.length > 0, + notification_channels: notificationChannels, + threshold_id: server.threshold_id || "none", + template_id: server.template_id || "none", + }); + } + }, [server]); + + // Load data when dialog opens + useEffect(() => { + if (open) { + loadAlertConfigurations(); + loadTemplates(); + loadThresholds(); + } + }, [open]); + + // Load existing threshold data when thresholds are loaded and we have a server with threshold_id + useEffect(() => { + if (server && server.threshold_id && thresholds.length > 0) { + console.log("Loading existing threshold data for server:", server.threshold_id); + const existingThreshold = thresholds.find(t => t.id === server.threshold_id); + if (existingThreshold) { + console.log("Found existing threshold:", existingThreshold); + setSelectedThreshold(existingThreshold); + // Handle the API response format with proper field names and type conversion + setThresholdFormData({ + cpu_threshold: parseInt(String(existingThreshold.cpu_threshold)) || 80, + ram_threshold: parseInt(String((existingThreshold as any).ram_threshold_message || existingThreshold.ram_threshold)) || 80, + disk_threshold: parseInt(String(existingThreshold.disk_threshold)) || 80, + network_threshold: parseInt(String(existingThreshold.network_threshold)) || 80, + }); + } + } + }, [server, thresholds]); + + // Update selected template when form data or templates change + useEffect(() => { + if (formData.template_id && formData.template_id !== "none" && templates.length > 0) { + const template = templates.find(t => t.id === formData.template_id); + setSelectedTemplate(template || null); + } else { + setSelectedTemplate(null); + } + }, [formData.template_id, templates]); + + // Update selected threshold when threshold_id changes in form + useEffect(() => { + if (formData.threshold_id && formData.threshold_id !== "none" && thresholds.length > 0) { + const threshold = thresholds.find(t => t.id === formData.threshold_id); + setSelectedThreshold(threshold || null); + if (threshold) { + // Handle the API response format with proper field names and type conversion + setThresholdFormData({ + cpu_threshold: parseInt(String(threshold.cpu_threshold)) || 80, + ram_threshold: parseInt(String((threshold as any).ram_threshold_message || threshold.ram_threshold)) || 80, + disk_threshold: parseInt(String(threshold.disk_threshold)) || 80, + network_threshold: parseInt(String(threshold.network_threshold)) || 80, + }); + } + } else if (formData.threshold_id === "none") { + setSelectedThreshold(null); + setThresholdFormData({ + cpu_threshold: 80, + ram_threshold: 80, + disk_threshold: 80, + network_threshold: 80, + }); + } + }, [formData.threshold_id, thresholds]); + + const loadAlertConfigurations = async () => { + try { + setLoadingAlertConfigs(true); + const configs = await alertConfigService.getAlertConfigurations(); + setAlertConfigs(configs); + } catch (error) { + console.error('Error loading alert configurations:', error); + toast({ + variant: "destructive", + title: "Error", + description: "Failed to load notification channels", + }); + } finally { + setLoadingAlertConfigs(false); + } + }; + + const loadTemplates = async () => { + try { + setLoadingTemplates(true); + const templateList = await templateService.getTemplates(); + setTemplates(templateList); + } catch (error) { + console.error('Error loading templates:', error); + toast({ + variant: "destructive", + title: "Error", + description: "Failed to load templates", + }); + } finally { + setLoadingTemplates(false); + } + }; + + const loadThresholds = async () => { + try { + setLoadingThresholds(true); + const thresholdList = await serverThresholdService.getServerThresholds(); + setThresholds(thresholdList); + } catch (error) { + console.error('Error loading server thresholds:', error); + toast({ + variant: "destructive", + title: "Error", + description: "Failed to load server thresholds", + }); + } finally { + setLoadingThresholds(false); + } + }; + + const handleThresholdUpdate = async () => { + if (!selectedThreshold) return; + + try { + // Use the correct field name for RAM threshold + const updateData = { + cpu_threshold: thresholdFormData.cpu_threshold, + ram_threshold_message: thresholdFormData.ram_threshold, // Use the correct field name + disk_threshold: thresholdFormData.disk_threshold, + network_threshold: thresholdFormData.network_threshold, + }; + + await serverThresholdService.updateServerThreshold(selectedThreshold.id, updateData); + + // Update local state + setSelectedThreshold({ + ...selectedThreshold, + ...thresholdFormData, + }); + + // Update thresholds list + setThresholds(prev => prev.map(t => + t.id === selectedThreshold.id + ? { ...t, ...thresholdFormData } + : t + )); + + toast({ + title: "Threshold updated", + description: "Server threshold values have been updated successfully.", + }); + } catch (error) { + console.error('Error updating threshold:', error); + toast({ + variant: "destructive", + title: "Error", + description: "Failed to update threshold values.", + }); + } + }; + + const handleNotificationChannelToggle = (channelId: string, checked: boolean) => { + setFormData(prev => ({ + ...prev, + notification_channels: checked + ? [...prev.notification_channels, channelId] + : prev.notification_channels.filter(id => id !== channelId) + })); + }; + + const removeNotificationChannel = (channelId: string) => { + setFormData(prev => ({ + ...prev, + notification_channels: prev.notification_channels.filter(id => id !== channelId) + })); + }; + + const handleSubmit = async (e: React.FormEvent) => { + e.preventDefault(); + if (!server || isSubmitting) return; + + try { + setIsSubmitting(true); + + // Convert notification channels array to comma-separated string + const notificationChannelsString = formData.notification_enabled + ? formData.notification_channels.join(',') + : ""; + + const updateData = { + name: formData.name, + check_interval: formData.check_interval, + docker: formData.docker_monitoring ? "true" : "false", + notification_id: notificationChannelsString, + threshold_id: formData.notification_enabled && formData.threshold_id !== "none" ? formData.threshold_id : "", + template_id: formData.notification_enabled && formData.template_id !== "none" ? formData.template_id : "", + updated: new Date().toISOString(), + }; + + await pb.collection('servers').update(server.id, updateData); + + toast({ + title: "Server updated", + description: `${formData.name} has been updated successfully.`, + }); + + onServerUpdated(); + onOpenChange(false); + + } catch (error) { + console.error('Error updating server:', error); + toast({ + variant: "destructive", + title: "Error", + description: "Failed to update server. Please try again.", + }); + } finally { + setIsSubmitting(false); + } + }; + + const handleCancel = () => { + if (server) { + const notificationChannels = server.notification_id + ? server.notification_id.split(',').map(id => id.trim()).filter(id => id) + : []; + + setFormData({ + name: server.name || "", + check_interval: server.check_interval || 60, + retry_attempts: 3, + docker_monitoring: server.docker === "true", + notification_enabled: notificationChannels.length > 0, + notification_channels: notificationChannels, + threshold_id: server.threshold_id || "none", + template_id: server.template_id || "none", + }); + } + onOpenChange(false); + }; + + return ( + + + + Edit Server Configuration + + +
+
+
+ + setFormData(prev => ({ ...prev, name: e.target.value }))} + placeholder="Enter server name" + required + /> +
+ +
+ + +
+ +
+ + +
+ +
+ +
+ setFormData(prev => ({ + ...prev, + docker_monitoring: checked + }))} + /> + +
+
+
+ + {/* Notification Status Toggle */} +
+
+ setFormData(prev => ({ + ...prev, + notification_enabled: checked, + notification_channels: checked ? prev.notification_channels : [], + threshold_id: checked ? prev.threshold_id : "none", + template_id: checked ? prev.template_id : "none" + }))} + /> + +
+ + {/* Expanded Notification Settings */} + {formData.notification_enabled && ( + + + Notification Settings + + + {/* Multiple Notification Channels Selection */} +
+ +
+ {loadingAlertConfigs ? ( +
Loading channels...
+ ) : alertConfigs.length > 0 ? ( + alertConfigs.map((config) => ( +
+ + handleNotificationChannelToggle(config.id || "", checked as boolean) + } + /> + +
+ )) + ) : ( +
No notification channels available
+ )} +
+ + {/* Selected Channels Display */} + {formData.notification_channels.length > 0 && ( +
+ +
+ {formData.notification_channels.map((channelId) => { + const channel = alertConfigs.find(c => c.id === channelId); + return ( +
+ {channel?.notify_name || channelId} + +
+ ); + })} +
+
+ )} +
+ + {/* Server Set Threshold Selection */} +
+ + +
+ + {/* Editable Threshold Details */} + {selectedThreshold && ( + + + Threshold Details: {selectedThreshold.name} + + + +
+
+ + setThresholdFormData(prev => ({ + ...prev, + cpu_threshold: parseInt(e.target.value) || 0 + }))} + className="mt-1" + /> +
+
+ + setThresholdFormData(prev => ({ + ...prev, + ram_threshold: parseInt(e.target.value) || 0 + }))} + className="mt-1" + /> +
+
+ + setThresholdFormData(prev => ({ + ...prev, + disk_threshold: parseInt(e.target.value) || 0 + }))} + className="mt-1" + /> +
+
+ + setThresholdFormData(prev => ({ + ...prev, + network_threshold: parseInt(e.target.value) || 0 + }))} + className="mt-1" + /> +
+
+
+
+ )} + + {/* Server Template Selection */} +
+ + +
+ + {/* Template Details */} + {selectedTemplate && ( + + + Template Details: {selectedTemplate.name} + + +
+
+ +

+ {selectedTemplate.up_message || "No RAM threshold message defined"} +

+
+
+ +

+ {selectedTemplate.down_message || "No CPU threshold message defined"} +

+
+
+ +

+ {selectedTemplate.incident_message || "No disk threshold message defined"} +

+
+
+ +

+ {selectedTemplate.maintenance_message || "No network threshold message defined"} +

+
+
+
+
+ )} +
+
+ )} +
+ +
+ + +
+ +
+
+ ); +}; \ No newline at end of file diff --git a/application/src/components/servers/OneClickInstallTab.tsx b/application/src/components/servers/OneClickInstallTab.tsx index 767e73e..1067d20 100644 --- a/application/src/components/servers/OneClickInstallTab.tsx +++ b/application/src/components/servers/OneClickInstallTab.tsx @@ -27,7 +27,7 @@ export const OneClickInstallTab: React.FC = ({ onDialogClose, }) => { const getOneClickInstallCommand = () => { - const scriptUrl = "https://raw.githubusercontent.com/operacle/checke-server-agent/refs/heads/main/server-agent.sh"; + const scriptUrl = "https://raw.githubusercontent.com/operacle/checkcle/refs/heads/main/scripts/server-agent.sh"; return `curl -L -o server-agent.sh "${scriptUrl}" chmod +x server-agent.sh diff --git a/application/src/components/servers/ServerHistoryCharts.tsx b/application/src/components/servers/ServerHistoryCharts.tsx index 274f966..688c69f 100644 --- a/application/src/components/servers/ServerHistoryCharts.tsx +++ b/application/src/components/servers/ServerHistoryCharts.tsx @@ -24,7 +24,7 @@ export const ServerHistoryCharts = ({ serverId }: ServerHistoryChartsProps) => { isFetching } = useServerHistoryData(serverId); - //console.log('ServerHistoryCharts: Rendering with serverId:', serverId, 'timeRange:', timeRange); + console.log('ServerHistoryCharts: Rendering with serverId:', serverId, 'timeRange:', timeRange); // Memoize latest data calculation to prevent unnecessary recalculations const latestData = useMemo(() => { @@ -48,10 +48,10 @@ export const ServerHistoryCharts = ({ serverId }: ServerHistoryChartsProps) => {
{/* Skeleton loading cards */} -
+
{[1, 2, 3, 4].map((index) => ( - +
@@ -60,7 +60,7 @@ export const ServerHistoryCharts = ({ serverId }: ServerHistoryChartsProps) => {
-
+
@@ -71,7 +71,7 @@ export const ServerHistoryCharts = ({ serverId }: ServerHistoryChartsProps) => { } if (error) { - // console.error('ServerHistoryCharts: Error loading data:', error); + console.error('ServerHistoryCharts: Error loading data:', error); return (
@@ -122,11 +122,11 @@ export const ServerHistoryCharts = ({ serverId }: ServerHistoryChartsProps) => { ); } - // console.log('ServerHistoryCharts: Rendering charts with', chartData.length, 'data points for time range:', timeRange); + console.log('ServerHistoryCharts: Rendering charts with', chartData.length, 'data points for time range:', timeRange); return (
-
+

Historical Performance

@@ -143,12 +143,20 @@ export const ServerHistoryCharts = ({ serverId }: ServerHistoryChartsProps) => {
- {/* Use CSS Grid for better performance than flexbox */} -
- - - - + {/* Improved responsive grid layout */} +
+
+ +
+
+ +
+
+ +
+
+ +
); diff --git a/application/src/components/servers/ServerTable.tsx b/application/src/components/servers/ServerTable.tsx index dd1c43b..02ceb43 100644 --- a/application/src/components/servers/ServerTable.tsx +++ b/application/src/components/servers/ServerTable.tsx @@ -1,4 +1,3 @@ - import { useState } from "react"; import { useNavigate } from "react-router-dom"; import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "@/components/ui/table"; @@ -13,9 +12,11 @@ import { RefreshCw, Search, Eye, Activity, MoreHorizontal, Pause, Play, Edit, Tr import { Server } from "@/types/server.types"; import { ServerStatusBadge } from "./ServerStatusBadge"; import { OSTypeIcon } from "./OSTypeIcon"; +import { EditServerDialog } from "./EditServerDialog"; import { serverService } from "@/services/serverService"; import { useToast } from "@/hooks/use-toast"; import { pb } from "@/lib/pocketbase"; +import { useTheme } from "@/contexts/ThemeContext"; interface ServerTableProps { servers: Server[]; @@ -24,8 +25,10 @@ interface ServerTableProps { } export const ServerTable = ({ servers, isLoading, onRefresh }: ServerTableProps) => { + const { theme } = useTheme(); const [searchTerm, setSearchTerm] = useState(""); const [deleteDialogOpen, setDeleteDialogOpen] = useState(false); + const [editDialogOpen, setEditDialogOpen] = useState(false); const [selectedServer, setSelectedServer] = useState(null); const [isDeleting, setIsDeleting] = useState(false); const [pausingServers, setPausingServers] = useState>(new Set()); @@ -104,9 +107,9 @@ export const ServerTable = ({ servers, isLoading, onRefresh }: ServerTableProps) } }; - const handleEdit = (serverId: string) => { - // TODO: Implement edit functionality - console.log('Edit server:', serverId); + const handleEdit = (server: Server) => { + setSelectedServer(server); + setEditDialogOpen(true); }; const handleDelete = (server: Server) => { @@ -165,8 +168,8 @@ export const ServerTable = ({ servers, isLoading, onRefresh }: ServerTableProps) return ( <> - - + +
Servers
@@ -185,185 +188,191 @@ export const ServerTable = ({ servers, isLoading, onRefresh }: ServerTableProps)
- + {filteredServers.length === 0 ? ( -
+

No servers found

) : ( -
-
-
- - - Name - Status - OS - IP Address - CPU - Memory - Disk - Uptime - Last Checked - Actions - - - - {filteredServers.map((server) => { - const cpuUsage = server.cpu_usage || 0; - const memoryUsage = server.ram_total > 0 ? (server.ram_used / server.ram_total) * 100 : 0; - const diskUsage = server.disk_total > 0 ? (server.disk_used / server.disk_total) * 100 : 0; - const isPaused = server.status === "paused"; - const isProcessing = pausingServers.has(server.id); +
+
+ + + Name + Status + OS + IP Address + CPU + Memory + Disk + Uptime + Last Checked + Actions + + + + {filteredServers.map((server) => { + const cpuUsage = server.cpu_usage || 0; + const memoryUsage = server.ram_total > 0 ? (server.ram_used / server.ram_total) * 100 : 0; + const diskUsage = server.disk_total > 0 ? (server.disk_used / server.disk_total) * 100 : 0; + const isPaused = server.status === "paused"; + const isProcessing = pausingServers.has(server.id); - return ( - - -
- {server.name} + return ( + + +
+ {server.name} +
+
+ + + + +
+ + + {server.os_type} + +
+
+ + + {server.ip_address} + + + +
+
+ {cpuUsage.toFixed(1)}% + {server.cpu_cores} cores
- - - - - -
- - - {server.os_type} - + 90 ? "bg-red-500" : + cpuUsage > 75 ? "bg-orange-500" : + cpuUsage > 60 ? "bg-yellow-500" : "bg-green-500" + } + /> +
+
+ +
+
+ {memoryUsage.toFixed(1)}% + {serverService.formatBytes(server.ram_total)}
- - - - {server.ip_address} - - - -
-
- {cpuUsage.toFixed(1)}% - {server.cpu_cores} cores -
- 90 ? "bg-red-500" : - cpuUsage > 75 ? "bg-orange-500" : - cpuUsage > 60 ? "bg-yellow-500" : "bg-green-500" - } - /> + 90 ? "bg-red-500" : + memoryUsage > 75 ? "bg-yellow-500" : "bg-blue-500" + } + /> +
+
+ +
+
+ {diskUsage.toFixed(1)}% + {serverService.formatBytes(server.disk_total)}
- - -
-
- {memoryUsage.toFixed(1)}% - {serverService.formatBytes(server.ram_total)} -
- 90 ? "bg-red-500" : - memoryUsage > 75 ? "bg-yellow-500" : "bg-blue-500" - } - /> -
-
- -
-
- {diskUsage.toFixed(1)}% - {serverService.formatBytes(server.disk_total)} -
- 95 ? "bg-red-500" : - diskUsage > 85 ? "bg-yellow-500" : "bg-orange-500" - } - /> -
-
- -
- {server.uptime} -
-
- -
- {new Date(server.last_checked).toLocaleString()} -
-
- - - - - - - handleViewDetails(server.id)}> - - View Server Detail - - {server.docker === 'true' && ( - handleViewContainers(server.id)}> - - Container Monitoring - + 95 ? "bg-red-500" : + diskUsage > 85 ? "bg-yellow-500" : "bg-orange-500" + } + /> +
+
+ +
+ {server.uptime} +
+
+ +
+ {new Date(server.last_checked).toLocaleString()} +
+
+ + + + + + + handleViewDetails(server.id)}> + + View Server Detail + + {server.docker === 'true' && ( + handleViewContainers(server.id)}> + + Container Monitoring - - handleEdit(server.id)}> - - Edit Server - - handleDelete(server)} - className="text-red-600 focus:text-red-600" - > - - Delete Server - - - - - - ); - })} - -
-
+ )} + + handlePauseResume(server)} + disabled={isProcessing} + > + {isPaused ? ( + <> + + Resume Monitoring + + ) : ( + <> + + Pause Monitoring + + )} + + + handleEdit(server)}> + + Edit Server + + handleDelete(server)} + className="text-red-600 focus:text-red-600" + > + + Delete Server + + + + + + ); + })} + +
)} + {/* Edit Server Dialog */} + + {/* Delete Confirmation Dialog */} diff --git a/application/src/components/servers/charts/CPUChart.tsx b/application/src/components/servers/charts/CPUChart.tsx index c5fb013..29dff23 100644 --- a/application/src/components/servers/charts/CPUChart.tsx +++ b/application/src/components/servers/charts/CPUChart.tsx @@ -1,7 +1,7 @@ import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"; import { ChartContainer, ChartTooltip } from "@/components/ui/chart"; -import { AreaChart, Area, XAxis, YAxis, CartesianGrid } from "recharts"; +import { AreaChart, Area, XAxis, YAxis, CartesianGrid, ResponsiveContainer } from "recharts"; import { Cpu } from "lucide-react"; import { useTheme } from "@/contexts/ThemeContext"; import { DetailedTooltipContent } from "./tooltips/DetailedTooltipContent"; @@ -18,65 +18,62 @@ export const CPUChart = ({ data, latestData }: CPUChartProps) => { const getAxisColor = () => theme === 'dark' ? '#9ca3af' : '#6b7280'; return ( - - + +
- +
- CPU Usage + CPU Usage
{latestData && ( -
+
{latestData.cpuUsage}%
{latestData.cpuCores} cores
)} - - - - - - - - - - - - - } - cursor={{ stroke: getGridColor() }} - /> - - - + +
+ + + + + + + + + + + + } + cursor={{ stroke: getGridColor() }} + /> + + + +
); diff --git a/application/src/components/servers/charts/DiskChart.tsx b/application/src/components/servers/charts/DiskChart.tsx index da82c15..d94098c 100644 --- a/application/src/components/servers/charts/DiskChart.tsx +++ b/application/src/components/servers/charts/DiskChart.tsx @@ -1,7 +1,7 @@ import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"; import { ChartContainer, ChartTooltip } from "@/components/ui/chart"; -import { AreaChart, Area, XAxis, YAxis, CartesianGrid } from "recharts"; +import { AreaChart, Area, XAxis, YAxis, CartesianGrid, ResponsiveContainer } from "recharts"; import { HardDrive } from "lucide-react"; import { useTheme } from "@/contexts/ThemeContext"; import { DetailedTooltipContent } from "./tooltips/DetailedTooltipContent"; @@ -18,65 +18,62 @@ export const DiskChart = ({ data, latestData }: DiskChartProps) => { const getAxisColor = () => theme === 'dark' ? '#9ca3af' : '#6b7280'; return ( - - + +
- +
- Disk Usage + Disk Usage
{latestData && ( -
+
{latestData.diskUsagePercent}%
{latestData.diskUsed} / {latestData.diskTotal}
)} - - - - - - - - - - - - - } - cursor={{ stroke: getGridColor() }} - /> - - - + +
+ + + + + + + + + + + + } + cursor={{ stroke: getGridColor() }} + /> + + + +
); diff --git a/application/src/components/servers/charts/MemoryChart.tsx b/application/src/components/servers/charts/MemoryChart.tsx index a0abd7b..be341ff 100644 --- a/application/src/components/servers/charts/MemoryChart.tsx +++ b/application/src/components/servers/charts/MemoryChart.tsx @@ -1,7 +1,7 @@ import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"; import { ChartContainer, ChartTooltip } from "@/components/ui/chart"; -import { AreaChart, Area, XAxis, YAxis, CartesianGrid } from "recharts"; +import { AreaChart, Area, XAxis, YAxis, CartesianGrid, ResponsiveContainer } from "recharts"; import { MemoryStick } from "lucide-react"; import { useTheme } from "@/contexts/ThemeContext"; import { DetailedTooltipContent } from "./tooltips/DetailedTooltipContent"; @@ -18,65 +18,62 @@ export const MemoryChart = ({ data, latestData }: MemoryChartProps) => { const getAxisColor = () => theme === 'dark' ? '#9ca3af' : '#6b7280'; return ( - - + +
- +
- Memory Usage + Memory Usage
{latestData && ( -
+
{latestData.ramUsagePercent}%
{latestData.ramUsed} / {latestData.ramTotal}
)} - - - - - - - - - - - - - } - cursor={{ stroke: getGridColor() }} - /> - - - + +
+ + + + + + + + + + + + } + cursor={{ stroke: getGridColor() }} + /> + + + +
); diff --git a/application/src/components/servers/charts/NetworkChart.tsx b/application/src/components/servers/charts/NetworkChart.tsx index cbb25b5..69985d6 100644 --- a/application/src/components/servers/charts/NetworkChart.tsx +++ b/application/src/components/servers/charts/NetworkChart.tsx @@ -1,7 +1,7 @@ import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"; import { ChartContainer, ChartTooltip } from "@/components/ui/chart"; -import { LineChart, Line, XAxis, YAxis, CartesianGrid } from "recharts"; +import { LineChart, Line, XAxis, YAxis, CartesianGrid, ResponsiveContainer } from "recharts"; import { Wifi } from "lucide-react"; import { useTheme } from "@/contexts/ThemeContext"; import { NetworkTooltipContent } from "./tooltips/NetworkTooltipContent"; @@ -18,84 +18,77 @@ export const NetworkChart = ({ data, latestData }: NetworkChartProps) => { const getAxisColor = () => theme === 'dark' ? '#9ca3af' : '#6b7280'; return ( - - + +
- +
- Network Traffic + Network Traffic
{latestData && ( -
+
{latestData.networkRxSpeed} KB/s ↓
{latestData.networkTxSpeed} KB/s ↑
)} - - - - - - - - - - - - - - - - - - - - } - cursor={{ stroke: getGridColor() }} - /> - - - - + +
+ + + + + + + + + + + + + + + + + + + } + cursor={{ stroke: getGridColor() }} + /> + + + + +
); diff --git a/application/src/components/servers/charts/dataUtils.ts b/application/src/components/servers/charts/dataUtils.ts index d58e2a0..20976fe 100644 --- a/application/src/components/servers/charts/dataUtils.ts +++ b/application/src/components/servers/charts/dataUtils.ts @@ -1,4 +1,3 @@ - export const formatBytes = (bytes: number, decimals = 2) => { if (bytes === 0) return '0 B'; const k = 1024; @@ -49,34 +48,82 @@ export const timeRangeOptions = [ { value: '3m' as TimeRange, label: 'Last 90 days', hours: 24 * 90 }, ]; -// Optimized time range filtering with early returns export const filterMetricsByTimeRange = (metrics: any[], timeRange: TimeRange): any[] => { - if (!metrics?.length) return []; + if (!metrics?.length) { + console.log('🔍 filterMetricsByTimeRange: No metrics provided'); + return []; + } + + console.log('🔍 filterMetricsByTimeRange: Starting with', metrics.length, 'metrics for', timeRange); const now = new Date(); + + // For 60m, let's be very specific about what we're looking for + if (timeRange === '60m') { + console.log('⏰ 60m filter: Current time:', now.toISOString()); + + // Try exact 60 minutes first + const cutoffTime60m = new Date(now.getTime() - (60 * 60 * 1000)); + console.log('⏰ 60m filter: Cutoff time:', cutoffTime60m.toISOString()); + + const filtered60m = metrics.filter(metric => { + const metricTime = new Date(metric.created || metric.timestamp); + const isWithinRange = metricTime >= cutoffTime60m && metricTime <= now; + + if (!isWithinRange) { + const ageMinutes = Math.round((now.getTime() - metricTime.getTime()) / (1000 * 60)); + console.log('⏰ Excluding record:', { + created: metric.created || metric.timestamp, + ageMinutes: ageMinutes, + reason: ageMinutes > 60 ? 'too old' : 'future date' + }); + } + + return isWithinRange; + }); + + console.log('✅ 60m strict filter result:', filtered60m.length, 'records'); + + if (filtered60m.length > 0) { + return filtered60m.sort((a, b) => + new Date(a.created || a.timestamp).getTime() - new Date(b.created || b.timestamp).getTime() + ); + } + + // If no data in exactly 60m, show what we have and return it anyway for debugging + console.log('⚠️ No data in 60m range, showing all available data ages:'); + metrics.forEach((metric, index) => { + const metricTime = new Date(metric.created || metric.timestamp); + const ageMinutes = Math.round((now.getTime() - metricTime.getTime()) / (1000 * 60)); + console.log(`Record ${index}: ${metric.created || metric.timestamp} (${ageMinutes} minutes ago)`); + }); + + // Return the most recent data regardless of age + const sorted = metrics.sort((a, b) => + new Date(b.created || b.timestamp).getTime() - new Date(a.created || a.timestamp).getTime() + ); + console.log('🔄 Returning most recent available data for 60m view'); + return sorted.slice(0, 20); // Show last 20 records + } + + // For other time ranges, use normal filtering const selectedRange = timeRangeOptions.find(opt => opt.value === timeRange); if (!selectedRange) return metrics; - // Add small buffer to avoid edge cases - const bufferMinutes = timeRange === '60m' ? 5 : timeRange === '1d' ? 30 : 60; - const cutoffTime = new Date(now.getTime() - (selectedRange.hours * 60 * 60 * 1000) - (bufferMinutes * 60 * 1000)); - -// console.log('filterMetricsByTimeRange: timeRange:', timeRange, 'cutoffTime:', cutoffTime.toISOString()); + const cutoffTime = new Date(now.getTime() - (selectedRange.hours * 60 * 60 * 1000)); const filtered = metrics.filter(metric => { const metricTime = new Date(metric.created || metric.timestamp); return metricTime >= cutoffTime && metricTime <= now; }); -// console.log('filterMetricsByTimeRange: Filtered', metrics.length, 'to', filtered.length, 'metrics'); + console.log('✅ Filtered', metrics.length, 'to', filtered.length, 'metrics for', timeRange); - // Sort by timestamp for proper chart display return filtered.sort((a, b) => new Date(a.created || a.timestamp).getTime() - new Date(b.created || b.timestamp).getTime() ); }; -// Optimized timestamp formatting with caching const timestampCache = new Map(); const formatTimestamp = (timestamp: string, timeRange: TimeRange): string => { @@ -118,7 +165,6 @@ const formatTimestamp = (timestamp: string, timeRange: TimeRange): string => { }); } - // Cache the result (limit cache size) if (timestampCache.size > 1000) { timestampCache.clear(); } @@ -127,37 +173,49 @@ const formatTimestamp = (timestamp: string, timeRange: TimeRange): string => { return formatted; }; -// Optimized chart data formatting with better performance export const formatChartData = (metrics: any[], timeRange: TimeRange) => { -// console.log('formatChartData: Input metrics count:', metrics?.length || 0, 'timeRange:', timeRange); + console.log('📊 formatChartData: Processing', { + inputCount: metrics?.length || 0, + timeRange, + sampleMetric: metrics?.[0] ? { + id: metrics[0].id, + created: metrics[0].created, + timestamp: metrics[0].timestamp, + server_id: metrics[0].server_id + } : null + }); - if (!metrics?.length) return []; + if (!metrics?.length) { + console.log('❌ formatChartData: No metrics provided'); + return []; + } const filteredMetrics = filterMetricsByTimeRange(metrics, timeRange); -// console.log('formatChartData: After time filtering:', filteredMetrics?.length || 0, 'metrics'); + console.log('📊 formatChartData: After time filtering:', filteredMetrics?.length || 0, 'metrics'); - if (!filteredMetrics.length) return []; + if (!filteredMetrics.length) { + console.log('❌ formatChartData: No metrics after time filtering'); + return []; + } // Dynamic sampling based on time range and data volume let maxDataPoints: number; switch (timeRange) { - case '60m': maxDataPoints = 60; break; // 1 point per minute max - case '1d': maxDataPoints = 144; break; // 1 point per 10 minutes max - case '7d': maxDataPoints = 168; break; // 1 point per hour max - case '1m': maxDataPoints = 120; break; // 1 point per 6 hours max - case '3m': maxDataPoints = 90; break; // 1 point per day max + case '60m': maxDataPoints = 60; break; + case '1d': maxDataPoints = 144; break; + case '7d': maxDataPoints = 168; break; + case '1m': maxDataPoints = 120; break; + case '3m': maxDataPoints = 90; break; default: maxDataPoints = 100; } - // Smart sampling - only sample if we have significantly more data const sampledMetrics = filteredMetrics.length > maxDataPoints * 1.2 ? filteredMetrics.filter((_, index) => index % Math.ceil(filteredMetrics.length / maxDataPoints) === 0) : filteredMetrics; -// console.log('formatChartData: After sampling:', sampledMetrics.length, 'metrics for display'); + console.log('📊 formatChartData: After sampling:', sampledMetrics.length, 'metrics for display'); - // Batch process the data transformation for better performance - return sampledMetrics.map((metric) => { + const formattedData = sampledMetrics.map((metric) => { const cpuUsage = typeof metric.cpu_usage === 'string' ? parseFloat(metric.cpu_usage.replace('%', '')) : parseFloat(metric.cpu_usage) || 0; @@ -210,4 +268,7 @@ export const formatChartData = (metrics: any[], timeRange: TimeRange) => { networkTxSpeed: Math.round(networkTxSpeed * 100) / 100, }; }); + + console.log('✅ formatChartData: Final formatted data count:', formattedData.length); + return formattedData; }; \ No newline at end of file diff --git a/application/src/pages/InstanceMonitoring.tsx b/application/src/pages/InstanceMonitoring.tsx index 6d9f7f0..c8dc476 100644 --- a/application/src/pages/InstanceMonitoring.tsx +++ b/application/src/pages/InstanceMonitoring.tsx @@ -98,9 +98,9 @@ const InstanceMonitoring = () => { toggleSidebar={toggleSidebar} />
-
+
{/* Header Section */} -
+

@@ -118,12 +118,12 @@ const InstanceMonitoring = () => {

{/* Stats Cards Section */} -
+
- {/* Server Table Section - This will take remaining space */} -
+ {/* Server Table Section */} +
diff --git a/application/src/pages/ServerDetail.tsx b/application/src/pages/ServerDetail.tsx index 3dee273..1a432b2 100644 --- a/application/src/pages/ServerDetail.tsx +++ b/application/src/pages/ServerDetail.tsx @@ -1,4 +1,3 @@ - import { useState, useEffect } from "react"; import { useParams, useNavigate } from "react-router-dom"; import { useQuery } from "@tanstack/react-query"; @@ -10,8 +9,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, Database } from "lucide-react"; -import { ServerMetricsCharts } from "@/components/servers/ServerMetricsCharts"; +import { ArrowLeft, Server } from "lucide-react"; import { ServerMetricsOverview } from "@/components/servers/ServerMetricsOverview"; import { ServerHistoryCharts } from "@/components/servers/ServerHistoryCharts"; import { ServerSystemInfoCard } from "@/components/servers/ServerSystemInfoCard"; @@ -24,7 +22,7 @@ const ServerDetail = () => { const { sidebarCollapsed, toggleSidebar } = useSidebar(); const [currentUser, setCurrentUser] = useState(authService.getCurrentUser()); - //console.log('ServerDetail component loaded with serverId:', serverId); + console.log('ServerDetail component loaded with serverId:', serverId); const { data: server, @@ -36,6 +34,70 @@ const ServerDetail = () => { enabled: !!serverId }); + const getOSLogo = (server: any) => { + if (!server) return null; + + // Parse system_info if it's a string, but handle both JSON and plain text + let systemInfo: any = {}; + let systemInfoText = ''; + + if (server.system_info) { + if (typeof server.system_info === 'string') { + // Try to parse as JSON first + try { + systemInfo = JSON.parse(server.system_info); + } catch (error) { + // If JSON parsing fails, treat it as plain text + console.log('system_info is plain text:', server.system_info); + systemInfoText = server.system_info.toLowerCase(); + } + } else { + systemInfo = server.system_info; + } + } + + // Check system_info (both JSON and plain text), then fallback to os_type + const osFromJson = systemInfo.OSName || ''; + const osFromText = systemInfoText; + const osFromType = server.os_type || ''; + + // Combine all OS information for detection + const combinedOSInfo = `${osFromJson} ${osFromText} ${osFromType}`.toLowerCase(); + + console.log('OS detection info:', { osFromJson, osFromText, osFromType, combinedOSInfo }); + + // Check for specific OS distributions first (most specific to least specific) + if (combinedOSInfo.includes('ubuntu')) { + return '/upload/os/ubuntu.png'; + } else if (combinedOSInfo.includes('debian')) { + return '/upload/os/debian.png'; + } else if (combinedOSInfo.includes('centos')) { + return '/upload/os/centos.png'; + } else if (combinedOSInfo.includes('rhel') || combinedOSInfo.includes('red hat')) { + return '/upload/os/rhel.png'; + } else if (combinedOSInfo.includes('fedora')) { + return '/upload/os/fedora.png'; + } else if (combinedOSInfo.includes('suse') || combinedOSInfo.includes('opensuse')) { + return '/upload/os/suse.png'; + } else if (combinedOSInfo.includes('arch')) { + return '/upload/os/arch.png'; + } else if (combinedOSInfo.includes('alpine')) { + return '/upload/os/alpine.png'; + } else if (combinedOSInfo.includes('windows')) { + return '/upload/os/windows.png'; + } else if (combinedOSInfo.includes('macos') || combinedOSInfo.includes('darwin') || combinedOSInfo.includes('mac os')) { + return '/upload/os/macos.png'; + } else if (combinedOSInfo.includes('freebsd')) { + return '/upload/os/freebsd.png'; + } else if (combinedOSInfo.includes('linux') || combinedOSInfo.includes('gnu')) { + // Default to linux.png for any Linux-based system that doesn't match specific distributions + return '/upload/os/linux.png'; + } + + // Final fallback - if we can't determine the OS, default to linux.png + return '/upload/os/linux.png'; + }; + const handleLogout = () => { authService.logout(); navigate('/login'); @@ -46,7 +108,6 @@ const ServerDetail = () => { }; if (serverError) { - // console.error('Server detail error:', serverError); return (
@@ -101,7 +162,7 @@ const ServerDetail = () => { return (
-
+
{
-
- +
+ {server && getOSLogo(server) ? ( + OS Logo + ) : ( + + )}

{server?.name || 'Server Detail'}

-

+

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

- {/* System Info Card */} + + {/* System Info Card */} {server && (
@@ -159,17 +228,10 @@ const ServerDetail = () => {
)} - {/* Historical Charts Section */} - {server && ( -
- -
- )} - - {/* Metrics Charts Section */} + {/* Historical Charts Section - Single comprehensive section */} {server && (
- +
)}
diff --git a/application/src/services/serverService.ts b/application/src/services/serverService.ts index c808783..0f25142 100644 --- a/application/src/services/serverService.ts +++ b/application/src/services/serverService.ts @@ -7,7 +7,7 @@ export const serverService = { const records = await pb.collection('servers').getFullList(); return records; } catch (error) { - // console.error('Error fetching servers:', error); + console.error('Error fetching servers:', error); throw error; } }, @@ -17,190 +17,151 @@ export const serverService = { const record = await pb.collection('servers').getOne(serverId); return record; } catch (error) { - // console.error('Error fetching server:', error); + console.error('Error fetching server:', error); throw error; } }, async getServerMetrics(serverId: string, timeRange?: string): Promise { try { - // console.log('serverService.getServerMetrics: Fetching metrics for serverId:', serverId, 'timeRange:', timeRange); + console.log('🔍 serverService.getServerMetrics: Starting with serverId:', serverId, 'timeRange:', timeRange); // First, get the server to find the correct server_id for metrics let server; try { server = await this.getServer(serverId); - // console.log('serverService.getServerMetrics: Found server:', server); + console.log('✅ serverService.getServerMetrics: Found server:', { + id: server.id, + server_id: server.server_id, + name: server.name + }); } catch (error) { - // console.log('serverService.getServerMetrics: Could not fetch server details:', error); + console.log('❌ serverService.getServerMetrics: Could not fetch server details:', error); } - // Try multiple filter strategies to find data - let filter = ''; - let metricsServerId = serverId; - - // Strategy 1: Use server.server_id if available - if (server && server.server_id) { - metricsServerId = server.server_id; - filter = `server_id = "${metricsServerId}"`; - // console.log('serverService.getServerMetrics: Strategy 1 - Using server.server_id for metrics:', metricsServerId); - } else { - // Strategy 2: Use the serverId directly - filter = `server_id = "${serverId}"`; - // console.log('serverService.getServerMetrics: Strategy 2 - Using serverId directly for metrics:', serverId); - } - - // 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 from:', cutoffISO, 'to now'); - } - - // console.log('serverService.getServerMetrics: Final filter:', filter); - - // Fetch filtered records with proper sorting - let records = await pb.collection('server_metrics').getFullList({ - filter: filter, + // Let's first check what data exists in the database for this server + console.log('🔍 Checking all records for this server...'); + const allServerRecords = await pb.collection('server_metrics').getFullList({ + filter: `server_id = "${serverId}" || server_id = "${server?.server_id}" || server_id = "${server?.id}"`, sort: '-created', requestKey: null }); - - // console.log('serverService.getServerMetrics: Found', records.length, 'records with primary filter'); - // If no records found with primary strategy, try fallback strategies - if (records.length === 0) { - // console.log('serverService.getServerMetrics: No records found, trying fallback strategies...'); - - // Fallback 1: Try without agent_id filter - let fallbackFilter = `server_id = "${metricsServerId}"`; - 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(); - fallbackFilter += ` && created >= "${cutoffISO}"`; - } - - // console.log('serverService.getServerMetrics: Trying fallback filter without agent_id:', fallbackFilter); - records = await pb.collection('server_metrics').getFullList({ - filter: fallbackFilter, - sort: '-created', - requestKey: null + console.log('📊 Found total records for server:', allServerRecords.length); + if (allServerRecords.length > 0) { + console.log('📅 Date range of all records:', { + newest: allServerRecords[0]?.created, + oldest: allServerRecords[allServerRecords.length - 1]?.created }); - // console.log('serverService.getServerMetrics: Fallback found', records.length, 'records'); - - // Fallback 2: Try with different server_id strategies - if (records.length === 0) { - const alternativeIds = [serverId, server?.server_id, server?.id].filter(Boolean); - // console.log('serverService.getServerMetrics: Trying alternative server IDs:', alternativeIds); + // Check last 5 records + console.log('🔄 Last 5 records timestamps:', allServerRecords.slice(0, 5).map(r => ({ + created: r.created, + age_minutes: Math.round((new Date().getTime() - new Date(r.created).getTime()) / (1000 * 60)) + }))); + } + + // Calculate time range for filtering + const now = new Date(); + let cutoffTime; + + if (timeRange === '60m') { + cutoffTime = new Date(now.getTime() - (60 * 60 * 1000)); // Exactly 60 minutes + console.log('⏰ 60m filter: Looking for records newer than:', cutoffTime.toISOString()); + console.log('⏰ Current time:', now.toISOString()); + console.log('⏰ Time difference in minutes:', Math.round((now.getTime() - cutoffTime.getTime()) / (1000 * 60))); + } else if (timeRange === '1d') { + cutoffTime = new Date(now.getTime() - (24 * 60 * 60 * 1000)); + } else if (timeRange === '7d') { + cutoffTime = new Date(now.getTime() - (7 * 24 * 60 * 60 * 1000)); + } else if (timeRange === '1m') { + cutoffTime = new Date(now.getTime() - (30 * 24 * 60 * 60 * 1000)); + } else if (timeRange === '3m') { + cutoffTime = new Date(now.getTime() - (90 * 24 * 60 * 60 * 1000)); + } else { + cutoffTime = new Date(now.getTime() - (24 * 60 * 60 * 1000)); + } + + // Try to get filtered records + const searchStrategies = [ + server?.server_id, + serverId, + server?.id + ].filter(Boolean); + + let filteredRecords: any[] = []; + + for (const strategy of searchStrategies) { + try { + const cutoffISO = cutoffTime.toISOString(); + const filter = `server_id = "${strategy}" && created >= "${cutoffISO}"`; - for (const altId of alternativeIds) { - if (altId && altId !== metricsServerId) { - let altFilter = `server_id = "${altId}"`; - 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(); - altFilter += ` && created >= "${cutoffISO}"`; - } + console.log(`🔍 Trying filter: ${filter}`); + + const records = await pb.collection('server_metrics').getFullList({ + filter: filter, + sort: '-created', + requestKey: null + }); + + console.log(`📊 Strategy "${strategy}" found ${records.length} records within time range`); + + if (records.length > 0) { + filteredRecords = records; + console.log('✅ Using records from strategy:', strategy); + break; + } + } catch (error) { + console.error(`❌ Error with strategy ${strategy}:`, error); + continue; + } + } + + // If no filtered records found and it's 60m, let's see what we have in a larger window + if (filteredRecords.length === 0 && timeRange === '60m') { + console.log('⚠️ No records found in 60m window, checking last 24 hours...'); + + const last24h = new Date(now.getTime() - (24 * 60 * 60 * 1000)); + + for (const strategy of searchStrategies) { + try { + const filter = `server_id = "${strategy}" && created >= "${last24h.toISOString()}"`; + + const records = await pb.collection('server_metrics').getFullList({ + filter: filter, + sort: '-created', + requestKey: null + }); + + console.log(`📊 Last 24h check for "${strategy}": ${records.length} records`); + + if (records.length > 0) { + console.log('📅 Sample record ages (minutes ago):', records.slice(0, 3).map(r => + Math.round((now.getTime() - new Date(r.created).getTime()) / (1000 * 60)) + )); - // console.log('serverService.getServerMetrics: Trying alternative ID filter:', altFilter); - const altRecords = await pb.collection('server_metrics').getFullList({ - filter: altFilter, - sort: '-created', - requestKey: null - }); - - if (altRecords.length > 0) { - // console.log('serverService.getServerMetrics: Alternative ID found', altRecords.length, 'records'); - records = altRecords; - break; - } + // Return all recent records for 60m if we have any + filteredRecords = records; + break; } + } catch (error) { + console.error(`❌ Error with 24h fallback for ${strategy}:`, error); + continue; } } } - // console.log('serverService.getServerMetrics: Final result:', records.length, 'records found'); - if (records.length > 0) { - // console.log('serverService.getServerMetrics: Sample record:', records[0]); + console.log('🎯 Final result:', filteredRecords.length, 'records found for', timeRange); + if (filteredRecords.length > 0) { + console.log('📅 Returned records age range (minutes ago):', { + newest: Math.round((now.getTime() - new Date(filteredRecords[0].created).getTime()) / (1000 * 60)), + oldest: Math.round((now.getTime() - new Date(filteredRecords[filteredRecords.length - 1].created).getTime()) / (1000 * 60)) + }); } - return records; + return filteredRecords; } catch (error) { - // console.error('Error fetching server metrics:', error); + console.error('❌ Error fetching server metrics:', error); throw error; } }, diff --git a/application/src/services/serverThresholdService.ts b/application/src/services/serverThresholdService.ts new file mode 100644 index 0000000..18a8162 --- /dev/null +++ b/application/src/services/serverThresholdService.ts @@ -0,0 +1,85 @@ + +import { pb } from "@/lib/pocketbase"; + +export interface ServerThreshold { + id: string; + name: string; + cpu_threshold: number; + ram_threshold: number; + disk_threshold: number; + network_threshold: number; + created: string; + updated: string; +} + +export interface CreateUpdateServerThresholdData { + name: string; + cpu_threshold: number; + ram_threshold: number; + disk_threshold: number; + network_threshold: number; +} + +export const serverThresholdService = { + async getServerThresholds(): Promise { + try { + console.log("Fetching server threshold templates"); + const response = await pb.collection('server_threshold_templates').getList(1, 50, { + sort: '-created', + }); + console.log("Server threshold templates response:", response); + return response.items as unknown as ServerThreshold[]; + } catch (error) { + console.error("Error fetching server threshold templates:", error); + throw error; + } + }, + + async getServerThreshold(id: string): Promise { + try { + console.log(`Fetching server threshold template with id: ${id}`); + const response = await pb.collection('server_threshold_templates').getOne(id); + console.log("Server threshold template response:", response); + return response as unknown as ServerThreshold; + } catch (error) { + console.error(`Error fetching server threshold template with id ${id}:`, error); + throw error; + } + }, + + async createServerThreshold(data: CreateUpdateServerThresholdData): Promise { + try { + console.log("Creating new server threshold template with data:", data); + const response = await pb.collection('server_threshold_templates').create(data); + console.log("Create server threshold template response:", response); + return response as unknown as ServerThreshold; + } catch (error) { + console.error("Error creating server threshold template:", error); + throw error; + } + }, + + async updateServerThreshold(id: string, data: Partial): Promise { + try { + console.log(`Updating server threshold template with id: ${id}`, data); + const response = await pb.collection('server_threshold_templates').update(id, data); + console.log("Update server threshold template response:", response); + return response as unknown as ServerThreshold; + } catch (error) { + console.error(`Error updating server threshold template with id ${id}:`, error); + throw error; + } + }, + + async deleteServerThreshold(id: string): Promise { + try { + console.log(`Deleting server threshold template with id: ${id}`); + await pb.collection('server_threshold_templates').delete(id); + console.log("Server threshold template deleted successfully"); + return true; + } catch (error) { + console.error(`Error deleting server threshold template with id ${id}:`, error); + throw error; + } + } +}; \ No newline at end of file diff --git a/application/src/services/templateService.ts b/application/src/services/templateService.ts index 85d5536..fc68e6c 100644 --- a/application/src/services/templateService.ts +++ b/application/src/services/templateService.ts @@ -1,4 +1,5 @@ + import { pb } from "@/lib/pocketbase"; export interface NotificationTemplate { @@ -37,62 +38,62 @@ export interface CreateUpdateTemplateData { export const templateService = { async getTemplates(): Promise { try { - console.log("Fetching notification templates"); - const response = await pb.collection('notification_templates').getList(1, 50, { + console.log("Fetching server notification templates"); + const response = await pb.collection('server_notification_templates').getList(1, 50, { sort: '-created', }); - console.log("Templates response:", response); + console.log("Server templates response:", response); return response.items as unknown as NotificationTemplate[]; } catch (error) { - console.error("Error fetching templates:", error); + console.error("Error fetching server templates:", error); throw error; } }, async getTemplate(id: string): Promise { try { - console.log(`Fetching template with id: ${id}`); - const response = await pb.collection('notification_templates').getOne(id); - console.log("Template response:", response); + console.log(`Fetching server template with id: ${id}`); + const response = await pb.collection('server_notification_templates').getOne(id); + console.log("Server template response:", response); return response as unknown as NotificationTemplate; } catch (error) { - console.error(`Error fetching template with id ${id}:`, error); + console.error(`Error fetching server template with id ${id}:`, error); throw error; } }, async createTemplate(data: CreateUpdateTemplateData): Promise { try { - console.log("Creating new template with data:", data); - const response = await pb.collection('notification_templates').create(data); - console.log("Create template response:", response); + console.log("Creating new server template with data:", data); + const response = await pb.collection('server_notification_templates').create(data); + console.log("Create server template response:", response); return response as unknown as NotificationTemplate; } catch (error) { - console.error("Error creating template:", error); + console.error("Error creating server template:", error); throw error; } }, async updateTemplate(id: string, data: Partial): Promise { try { - console.log(`Updating template with id: ${id}`, data); - const response = await pb.collection('notification_templates').update(id, data); - console.log("Update template response:", response); + console.log(`Updating server template with id: ${id}`, data); + const response = await pb.collection('server_notification_templates').update(id, data); + console.log("Update server template response:", response); return response as unknown as NotificationTemplate; } catch (error) { - console.error(`Error updating template with id ${id}:`, error); + console.error(`Error updating server template with id ${id}:`, error); throw error; } }, async deleteTemplate(id: string): Promise { try { - console.log(`Deleting template with id: ${id}`); - await pb.collection('notification_templates').delete(id); - console.log("Template deleted successfully"); + console.log(`Deleting server template with id: ${id}`); + await pb.collection('server_notification_templates').delete(id); + console.log("Server template deleted successfully"); return true; } catch (error) { - console.error(`Error deleting template with id ${id}:`, error); + console.error(`Error deleting server template with id ${id}:`, error); throw error; } } diff --git a/application/src/types/server.types.ts b/application/src/types/server.types.ts index c346d01..90e0f2b 100644 --- a/application/src/types/server.types.ts +++ b/application/src/types/server.types.ts @@ -19,6 +19,7 @@ export interface Server { last_checked: string; server_token: string; template_id: string; + threshold_id: string; notification_id: string; timestamp: string; connection: string; From c098ecb6bfd9628e2b9844a084caaa449ff19931 Mon Sep 17 00:00:00 2001 From: Tola Leng Date: Sat, 19 Jul 2025 15:37:00 +0700 Subject: [PATCH 13/21] Fix: Apply server table background to SSL table - Apply the same background color and styling from the server table to the SSL table for visual consistency. --- .../ssl-domain/SSLCertificatesTable.tsx | 54 +++++++++---------- 1 file changed, 27 insertions(+), 27 deletions(-) diff --git a/application/src/components/ssl-domain/SSLCertificatesTable.tsx b/application/src/components/ssl-domain/SSLCertificatesTable.tsx index ce6fb15..75df57a 100644 --- a/application/src/components/ssl-domain/SSLCertificatesTable.tsx +++ b/application/src/components/ssl-domain/SSLCertificatesTable.tsx @@ -1,8 +1,8 @@ + import React, { useState } from "react"; import { useQuery, useQueryClient } from "@tanstack/react-query"; import { Plus } from "lucide-react"; import { Button } from "@/components/ui/button"; -import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"; import { Table, TableBody, @@ -37,10 +37,12 @@ import { fetchSSLCertificates, addSSLCertificate, deleteSSLCertificate } from "@ import { pb } from "@/lib/pocketbase"; import { SSLCertificate } from "@/types/ssl.types"; import { useLanguage } from "@/contexts/LanguageContext"; +import { useTheme } from "@/contexts/ThemeContext"; import { toast } from "sonner"; export const SSLCertificatesTable = () => { const { t } = useLanguage(); + const { theme } = useTheme(); const queryClient = useQueryClient(); const [showAddDialog, setShowAddDialog] = useState(false); const [showEditDialog, setShowEditDialog] = useState(false); @@ -65,7 +67,7 @@ export const SSLCertificatesTable = () => { setShowAddDialog(false); toast.success(t('certificateAdded')); } catch (error) { - console.error("Error adding certificate:", error); + // console.error("Error adding certificate:", error); toast.error(t('failedToAddCertificate')); } finally { setIsSubmitting(false); @@ -87,7 +89,7 @@ export const SSLCertificatesTable = () => { setSelectedCertificate(null); toast.success(t('certificateUpdated')); } catch (error) { - console.error("Error updating certificate:", error); + // console.error("Error updating certificate:", error); toast.error(t('failedToUpdateCertificate')); } finally { setIsSubmitting(false); @@ -105,7 +107,7 @@ export const SSLCertificatesTable = () => { setSelectedCertificate(null); toast.success(t('certificateDeleted')); } catch (error) { - console.error("Error deleting certificate:", error); + // console.error("Error deleting certificate:", error); toast.error(t('failedToDeleteCertificate')); } finally { setIsSubmitting(false); @@ -128,24 +130,28 @@ export const SSLCertificatesTable = () => { }; return ( - <> - - +
+ {certificates.length === 0 ? ( +
+ {t('noCertificatesFound')} +
+ ) : ( +
- - - {t('domain')} - {t('status')} - {t('issuer')} - {t('validUntil')} - {t('daysLeft')} - Check Interval - + + + {t('domain')} + {t('status')} + {t('issuer')} + {t('validUntil')} + {t('daysLeft')} + Check Interval + Actions {certificates.map((certificate) => ( - + {certificate.domain} @@ -164,7 +170,7 @@ export const SSLCertificatesTable = () => { {certificate.check_interval || 1} {t('days')} - + { ))}
- - {certificates.length === 0 && ( -
- {t('noCertificatesFound')} -
- )} - - +
+ )} {/* View Certificate Dialog */} { - +
); }; \ No newline at end of file From e5afe276eeae2703b85b9e3eb17844b8043927fa Mon Sep 17 00:00:00 2001 From: Tola Leng Date: Mon, 21 Jul 2025 00:57:04 +0700 Subject: [PATCH 14/21] Feat: Add Heatmap button to service detail page - Adds a "Heatmap" button to the service detail page, displaying a monthly health heatmap of the service, with statuses color-coded as green, red, and warning (yellow/orange). --- .../src/components/services/HeatmapChart.tsx | 174 ++++++++++++++++++ .../src/components/services/HeatmapDialog.tsx | 97 ++++++++++ .../services/ServiceDetailContent.tsx | 2 +- .../src/components/services/ServiceHeader.tsx | 145 +++++++++------ 4 files changed, 356 insertions(+), 62 deletions(-) create mode 100644 application/src/components/services/HeatmapChart.tsx create mode 100644 application/src/components/services/HeatmapDialog.tsx diff --git a/application/src/components/services/HeatmapChart.tsx b/application/src/components/services/HeatmapChart.tsx new file mode 100644 index 0000000..f0b5539 --- /dev/null +++ b/application/src/components/services/HeatmapChart.tsx @@ -0,0 +1,174 @@ + +import { UptimeData } from "@/types/service.types"; +import { format, startOfMonth, endOfMonth, eachDayOfInterval, isSameDay } from "date-fns"; + +interface HeatmapChartProps { + uptimeData: UptimeData[]; + selectedMonth: Date; +} + +export const HeatmapChart = ({ uptimeData, selectedMonth }: HeatmapChartProps) => { + const monthStart = startOfMonth(selectedMonth); + const monthEnd = endOfMonth(selectedMonth); + const daysInMonth = eachDayOfInterval({ start: monthStart, end: monthEnd }); + + const getStatusForDay = (day: Date) => { + const dayData = uptimeData.filter(data => + isSameDay(new Date(data.timestamp), day) + ); + + if (dayData.length === 0) return 'no-data'; + + // Calculate the predominant status for the day + const statusCounts = dayData.reduce((acc, data) => { + acc[data.status] = (acc[data.status] || 0) + 1; + return acc; + }, {} as Record); + + const predominantStatus = Object.entries(statusCounts) + .sort(([,a], [,b]) => b - a)[0][0]; + + return predominantStatus; + }; + + const getStatusColor = (status: string) => { + switch (status) { + case 'up': + return 'bg-emerald-500'; + case 'down': + return 'bg-red-500'; + case 'warning': + return 'bg-amber-500'; + case 'paused': + return 'bg-slate-500'; + default: + return 'bg-slate-700'; + } + }; + + const getStatusLabel = (status: string) => { + switch (status) { + case 'up': + return 'Up'; + case 'down': + return 'Down'; + case 'warning': + return 'Warning'; + case 'paused': + return 'Paused'; + default: + return 'No Data'; + } + }; + + // Group days by weeks + const weeks: Date[][] = []; + let currentWeek: Date[] = []; + + daysInMonth.forEach((day, index) => { + if (index === 0) { + // Fill empty days at the start of the month + const dayOfWeek = day.getDay(); + for (let i = 0; i < dayOfWeek; i++) { + currentWeek.push(new Date(0)); // Placeholder for empty cells + } + } + + currentWeek.push(day); + + if (currentWeek.length === 7) { + weeks.push([...currentWeek]); + currentWeek = []; + } + }); + + // Add remaining days to last week + if (currentWeek.length > 0) { + while (currentWeek.length < 7) { + currentWeek.push(new Date(0)); // Placeholder for empty cells + } + weeks.push(currentWeek); + } + + return ( +
+ {/* Header */} +
+

+ Service Health - {format(selectedMonth, 'MMMM yyyy')} +

+

+ Daily status overview for the current month +

+
+ + {/* Calendar Grid */} +
+ {/* Day labels */} +
+
Sun
+
Mon
+
Tue
+
Wed
+
Thu
+
Fri
+
Sat
+
+ + {/* Calendar days */} +
+ {weeks.map((week, weekIndex) => ( +
+ {week.map((day, dayIndex) => { + const isPlaceholder = day.getTime() === 0; + const status = isPlaceholder ? 'no-data' : getStatusForDay(day); + const dayData = isPlaceholder ? [] : uptimeData.filter(data => + isSameDay(new Date(data.timestamp), day) + ); + + return ( +
0 ? ` (${dayData.length} checks)` : ''}`} + > + {isPlaceholder ? '' : format(day, 'd')} +
+ ); + })} +
+ ))} +
+
+ + {/* Status Legend */} +
+ Status: +
+
+
+ Up +
+
+
+ Warning +
+
+
+ Down +
+
+
+ Paused +
+
+
+
+ ); +}; \ No newline at end of file diff --git a/application/src/components/services/HeatmapDialog.tsx b/application/src/components/services/HeatmapDialog.tsx new file mode 100644 index 0000000..ef15c8b --- /dev/null +++ b/application/src/components/services/HeatmapDialog.tsx @@ -0,0 +1,97 @@ + +import { useState } from "react"; +import { ChevronLeft, ChevronRight } from "lucide-react"; +import { Button } from "@/components/ui/button"; +import { + Dialog, + DialogContent, + DialogDescription, + DialogHeader, + DialogTitle, +} from "@/components/ui/dialog"; +import { HeatmapChart } from "./HeatmapChart"; +import { UptimeData } from "@/types/service.types"; +import { addMonths, subMonths, format } from "date-fns"; + +interface HeatmapDialogProps { + open: boolean; + onOpenChange: (open: boolean) => void; + serviceName: string; + uptimeData: UptimeData[]; +} + +export const HeatmapDialog = ({ + open, + onOpenChange, + serviceName, + uptimeData +}: HeatmapDialogProps) => { + const [selectedMonth, setSelectedMonth] = useState(new Date()); + + const handlePreviousMonth = () => { + setSelectedMonth(prev => subMonths(prev, 1)); + }; + + const handleNextMonth = () => { + setSelectedMonth(prev => addMonths(prev, 1)); + }; + + const handleCurrentMonth = () => { + setSelectedMonth(new Date()); + }; + + return ( + + + + + Health Heatmap - {serviceName} + + + Monthly overview of service health status with daily breakdown + + + +
+ {/* Month Navigation */} +
+ + + + + +
+ + {/* Heatmap Chart */} + +
+
+
+ ); +}; \ No newline at end of file diff --git a/application/src/components/services/ServiceDetailContent.tsx b/application/src/components/services/ServiceDetailContent.tsx index d925312..8c26143 100644 --- a/application/src/components/services/ServiceDetailContent.tsx +++ b/application/src/components/services/ServiceDetailContent.tsx @@ -1,4 +1,3 @@ - import { Service, UptimeData } from "@/types/service.types"; import { ServiceHeader } from "@/components/services/ServiceHeader"; import { ServiceStatsCards } from "@/components/services/ServiceStatsCards"; @@ -37,6 +36,7 @@ export const ServiceDetailContent = ({ onStatusChange={onStatusChange} selectedRegionalAgent={selectedRegionalAgent} onRegionalAgentChange={onRegionalAgentChange} + uptimeData={uptimeData} /> diff --git a/application/src/components/services/ServiceHeader.tsx b/application/src/components/services/ServiceHeader.tsx index 5016297..9bf2b15 100644 --- a/application/src/components/services/ServiceHeader.tsx +++ b/application/src/components/services/ServiceHeader.tsx @@ -1,93 +1,116 @@ -import { ArrowLeft, Globe } from "lucide-react"; +import { ArrowLeft, Globe, BarChart3 } from "lucide-react"; import { useNavigate } from "react-router-dom"; import { Button } from "@/components/ui/button"; import { StatusBadge } from "@/components/services/StatusBadge"; import { ServiceMonitoringButton } from "@/components/services/ServiceMonitoringButton"; import { RegionalAgentFilter } from "@/components/services/RegionalAgentFilter"; -import { Service } from "@/types/service.types"; +import { HeatmapDialog } from "./HeatmapDialog"; +import { Service, UptimeData } from "@/types/service.types"; import { useLanguage } from "@/contexts/LanguageContext"; import { cn } from "@/lib/utils"; +import { useState } from "react"; interface ServiceHeaderProps { service: Service; onStatusChange?: (newStatus: "up" | "down" | "paused" | "warning") => void; selectedRegionalAgent?: string; onRegionalAgentChange?: (agent: string) => void; + uptimeData?: UptimeData[]; } export function ServiceHeader({ service, onStatusChange, selectedRegionalAgent, - onRegionalAgentChange + onRegionalAgentChange, + uptimeData = [] }: ServiceHeaderProps) { const navigate = useNavigate(); const { t } = useLanguage(); + const [showHeatmap, setShowHeatmap] = useState(false); return ( -
- - -
-
-

{service.name}

- - {/* Pulsating Circle Animation */} -
- - + <> +
+ + +
+
+

{service.name}

+ + {/* Pulsating Circle Animation */} +
+ + +
+ + {service.url && ( + + + {service.url} + + )}
- {service.url && ( - + + +
- -
- - - {selectedRegionalAgent !== undefined && onRegionalAgentChange && ( - - )} + + Heatmap + + {selectedRegionalAgent !== undefined && onRegionalAgentChange && ( + + )} +
-
+ + + ); } \ No newline at end of file From ff19d46391c0d845c3bb0dc49a155d50da8255c4 Mon Sep 17 00:00:00 2001 From: Tola Leng Date: Mon, 21 Jul 2025 00:57:47 +0700 Subject: [PATCH 15/21] Updated favicon logo file --- application/src/components/dashboard/sidebar/SidebarHeader.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/application/src/components/dashboard/sidebar/SidebarHeader.tsx b/application/src/components/dashboard/sidebar/SidebarHeader.tsx index 4ba177c..0ebbe2b 100644 --- a/application/src/components/dashboard/sidebar/SidebarHeader.tsx +++ b/application/src/components/dashboard/sidebar/SidebarHeader.tsx @@ -13,7 +13,7 @@ export const SidebarHeader: React.FC = ({ collapsed }) => {
CheckCle From d0ecaaaf8fe3c92ccdb990d11b0b271afe3707dd Mon Sep 17 00:00:00 2001 From: Tola Leng Date: Mon, 21 Jul 2025 01:29:24 +0700 Subject: [PATCH 16/21] Refactor: Improve chart color and style - Fixed chart colors to be fixed with random colors. - Added another chart style. --- .../components/services/ResponseTimeChart.tsx | 453 +++++++++++------- 1 file changed, 287 insertions(+), 166 deletions(-) diff --git a/application/src/components/services/ResponseTimeChart.tsx b/application/src/components/services/ResponseTimeChart.tsx index 51d04e2..3d70d59 100644 --- a/application/src/components/services/ResponseTimeChart.tsx +++ b/application/src/components/services/ResponseTimeChart.tsx @@ -1,27 +1,34 @@ -import { AreaChart, Area, XAxis, YAxis, CartesianGrid, Tooltip, ResponsiveContainer, ReferenceLine, LineChart, Line } from "recharts"; +import { AreaChart, Area, XAxis, YAxis, CartesianGrid, Tooltip, ResponsiveContainer, ReferenceLine, LineChart, Line, BarChart, Bar } from "recharts"; import { UptimeData } from "@/types/service.types"; import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"; import { useTheme } from "@/contexts/ThemeContext"; -import { useMemo } from "react"; +import { useMemo, useState } from "react"; import { format } from "date-fns"; +import { Button } from "@/components/ui/button"; +import { AreaChart as AreaChartIcon, BarChart3, TrendingUp } from "lucide-react"; interface ResponseTimeChartProps { uptimeData: UptimeData[]; } +type ChartType = 'area' | 'line' | 'bar'; + export function ResponseTimeChart({ uptimeData }: ResponseTimeChartProps) { const { theme } = useTheme(); + const [chartType, setChartType] = useState('area'); - // Modern color palette for different chart lines with solid colors at 90-100% opacity - const modernColors = [ - { stroke: '#f59e0b', fill: 'rgba(111, 86, 63, 0.95)' }, // Yellow (changed from blue) - { stroke: '#10b981', fill: 'rgba(0, 84, 56, 0.95)' }, // Emerald - { stroke: '#3b82f6', fill: 'rgba(59, 130, 246, 0.95)' }, // Blue (moved to second position) - { stroke: '#ef4444', fill: 'rgba(239, 68, 68, 0.95)' }, // Red - { stroke: '#8b5cf6', fill: 'rgba(139, 92, 246, 0.95)' }, // Violet - { stroke: '#06b6d4', fill: 'rgba(6, 182, 212, 0.95)' }, // Cyan - { stroke: '#f97316', fill: 'rgba(231, 148, 89, 0.95)' }, // Orange - { stroke: '#84cc16', fill: 'rgba(132, 204, 22, 0.95)' }, // Lime + // Fixed color palette - consistent colors that don't change + const fixedColors = [ + { stroke: '#3b82f6', fill: 'rgba(59, 130, 246, 0.2)', name: 'Ocean Blue' }, + { stroke: '#10b981', fill: 'rgba(16, 185, 129, 0.2)', name: 'Emerald Green' }, + { stroke: '#f59e0b', fill: 'rgba(245, 158, 11, 0.2)', name: 'Golden Amber' }, + { stroke: '#ef4444', fill: 'rgba(239, 68, 68, 0.2)', name: 'Ruby Red' }, + { stroke: '#8b5cf6', fill: 'rgba(139, 92, 246, 0.2)', name: 'Royal Purple' }, + { stroke: '#06b6d4', fill: 'rgba(6, 182, 212, 0.2)', name: 'Sky Cyan' }, + { stroke: '#f97316', fill: 'rgba(249, 115, 22, 0.2)', name: 'Sunset Orange' }, + { stroke: '#84cc16', fill: 'rgba(132, 204, 22, 0.2)', name: 'Fresh Lime' }, + { stroke: '#ec4899', fill: 'rgba(236, 72, 153, 0.2)', name: 'Vibrant Pink' }, + { stroke: '#14b8a6', fill: 'rgba(20, 184, 166, 0.2)', name: 'Ocean Teal' }, ]; // Check if we have data from multiple sources @@ -160,7 +167,7 @@ export function ResponseTimeChart({ uptimeData }: ResponseTimeChartProps) { return [Math.max(0, minValue - padding), maxValue + padding]; }, [chartData, hasMultipleSources]); - // Get unique sources for legend with proper labeling and colors + // Get unique sources for legend with consistent fixed colors const sources = useMemo(() => { if (!hasMultipleSources) return []; @@ -175,25 +182,29 @@ export function ResponseTimeChart({ uptimeData }: ResponseTimeChartProps) { } }); - const regionalSources = Array.from(sourceSet).map((source, index) => { + // Sort sources to ensure consistent ordering + const sortedSources = Array.from(sourceSet).sort(); + + const regionalSources = sortedSources.map((source, index) => { const data = sourceInfo.get(source); const [regionName, agentId] = source.split('|'); // Create proper label with region and agent info let label = regionName; if (data?.agent_id && data.agent_id !== '1') { - label = `${regionName} (${data.agent_id})`; + label = `${regionName} (Agent ${data.agent_id})`; } else if (regionName === 'Default' && data?.agent_id === '1') { - label = `Default System Check (Agent 1)`; + label = `Default System Check`; } - const colorIndex = index % modernColors.length; + const colorIndex = index % fixedColors.length; return { key: `regional_${source.replace('|', '_')}`, label: label, - stroke: modernColors[colorIndex].stroke, - fill: modernColors[colorIndex].fill + stroke: fixedColors[colorIndex].stroke, + fill: fixedColors[colorIndex].fill, + colorName: fixedColors[colorIndex].name }; }); @@ -205,9 +216,10 @@ export function ResponseTimeChart({ uptimeData }: ResponseTimeChartProps) { const defaultSources = (hasPureDefault && !hasRegionalDefault) ? [{ key: 'default', - label: 'Default', - stroke: modernColors[regionalSources.length % modernColors.length].stroke, - fill: modernColors[regionalSources.length % modernColors.length].fill + label: 'Default System', + stroke: fixedColors[regionalSources.length % fixedColors.length].stroke, + fill: fixedColors[regionalSources.length % fixedColors.length].fill, + colorName: fixedColors[regionalSources.length % fixedColors.length].name }] : []; return [...defaultSources, ...regionalSources]; @@ -219,13 +231,13 @@ export function ResponseTimeChart({ uptimeData }: ResponseTimeChartProps) { const data = payload[0].payload; return ( -
-

{String(label)}

-

{String(data.date)}

+
+

{String(label)}

+

{String(data.date)}

{hasMultipleSources ? ( // Multi-source tooltip -
+
{sources.map(source => { const valueKey = source.key === 'default' ? 'defaultValue' : `${source.key}_value`; const statusKey = source.key === 'default' ? 'defaultStatus' : `${source.key}_status`; @@ -234,35 +246,45 @@ export function ResponseTimeChart({ uptimeData }: ResponseTimeChartProps) { if (value === undefined && status === undefined) return null; - let statusColor = "bg-gray-800"; + let statusBadgeClass = "px-2 py-1 rounded-full text-xs font-medium"; let statusText = "No Data"; if (status === "up") { - statusColor = "bg-emerald-800"; + statusBadgeClass += " bg-green-100 text-green-800 dark:bg-green-900 dark:text-green-200"; statusText = "Up"; } else if (status === "down") { - statusColor = "bg-red-800"; + statusBadgeClass += " bg-red-100 text-red-800 dark:bg-red-900 dark:text-red-200"; statusText = "Down"; } else if (status === "warning") { - statusColor = "bg-yellow-800"; + statusBadgeClass += " bg-yellow-100 text-yellow-800 dark:bg-yellow-900 dark:text-yellow-200"; statusText = "Warning"; } else if (status === "paused") { - statusColor = "bg-gray-800"; + statusBadgeClass += " bg-gray-100 text-gray-800 dark:bg-gray-800 dark:text-gray-200"; statusText = "Paused"; + } else { + statusBadgeClass += " bg-gray-100 text-gray-600 dark:bg-gray-800 dark:text-gray-400"; } return ( -
-
+
+
- {String(source.label)} +
+ {String(source.label)} + {source.colorName} +
-
- {status === "paused" ? "Paused" : - value !== null && value !== undefined ? `${value} ms` : "No data"} +
+ + {statusText} + +
+ {status === "paused" ? "Paused" : + value !== null && value !== undefined ? `${value} ms` : "No data"} +
); @@ -271,19 +293,19 @@ export function ResponseTimeChart({ uptimeData }: ResponseTimeChartProps) { ) : ( // Single source tooltip - showing status with color <> -
-
+
- { + { data.status === "up" ? "Up" : data.status === "down" ? "Down" : data.status === "warning" ? "Warning" : "Paused" }
-

+

{data.status === "paused" ? "Monitoring paused" : data.value !== null ? `${data.value} ms` : "No data"}

@@ -295,6 +317,182 @@ export function ResponseTimeChart({ uptimeData }: ResponseTimeChartProps) { return null; }; + // Render different chart types + const renderChart = () => { + const commonProps = { + data: chartData, + margin: { top: 10, right: 30, left: 0, bottom: 30 } + }; + + const commonAxisProps = { + xAxis: { + dataKey: "time", + stroke: theme === 'dark' ? '#666' : '#9ca3af', + angle: -45, + textAnchor: "end" as const, + tick: { fontSize: 10 }, + height: 60, + interval: "preserveStartEnd" as const, + minTickGap: 5 + }, + yAxis: { + stroke: theme === 'dark' ? '#666' : '#9ca3af', + allowDecimals: false, + domain: yAxisDomain + } + }; + + if (hasMultipleSources) { + switch (chartType) { + case 'line': + return ( + + + + + } /> + + {sources.find(s => s.key === 'default') && ( + s.key === 'default')?.stroke} + strokeWidth={3} + dot={{ r: 4, strokeWidth: 2 }} + connectNulls={false} + /> + )} + + {sources.filter(s => s.key !== 'default').map((source) => ( + + ))} + + ); + + case 'bar': + return ( + + + + + } /> + + {sources.find(s => s.key === 'default') && ( + s.key === 'default')?.stroke} + opacity={0.8} + /> + )} + + {sources.filter(s => s.key !== 'default').map((source) => ( + + ))} + + ); + + default: // area + return ( + + + + + } /> + + {sources.find(s => s.key === 'default') && ( + s.key === 'default')?.stroke} + fill={sources.find(s => s.key === 'default')?.fill} + strokeWidth={3} + dot={false} + connectNulls={false} + /> + )} + + {sources.filter(s => s.key !== 'default').map((source) => ( + + ))} + + ); + } + } else { + // Single source charts remain the same + return ( + + + + + } /> + + + + + + + + {chartData.map((entry, index) => + entry.status === 'paused' ? ( + + ) : null + )} + + ); + } + }; + // Check if we have any data to display const hasData = uptimeData.length > 0; @@ -319,21 +517,55 @@ export function ResponseTimeChart({ uptimeData }: ResponseTimeChartProps) { Response Time History - {hasData && ( - - {dateRangeDisplay} - - )} +
+ {hasMultipleSources && ( +
+ + + +
+ )} + {hasData && ( + + {dateRangeDisplay} + + )} +
{hasMultipleSources && ( -
+
+
+ Monitoring Sources: +
{sources.map(source => ( -
+
- {String(source.label)} + {String(source.label)} + ({source.colorName})
))}
@@ -347,118 +579,7 @@ export function ResponseTimeChart({ uptimeData }: ResponseTimeChartProps) { ) : (
- {hasMultipleSources ? ( - - - - - } /> - - {/* Default monitoring area with modern solid background */} - {sources.find(s => s.key === 'default') && ( - s.key === 'default')?.stroke} - fill={sources.find(s => s.key === 'default')?.fill} - strokeWidth={2.5} - dot={false} - connectNulls={false} - /> - )} - - {/* Regional monitoring areas with modern solid backgrounds */} - {sources.filter(s => s.key !== 'default').map((source) => ( - - ))} - - ) : ( - // For single regional agent or default monitoring only - use solid background colors - - - - - } /> - - {/* Modern solid background areas for different statuses */} - - - - - - - {/* Reference lines for paused status */} - {chartData.map((entry, index) => - entry.status === 'paused' ? ( - - ) : null - )} - - )} + {renderChart()}
)} From 50c742d82d94ca410d988dd5e20df94e4db979df Mon Sep 17 00:00:00 2001 From: Tola Leng Date: Mon, 21 Jul 2025 15:26:33 +0700 Subject: [PATCH 17/21] Added new favicon logo file --- application/public/favicon.ico | Bin 46108 -> 57958 bytes application/public/favicon_sidebar.ico | Bin 0 -> 49929 bytes 2 files changed, 0 insertions(+), 0 deletions(-) create mode 100644 application/public/favicon_sidebar.ico diff --git a/application/public/favicon.ico b/application/public/favicon.ico index 7ae5c720ad60353831cd826babd2f54938ee5d72..d5dfc625e98e0e0a40b2ea32a72be6dd3d447628 100644 GIT binary patch literal 57958 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ΠmOKPHXLX{9Wv( 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 QTLJgq)l@1~Fbn>F08L%5yZ`_I literal 46108 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 av7++uUuMnWsx~xae2S;vzUuEIF8nV7E56MD diff --git a/application/public/favicon_sidebar.ico b/application/public/favicon_sidebar.ico new file mode 100644 index 0000000000000000000000000000000000000000..074b8f96e658e5556a8bf93838189246ece3c57b GIT binary patch literal 49929 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 KP0Nib|NB2@K!5T8 literal 0 HcmV?d00001 From 60960cb3e021780e68a47466a89e8e9ed25a80ce Mon Sep 17 00:00:00 2001 From: Tola Leng Date: Mon, 21 Jul 2025 15:51:54 +0700 Subject: [PATCH 18/21] Refactor: Update server progress bar style - Change the server progress bar style in the Instance Monitoring and server table. --- .../src/components/servers/ServerTable.tsx | 126 ++++++++++++------ 1 file changed, 82 insertions(+), 44 deletions(-) diff --git a/application/src/components/servers/ServerTable.tsx b/application/src/components/servers/ServerTable.tsx index 02ceb43..57dc751 100644 --- a/application/src/components/servers/ServerTable.tsx +++ b/application/src/components/servers/ServerTable.tsx @@ -5,7 +5,6 @@ import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"; import { Button } from "@/components/ui/button"; import { Input } from "@/components/ui/input"; import { Badge } from "@/components/ui/badge"; -import { Progress } from "@/components/ui/progress"; import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuSeparator, DropdownMenuTrigger } from "@/components/ui/dropdown-menu"; import { AlertDialog, AlertDialogAction, AlertDialogCancel, AlertDialogContent, AlertDialogDescription, AlertDialogFooter, AlertDialogHeader, AlertDialogTitle } from "@/components/ui/alert-dialog"; import { RefreshCw, Search, Eye, Activity, MoreHorizontal, Pause, Play, Edit, Trash2 } from "lucide-react"; @@ -150,6 +149,70 @@ export const ServerTable = ({ servers, isLoading, onRefresh }: ServerTableProps) } }; + const CustomProgressBar = ({ + value, + label, + subtitle, + type + }: { + value: number; + label: string; + subtitle: string; + type: 'cpu' | 'memory' | 'disk' + }) => { + const getGradientColors = (type: string, value: number) => { + if (type === 'cpu') { + if (value > 90) return 'from-red-500 to-red-600'; + if (value > 75) return 'from-orange-500 to-orange-600'; + if (value > 60) return 'from-yellow-500 to-yellow-600'; + return 'from-green-500 to-green-600'; + } + if (type === 'memory') { + if (value > 90) return 'from-red-500 to-red-600'; + if (value > 75) return 'from-yellow-500 to-yellow-600'; + return 'from-blue-500 to-blue-600'; + } + if (type === 'disk') { + if (value > 95) return 'from-red-500 to-red-600'; + if (value > 85) return 'from-yellow-500 to-yellow-600'; + return 'from-orange-500 to-orange-600'; + } + return 'from-gray-500 to-gray-600'; + }; + + const getTextColor = (value: number) => { + if (value > 90) return 'text-red-600 dark:text-red-400'; + if (value > 75) return 'text-orange-600 dark:text-orange-400'; + if (value > 60) return 'text-yellow-600 dark:text-yellow-400'; + return 'text-green-600 dark:text-green-400'; + }; + + return ( +
+
+ + {label} + + + {subtitle} + +
+
+
+
+
+
+
+
+ +
+
+ ); + }; + if (isLoading) { return ( @@ -242,53 +305,28 @@ export const ServerTable = ({ servers, isLoading, onRefresh }: ServerTableProps) -
-
- {cpuUsage.toFixed(1)}% - {server.cpu_cores} cores -
- 90 ? "bg-red-500" : - cpuUsage > 75 ? "bg-orange-500" : - cpuUsage > 60 ? "bg-yellow-500" : "bg-green-500" - } - /> -
+
-
-
- {memoryUsage.toFixed(1)}% - {serverService.formatBytes(server.ram_total)} -
- 90 ? "bg-red-500" : - memoryUsage > 75 ? "bg-yellow-500" : "bg-blue-500" - } - /> -
+
-
-
- {diskUsage.toFixed(1)}% - {serverService.formatBytes(server.disk_total)} -
- 95 ? "bg-red-500" : - diskUsage > 85 ? "bg-yellow-500" : "bg-orange-500" - } - /> -
+
From 3f3ce9122db7ca1f08cc4969b81d260411844ad7 Mon Sep 17 00:00:00 2001 From: Tola Leng Date: Mon, 21 Jul 2025 16:35:28 +0700 Subject: [PATCH 19/21] Adjust the size of the server title header. --- application/src/pages/InstanceMonitoring.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/application/src/pages/InstanceMonitoring.tsx b/application/src/pages/InstanceMonitoring.tsx index c8dc476..8ab5641 100644 --- a/application/src/pages/InstanceMonitoring.tsx +++ b/application/src/pages/InstanceMonitoring.tsx @@ -103,10 +103,10 @@ const InstanceMonitoring = () => {
-

+

Instance Monitoring

-

+

Monitor and manage your server instances in real-time

From d17a70dfa8f374732c28fd1198e0be4886ecc4c4 Mon Sep 17 00:00:00 2001 From: Tola Leng Date: Mon, 21 Jul 2025 16:38:57 +0700 Subject: [PATCH 20/21] Updated server schema for migration --- .../1752916282_updated_webhook_configs.js | 125 ++++++++++++++++++ ...1752921327_updated_alert_configurations.js | 42 ++++++ ...1752921397_updated_alert_configurations.js | 29 ++++ .../1753085447_updated_server_metrics.js | 29 ++++ 4 files changed, 225 insertions(+) create mode 100644 server/pb_migrations/1752916282_updated_webhook_configs.js create mode 100644 server/pb_migrations/1752921327_updated_alert_configurations.js create mode 100644 server/pb_migrations/1752921397_updated_alert_configurations.js create mode 100644 server/pb_migrations/1753085447_updated_server_metrics.js diff --git a/server/pb_migrations/1752916282_updated_webhook_configs.js b/server/pb_migrations/1752916282_updated_webhook_configs.js new file mode 100644 index 0000000..79f94a7 --- /dev/null +++ b/server/pb_migrations/1752916282_updated_webhook_configs.js @@ -0,0 +1,125 @@ +/// +migrate((app) => { + const collection = app.findCollectionByNameOrId("pbc_45665081") + + // remove field + collection.fields.removeById("select4246785570") + + // add field + collection.fields.addAt(9, new Field({ + "autogeneratePattern": "", + "hidden": false, + "id": "text1579384326", + "max": 0, + "min": 0, + "name": "name", + "pattern": "", + "presentable": false, + "primaryKey": false, + "required": false, + "system": false, + "type": "text" + })) + + // add field + collection.fields.addAt(10, new Field({ + "autogeneratePattern": "", + "hidden": false, + "id": "text1582905952", + "max": 0, + "min": 0, + "name": "method", + "pattern": "", + "presentable": false, + "primaryKey": false, + "required": false, + "system": false, + "type": "text" + })) + + // add field + collection.fields.addAt(11, new Field({ + "autogeneratePattern": "", + "hidden": false, + "id": "text1414600993", + "max": 0, + "min": 0, + "name": "payload_template", + "pattern": "", + "presentable": false, + "primaryKey": false, + "required": false, + "system": false, + "type": "text" + })) + + // update field + collection.fields.addAt(7, new Field({ + "autogeneratePattern": "", + "hidden": false, + "id": "text2168550802", + "max": 0, + "min": 0, + "name": "trigger_events", + "pattern": "", + "presentable": false, + "primaryKey": false, + "required": false, + "system": false, + "type": "text" + })) + + return app.save(collection) +}, (app) => { + const collection = app.findCollectionByNameOrId("pbc_45665081") + + // add field + collection.fields.addAt(4, new Field({ + "hidden": false, + "id": "select4246785570", + "maxSelect": 1, + "name": "event_filters", + "presentable": false, + "required": false, + "system": false, + "type": "select", + "values": [ + "down", + "up", + "ssl_expired", + "warning", + "ssl_ok", + "high_cpu", + "high_memory", + "agent_offline", + "custom_alert" + ] + })) + + // remove field + collection.fields.removeById("text1579384326") + + // remove field + collection.fields.removeById("text1582905952") + + // remove field + collection.fields.removeById("text1414600993") + + // update field + collection.fields.addAt(8, new Field({ + "autogeneratePattern": "", + "hidden": false, + "id": "text2168550802", + "max": 0, + "min": 0, + "name": "timeout", + "pattern": "", + "presentable": false, + "primaryKey": false, + "required": false, + "system": false, + "type": "text" + })) + + return app.save(collection) +}) diff --git a/server/pb_migrations/1752921327_updated_alert_configurations.js b/server/pb_migrations/1752921327_updated_alert_configurations.js new file mode 100644 index 0000000..728ff90 --- /dev/null +++ b/server/pb_migrations/1752921327_updated_alert_configurations.js @@ -0,0 +1,42 @@ +/// +migrate((app) => { + const collection = app.findCollectionByNameOrId("pbc_1938176441") + + // update field + collection.fields.addAt(10, new Field({ + "hidden": false, + "id": "select1358543748", + "maxSelect": 1, + "name": "status", + "presentable": false, + "required": false, + "system": false, + "type": "select", + "values": [ + "disabled", + "enabled" + ] + })) + + return app.save(collection) +}, (app) => { + const collection = app.findCollectionByNameOrId("pbc_1938176441") + + // update field + collection.fields.addAt(10, new Field({ + "hidden": false, + "id": "select1358543748", + "maxSelect": 1, + "name": "enabled", + "presentable": false, + "required": false, + "system": false, + "type": "select", + "values": [ + "true", + "false" + ] + })) + + return app.save(collection) +}) diff --git a/server/pb_migrations/1752921397_updated_alert_configurations.js b/server/pb_migrations/1752921397_updated_alert_configurations.js new file mode 100644 index 0000000..fad5689 --- /dev/null +++ b/server/pb_migrations/1752921397_updated_alert_configurations.js @@ -0,0 +1,29 @@ +/// +migrate((app) => { + const collection = app.findCollectionByNameOrId("pbc_1938176441") + + // add field + collection.fields.addAt(14, new Field({ + "autogeneratePattern": "", + "hidden": false, + "id": "text1553704459", + "max": 0, + "min": 0, + "name": "webhook_id", + "pattern": "", + "presentable": false, + "primaryKey": false, + "required": false, + "system": false, + "type": "text" + })) + + return app.save(collection) +}, (app) => { + const collection = app.findCollectionByNameOrId("pbc_1938176441") + + // remove field + collection.fields.removeById("text1553704459") + + return app.save(collection) +}) diff --git a/server/pb_migrations/1753085447_updated_server_metrics.js b/server/pb_migrations/1753085447_updated_server_metrics.js new file mode 100644 index 0000000..c0ecf6c --- /dev/null +++ b/server/pb_migrations/1753085447_updated_server_metrics.js @@ -0,0 +1,29 @@ +/// +migrate((app) => { + const collection = app.findCollectionByNameOrId("pbc_1998570700") + + // add field + collection.fields.addAt(19, new Field({ + "autogeneratePattern": "", + "hidden": false, + "id": "text3065852031", + "max": 0, + "min": 0, + "name": "message", + "pattern": "", + "presentable": false, + "primaryKey": false, + "required": false, + "system": false, + "type": "text" + })) + + return app.save(collection) +}, (app) => { + const collection = app.findCollectionByNameOrId("pbc_1998570700") + + // remove field + collection.fields.removeById("text3065852031") + + return app.save(collection) +}) From 2d2122d2e5842c36c0598003b1c65317a33c3540 Mon Sep 17 00:00:00 2001 From: Tola Leng Date: Mon, 21 Jul 2025 22:18:27 +0700 Subject: [PATCH 21/21] Improved the Server feature History Data --- application/src/api/realtime/index.ts | 2 +- .../components/servers/EditServerDialog.tsx | 16 ++-- .../servers/ServerHistoryCharts.tsx | 6 +- .../src/components/servers/ServerTable.tsx | 45 ++++------- .../components/servers/charts/dataUtils.ts | 58 +++++++------- .../charts/hooks/useServerHistoryData.ts | 2 +- application/src/pages/ContainerMonitoring.tsx | 14 ++-- application/src/pages/ServerDetail.tsx | 6 +- application/src/services/dockerService.ts | 38 +++++----- application/src/services/serverService.ts | 76 +++++++++---------- .../src/services/serverThresholdService.ts | 30 ++++---- application/src/services/templateService.ts | 30 ++++---- 12 files changed, 155 insertions(+), 168 deletions(-) diff --git a/application/src/api/realtime/index.ts b/application/src/api/realtime/index.ts index 3a53ee0..e114a68 100644 --- a/application/src/api/realtime/index.ts +++ b/application/src/api/realtime/index.ts @@ -2,7 +2,7 @@ // This file handles realtime notifications in a client-side environment // In a production app, this would be a server-side endpoint -console.log("API Realtime endpoint loaded"); +//console.log("API Realtime endpoint loaded"); // Simple implementation that simulates sending notifications export default async function handler(req) { diff --git a/application/src/components/servers/EditServerDialog.tsx b/application/src/components/servers/EditServerDialog.tsx index 9c6b618..cce5755 100644 --- a/application/src/components/servers/EditServerDialog.tsx +++ b/application/src/components/servers/EditServerDialog.tsx @@ -78,7 +78,7 @@ export const EditServerDialog: React.FC = ({ // Initialize form data when server changes useEffect(() => { if (server) { - console.log("Setting form data for server:", server); + // console.log("Setting form data for server:", server); // Parse comma-separated notification_id into array const notificationChannels = server.notification_id ? server.notification_id.split(',').map(id => id.trim()).filter(id => id) @@ -109,10 +109,10 @@ export const EditServerDialog: React.FC = ({ // Load existing threshold data when thresholds are loaded and we have a server with threshold_id useEffect(() => { if (server && server.threshold_id && thresholds.length > 0) { - console.log("Loading existing threshold data for server:", server.threshold_id); + // console.log("Loading existing threshold data for server:", server.threshold_id); const existingThreshold = thresholds.find(t => t.id === server.threshold_id); if (existingThreshold) { - console.log("Found existing threshold:", existingThreshold); + // console.log("Found existing threshold:", existingThreshold); setSelectedThreshold(existingThreshold); // Handle the API response format with proper field names and type conversion setThresholdFormData({ @@ -166,7 +166,7 @@ export const EditServerDialog: React.FC = ({ const configs = await alertConfigService.getAlertConfigurations(); setAlertConfigs(configs); } catch (error) { - console.error('Error loading alert configurations:', error); + // console.error('Error loading alert configurations:', error); toast({ variant: "destructive", title: "Error", @@ -183,7 +183,7 @@ export const EditServerDialog: React.FC = ({ const templateList = await templateService.getTemplates(); setTemplates(templateList); } catch (error) { - console.error('Error loading templates:', error); + // console.error('Error loading templates:', error); toast({ variant: "destructive", title: "Error", @@ -200,7 +200,7 @@ export const EditServerDialog: React.FC = ({ const thresholdList = await serverThresholdService.getServerThresholds(); setThresholds(thresholdList); } catch (error) { - console.error('Error loading server thresholds:', error); + // console.error('Error loading server thresholds:', error); toast({ variant: "destructive", title: "Error", @@ -243,7 +243,7 @@ export const EditServerDialog: React.FC = ({ description: "Server threshold values have been updated successfully.", }); } catch (error) { - console.error('Error updating threshold:', error); + // console.error('Error updating threshold:', error); toast({ variant: "destructive", title: "Error", @@ -301,7 +301,7 @@ export const EditServerDialog: React.FC = ({ onOpenChange(false); } catch (error) { - console.error('Error updating server:', error); + // console.error('Error updating server:', error); toast({ variant: "destructive", title: "Error", diff --git a/application/src/components/servers/ServerHistoryCharts.tsx b/application/src/components/servers/ServerHistoryCharts.tsx index 688c69f..63e313d 100644 --- a/application/src/components/servers/ServerHistoryCharts.tsx +++ b/application/src/components/servers/ServerHistoryCharts.tsx @@ -24,7 +24,7 @@ export const ServerHistoryCharts = ({ serverId }: ServerHistoryChartsProps) => { isFetching } = useServerHistoryData(serverId); - console.log('ServerHistoryCharts: Rendering with serverId:', serverId, 'timeRange:', timeRange); + // console.log('ServerHistoryCharts: Rendering with serverId:', serverId, 'timeRange:', timeRange); // Memoize latest data calculation to prevent unnecessary recalculations const latestData = useMemo(() => { @@ -71,7 +71,7 @@ export const ServerHistoryCharts = ({ serverId }: ServerHistoryChartsProps) => { } if (error) { - console.error('ServerHistoryCharts: Error loading data:', error); + // console.error('ServerHistoryCharts: Error loading data:', error); return (
@@ -122,7 +122,7 @@ export const ServerHistoryCharts = ({ serverId }: ServerHistoryChartsProps) => { ); } - console.log('ServerHistoryCharts: Rendering charts with', chartData.length, 'data points for time range:', timeRange); +// console.log('ServerHistoryCharts: Rendering charts with', chartData.length, 'data points for time range:', timeRange); return (
diff --git a/application/src/components/servers/ServerTable.tsx b/application/src/components/servers/ServerTable.tsx index 57dc751..89c948f 100644 --- a/application/src/components/servers/ServerTable.tsx +++ b/application/src/components/servers/ServerTable.tsx @@ -59,39 +59,26 @@ export const ServerTable = ({ servers, isLoading, onRefresh }: ServerTableProps) try { setPausingServers(prev => new Set(prev).add(serverId)); - if (isPaused) { - // Resume monitoring - await pb.collection('servers').update(serverId, { - status: "up", - last_checked: new Date().toISOString() - }); - - toast({ - title: "Server resumed", - description: `Monitoring resumed for ${server.name}`, - }); - - console.log(`Resume server monitoring: ${serverId}`); - } else { - // Pause monitoring - await pb.collection('servers').update(serverId, { - status: "paused", - last_checked: new Date().toISOString() - }); - - toast({ - title: "Server paused", - description: `Monitoring paused for ${server.name}`, - }); - - console.log(`Pause server monitoring: ${serverId}`); - } + // Only update the status field, preserving all other server configuration + const updateData = { + status: isPaused ? "up" : "paused", + last_checked: new Date().toISOString() + }; + + await pb.collection('servers').update(serverId, updateData); + + toast({ + title: isPaused ? "Server resumed" : "Server paused", + description: `Monitoring ${isPaused ? 'resumed' : 'paused'} for ${server.name}`, + }); + + // console.log(`${isPaused ? 'Resume' : 'Pause'} server monitoring: ${serverId}`); // Refresh the server list to show updated status onRefresh(); } catch (error) { - console.error('Error updating server status:', error); + // console.error('Error updating server status:', error); toast({ variant: "destructive", title: "Error", @@ -138,7 +125,7 @@ export const ServerTable = ({ servers, isLoading, onRefresh }: ServerTableProps) setSelectedServer(null); } catch (error) { - console.error('Error deleting server:', error); + // console.error('Error deleting server:', error); toast({ variant: "destructive", title: "Error", diff --git a/application/src/components/servers/charts/dataUtils.ts b/application/src/components/servers/charts/dataUtils.ts index 20976fe..4f3355e 100644 --- a/application/src/components/servers/charts/dataUtils.ts +++ b/application/src/components/servers/charts/dataUtils.ts @@ -50,21 +50,21 @@ export const timeRangeOptions = [ export const filterMetricsByTimeRange = (metrics: any[], timeRange: TimeRange): any[] => { if (!metrics?.length) { - console.log('🔍 filterMetricsByTimeRange: No metrics provided'); + // console.log('🔍 filterMetricsByTimeRange: No metrics provided'); return []; } - console.log('🔍 filterMetricsByTimeRange: Starting with', metrics.length, 'metrics for', timeRange); + //console.log('🔍 filterMetricsByTimeRange: Starting with', metrics.length, 'metrics for', timeRange); const now = new Date(); // For 60m, let's be very specific about what we're looking for if (timeRange === '60m') { - console.log('⏰ 60m filter: Current time:', now.toISOString()); + // console.log('⏰ 60m filter: Current time:', now.toISOString()); // Try exact 60 minutes first const cutoffTime60m = new Date(now.getTime() - (60 * 60 * 1000)); - console.log('⏰ 60m filter: Cutoff time:', cutoffTime60m.toISOString()); + // console.log('⏰ 60m filter: Cutoff time:', cutoffTime60m.toISOString()); const filtered60m = metrics.filter(metric => { const metricTime = new Date(metric.created || metric.timestamp); @@ -72,17 +72,17 @@ export const filterMetricsByTimeRange = (metrics: any[], timeRange: TimeRange): if (!isWithinRange) { const ageMinutes = Math.round((now.getTime() - metricTime.getTime()) / (1000 * 60)); - console.log('⏰ Excluding record:', { - created: metric.created || metric.timestamp, - ageMinutes: ageMinutes, - reason: ageMinutes > 60 ? 'too old' : 'future date' - }); + // console.log('⏰ Excluding record:', { + // created: metric.created || metric.timestamp, + // ageMinutes: ageMinutes, + // reason: ageMinutes > 60 ? 'too old' : 'future date' + // }); } return isWithinRange; }); - console.log('✅ 60m strict filter result:', filtered60m.length, 'records'); + // console.log('✅ 60m strict filter result:', filtered60m.length, 'records'); if (filtered60m.length > 0) { return filtered60m.sort((a, b) => @@ -91,18 +91,18 @@ export const filterMetricsByTimeRange = (metrics: any[], timeRange: TimeRange): } // If no data in exactly 60m, show what we have and return it anyway for debugging - console.log('⚠️ No data in 60m range, showing all available data ages:'); + // console.log('⚠️ No data in 60m range, showing all available data ages:'); metrics.forEach((metric, index) => { const metricTime = new Date(metric.created || metric.timestamp); const ageMinutes = Math.round((now.getTime() - metricTime.getTime()) / (1000 * 60)); - console.log(`Record ${index}: ${metric.created || metric.timestamp} (${ageMinutes} minutes ago)`); + // console.log(`Record ${index}: ${metric.created || metric.timestamp} (${ageMinutes} minutes ago)`); }); // Return the most recent data regardless of age const sorted = metrics.sort((a, b) => new Date(b.created || b.timestamp).getTime() - new Date(a.created || a.timestamp).getTime() ); - console.log('🔄 Returning most recent available data for 60m view'); + // console.log('🔄 Returning most recent available data for 60m view'); return sorted.slice(0, 20); // Show last 20 records } @@ -117,7 +117,7 @@ export const filterMetricsByTimeRange = (metrics: any[], timeRange: TimeRange): return metricTime >= cutoffTime && metricTime <= now; }); - console.log('✅ Filtered', metrics.length, 'to', filtered.length, 'metrics for', timeRange); + // console.log('✅ Filtered', metrics.length, 'to', filtered.length, 'metrics for', timeRange); return filtered.sort((a, b) => new Date(a.created || a.timestamp).getTime() - new Date(b.created || b.timestamp).getTime() @@ -174,27 +174,27 @@ const formatTimestamp = (timestamp: string, timeRange: TimeRange): string => { }; export const formatChartData = (metrics: any[], timeRange: TimeRange) => { - console.log('📊 formatChartData: Processing', { - inputCount: metrics?.length || 0, - timeRange, - sampleMetric: metrics?.[0] ? { - id: metrics[0].id, - created: metrics[0].created, - timestamp: metrics[0].timestamp, - server_id: metrics[0].server_id - } : null - }); + // console.log('📊 formatChartData: Processing', { + // inputCount: metrics?.length || 0, + // timeRange, + // sampleMetric: metrics?.[0] ? { + // id: metrics[0].id, + // created: metrics[0].created, + // timestamp: metrics[0].timestamp, + // server_id: metrics[0].server_id + // } : null + // }); if (!metrics?.length) { - console.log('❌ formatChartData: No metrics provided'); + // console.log('❌ formatChartData: No metrics provided'); return []; } const filteredMetrics = filterMetricsByTimeRange(metrics, timeRange); - console.log('📊 formatChartData: After time filtering:', filteredMetrics?.length || 0, 'metrics'); + // console.log('📊 formatChartData: After time filtering:', filteredMetrics?.length || 0, 'metrics'); if (!filteredMetrics.length) { - console.log('❌ formatChartData: No metrics after time filtering'); + // console.log('❌ formatChartData: No metrics after time filtering'); return []; } @@ -213,7 +213,7 @@ export const formatChartData = (metrics: any[], timeRange: TimeRange) => { ? filteredMetrics.filter((_, index) => index % Math.ceil(filteredMetrics.length / maxDataPoints) === 0) : filteredMetrics; - console.log('📊 formatChartData: After sampling:', sampledMetrics.length, 'metrics for display'); + // console.log('📊 formatChartData: After sampling:', sampledMetrics.length, 'metrics for display'); const formattedData = sampledMetrics.map((metric) => { const cpuUsage = typeof metric.cpu_usage === 'string' ? @@ -269,6 +269,6 @@ export const formatChartData = (metrics: any[], timeRange: TimeRange) => { }; }); - console.log('✅ formatChartData: Final formatted data count:', formattedData.length); + // console.log('✅ formatChartData: Final formatted data count:', formattedData.length); return formattedData; }; \ No newline at end of file diff --git a/application/src/components/servers/charts/hooks/useServerHistoryData.ts b/application/src/components/servers/charts/hooks/useServerHistoryData.ts index 954f1b2..04e0365 100644 --- a/application/src/components/servers/charts/hooks/useServerHistoryData.ts +++ b/application/src/components/servers/charts/hooks/useServerHistoryData.ts @@ -7,7 +7,7 @@ import { formatChartData } from "../dataUtils"; type TimeRange = '60m' | '1d' | '7d' | '1m' | '3m'; export const useServerHistoryData = (serverId: string) => { - const [timeRange, setTimeRange] = useState("1d"); + const [timeRange, setTimeRange] = useState("60m"); const { data: metrics = [], diff --git a/application/src/pages/ContainerMonitoring.tsx b/application/src/pages/ContainerMonitoring.tsx index c302806..b5090d4 100644 --- a/application/src/pages/ContainerMonitoring.tsx +++ b/application/src/pages/ContainerMonitoring.tsx @@ -29,7 +29,7 @@ const ContainerMonitoring = () => { }); const [currentUser, setCurrentUser] = useState(authService.getCurrentUser()); - console.log('ContainerMonitoring component loaded with serverId:', serverId); + // console.log('ContainerMonitoring component loaded with serverId:', serverId); const { data: containers = [], @@ -39,26 +39,26 @@ const ContainerMonitoring = () => { } = useQuery({ queryKey: ['docker-containers', serverId], queryFn: () => { - console.log('Query function called with serverId:', serverId); + // console.log('Query function called with serverId:', serverId); return serverId ? dockerService.getContainersByServerId(serverId) : dockerService.getContainers(); }, refetchInterval: 30000 // Refetch every 30 seconds }); - console.log('Query state:', { containers, isLoading, error }); + // console.log('Query state:', { containers, isLoading, error }); useEffect(() => { - console.log('Containers changed:', containers); + // console.log('Containers changed:', containers); if (containers.length > 0) { dockerService.getContainerStats(containers).then(newStats => { - console.log('Stats calculated:', newStats); + // console.log('Stats calculated:', newStats); setStats(newStats); }); } }, [containers]); const handleRefresh = () => { - console.log('Manual refresh triggered'); + // console.log('Manual refresh triggered'); refetch(); }; @@ -72,7 +72,7 @@ const ContainerMonitoring = () => { }; if (error) { - console.error('Container monitoring error:', error); + // console.error('Container monitoring error:', error); return (
diff --git a/application/src/pages/ServerDetail.tsx b/application/src/pages/ServerDetail.tsx index 1a432b2..9b714e7 100644 --- a/application/src/pages/ServerDetail.tsx +++ b/application/src/pages/ServerDetail.tsx @@ -22,7 +22,7 @@ const ServerDetail = () => { const { sidebarCollapsed, toggleSidebar } = useSidebar(); const [currentUser, setCurrentUser] = useState(authService.getCurrentUser()); - console.log('ServerDetail component loaded with serverId:', serverId); + //console.log('ServerDetail component loaded with serverId:', serverId); const { data: server, @@ -48,7 +48,7 @@ const ServerDetail = () => { systemInfo = JSON.parse(server.system_info); } catch (error) { // If JSON parsing fails, treat it as plain text - console.log('system_info is plain text:', server.system_info); + // console.log('system_info is plain text:', server.system_info); systemInfoText = server.system_info.toLowerCase(); } } else { @@ -64,7 +64,7 @@ const ServerDetail = () => { // Combine all OS information for detection const combinedOSInfo = `${osFromJson} ${osFromText} ${osFromType}`.toLowerCase(); - console.log('OS detection info:', { osFromJson, osFromText, osFromType, combinedOSInfo }); + // console.log('OS detection info:', { osFromJson, osFromText, osFromType, combinedOSInfo }); // Check for specific OS distributions first (most specific to least specific) if (combinedOSInfo.includes('ubuntu')) { diff --git a/application/src/services/dockerService.ts b/application/src/services/dockerService.ts index d01e98e..8047240 100644 --- a/application/src/services/dockerService.ts +++ b/application/src/services/dockerService.ts @@ -5,25 +5,25 @@ import { DockerContainer, DockerMetrics, DockerStats } from "@/types/docker.type class DockerService { async getContainers(): Promise { try { - console.log('Fetching all Docker containers...'); + // console.log('Fetching all Docker containers...'); const records = await pb.collection('dockers').getFullList({ sort: '-created', }); - console.log('Docker containers fetched:', records); + // console.log('Docker containers fetched:', records); return records as DockerContainer[]; } catch (error) { - console.error('Error fetching Docker containers:', error); + // console.error('Error fetching Docker containers:', error); throw error; } } async getContainersByServerId(serverId: string): Promise { try { - console.log('Fetching Docker containers for server ID:', serverId); + // console.log('Fetching Docker containers for server ID:', serverId); // First, try to get the server details to find the correct hostname const server = await pb.collection('servers').getOne(serverId); - console.log('Server details:', server); + // console.log('Server details:', server); // Try multiple filter approaches to find containers const filters = [ @@ -36,57 +36,57 @@ class DockerService { let containers: DockerContainer[] = []; for (const filter of filters) { - console.log('Trying filter:', filter); + // console.log('Trying filter:', filter); try { const records = await pb.collection('dockers').getFullList({ filter: filter, sort: '-created', }); - console.log(`Filter "${filter}" returned:`, records); + // console.log(`Filter "${filter}" returned:`, records); if (records.length > 0) { containers = records as DockerContainer[]; break; } } catch (filterError) { - console.warn(`Filter "${filter}" failed:`, filterError); + // console.warn(`Filter "${filter}" failed:`, filterError); continue; } } // If no containers found with filters, get all and log for debugging if (containers.length === 0) { - console.log('No containers found with filters, fetching all for debugging...'); + // console.log('No containers found with filters, fetching all for debugging...'); const allContainers = await pb.collection('dockers').getFullList({ sort: '-created', }); - console.log('All available Docker containers:', allContainers); - console.log('Looking for containers that might match server:', { - serverId, - serverHostname: server.hostname, - serverIp: server.ip_address - }); + // console.log('All available Docker containers:', allContainers); + // console.log('Looking for containers that might match server:', { + //// serverId, + // serverHostname: server.hostname, + // serverIp: server.ip_address + // }); } return containers; } catch (error) { - console.error('Error fetching Docker containers by server ID:', error); + // console.error('Error fetching Docker containers by server ID:', error); throw error; } } async getContainerMetrics(dockerId: string): Promise { try { - console.log('Fetching metrics for docker ID:', dockerId); + // console.log('Fetching metrics for docker ID:', dockerId); const records = await pb.collection('docker_metrics').getFullList({ filter: `docker_id = "${dockerId}"`, sort: '-timestamp', perPage: 100, }); - console.log('Docker metrics fetched:', records); + // console.log('Docker metrics fetched:', records); return records as DockerMetrics[]; } catch (error) { - console.error('Error fetching Docker metrics:', error); + // console.error('Error fetching Docker metrics:', error); throw error; } } diff --git a/application/src/services/serverService.ts b/application/src/services/serverService.ts index 0f25142..1bdda23 100644 --- a/application/src/services/serverService.ts +++ b/application/src/services/serverService.ts @@ -7,7 +7,7 @@ export const serverService = { const records = await pb.collection('servers').getFullList(); return records; } catch (error) { - console.error('Error fetching servers:', error); + // console.error('Error fetching servers:', error); throw error; } }, @@ -17,48 +17,48 @@ export const serverService = { const record = await pb.collection('servers').getOne(serverId); return record; } catch (error) { - console.error('Error fetching server:', error); + // console.error('Error fetching server:', error); throw error; } }, async getServerMetrics(serverId: string, timeRange?: string): Promise { try { - console.log('🔍 serverService.getServerMetrics: Starting with serverId:', serverId, 'timeRange:', timeRange); + // console.log('🔍 serverService.getServerMetrics: Starting with serverId:', serverId, 'timeRange:', timeRange); // First, get the server to find the correct server_id for metrics let server; try { server = await this.getServer(serverId); - console.log('✅ serverService.getServerMetrics: Found server:', { - id: server.id, - server_id: server.server_id, - name: server.name - }); + // console.log('✅ serverService.getServerMetrics: Found server:', { + // id: server.id, + // server_id: server.server_id, + // name: server.name + // }); } catch (error) { - console.log('❌ serverService.getServerMetrics: Could not fetch server details:', error); + // console.log('❌ serverService.getServerMetrics: Could not fetch server details:', error); } // Let's first check what data exists in the database for this server - console.log('🔍 Checking all records for this server...'); + // console.log('🔍 Checking all records for this server...'); const allServerRecords = await pb.collection('server_metrics').getFullList({ filter: `server_id = "${serverId}" || server_id = "${server?.server_id}" || server_id = "${server?.id}"`, sort: '-created', requestKey: null }); - console.log('📊 Found total records for server:', allServerRecords.length); + // console.log('📊 Found total records for server:', allServerRecords.length); if (allServerRecords.length > 0) { - console.log('📅 Date range of all records:', { - newest: allServerRecords[0]?.created, - oldest: allServerRecords[allServerRecords.length - 1]?.created - }); + // console.log('📅 Date range of all records:', { + // newest: allServerRecords[0]?.created, + // oldest: allServerRecords[allServerRecords.length - 1]?.created + // }); // Check last 5 records - console.log('🔄 Last 5 records timestamps:', allServerRecords.slice(0, 5).map(r => ({ - created: r.created, - age_minutes: Math.round((new Date().getTime() - new Date(r.created).getTime()) / (1000 * 60)) - }))); + // console.log('🔄 Last 5 records timestamps:', allServerRecords.slice(0, 5).map(r => ({ + // created: r.created, + // age_minutes: Math.round((new Date().getTime() - new Date(r.created).getTime()) / (1000 * 60)) + // }))); } // Calculate time range for filtering @@ -67,9 +67,9 @@ export const serverService = { if (timeRange === '60m') { cutoffTime = new Date(now.getTime() - (60 * 60 * 1000)); // Exactly 60 minutes - console.log('⏰ 60m filter: Looking for records newer than:', cutoffTime.toISOString()); - console.log('⏰ Current time:', now.toISOString()); - console.log('⏰ Time difference in minutes:', Math.round((now.getTime() - cutoffTime.getTime()) / (1000 * 60))); + // console.log('⏰ 60m filter: Looking for records newer than:', cutoffTime.toISOString()); + // console.log('⏰ Current time:', now.toISOString()); + // console.log('⏰ Time difference in minutes:', Math.round((now.getTime() - cutoffTime.getTime()) / (1000 * 60))); } else if (timeRange === '1d') { cutoffTime = new Date(now.getTime() - (24 * 60 * 60 * 1000)); } else if (timeRange === '7d') { @@ -96,7 +96,7 @@ export const serverService = { const cutoffISO = cutoffTime.toISOString(); const filter = `server_id = "${strategy}" && created >= "${cutoffISO}"`; - console.log(`🔍 Trying filter: ${filter}`); + // console.log(`🔍 Trying filter: ${filter}`); const records = await pb.collection('server_metrics').getFullList({ filter: filter, @@ -104,22 +104,22 @@ export const serverService = { requestKey: null }); - console.log(`📊 Strategy "${strategy}" found ${records.length} records within time range`); + // console.log(`📊 Strategy "${strategy}" found ${records.length} records within time range`); if (records.length > 0) { filteredRecords = records; - console.log('✅ Using records from strategy:', strategy); + // console.log('✅ Using records from strategy:', strategy); break; } } catch (error) { - console.error(`❌ Error with strategy ${strategy}:`, error); + // console.error(`❌ Error with strategy ${strategy}:`, error); continue; } } // If no filtered records found and it's 60m, let's see what we have in a larger window if (filteredRecords.length === 0 && timeRange === '60m') { - console.log('⚠️ No records found in 60m window, checking last 24 hours...'); + // console.log('⚠️ No records found in 60m window, checking last 24 hours...'); const last24h = new Date(now.getTime() - (24 * 60 * 60 * 1000)); @@ -133,35 +133,35 @@ export const serverService = { requestKey: null }); - console.log(`📊 Last 24h check for "${strategy}": ${records.length} records`); + // console.log(`📊 Last 24h check for "${strategy}": ${records.length} records`); if (records.length > 0) { - console.log('📅 Sample record ages (minutes ago):', records.slice(0, 3).map(r => - Math.round((now.getTime() - new Date(r.created).getTime()) / (1000 * 60)) - )); + // console.log('📅 Sample record ages (minutes ago):', records.slice(0, 3).map(r => + // Math.round((now.getTime() - new Date(r.created).getTime()) / (1000 * 60)) + // )); // Return all recent records for 60m if we have any filteredRecords = records; break; } } catch (error) { - console.error(`❌ Error with 24h fallback for ${strategy}:`, error); + // console.error(`❌ Error with 24h fallback for ${strategy}:`, error); continue; } } } - console.log('🎯 Final result:', filteredRecords.length, 'records found for', timeRange); + // console.log('🎯 Final result:', filteredRecords.length, 'records found for', timeRange); if (filteredRecords.length > 0) { - console.log('📅 Returned records age range (minutes ago):', { - newest: Math.round((now.getTime() - new Date(filteredRecords[0].created).getTime()) / (1000 * 60)), - oldest: Math.round((now.getTime() - new Date(filteredRecords[filteredRecords.length - 1].created).getTime()) / (1000 * 60)) - }); + // console.log('📅 Returned records age range (minutes ago):', { + // newest: Math.round((now.getTime() - new Date(filteredRecords[0].created).getTime()) / (1000 * 60)), + // oldest: Math.round((now.getTime() - new Date(filteredRecords[filteredRecords.length - 1].created).getTime()) / (1000 * 60)) + // }); } return filteredRecords; } catch (error) { - console.error('❌ Error fetching server metrics:', error); + // console.error('❌ Error fetching server metrics:', error); throw error; } }, diff --git a/application/src/services/serverThresholdService.ts b/application/src/services/serverThresholdService.ts index 18a8162..4a2804c 100644 --- a/application/src/services/serverThresholdService.ts +++ b/application/src/services/serverThresholdService.ts @@ -23,62 +23,62 @@ export interface CreateUpdateServerThresholdData { export const serverThresholdService = { async getServerThresholds(): Promise { try { - console.log("Fetching server threshold templates"); + // console.log("Fetching server threshold templates"); const response = await pb.collection('server_threshold_templates').getList(1, 50, { sort: '-created', }); - console.log("Server threshold templates response:", response); + // console.log("Server threshold templates response:", response); return response.items as unknown as ServerThreshold[]; } catch (error) { - console.error("Error fetching server threshold templates:", error); + // console.error("Error fetching server threshold templates:", error); throw error; } }, async getServerThreshold(id: string): Promise { try { - console.log(`Fetching server threshold template with id: ${id}`); + // console.log(`Fetching server threshold template with id: ${id}`); const response = await pb.collection('server_threshold_templates').getOne(id); - console.log("Server threshold template response:", response); + // console.log("Server threshold template response:", response); return response as unknown as ServerThreshold; } catch (error) { - console.error(`Error fetching server threshold template with id ${id}:`, error); + // console.error(`Error fetching server threshold template with id ${id}:`, error); throw error; } }, async createServerThreshold(data: CreateUpdateServerThresholdData): Promise { try { - console.log("Creating new server threshold template with data:", data); + // console.log("Creating new server threshold template with data:", data); const response = await pb.collection('server_threshold_templates').create(data); - console.log("Create server threshold template response:", response); + // console.log("Create server threshold template response:", response); return response as unknown as ServerThreshold; } catch (error) { - console.error("Error creating server threshold template:", error); + // console.error("Error creating server threshold template:", error); throw error; } }, async updateServerThreshold(id: string, data: Partial): Promise { try { - console.log(`Updating server threshold template with id: ${id}`, data); + // console.log(`Updating server threshold template with id: ${id}`, data); const response = await pb.collection('server_threshold_templates').update(id, data); - console.log("Update server threshold template response:", response); + // console.log("Update server threshold template response:", response); return response as unknown as ServerThreshold; } catch (error) { - console.error(`Error updating server threshold template with id ${id}:`, error); + // console.error(`Error updating server threshold template with id ${id}:`, error); throw error; } }, async deleteServerThreshold(id: string): Promise { try { - console.log(`Deleting server threshold template with id: ${id}`); + // console.log(`Deleting server threshold template with id: ${id}`); await pb.collection('server_threshold_templates').delete(id); - console.log("Server threshold template deleted successfully"); + // console.log("Server threshold template deleted successfully"); return true; } catch (error) { - console.error(`Error deleting server threshold template with id ${id}:`, error); + // console.error(`Error deleting server threshold template with id ${id}:`, error); throw error; } } diff --git a/application/src/services/templateService.ts b/application/src/services/templateService.ts index fc68e6c..f1c37fe 100644 --- a/application/src/services/templateService.ts +++ b/application/src/services/templateService.ts @@ -38,62 +38,62 @@ export interface CreateUpdateTemplateData { export const templateService = { async getTemplates(): Promise { try { - console.log("Fetching server notification templates"); + // console.log("Fetching server notification templates"); const response = await pb.collection('server_notification_templates').getList(1, 50, { sort: '-created', }); - console.log("Server templates response:", response); + // console.log("Server templates response:", response); return response.items as unknown as NotificationTemplate[]; } catch (error) { - console.error("Error fetching server templates:", error); + // console.error("Error fetching server templates:", error); throw error; } }, async getTemplate(id: string): Promise { try { - console.log(`Fetching server template with id: ${id}`); + // console.log(`Fetching server template with id: ${id}`); const response = await pb.collection('server_notification_templates').getOne(id); - console.log("Server template response:", response); + // console.log("Server template response:", response); return response as unknown as NotificationTemplate; } catch (error) { - console.error(`Error fetching server template with id ${id}:`, error); + // console.error(`Error fetching server template with id ${id}:`, error); throw error; } }, async createTemplate(data: CreateUpdateTemplateData): Promise { try { - console.log("Creating new server template with data:", data); + // console.log("Creating new server template with data:", data); const response = await pb.collection('server_notification_templates').create(data); - console.log("Create server template response:", response); + // console.log("Create server template response:", response); return response as unknown as NotificationTemplate; } catch (error) { - console.error("Error creating server template:", error); + // console.error("Error creating server template:", error); throw error; } }, async updateTemplate(id: string, data: Partial): Promise { try { - console.log(`Updating server template with id: ${id}`, data); + // console.log(`Updating server template with id: ${id}`, data); const response = await pb.collection('server_notification_templates').update(id, data); - console.log("Update server template response:", response); + // console.log("Update server template response:", response); return response as unknown as NotificationTemplate; } catch (error) { - console.error(`Error updating server template with id ${id}:`, error); + // console.error(`Error updating server template with id ${id}:`, error); throw error; } }, async deleteTemplate(id: string): Promise { try { - console.log(`Deleting server template with id: ${id}`); + // console.log(`Deleting server template with id: ${id}`); await pb.collection('server_notification_templates').delete(id); - console.log("Server template deleted successfully"); + // console.log("Server template deleted successfully"); return true; } catch (error) { - console.error(`Error deleting server template with id ${id}:`, error); + // console.error(`Error deleting server template with id ${id}:`, error); throw error; } }