feat: initial commit - ServerManager Pro v2.0.0
This commit is contained in:
@@ -0,0 +1,207 @@
|
||||
import React, { useState, useEffect } from 'react';
|
||||
|
||||
const ServerMonitoring = () => {
|
||||
const [servers, setServers] = useState([]);
|
||||
const [monitoringData, setMonitoringData] = useState({});
|
||||
const [autoRefresh, setAutoRefresh] = useState(true);
|
||||
|
||||
useEffect(() => {
|
||||
loadServers();
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
if (autoRefresh) {
|
||||
const interval = setInterval(() => {
|
||||
servers.forEach(server => {
|
||||
checkServerStatus(server);
|
||||
});
|
||||
}, 30000);
|
||||
return () => clearInterval(interval);
|
||||
}
|
||||
}, [autoRefresh, servers]);
|
||||
|
||||
const loadServers = async () => {
|
||||
if (window.electronAPI) {
|
||||
const result = await window.electronAPI.loadServers();
|
||||
if (result.success) {
|
||||
setServers(result.servers);
|
||||
result.servers.forEach(server => checkServerStatus(server));
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const checkServerStatus = async (server) => {
|
||||
if (!window.electronAPI) return;
|
||||
|
||||
// Ping проверка
|
||||
const pingResult = await window.electronAPI.pingServer(server);
|
||||
|
||||
// SNMP мониторинг (если доступен)
|
||||
let snmpData = null;
|
||||
try {
|
||||
const snmpResult = await window.electronAPI.getSnmpData(server, 'public');
|
||||
if (snmpResult.success) snmpData = snmpResult.data;
|
||||
} catch (error) {}
|
||||
|
||||
// SSH мониторинг (если есть учетные данные)
|
||||
let sshStats = null;
|
||||
if (server.username && server.password) {
|
||||
try {
|
||||
const sshResult = await window.electronAPI.getSshStats(server);
|
||||
if (sshResult.success) sshStats = sshResult.stats;
|
||||
} catch (error) {}
|
||||
}
|
||||
|
||||
setMonitoringData(prev => ({
|
||||
...prev,
|
||||
[server.id]: {
|
||||
ping: pingResult,
|
||||
snmp: snmpData,
|
||||
ssh: sshStats,
|
||||
lastUpdate: new Date()
|
||||
}
|
||||
}));
|
||||
};
|
||||
|
||||
const getStatusColor = (online) => {
|
||||
return online ? 'bg-green-500' : 'bg-red-500';
|
||||
};
|
||||
|
||||
const getUsageColor = (percent) => {
|
||||
if (percent < 70) return 'bg-green-500';
|
||||
if (percent < 90) return 'bg-yellow-500';
|
||||
return 'bg-red-500';
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="p-6">
|
||||
<div className="flex justify-between items-center mb-6">
|
||||
<div>
|
||||
<h1 className="text-2xl font-bold text-gray-800 dark:text-white">Мониторинг серверов</h1>
|
||||
<p className="text-gray-600 dark:text-gray-400">Реальное время состояние серверов</p>
|
||||
</div>
|
||||
<div className="flex items-center space-x-4">
|
||||
<label className="flex items-center space-x-2">
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={autoRefresh}
|
||||
onChange={(e) => setAutoRefresh(e.target.checked)}
|
||||
className="rounded border-gray-300"
|
||||
/>
|
||||
<span className="text-sm text-gray-700 dark:text-gray-300">Автообновление (30с)</span>
|
||||
</label>
|
||||
<button
|
||||
onClick={loadServers}
|
||||
className="px-4 py-2 bg-primary-600 text-white rounded-lg hover:bg-primary-700"
|
||||
>
|
||||
Обновить
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="grid grid-cols-1 lg:grid-cols-2 xl:grid-cols-3 gap-6">
|
||||
{servers.map(server => {
|
||||
const data = monitoringData[server.id];
|
||||
const isOnline = data?.ping?.online;
|
||||
|
||||
return (
|
||||
<div key={server.id} className="bg-white dark:bg-gray-800 rounded-lg shadow-lg p-6 border border-gray-200 dark:border-gray-700">
|
||||
<div className="flex justify-between items-start mb-4">
|
||||
<div>
|
||||
<h3 className="font-semibold text-lg text-gray-800 dark:text-white">{server.name}</h3>
|
||||
<p className="text-sm text-gray-600 dark:text-gray-400">{server.ip}</p>
|
||||
</div>
|
||||
<div className="flex items-center space-x-2">
|
||||
<div className={`w-3 h-3 rounded-full ${getStatusColor(isOnline)}`}></div>
|
||||
<span className={`text-sm font-medium ${isOnline ? 'text-green-600' : 'text-red-600'}`}>
|
||||
{isOnline ? 'Онлайн' : 'Офлайн'}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{data && (
|
||||
<div className="space-y-4">
|
||||
{/* Ping информация */}
|
||||
<div className="grid grid-cols-2 gap-4 text-sm">
|
||||
<div>
|
||||
<span className="text-gray-600 dark:text-gray-400">Ping:</span>
|
||||
<div className="font-semibold text-gray-800 dark:text-white">
|
||||
{data.ping?.responseTime ? `${data.ping.responseTime}ms` : 'N/A'}
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<span className="text-gray-600 dark:text-gray-400">Потери:</span>
|
||||
<div className="font-semibold text-gray-800 dark:text-white">
|
||||
{data.ping?.packetLoss ? `${data.ping.packetLoss}%` : 'N/A'}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* CPU Usage */}
|
||||
{(data.snmp?.cpuLoad || data.ssh?.cpuLoad) && (
|
||||
<div>
|
||||
<div className="flex justify-between text-sm mb-1">
|
||||
<span className="text-gray-600 dark:text-gray-400">Загрузка CPU</span>
|
||||
<span className="font-semibold text-gray-800 dark:text-white">
|
||||
{Math.round(data.ssh?.cpuLoad || data.snmp?.cpuLoad)}%
|
||||
</span>
|
||||
</div>
|
||||
<div className="w-full bg-gray-200 dark:bg-gray-700 rounded-full h-2">
|
||||
<div
|
||||
className={`h-2 rounded-full ${getUsageColor(data.ssh?.cpuLoad || data.snmp?.cpuLoad)}`}
|
||||
style={{ width: `${Math.min(100, data.ssh?.cpuLoad || data.snmp?.cpuLoad)}%` }}
|
||||
></div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Memory Usage */}
|
||||
{(data.ssh?.memoryUsed && data.ssh?.memoryTotal) && (
|
||||
<div>
|
||||
<div className="flex justify-between text-sm mb-1">
|
||||
<span className="text-gray-600 dark:text-gray-400">Память</span>
|
||||
<span className="font-semibold text-gray-800 dark:text-white">
|
||||
{Math.round((data.ssh.memoryUsed / data.ssh.memoryTotal) * 100)}%
|
||||
</span>
|
||||
</div>
|
||||
<div className="w-full bg-gray-200 dark:bg-gray-700 rounded-full h-2">
|
||||
<div
|
||||
className={`h-2 rounded-full ${getUsageColor((data.ssh.memoryUsed / data.ssh.memoryTotal) * 100)}`}
|
||||
style={{ width: `${Math.min(100, (data.ssh.memoryUsed / data.ssh.memoryTotal) * 100)}%` }}
|
||||
></div>
|
||||
</div>
|
||||
<div className="text-xs text-gray-500 dark:text-gray-400 mt-1">
|
||||
{data.ssh.memoryUsed}MB / {data.ssh.memoryTotal}MB
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Last update */}
|
||||
<div className="text-xs text-gray-500 dark:text-gray-400 border-t pt-2">
|
||||
Обновлено: {data.lastUpdate?.toLocaleTimeString()}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{!data && (
|
||||
<div className="text-center py-4 text-gray-500 dark:text-gray-400">
|
||||
Загрузка данных...
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
|
||||
{servers.length === 0 && (
|
||||
<div className="text-center py-12">
|
||||
<div className="text-gray-400 text-6xl mb-4">📊</div>
|
||||
<h3 className="text-lg font-medium text-gray-900 dark:text-white">Нет серверов для мониторинга</h3>
|
||||
<p className="text-gray-500 dark:text-gray-400 mt-1">Добавьте серверы в разделе "Серверы"</p>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default ServerMonitoring;
|
||||
Reference in New Issue
Block a user