diff --git a/application/src/components/servers/OSTypeIcon.tsx b/application/src/components/servers/OSTypeIcon.tsx
new file mode 100644
index 0000000..151883f
--- /dev/null
+++ b/application/src/components/servers/OSTypeIcon.tsx
@@ -0,0 +1,27 @@
+
+import { Monitor, Server, Smartphone, Laptop } from "lucide-react";
+
+interface OSTypeIconProps {
+ osType: string;
+ className?: string;
+}
+
+export const OSTypeIcon = ({ osType, className = "h-4 w-4" }: OSTypeIconProps) => {
+ const getOSIcon = (os: string) => {
+ const osLower = os.toLowerCase();
+
+ if (osLower.includes('linux') || osLower.includes('ubuntu') || osLower.includes('centos')) {
+ return ;
+ } else if (osLower.includes('windows')) {
+ return ;
+ } else if (osLower.includes('mac') || osLower.includes('darwin')) {
+ return ;
+ } else if (osLower.includes('android') || osLower.includes('ios')) {
+ return ;
+ } else {
+ return ;
+ }
+ };
+
+ return getOSIcon(osType);
+};
\ No newline at end of file
diff --git a/application/src/components/servers/ServerHistoryCharts.tsx b/application/src/components/servers/ServerHistoryCharts.tsx
new file mode 100644
index 0000000..a53371a
--- /dev/null
+++ b/application/src/components/servers/ServerHistoryCharts.tsx
@@ -0,0 +1,467 @@
+import { useQuery } from "@tanstack/react-query";
+import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
+import { ChartContainer, ChartTooltip, ChartTooltipContent } from "@/components/ui/chart";
+import { LineChart, Line, XAxis, YAxis, CartesianGrid, ResponsiveContainer } from "recharts";
+import { serverService } from "@/services/serverService";
+import { Loader2, TrendingUp, Cpu, HardDrive, Wifi, MemoryStick } from "lucide-react";
+import { useTheme } from "@/contexts/ThemeContext";
+import { DateRangeFilter } from "@/components/services/DateRangeFilter";
+import { useState } from "react";
+
+interface ServerHistoryChartsProps {
+ serverId: string;
+}
+
+export const ServerHistoryCharts = ({ serverId }: ServerHistoryChartsProps) => {
+ const { theme } = useTheme();
+ const [dateRange, setDateRange] = useState({ start: new Date(Date.now() - 24 * 60 * 60 * 1000), end: new Date() });
+
+ console.log('ServerHistoryCharts: Rendering with serverId:', serverId);
+
+ const {
+ data: metrics = [],
+ isLoading,
+ error
+ } = useQuery({
+ queryKey: ['server-metrics-history', serverId],
+ queryFn: async () => {
+ console.log('ServerHistoryCharts: Fetching metrics for serverId:', serverId);
+ const result = await serverService.getServerMetrics(serverId);
+ console.log('ServerHistoryCharts: Raw metrics result:', result);
+ return result;
+ },
+ enabled: !!serverId,
+ refetchInterval: 60000, // Refresh every minute
+ retry: 1
+ });
+
+ const handleDateRangeChange = (startDate: Date, endDate: Date, option: any) => {
+ setDateRange({ start: startDate, end: endDate });
+ console.log('Date range changed:', { startDate, endDate, option });
+ };
+
+ console.log('ServerHistoryCharts: Query state:', {
+ metricsCount: metrics.length,
+ isLoading,
+ error: error?.message,
+ firstMetric: metrics[0],
+ serverId
+ });
+
+ const formatBytes = (bytes: number) => {
+ if (bytes === 0) return '0 B';
+ const k = 1024;
+ const sizes = ['B', 'KB', 'MB', 'GB', 'TB'];
+ const i = Math.floor(Math.log(bytes) / Math.log(k));
+ return parseFloat((bytes / Math.pow(k, i)).toFixed(1)) + ' ' + sizes[i];
+ };
+
+ const parseValueWithUnit = (value: string | number) => {
+ if (typeof value === 'number') return value;
+ const match = value.toString().match(/^([\d.]+)/);
+ return match ? parseFloat(match[1]) : 0;
+ };
+
+ const convertToBytes = (value: string | number): number => {
+ if (typeof value === 'number') return value;
+
+ const str = value.toString();
+ const numMatch = str.match(/^([\d.]+)/);
+ const unitMatch = str.match(/([A-Za-z]+)$/);
+
+ if (!numMatch) return 0;
+
+ const num = parseFloat(numMatch[1]);
+ const unit = unitMatch ? unitMatch[1].toUpperCase() : 'B';
+
+ const multipliers: { [key: string]: number } = {
+ 'B': 1,
+ 'KB': 1024,
+ 'MB': 1024 * 1024,
+ 'GB': 1024 * 1024 * 1024,
+ 'TB': 1024 * 1024 * 1024 * 1024
+ };
+
+ return num * (multipliers[unit] || 1);
+ };
+
+ const formatChartData = (metrics: any[]) => {
+ console.log('ServerHistoryCharts: Formatting chart data for', metrics.length, 'metrics');
+
+ if (!Array.isArray(metrics) || metrics.length === 0) {
+ console.log('ServerHistoryCharts: No metrics to format');
+ return [];
+ }
+
+ const formattedData = metrics.slice(-50).reverse().map((metric, index) => {
+ console.log(`ServerHistoryCharts: Processing metric ${index}:`, metric);
+
+ const cpuUsage = parseValueWithUnit(metric.cpu_usage || 0);
+ const ramUsedBytes = convertToBytes(metric.ram_used || 0);
+ const ramTotalBytes = convertToBytes(metric.ram_total || 0);
+ const diskUsedBytes = convertToBytes(metric.disk_used || 0);
+ const diskTotalBytes = convertToBytes(metric.disk_total || 0);
+
+ const ramUsagePercent = ramTotalBytes > 0 ? (ramUsedBytes / ramTotalBytes) * 100 : 0;
+ const diskUsagePercent = diskTotalBytes > 0 ? (diskUsedBytes / diskTotalBytes) * 100 : 0;
+
+ return {
+ timestamp: new Date(metric.timestamp || metric.created).toLocaleTimeString('en-US', {
+ hour: '2-digit',
+ minute: '2-digit'
+ }),
+ fullTimestamp: new Date(metric.timestamp || metric.created).toLocaleString(),
+ cpuUsage: Math.round(cpuUsage * 100) / 100,
+ ramUsagePercent: Math.round(ramUsagePercent * 100) / 100,
+ diskUsagePercent: Math.round(diskUsagePercent * 100) / 100,
+ ramUsedBytes,
+ ramTotalBytes,
+ diskUsedBytes,
+ diskTotalBytes,
+ networkRxBytes: metric.network_rx_bytes || 0,
+ networkTxBytes: metric.network_tx_bytes || 0,
+ networkRxSpeed: parseValueWithUnit(metric.network_rx_speed || 0),
+ networkTxSpeed: parseValueWithUnit(metric.network_tx_speed || 0),
+ };
+ });
+
+ console.log('ServerHistoryCharts: Formatted chart data:', formattedData);
+ return formattedData;
+ };
+
+ const chartData = formatChartData(metrics);
+
+ const getGridColor = () => theme === 'dark' ? '#374151' : '#e5e7eb';
+ const getAxisColor = () => theme === 'dark' ? '#9ca3af' : '#6b7280';
+
+ if (isLoading) {
+ return (
+
+
+
+
+
Historical Performance
+
+
+
+
+ {[1, 2, 3, 4].map((index) => (
+
+
+
+
+
+
+
+
+
+
+
+ ))}
+
+
+ );
+ }
+
+ if (error) {
+ console.error('ServerHistoryCharts: Error loading data:', error);
+ return (
+
+
+
+
+
Historical Performance
+
+
+
+
+
+
+
Error loading chart data
+
{error?.message}
+
Server ID: {serverId}
+
+
+
+
+ );
+ }
+
+ if (chartData.length === 0) {
+ return (
+
+
+
+
+
Historical Performance
+
+
+
+
+
+
+
No historical data available
+
Metrics count: {metrics.length}
+
Server ID: {serverId}
+
+ {metrics.length > 0 ? 'Data exists but failed to format' : 'No metrics data found'}
+
+
+
+
+
+ );
+ }
+
+ console.log('ServerHistoryCharts: Rendering individual charts with', chartData.length, 'data points');
+
+ return (
+
+
+
+
+
Historical Performance
+ ({chartData.length} data points)
+
+
+
+
+
+ {/* CPU Usage Chart */}
+
+
+
+
+
+
+ CPU Usage
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ }
+ cursor={{ stroke: getGridColor() }}
+ />
+
+
+
+
+
+
+ {/* Memory Usage Chart */}
+
+
+
+
+
+
+ Memory Usage
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ }
+ cursor={{ stroke: getGridColor() }}
+ />
+
+
+
+
+
+
+ {/* Disk Usage Chart */}
+
+
+
+
+
+
+ Disk Usage
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ }
+ cursor={{ stroke: getGridColor() }}
+ />
+
+
+
+
+
+
+ {/* Network Traffic Chart */}
+
+
+
+
+
+
+ Network Traffic
+
+
+
+
+
+
+
+
+
+
+
+
+
+ formatBytes(value) + '/s'}
+ label={{ value: 'Network Speed', angle: -90, position: 'insideLeft' }}
+ />
+ }
+ cursor={{ stroke: getGridColor() }}
+ formatter={(value, name) => [formatBytes(Number(value)) + '/s', name]}
+ />
+
+
+
+
+
+
+
+
+ );
+};
\ No newline at end of file
diff --git a/application/src/components/servers/ServerMetricsCharts.tsx b/application/src/components/servers/ServerMetricsCharts.tsx
new file mode 100644
index 0000000..70dd5be
--- /dev/null
+++ b/application/src/components/servers/ServerMetricsCharts.tsx
@@ -0,0 +1,109 @@
+
+import { useState } from "react";
+import { useQuery } from "@tanstack/react-query";
+import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs";
+import { serverService } from "@/services/serverService";
+import { Loader2, Cpu, HardDrive, Network, MemoryStick } from "lucide-react";
+import { formatChartData } from "./charts/dataUtils";
+import { TimeRangeSelector } from "./charts/TimeRangeSelector";
+import { CPUCharts } from "./charts/CPUCharts";
+import { MemoryCharts } from "./charts/MemoryCharts";
+import { DiskCharts } from "./charts/DiskCharts";
+import { NetworkCharts } from "./charts/NetworkCharts";
+
+interface ServerMetricsChartsProps {
+ serverId: string;
+}
+
+type TimeRange = '60m' | '1d' | '7d' | '1m' | '3m';
+
+export const ServerMetricsCharts = ({ serverId }: ServerMetricsChartsProps) => {
+ const [timeRange, setTimeRange] = useState("1d");
+
+ const {
+ data: metrics = [],
+ isLoading,
+ error
+ } = useQuery({
+ queryKey: ['server-metrics', serverId, timeRange],
+ queryFn: () => serverService.getServerMetrics(serverId),
+ enabled: !!serverId,
+ refetchInterval: 30000
+ });
+
+ const chartData = formatChartData(metrics, timeRange);
+
+ if (isLoading) {
+ return (
+
+
+ Loading server metrics...
+
+ );
+ }
+
+ if (error) {
+ return (
+
+
Error loading server metrics: {error.message}
+
+ );
+ }
+
+ if (chartData.length === 0) {
+ return (
+
+
No server metrics data available
+
+ );
+ }
+
+ return (
+
+
+
+
Server Metrics
+ ({chartData.length} data points)
+
+
+
+
+
+
+
+
+ CPU
+
+
+
+ Memory
+
+
+
+ Disk
+
+
+
+ Network
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ );
+};
\ No newline at end of file
diff --git a/application/src/components/servers/ServerMetricsOverview.tsx b/application/src/components/servers/ServerMetricsOverview.tsx
new file mode 100644
index 0000000..840db9c
--- /dev/null
+++ b/application/src/components/servers/ServerMetricsOverview.tsx
@@ -0,0 +1,249 @@
+
+import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
+import { Server } from "@/types/server.types";
+import { Cpu, HardDrive, MemoryStick, Clock, Activity, Info } from "lucide-react";
+import { useTheme } from "@/contexts/ThemeContext";
+
+interface ServerMetricsOverviewProps {
+ server: Server;
+}
+
+export const ServerMetricsOverview = ({ server }: ServerMetricsOverviewProps) => {
+ const { theme } = useTheme();
+
+ const formatBytes = (bytes: number) => {
+ if (bytes === 0) return '0 B';
+ const k = 1024;
+ const sizes = ['B', 'KB', 'MB', 'GB', 'TB'];
+ const i = Math.floor(Math.log(bytes) / Math.log(k));
+ return parseFloat((bytes / Math.pow(k, i)).toFixed(1)) + ' ' + sizes[i];
+ };
+
+ const getStatusColor = (status: string) => {
+ switch (status) {
+ case 'up':
+ return 'text-green-500';
+ case 'down':
+ return 'text-red-500';
+ case 'warning':
+ return 'text-yellow-500';
+ default:
+ return 'text-muted-foreground';
+ }
+ };
+
+ const cpuUsagePercent = server.cpu_usage || 0;
+ const ramUsagePercent = server.ram_total > 0 ? (server.ram_used / server.ram_total) * 100 : 0;
+ const diskUsagePercent = server.disk_total > 0 ? (server.disk_used / server.disk_total) * 100 : 0;
+
+ const MetricCard = ({ title, used, total, free, percentage, icon: Icon, color, additionalInfo }: {
+ title: string;
+ used: string;
+ total: string;
+ free: string;
+ percentage: number;
+ icon: any;
+ color: string;
+ additionalInfo?: string;
+ }) => (
+
+
+
+
+
+
+ {percentage.toFixed(1)}%
+
+
+
+
+
+
+
+ Used:
+ {used}
+
+
+ Free:
+ {free}
+
+
+
+
+ Total:
+ {total}
+
+ {additionalInfo && (
+
+ Cores:
+ {additionalInfo}
+
+ )}
+
+
+
+
+
+ );
+
+ return (
+
+ {/* Status Card */}
+
+
+
+
+
+ Status
+
+
+
+
+
+
+ Agent:
+ {server.agent_status}
+
+
+ Connection:
+ {server.connection}
+
+
+
+
+
+ {/* Uptime Card */}
+
+
+
+
+
+
+
+ Uptime
+
+
+
+
+ {server.uptime}
+
+
+
Last Check:
+
+ {new Date(server.last_checked).toLocaleString()}
+
+
+
+
+
+ {/* CPU Metric */}
+
+
+ {/* Memory Metric */}
+
+
+ {/* Disk Metric */}
+
+
+ {/* System Info Card */}
+
+
+
+
+
+
+
+ System Information
+
+
+
+
+
+
+
Operating System:
+
{server.os_type}
+
+
+
Hostname:
+
{server.hostname}
+
+
+
+
+
IP Address:
+
{server.ip_address}
+
+ {server.system_info && (
+
+
System Details:
+
+ {server.system_info}
+
+
+ )}
+
+
+
+
+
+ );
+};
\ No newline at end of file
diff --git a/application/src/components/servers/ServerStatsCards.tsx b/application/src/components/servers/ServerStatsCards.tsx
new file mode 100644
index 0000000..3c6e035
--- /dev/null
+++ b/application/src/components/servers/ServerStatsCards.tsx
@@ -0,0 +1,84 @@
+
+import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
+import { Server, Activity, AlertTriangle, Power } from "lucide-react";
+import { ServerStats } from "@/types/server.types";
+import { useTheme } from "@/contexts/ThemeContext";
+
+interface ServerStatsCardsProps {
+ stats: ServerStats;
+}
+
+export const ServerStatsCards = ({ stats }: ServerStatsCardsProps) => {
+ const { theme } = useTheme();
+
+ const cards = [
+ {
+ title: "TOTAL SERVERS",
+ value: stats.total,
+ icon: Server,
+ gradient: theme === 'dark'
+ ? "linear-gradient(135deg, rgba(139, 69, 19, 0.8) 0%, rgba(160, 82, 45, 0.6) 100%)"
+ : "linear-gradient(135deg, #8b4513 0%, #a0522d 100%)"
+ },
+ {
+ title: "ONLINE SERVERS",
+ value: stats.online,
+ icon: Activity,
+ gradient: theme === 'dark'
+ ? "linear-gradient(135deg, rgba(67, 160, 71, 0.8) 0%, rgba(102, 187, 106, 0.6) 100%)"
+ : "linear-gradient(135deg, #43a047 0%, #66bb6a 100%)"
+ },
+ {
+ title: "OFFLINE SERVERS",
+ value: stats.offline,
+ icon: Power,
+ gradient: theme === 'dark'
+ ? "linear-gradient(135deg, rgba(229, 57, 53, 0.8) 0%, rgba(239, 83, 80, 0.6) 100%)"
+ : "linear-gradient(135deg, #e53935 0%, #ef5350 100%)"
+ },
+ {
+ title: "WARNING SERVERS",
+ value: stats.warning,
+ icon: AlertTriangle,
+ gradient: theme === 'dark'
+ ? "linear-gradient(135deg, rgba(255, 152, 0, 0.8) 0%, rgba(255, 183, 77, 0.6) 100%)"
+ : "linear-gradient(135deg, #ff9800 0%, #ffb74d 100%)"
+ }
+ ];
+
+ return (
+
+ {cards.map((card, index) => (
+
+ {/* Grid Pattern Overlay */}
+
+
+ {card.title}
+
+
+ {card.value}
+
+
+
+
+
+ ))}
+
+ );
+};
\ No newline at end of file
diff --git a/application/src/components/servers/ServerStatusBadge.tsx b/application/src/components/servers/ServerStatusBadge.tsx
new file mode 100644
index 0000000..ac17edf
--- /dev/null
+++ b/application/src/components/servers/ServerStatusBadge.tsx
@@ -0,0 +1,41 @@
+
+import { Badge } from "@/components/ui/badge";
+
+interface ServerStatusBadgeProps {
+ status: 'up' | 'down' | 'warning';
+}
+
+export const ServerStatusBadge = ({ status }: ServerStatusBadgeProps) => {
+ const getStatusConfig = (status: string) => {
+ switch (status) {
+ case 'up':
+ return {
+ label: 'Online',
+ className: 'bg-green-100 text-green-800 border-green-200'
+ };
+ case 'down':
+ return {
+ label: 'Offline',
+ className: 'bg-red-100 text-red-800 border-red-200'
+ };
+ case 'warning':
+ return {
+ label: 'Warning',
+ className: 'bg-yellow-100 text-yellow-800 border-yellow-200'
+ };
+ default:
+ return {
+ label: 'Unknown',
+ className: 'bg-gray-100 text-gray-800 border-gray-200'
+ };
+ }
+ };
+
+ const config = getStatusConfig(status);
+
+ return (
+
+ {config.label}
+
+ );
+};
\ No newline at end of file
diff --git a/application/src/components/servers/ServerTable.tsx b/application/src/components/servers/ServerTable.tsx
new file mode 100644
index 0000000..85af02b
--- /dev/null
+++ b/application/src/components/servers/ServerTable.tsx
@@ -0,0 +1,280 @@
+
+import { useState } from "react";
+import { useNavigate } from "react-router-dom";
+import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "@/components/ui/table";
+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 { 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";
+
+interface ServerTableProps {
+ servers: Server[];
+ isLoading: boolean;
+ onRefresh: () => void;
+}
+
+export const ServerTable = ({ servers, isLoading, onRefresh }: ServerTableProps) => {
+ const [searchTerm, setSearchTerm] = useState("");
+ const [pausedServers, setPausedServers] = useState>(new Set());
+ const navigate = useNavigate();
+
+ const filteredServers = servers.filter(server =>
+ server.name.toLowerCase().includes(searchTerm.toLowerCase()) ||
+ server.hostname.toLowerCase().includes(searchTerm.toLowerCase()) ||
+ server.ip_address.toLowerCase().includes(searchTerm.toLowerCase())
+ );
+
+ const handleViewDetails = (serverId: string) => {
+ navigate(`/server-detail/${serverId}`);
+ };
+
+ const handleViewContainers = (serverId: string) => {
+ navigate(`/container-monitoring/${serverId}`);
+ };
+
+ const handlePauseResume = (serverId: string) => {
+ const isPaused = pausedServers.has(serverId);
+ if (isPaused) {
+ setPausedServers(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);
+ };
+
+ const handleDelete = (serverId: string) => {
+ // TODO: Implement delete functionality
+ console.log('Delete server:', serverId);
+ };
+
+ if (isLoading) {
+ return (
+
+
+ Servers
+
+
+
+
+ Loading servers...
+
+
+
+ );
+ }
+
+ return (
+
+
+
+
Servers
+
+
+
+ setSearchTerm(e.target.value)}
+ className="pl-8"
+ />
+
+
+
+
+
+
+ {filteredServers.length === 0 ? (
+
+ ) : (
+
+
+
+
+ Name
+ Status
+ OS
+ IP Address
+ CPU
+ Memory
+ Disk
+ Uptime
+ Docker
+ 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);
+
+ return (
+
+
+
+
{server.name}
+
{server.hostname}
+
+
+
+
+
+
+
+
+ {server.os_type}
+
+
+
+ {server.ip_address}
+
+
+
+
+ {cpuUsage.toFixed(1)}%
+ {server.cpu_cores} cores
+
+
+
+
+
+
+ {serverService.formatBytes(server.ram_used)}
+ {memoryUsage.toFixed(1)}%
+
+
+
+
+
+
+ {serverService.formatBytes(server.disk_used)}
+ {diskUsage.toFixed(1)}%
+
+
+
+
+ {server.uptime}
+
+
+ {server.docker === 'true' ? (
+
+
+ Enabled
+
+ ) : (
+
+ Disabled
+
+ )}
+
+
+
+ {new Date(server.last_checked).toLocaleString()}
+
+
+
+
+
+
+
+
+ handleViewDetails(server.id)}>
+
+ View Server Detail
+
+ {server.docker === 'true' && (
+ handleViewContainers(server.id)}>
+
+ Container Monitoring
+
+ )}
+
+ handlePauseResume(server.id)}>
+ {isPaused ? (
+ <>
+
+ Resume Monitoring
+ >
+ ) : (
+ <>
+
+ Pause Monitoring
+ >
+ )}
+
+
+ handleEdit(server.id)}>
+
+ Edit Server
+
+ handleDelete(server.id)}
+ className="text-red-600 focus:text-red-600"
+ >
+
+ Delete Server
+
+
+
+
+
+ );
+ })}
+
+
+
+ )}
+
+
+ );
+};
\ No newline at end of file
diff --git a/application/src/components/servers/charts/CPUCharts.tsx b/application/src/components/servers/charts/CPUCharts.tsx
new file mode 100644
index 0000000..db9f3f4
--- /dev/null
+++ b/application/src/components/servers/charts/CPUCharts.tsx
@@ -0,0 +1,141 @@
+
+import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
+import { ChartContainer, ChartTooltip, ChartTooltipContent } from "@/components/ui/chart";
+import { AreaChart, Area, XAxis, YAxis, CartesianGrid } from "recharts";
+import { Cpu } from "lucide-react";
+import { useChartConfig } from "./chartConfig";
+import { formatBytes } from "./dataUtils";
+
+interface CPUChartsProps {
+ data: any[];
+}
+
+export const CPUCharts = ({ data }: CPUChartsProps) => {
+ const { chartConfig, getGridColor, getAxisColor } = useChartConfig();
+
+ return (
+
+
+
+
+
+
+
+
+ CPU Usage
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ }
+ cursor={{ stroke: '#3b82f6', strokeWidth: 2, strokeOpacity: 0.5 }}
+ />
+
+
+
+
+
+
+
+
+
+
+
+
+
+ CPU Usage Distribution
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ }
+ cursor={{ stroke: '#3b82f6', strokeWidth: 2, strokeOpacity: 0.5 }}
+ />
+
+
+
+
+
+
+
+ );
+};
\ No newline at end of file
diff --git a/application/src/components/servers/charts/DiskCharts.tsx b/application/src/components/servers/charts/DiskCharts.tsx
new file mode 100644
index 0000000..1b6bf07
--- /dev/null
+++ b/application/src/components/servers/charts/DiskCharts.tsx
@@ -0,0 +1,138 @@
+
+import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
+import { ChartContainer, ChartTooltip, ChartTooltipContent } from "@/components/ui/chart";
+import { AreaChart, Area, XAxis, YAxis, CartesianGrid } from "recharts";
+import { HardDrive } from "lucide-react";
+import { useChartConfig } from "./chartConfig";
+import { formatBytes } from "./dataUtils";
+
+interface DiskChartsProps {
+ data: any[];
+}
+
+export const DiskCharts = ({ data }: DiskChartsProps) => {
+ const { chartConfig, getGridColor, getAxisColor } = useChartConfig();
+
+ return (
+
+
+
+
+
+
+
+
+ Disk Usage
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ }
+ cursor={{ stroke: '#f59e0b', strokeWidth: 2, strokeOpacity: 0.5 }}
+ />
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Disk Usage (Bytes)
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ formatBytes(value)}
+ />
+ }
+ cursor={{ stroke: '#f59e0b', strokeWidth: 2, strokeOpacity: 0.5 }}
+ formatter={(value, name) => [
+ name === 'Used Disk' ? formatBytes(Number(value)) :
+ name === 'Total Disk' ? formatBytes(Number(value)) : value,
+ name
+ ]}
+ />
+
+
+
+
+
+
+
+ );
+};
\ No newline at end of file
diff --git a/application/src/components/servers/charts/MemoryCharts.tsx b/application/src/components/servers/charts/MemoryCharts.tsx
new file mode 100644
index 0000000..4c748ce
--- /dev/null
+++ b/application/src/components/servers/charts/MemoryCharts.tsx
@@ -0,0 +1,138 @@
+
+import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
+import { ChartContainer, ChartTooltip, ChartTooltipContent } from "@/components/ui/chart";
+import { AreaChart, Area, XAxis, YAxis, CartesianGrid } from "recharts";
+import { MemoryStick } from "lucide-react";
+import { useChartConfig } from "./chartConfig";
+import { formatBytes } from "./dataUtils";
+
+interface MemoryChartsProps {
+ data: any[];
+}
+
+export const MemoryCharts = ({ data }: MemoryChartsProps) => {
+ const { chartConfig, getGridColor, getAxisColor } = useChartConfig();
+
+ return (
+
+
+
+
+
+
+
+
+ Memory Usage
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ }
+ cursor={{ stroke: '#10b981', strokeWidth: 2, strokeOpacity: 0.5 }}
+ />
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Memory Usage (Bytes)
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ formatBytes(value)}
+ />
+ }
+ cursor={{ stroke: '#10b981', strokeWidth: 2, strokeOpacity: 0.5 }}
+ formatter={(value, name) => [
+ name === 'Used Memory' ? formatBytes(Number(value)) :
+ name === 'Total Memory' ? formatBytes(Number(value)) : value,
+ name
+ ]}
+ />
+
+
+
+
+
+
+
+ );
+};
\ No newline at end of file
diff --git a/application/src/components/servers/charts/NetworkCharts.tsx b/application/src/components/servers/charts/NetworkCharts.tsx
new file mode 100644
index 0000000..2ad4c16
--- /dev/null
+++ b/application/src/components/servers/charts/NetworkCharts.tsx
@@ -0,0 +1,140 @@
+
+import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
+import { ChartContainer, ChartTooltip, ChartTooltipContent } from "@/components/ui/chart";
+import { LineChart, Line, AreaChart, Area, XAxis, YAxis, CartesianGrid } from "recharts";
+import { Network } from "lucide-react";
+import { useChartConfig } from "./chartConfig";
+
+interface NetworkChartsProps {
+ data: any[];
+}
+
+export const NetworkCharts = ({ data }: NetworkChartsProps) => {
+ const { chartConfig, getGridColor, getAxisColor } = useChartConfig();
+
+ return (
+
+
+
+
+
+
+
+
+ Network Traffic
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ }
+ cursor={{ stroke: '#8b5cf6', strokeWidth: 2, strokeOpacity: 0.5 }}
+ />
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Network Speed (KB/s)
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ }
+ cursor={{ stroke: '#8b5cf6', strokeWidth: 2, strokeOpacity: 0.5 }}
+ />
+
+
+
+
+
+
+
+ );
+};
\ No newline at end of file
diff --git a/application/src/components/servers/charts/TimeRangeSelector.tsx b/application/src/components/servers/charts/TimeRangeSelector.tsx
new file mode 100644
index 0000000..6847b1c
--- /dev/null
+++ b/application/src/components/servers/charts/TimeRangeSelector.tsx
@@ -0,0 +1,27 @@
+
+import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select";
+import { timeRangeOptions } from "./dataUtils";
+
+type TimeRange = '60m' | '1d' | '7d' | '1m' | '3m';
+
+interface TimeRangeSelectorProps {
+ value: TimeRange;
+ onChange: (value: TimeRange) => void;
+}
+
+export const TimeRangeSelector = ({ value, onChange }: TimeRangeSelectorProps) => {
+ return (
+
+ );
+};
\ No newline at end of file
diff --git a/application/src/components/servers/charts/chartConfig.ts b/application/src/components/servers/charts/chartConfig.ts
new file mode 100644
index 0000000..4f04a5e
--- /dev/null
+++ b/application/src/components/servers/charts/chartConfig.ts
@@ -0,0 +1,34 @@
+
+import { useTheme } from "@/contexts/ThemeContext";
+
+export const useChartConfig = () => {
+ const { theme } = useTheme();
+
+ const chartConfig = {
+ cpuUsage: {
+ label: "CPU Usage (%)",
+ color: theme === 'dark' ? "#60a5fa" : "#3b82f6",
+ },
+ ramUsagePercent: {
+ label: "RAM Usage (%)",
+ color: theme === 'dark' ? "#34d399" : "#10b981",
+ },
+ diskUsagePercent: {
+ label: "Disk Usage (%)",
+ color: theme === 'dark' ? "#fbbf24" : "#f59e0b",
+ },
+ networkRx: {
+ label: "Network RX",
+ color: theme === 'dark' ? "#a78bfa" : "#8b5cf6",
+ },
+ networkTx: {
+ label: "Network TX",
+ color: theme === 'dark' ? "#f87171" : "#ef4444",
+ },
+ };
+
+ const getGridColor = () => theme === 'dark' ? '#374151' : '#e5e7eb';
+ const getAxisColor = () => theme === 'dark' ? '#9ca3af' : '#6b7280';
+
+ return { chartConfig, getGridColor, getAxisColor, theme };
+};
\ No newline at end of file
diff --git a/application/src/components/servers/charts/dataUtils.ts b/application/src/components/servers/charts/dataUtils.ts
new file mode 100644
index 0000000..92f10f5
--- /dev/null
+++ b/application/src/components/servers/charts/dataUtils.ts
@@ -0,0 +1,121 @@
+
+export const formatBytes = (bytes: number, decimals = 2) => {
+ if (bytes === 0) return '0 B';
+ const k = 1024;
+ const dm = decimals < 0 ? 0 : decimals;
+ const sizes = ['B', 'KB', 'MB', 'GB', 'TB'];
+ const i = Math.floor(Math.log(bytes) / Math.log(k));
+ return parseFloat((bytes / Math.pow(k, i)).toFixed(dm)) + ' ' + sizes[i];
+};
+
+export const parseValueWithUnit = (value: string | number): { numeric: number; unit: string; original: string } => {
+ if (typeof value === 'number') {
+ return { numeric: value, unit: 'B', original: value.toString() };
+ }
+
+ const str = value.toString();
+ const match = str.match(/^([\d.]+)\s*([A-Za-z%]*)/);
+ if (match) {
+ const numeric = parseFloat(match[1]);
+ const unit = match[2] || '';
+ return { numeric, unit, original: str };
+ }
+ return { numeric: 0, unit: '', original: str };
+};
+
+export const convertToBytes = (value: string | number): number => {
+ if (typeof value === 'number') return value;
+
+ const parsed = parseValueWithUnit(value);
+ const multipliers: { [key: string]: number } = {
+ 'B': 1,
+ 'KB': 1024,
+ 'MB': 1024 * 1024,
+ 'GB': 1024 * 1024 * 1024,
+ 'TB': 1024 * 1024 * 1024 * 1024
+ };
+
+ const multiplier = multipliers[parsed.unit.toUpperCase()] || 1;
+ return parsed.numeric * multiplier;
+};
+
+type TimeRange = '60m' | '1d' | '7d' | '1m' | '3m';
+
+export const timeRangeOptions = [
+ { value: '60m' as TimeRange, label: '60 minutes', hours: 1 },
+ { value: '1d' as TimeRange, label: '1 day', hours: 24 },
+ { value: '7d' as TimeRange, label: '7 days', hours: 24 * 7 },
+ { value: '1m' as TimeRange, label: '1 month', hours: 24 * 30 },
+ { value: '3m' as TimeRange, label: '3 months', hours: 24 * 90 },
+];
+
+export const filterMetricsByTimeRange = (metrics: any[], timeRange: TimeRange): any[] => {
+ const now = new Date();
+ const selectedRange = timeRangeOptions.find(opt => opt.value === timeRange);
+ if (!selectedRange) return metrics;
+
+ const cutoffTime = new Date(now.getTime() - (selectedRange.hours * 60 * 60 * 1000));
+
+ return metrics.filter(metric => {
+ const metricTime = new Date(metric.timestamp);
+ return metricTime >= cutoffTime;
+ });
+};
+
+export const formatChartData = (metrics: any[], timeRange: TimeRange) => {
+ const filteredMetrics = filterMetricsByTimeRange(metrics, timeRange);
+
+ return filteredMetrics.slice(0, 100).reverse().map((metric, index) => {
+ const cpuUsage = typeof metric.cpu_usage === 'string' ?
+ parseFloat(metric.cpu_usage.replace('%', '')) :
+ parseFloat(metric.cpu_usage) || 0;
+
+ const ramUsedBytes = convertToBytes(metric.ram_used);
+ const ramTotalBytes = convertToBytes(metric.ram_total);
+ const ramFreeBytes = convertToBytes(metric.ram_free);
+ const ramUsagePercent = ramTotalBytes > 0 ? (ramUsedBytes / ramTotalBytes) * 100 : 0;
+
+ const diskUsedBytes = convertToBytes(metric.disk_used);
+ const diskTotalBytes = convertToBytes(metric.disk_total);
+ const diskFreeBytes = convertToBytes(metric.disk_free);
+ const diskUsagePercent = diskTotalBytes > 0 ? (diskUsedBytes / diskTotalBytes) * 100 : 0;
+
+ const networkRxBytes = metric.network_rx_bytes || 0;
+ const networkTxBytes = metric.network_tx_bytes || 0;
+ const networkRxSpeed = metric.network_rx_speed || 0;
+ const networkTxSpeed = metric.network_tx_speed || 0;
+
+ return {
+ timestamp: new Date(metric.timestamp).toLocaleTimeString('en-US', {
+ hour: '2-digit',
+ minute: '2-digit'
+ }),
+ cpuUsage: Math.round(cpuUsage * 100) / 100,
+ cpuCores: parseInt(metric.cpu_cores) || 0,
+ cpuFree: 100 - cpuUsage,
+
+ ramUsedBytes,
+ ramTotalBytes,
+ ramFreeBytes,
+ ramUsed: formatBytes(ramUsedBytes),
+ ramTotal: formatBytes(ramTotalBytes),
+ ramFree: formatBytes(ramFreeBytes),
+ ramUsagePercent: Math.round(ramUsagePercent * 100) / 100,
+
+ diskUsedBytes,
+ diskTotalBytes,
+ diskFreeBytes,
+ diskUsed: formatBytes(diskUsedBytes),
+ diskTotal: formatBytes(diskTotalBytes),
+ diskFree: formatBytes(diskFreeBytes),
+ diskUsagePercent: Math.round(diskUsagePercent * 100) / 100,
+
+ networkRxBytes,
+ networkTxBytes,
+ networkRx: formatBytes(networkRxBytes),
+ networkTx: formatBytes(networkTxBytes),
+ networkRxSpeed: Math.round(networkRxSpeed * 100) / 100,
+ networkTxSpeed: Math.round(networkTxSpeed * 100) / 100,
+ };
+ });
+};
\ No newline at end of file
diff --git a/application/src/pages/InstanceMonitoring.tsx b/application/src/pages/InstanceMonitoring.tsx
new file mode 100644
index 0000000..586cdfe
--- /dev/null
+++ b/application/src/pages/InstanceMonitoring.tsx
@@ -0,0 +1,108 @@
+import { useState, useEffect } from "react";
+import { useQuery } from "@tanstack/react-query";
+import { useTheme } from "@/contexts/ThemeContext";
+import { useLanguage } from "@/contexts/LanguageContext";
+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 { 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";
+const InstanceMonitoring = () => {
+ const {
+ theme
+ } = useTheme();
+ const {
+ t
+ } = useLanguage();
+ const {
+ sidebarCollapsed,
+ toggleSidebar
+ } = useSidebar();
+ const navigate = useNavigate();
+ const [stats, setStats] = useState({
+ total: 0,
+ online: 0,
+ offline: 0,
+ warning: 0
+ });
+ const [currentUser, setCurrentUser] = useState(authService.getCurrentUser());
+ const {
+ data: servers = [],
+ isLoading,
+ error,
+ refetch
+ } = useQuery({
+ queryKey: ['servers'],
+ queryFn: serverService.getServers,
+ refetchInterval: 30000 // Refetch every 30 seconds
+ });
+ useEffect(() => {
+ if (servers.length > 0) {
+ serverService.getServerStats(servers).then(setStats);
+ }
+ }, [servers]);
+ const handleRefresh = () => {
+ refetch();
+ };
+ const handleLogout = () => {
+ authService.logout();
+ navigate('/login');
+ };
+ if (error) {
+ return
+
+
+
+
+
+
Error loading servers
+
+ Unable to fetch server data. Please check your connection and try again.
+
+
+
+
+
+
;
+ }
+ return
+
+
+
+
+
+ {/* Header Section */}
+
+
+
+
+ Instance Monitoring
+
+
+ Monitor and manage your server instances in real-time
+
+
+
+
+
+ {/* Stats Cards Section */}
+
+
+
+
+ {/* Server Table Section */}
+
+
+
+
+
+
+
;
+};
+export default InstanceMonitoring;
\ No newline at end of file
diff --git a/application/src/pages/ServerDetail.tsx b/application/src/pages/ServerDetail.tsx
new file mode 100644
index 0000000..63ea12f
--- /dev/null
+++ b/application/src/pages/ServerDetail.tsx
@@ -0,0 +1,174 @@
+
+import { useState, useEffect } from "react";
+import { useParams, useNavigate } from "react-router-dom";
+import { useQuery } from "@tanstack/react-query";
+import { useTheme } from "@/contexts/ThemeContext";
+import { useLanguage } from "@/contexts/LanguageContext";
+import { useSidebar } from "@/contexts/SidebarContext";
+import { Sidebar } from "@/components/dashboard/Sidebar";
+import { Header } from "@/components/dashboard/Header";
+import { serverService } from "@/services/serverService";
+import { authService } from "@/services/authService";
+import { Button } from "@/components/ui/button";
+import { ArrowLeft, Server } from "lucide-react";
+import { ServerMetricsCharts } from "@/components/servers/ServerMetricsCharts";
+import { ServerMetricsOverview } from "@/components/servers/ServerMetricsOverview";
+import { ServerHistoryCharts } from "@/components/servers/ServerHistoryCharts";
+
+const ServerDetail = () => {
+ const { serverId } = useParams();
+ const navigate = useNavigate();
+ const { theme } = useTheme();
+ const { t } = useLanguage();
+ const { sidebarCollapsed, toggleSidebar } = useSidebar();
+ const [currentUser, setCurrentUser] = useState(authService.getCurrentUser());
+
+ console.log('ServerDetail component loaded with serverId:', serverId);
+
+ const {
+ data: server,
+ isLoading: serverLoading,
+ error: serverError
+ } = useQuery({
+ queryKey: ['server', serverId],
+ queryFn: () => serverService.getServer(serverId!),
+ enabled: !!serverId
+ });
+
+ const handleLogout = () => {
+ authService.logout();
+ navigate('/login');
+ };
+
+ const handleBackToServers = () => {
+ navigate('/instance-monitoring');
+ };
+
+ if (serverError) {
+ console.error('Server detail error:', serverError);
+ return (
+
+
+
+
+
+
+
Error loading server
+
+ Unable to fetch server data. Please check your connection and try again.
+
+
+ Error: {serverError?.message || 'Unknown error'}
+
+
+
+
+
+
+ );
+ }
+
+ if (serverLoading) {
+ return (
+
+
+
+
+
+
+
+
Loading server details...
+
+
+
+
+ );
+ }
+
+ return (
+
+
+
+
+
+
+ {/* Header Section */}
+
+
+
+
+
+
+
+
+
+
+
+ {server?.name || 'Server Detail'}
+
+
+
+ Monitor server performance metrics and system health
+ {server && (
+
+ {server.hostname} • {server.ip_address} • {server.os_type}
+
+ )}
+
+
+
+
+
+ {/* Server Overview Cards */}
+ {server && (
+
+
+
+ )}
+
+ {/* Historical Charts Section */}
+ {server && (
+
+
+
+ )}
+
+ {/* Metrics Charts Section */}
+ {server && (
+
+
+
+ )}
+
+
+
+
+ );
+};
+
+export default ServerDetail;
\ No newline at end of file
diff --git a/application/src/services/serverService.ts b/application/src/services/serverService.ts
new file mode 100644
index 0000000..e95d9d7
--- /dev/null
+++ b/application/src/services/serverService.ts
@@ -0,0 +1,113 @@
+
+import { pb } from '@/lib/pocketbase';
+import { Server, ServerStats } from '@/types/server.types';
+
+export const serverService = {
+ async getServers(): Promise {
+ try {
+ const records = await pb.collection('servers').getFullList();
+ return records;
+ } catch (error) {
+ console.error('Error fetching servers:', error);
+ throw error;
+ }
+ },
+
+ async getServer(serverId: string): Promise {
+ try {
+ const record = await pb.collection('servers').getOne(serverId);
+ return record;
+ } catch (error) {
+ console.error('Error fetching server:', error);
+ throw error;
+ }
+ },
+
+ async getServerMetrics(serverId: string): Promise {
+ try {
+ console.log('serverService.getServerMetrics: Fetching metrics for serverId:', serverId);
+
+ // 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);
+ } catch (error) {
+ console.log('serverService.getServerMetrics: Could not fetch server details:', error);
+ }
+
+ // Try to get metrics using the server's server_id field if available
+ let metricsServerId = serverId;
+ if (server && server.server_id) {
+ metricsServerId = server.server_id;
+ console.log('serverService.getServerMetrics: Using server.server_id for metrics:', metricsServerId);
+ }
+
+ // Try filtering by server_id first
+ let filteredRecords = await pb.collection('server_metrics').getFullList({
+ filter: `server_id = "${metricsServerId}"`,
+ sort: '-created',
+ requestKey: null
+ });
+
+ console.log('serverService.getServerMetrics: Filtered records by server_id:', filteredRecords.length);
+
+ // If no records found with server_id, try alternative approaches
+ if (filteredRecords.length === 0) {
+ console.log('serverService.getServerMetrics: No records found with server_id filter, trying alternatives...');
+
+ // Get all records to see what's available
+ const allRecords = await pb.collection('server_metrics').getFullList({
+ sort: '-created',
+ requestKey: null
+ });
+
+ console.log('serverService.getServerMetrics: Total server_metrics records:', allRecords.length);
+ if (allRecords.length > 0) {
+ console.log('serverService.getServerMetrics: Sample record fields:', Object.keys(allRecords[0]));
+ console.log('serverService.getServerMetrics: Sample server_id values:', allRecords.slice(0, 5).map(r => r.server_id));
+ }
+
+ // For now, return some sample data from available records if server matches pattern
+ // This is temporary until the correct server_id mapping is established
+ if (allRecords.length > 0) {
+ console.log('serverService.getServerMetrics: Using available records as fallback');
+ filteredRecords = allRecords.slice(0, 50); // Get recent 50 records
+ }
+ }
+
+ console.log('serverService.getServerMetrics: Returning', filteredRecords.length, 'records');
+ return filteredRecords;
+ } catch (error) {
+ console.error('Error fetching server metrics:', error);
+ throw error;
+ }
+ },
+
+ async getServerStats(servers: Server[]): Promise {
+ const total = servers.length;
+ const online = servers.filter(server => server.status === 'up').length;
+ const offline = servers.filter(server => server.status === 'down').length;
+ const warning = servers.filter(server => server.status === 'warning').length;
+
+ return {
+ total,
+ online,
+ offline,
+ warning
+ };
+ },
+
+ formatBytes(bytes: number): string {
+ if (bytes === 0) return '0 Bytes';
+ const k = 1024;
+ const sizes = ['Bytes', 'KB', 'MB', 'GB', 'TB'];
+ const i = Math.floor(Math.log(bytes) / Math.log(k));
+ return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i];
+ },
+
+ formatUptime(uptime: string): string {
+ // Simple uptime formatting - can be enhanced based on actual format
+ return uptime;
+ }
+};
\ No newline at end of file
diff --git a/application/src/services/types/serverService.ts b/application/src/services/types/serverService.ts
new file mode 100644
index 0000000..e95d9d7
--- /dev/null
+++ b/application/src/services/types/serverService.ts
@@ -0,0 +1,113 @@
+
+import { pb } from '@/lib/pocketbase';
+import { Server, ServerStats } from '@/types/server.types';
+
+export const serverService = {
+ async getServers(): Promise {
+ try {
+ const records = await pb.collection('servers').getFullList();
+ return records;
+ } catch (error) {
+ console.error('Error fetching servers:', error);
+ throw error;
+ }
+ },
+
+ async getServer(serverId: string): Promise {
+ try {
+ const record = await pb.collection('servers').getOne(serverId);
+ return record;
+ } catch (error) {
+ console.error('Error fetching server:', error);
+ throw error;
+ }
+ },
+
+ async getServerMetrics(serverId: string): Promise {
+ try {
+ console.log('serverService.getServerMetrics: Fetching metrics for serverId:', serverId);
+
+ // 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);
+ } catch (error) {
+ console.log('serverService.getServerMetrics: Could not fetch server details:', error);
+ }
+
+ // Try to get metrics using the server's server_id field if available
+ let metricsServerId = serverId;
+ if (server && server.server_id) {
+ metricsServerId = server.server_id;
+ console.log('serverService.getServerMetrics: Using server.server_id for metrics:', metricsServerId);
+ }
+
+ // Try filtering by server_id first
+ let filteredRecords = await pb.collection('server_metrics').getFullList({
+ filter: `server_id = "${metricsServerId}"`,
+ sort: '-created',
+ requestKey: null
+ });
+
+ console.log('serverService.getServerMetrics: Filtered records by server_id:', filteredRecords.length);
+
+ // If no records found with server_id, try alternative approaches
+ if (filteredRecords.length === 0) {
+ console.log('serverService.getServerMetrics: No records found with server_id filter, trying alternatives...');
+
+ // Get all records to see what's available
+ const allRecords = await pb.collection('server_metrics').getFullList({
+ sort: '-created',
+ requestKey: null
+ });
+
+ console.log('serverService.getServerMetrics: Total server_metrics records:', allRecords.length);
+ if (allRecords.length > 0) {
+ console.log('serverService.getServerMetrics: Sample record fields:', Object.keys(allRecords[0]));
+ console.log('serverService.getServerMetrics: Sample server_id values:', allRecords.slice(0, 5).map(r => r.server_id));
+ }
+
+ // For now, return some sample data from available records if server matches pattern
+ // This is temporary until the correct server_id mapping is established
+ if (allRecords.length > 0) {
+ console.log('serverService.getServerMetrics: Using available records as fallback');
+ filteredRecords = allRecords.slice(0, 50); // Get recent 50 records
+ }
+ }
+
+ console.log('serverService.getServerMetrics: Returning', filteredRecords.length, 'records');
+ return filteredRecords;
+ } catch (error) {
+ console.error('Error fetching server metrics:', error);
+ throw error;
+ }
+ },
+
+ async getServerStats(servers: Server[]): Promise {
+ const total = servers.length;
+ const online = servers.filter(server => server.status === 'up').length;
+ const offline = servers.filter(server => server.status === 'down').length;
+ const warning = servers.filter(server => server.status === 'warning').length;
+
+ return {
+ total,
+ online,
+ offline,
+ warning
+ };
+ },
+
+ formatBytes(bytes: number): string {
+ if (bytes === 0) return '0 Bytes';
+ const k = 1024;
+ const sizes = ['Bytes', 'KB', 'MB', 'GB', 'TB'];
+ const i = Math.floor(Math.log(bytes) / Math.log(k));
+ return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i];
+ },
+
+ formatUptime(uptime: string): string {
+ // Simple uptime formatting - can be enhanced based on actual format
+ return uptime;
+ }
+};
\ No newline at end of file
diff --git a/application/src/types/server.types.ts b/application/src/types/server.types.ts
new file mode 100644
index 0000000..de0c42d
--- /dev/null
+++ b/application/src/types/server.types.ts
@@ -0,0 +1,42 @@
+
+export interface Server {
+ collectionId: string;
+ collectionName: string;
+ id: string;
+ server_id: string;
+ name: string;
+ hostname: string;
+ ip_address: string;
+ os_type: string;
+ status: 'up' | 'down' | 'warning';
+ uptime: string;
+ ram_total: number;
+ ram_used: number;
+ cpu_cores: number;
+ cpu_usage: number;
+ disk_total: number;
+ disk_used: number;
+ last_checked: string;
+ server_token: string;
+ template_id: string;
+ notification_id: string;
+ timestamp: string;
+ connection: string;
+ agent_status: string;
+ system_info: string;
+ network_rx_bytes: string;
+ network_tx_bytes: string;
+ network_rx_speed: string;
+ network_tx_speed: string;
+ check_interval: number;
+ docker: string;
+ created: string;
+ updated: string;
+}
+
+export interface ServerStats {
+ total: number;
+ online: number;
+ offline: number;
+ warning: number;
+}
\ No newline at end of file