+
- {/* 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) ? (
+
})
+ ) : (
+
+ )}
{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;