feat: initial commit - ServerManager Pro v2.0.0

This commit is contained in:
2025-11-26 23:21:44 +03:00
commit af51c68d7f
39 changed files with 23191 additions and 0 deletions
+555
View File
@@ -0,0 +1,555 @@
const { app, BrowserWindow, ipcMain, shell, dialog, clipboard } = require('electron');
const path = require('path');
const isDev = require('electron-is-dev');
const { exec } = require('child_process');
const fs = require('fs');
// КРИТИЧЕСКИ ВАЖНО ДЛЯ LINUX - исправление песочницы
app.commandLine.appendSwitch('--no-sandbox');
app.commandLine.appendSwitch('--disable-setuid-sandbox');
app.commandLine.appendSwitch('--disable-gpu');
// Дополнительные флаги для Linux
if (process.platform === 'linux') {
app.commandLine.appendSwitch('--no-zygote');
app.commandLine.appendSwitch('--disable-dev-shm-usage');
}
app.disableHardwareAcceleration();
let mainWindow;
function createWindow() {
mainWindow = new BrowserWindow({
width: 1200,
height: 800,
minWidth: 800,
minHeight: 600,
webPreferences: {
nodeIntegration: false,
contextIsolation: true,
preload: path.join(__dirname, 'preload.js'),
webSecurity: false,
},
title: 'ServerManager Pro',
icon: path.join(__dirname, 'icon.png'),
show: false,
titleBarStyle: 'default'
});
const startUrl = isDev
? 'http://localhost:3000'
: `file://${path.join(__dirname, '../build/index.html')}`;
mainWindow.loadURL(startUrl);
mainWindow.once('ready-to-show', () => {
mainWindow.show();
mainWindow.focus();
});
mainWindow.on('closed', () => {
mainWindow = null;
});
// Открываем DevTools только в режиме разработки
if (isDev) {
mainWindow.webContents.openDevTools();
}
}
app.whenReady().then(() => {
createWindow();
app.on('activate', () => {
if (BrowserWindow.getAllWindows().length === 0) {
createWindow();
}
});
});
app.on('window-all-closed', () => {
if (process.platform !== 'darwin') {
app.quit();
}
});
// ==================== ВСПОМОГАТЕЛЬНЫЕ ФУНКЦИИ ====================
function ensureUserDataDir() {
const userDataPath = app.getPath('userData');
if (!fs.existsSync(userDataPath)) {
fs.mkdirSync(userDataPath, { recursive: true });
}
return userDataPath;
}
ipcMain.handle('connect-ssh', async (event, server) => {
return new Promise((resolve) => {
try {
const { ip, port = '22', username = 'root', password, sshKey } = server;
// Очищаем IP от лишних символов
const cleanIp = ip.trim().replace(/[^0-9.:]/g, '');
let sshCommand = `ssh -o StrictHostKeyChecking=no -o ConnectTimeout=10 ${username}@${cleanIp} -p ${port}`;
if (sshKey) {
const tempKeyPath = `/tmp/ssh_key_${Date.now()}`;
fs.writeFileSync(tempKeyPath, sshKey);
fs.chmodSync(tempKeyPath, 0o600);
sshCommand = `ssh -o StrictHostKeyChecking=no -o ConnectTimeout=10 -i ${tempKeyPath} ${username}@${cleanIp} -p ${port}`;
}
const terminals = [
{ cmd: 'gnome-terminal --', test: 'gnome-terminal' },
{ cmd: 'konsole -e', test: 'konsole' },
{ cmd: 'xfce4-terminal -x', test: 'xfce4-terminal' },
{ cmd: 'mate-terminal -x', test: 'mate-terminal' },
{ cmd: 'xterm -e', test: 'xterm' }
];
const tryTerminal = (index) => {
if (index >= terminals.length) {
// Если терминал не найден, показываем команду для копирования
const message = `Терминал не найден\n\nКоманда SSH:\n${sshCommand}\n\nПароль: ${password || 'не установлен'}\n\nСкопируйте команду и выполните вручную в терминале.`;
dialog.showMessageBox(mainWindow, {
type: 'info',
title: 'SSH Подключение',
message: 'Терминал не найден',
detail: message
}).then(() => {
resolve({ success: true });
});
return;
}
const terminal = terminals[index];
// Проверяем, доступен ли терминал
exec(`which ${terminal.test}`, (error) => {
if (error) {
tryTerminal(index + 1);
} else {
console.log('Using terminal:', terminal.cmd);
const fullCommand = `${terminal.cmd} bash -c "${sshCommand}; echo 'Нажмите Enter для закрытия...'; read"`;
exec(fullCommand, (error, stdout, stderr) => {
if (error) {
console.error('SSH terminal error:', error);
// Показываем ошибку пользователю
dialog.showMessageBox(mainWindow, {
type: 'error',
title: 'Ошибка SSH',
message: `Не удалось подключиться: ${error.message}`,
detail: stderr || 'Проверьте параметры подключения'
});
}
});
resolve({ success: true });
}
});
};
tryTerminal(0);
} catch (error) {
console.error('SSH connection error:', error);
resolve({ success: false, error: error.message });
}
});
});
// ==================== ВЕБ-ИНТЕРФЕЙС ====================
ipcMain.handle('open-web-interface', async (event, server) => {
try {
const { ip, webPort = '51821' } = server;
// Очищаем IP
const cleanIp = ip.trim().replace(/[^0-9.:]/g, '');
const url = `http://${cleanIp}:${webPort}`;
console.log('Opening web interface:', url);
// Проверяем доступность URL перед открытием
const { exec } = require('child_process');
// Проверяем доступность с помощью curl
exec(`curl -s --head --connect-timeout 3 ${url}`, (error) => {
if (error) {
// Если недоступно, показываем предупреждение
dialog.showMessageBox(mainWindow, {
type: 'warning',
title: 'Веб-интерфейс может быть недоступен',
message: 'Сервер может не отвечать',
detail: `URL: ${url}\n\nПроверьте:\n• Доступность сервера\n• Правильность порта\n• Запущен ли веб-сервис`
});
}
});
await shell.openExternal(url);
return { success: true };
} catch (error) {
console.error('Error opening web interface:', error);
const { ip, webPort = '51821' } = server;
const cleanIp = ip.trim().replace(/[^0-9.:]/g, '');
const url = `http://${cleanIp}:${webPort}`;
dialog.showMessageBox(mainWindow, {
type: 'info',
title: 'Веб-интерфейс',
message: 'Откройте в браузере',
detail: `URL: ${url}\n\nЕсли не открывается автоматически, скопируйте ссылку в браузер.`
});
return { success: false, error: error.message };
}
});
// ==================== УПРАВЛЕНИЕ ДАННЫМИ ====================
// Серверы
ipcMain.handle('load-servers', async () => {
try {
const userDataPath = ensureUserDataDir();
const serversPath = path.join(userDataPath, 'servers.json');
if (fs.existsSync(serversPath)) {
const data = fs.readFileSync(serversPath, 'utf8');
return { success: true, servers: JSON.parse(data) };
}
return { success: true, servers: [] };
} catch (error) {
return { success: false, error: error.message, servers: [] };
}
});
ipcMain.handle('save-servers', async (event, servers) => {
try {
const userDataPath = ensureUserDataDir();
const serversPath = path.join(userDataPath, 'servers.json');
fs.writeFileSync(serversPath, JSON.stringify(servers, null, 2));
return { success: true };
} catch (error) {
return { success: false, error: error.message };
}
});
// Пароли
ipcMain.handle('load-passwords', async () => {
try {
const userDataPath = ensureUserDataDir();
const passwordsPath = path.join(userDataPath, 'passwords.json');
if (fs.existsSync(passwordsPath)) {
const data = fs.readFileSync(passwordsPath, 'utf8');
return { success: true, passwords: JSON.parse(data) };
}
return { success: true, passwords: [] };
} catch (error) {
return { success: false, error: error.message, passwords: [] };
}
});
ipcMain.handle('save-passwords', async (event, passwords) => {
try {
const userDataPath = ensureUserDataDir();
const passwordsPath = path.join(userDataPath, 'passwords.json');
fs.writeFileSync(passwordsPath, JSON.stringify(passwords, null, 2));
return { success: true };
} catch (error) {
return { success: false, error: error.message };
}
});
// Шаблоны команд
ipcMain.handle('load-command-templates', async () => {
try {
const userDataPath = ensureUserDataDir();
const templatesPath = path.join(userDataPath, 'command-templates.json');
if (fs.existsSync(templatesPath)) {
const data = fs.readFileSync(templatesPath, 'utf8');
return { success: true, templates: JSON.parse(data) };
}
return { success: true, templates: [] };
} catch (error) {
return { success: false, error: error.message, templates: [] };
}
});
ipcMain.handle('save-command-templates', async (event, templates) => {
try {
const userDataPath = ensureUserDataDir();
const templatesPath = path.join(userDataPath, 'command-templates.json');
fs.writeFileSync(templatesPath, JSON.stringify(templates, null, 2));
return { success: true };
} catch (error) {
return { success: false, error: error.message };
}
});
// Базовые утилиты
ipcMain.handle('copy-to-clipboard', async (event, text) => {
try {
clipboard.writeText(text);
return { success: true };
} catch (error) {
return { success: false, error: error.message };
}
});
// Экспорт/импорт данных
ipcMain.handle('export-data', async (event, data) => {
const result = await dialog.showSaveDialog(mainWindow, {
title: 'Экспорт данных',
defaultPath: `servermanager-backup-${new Date().toISOString().split('T')[0]}.json`,
filters: [{ name: 'JSON', extensions: ['json'] }]
});
if (result.canceled) return { success: false, error: 'Отменено' };
try {
fs.writeFileSync(result.filePath, JSON.stringify(data, null, 2));
return { success: true };
} catch (error) {
return { success: false, error: error.message };
}
});
ipcMain.handle('import-data', async (event) => {
const result = await dialog.showOpenDialog(mainWindow, {
title: 'Импорт данных',
filters: [{ name: 'JSON', extensions: ['json'] }],
properties: ['openFile']
});
if (result.canceled) return { success: false, error: 'Отменено' };
try {
const data = fs.readFileSync(result.filePaths[0], 'utf8');
return { success: true, data: JSON.parse(data) };
} catch (error) {
return { success: false, error: error.message };
}
});
// Добавьте после других ipcMain.handle
// Уведомления
ipcMain.handle('show-notification', async (event, title, body) => {
try {
// Используем встроенные уведомления Electron
const notification = {
title: title,
body: body,
silent: true
};
// Показываем уведомление в главном процессе
const notif = new Notification(notification);
notif.show();
return { success: true };
} catch (error) {
console.error('Notification error:', error);
// Если уведомления не работают, просто логируем
console.log(`Уведомление: ${title} - ${body}`);
return { success: true };
}
});
// Логирование действий
ipcMain.handle('log-action', async (event, action) => {
try {
const userDataPath = ensureUserDataDir();
const logPath = path.join(userDataPath, 'actions.log');
const timestamp = new Date().toISOString();
const logEntry = `${timestamp}: ${action}\n`;
fs.appendFileSync(logPath, logEntry);
return { success: true };
} catch (error) {
return { success: false, error: error.message };
}
});
// Ping функция для проверки серверов
ipcMain.handle('ping-server', async (event, server) => {
try {
const { ip } = server;
const cleanIp = ip.trim().replace(/[^0-9.:]/g, '');
// Используем системный ping
const { exec } = require('child_process');
return new Promise((resolve) => {
exec(`ping -c 2 -W 1 ${cleanIp}`, (error, stdout, stderr) => {
if (error) {
resolve({
success: true,
online: false,
responseTime: null,
packetLoss: 100
});
} else {
// Парсим вывод ping для получения времени ответа
const timeMatch = stdout.match(/time=(\d+\.?\d*) ms/);
const responseTime = timeMatch ? parseFloat(timeMatch[1]) : null;
resolve({
success: true,
online: true,
responseTime: responseTime,
packetLoss: 0
});
}
});
});
} catch (error) {
return {
success: false,
online: false,
error: error.message
};
}
});
// ==================== СЕТЕВЫЕ ДАННЫЕ ====================
// Получение сетевых адаптеров
ipcMain.handle('get-network-adapters', async () => {
try {
const { networkInterfaces } = require('os');
const interfaces = networkInterfaces();
const adapters = [];
for (const [name, details] of Object.entries(interfaces)) {
for (const detail of details) {
if (detail.family === 'IPv4' && !detail.internal) {
adapters.push({
name: name,
description: `${name} Network Interface`,
status: 'Up',
ipAddress: detail.address,
macAddress: detail.mac,
type: getInterfaceType(name)
});
}
}
}
return { success: true, adapters };
} catch (error) {
console.error('Error getting network adapters:', error);
return { success: false, error: error.message, adapters: [] };
}
});
// Получение VPN подключений
ipcMain.handle('get-vpn-connections', async () => {
try {
const connections = [];
// Проверяем WireGuard
try {
const { execSync } = require('child_process');
const wireguardResult = execSync('ip link show type wireguard 2>/dev/null || echo ""', { encoding: 'utf8' });
if (wireguardResult && wireguardResult.includes('wireguard')) {
connections.push({
name: 'WireGuard VPN',
interface: 'wg0',
status: 'Connected',
description: 'WireGuard VPN Tunnel'
});
}
} catch (e) {
// WireGuard не установлен или нет интерфейсов
}
// Проверяем OpenVPN
try {
const { execSync } = require('child_process');
const openvpnResult = execSync('ps aux | grep openvpn | grep -v grep || echo ""', { encoding: 'utf8' });
if (openvpnResult && openvpnResult.includes('openvpn')) {
connections.push({
name: 'OpenVPN',
interface: 'tun0',
status: 'Connected',
description: 'OpenVPN Connection'
});
}
} catch (e) {
// OpenVPN не запущен
}
return { success: true, connections };
} catch (error) {
console.error('Error getting VPN connections:', error);
return { success: false, error: error.message, connections: [] };
}
});
// Вспомогательная функция для определения типа интерфейса
function getInterfaceType(name) {
if (name.includes('wl') || name.includes('wlan') || name.includes('wifi')) {
return 'Wi-Fi';
} else if (name.includes('eth') || name.includes('enp') || name.includes('ens')) {
return 'Ethernet';
} else if (name.includes('tun') || name.includes('tap')) {
return 'VPN';
} else if (name.includes('lo')) {
return 'Loopback';
} else {
return 'Ethernet';
}
}
// Заметки
ipcMain.handle('load-notes', async () => {
try {
const userDataPath = ensureUserDataDir();
const notesPath = path.join(userDataPath, 'notes.json');
if (fs.existsSync(notesPath)) {
const data = fs.readFileSync(notesPath, 'utf8');
return { success: true, notes: JSON.parse(data) };
}
return { success: true, notes: [] };
} catch (error) {
return { success: false, error: error.message, notes: [] };
}
});
ipcMain.handle('save-notes', async (event, notes) => {
try {
const userDataPath = ensureUserDataDir();
const notesPath = path.join(userDataPath, 'notes.json');
fs.writeFileSync(notesPath, JSON.stringify(notes, null, 2));
return { success: true };
} catch (error) {
return { success: false, error: error.message };
}
});
ipcMain.handle('load-note-folders', async () => {
try {
const userDataPath = ensureUserDataDir();
const foldersPath = path.join(userDataPath, 'note-folders.json');
if (fs.existsSync(foldersPath)) {
const data = fs.readFileSync(foldersPath, 'utf8');
return { success: true, folders: JSON.parse(data) };
}
return { success: true, folders: [] };
} catch (error) {
return { success: false, error: error.message, folders: [] };
}
});
ipcMain.handle('save-note-folders', async (event, folders) => {
try {
const userDataPath = ensureUserDataDir();
const foldersPath = path.join(userDataPath, 'note-folders.json');
fs.writeFileSync(foldersPath, JSON.stringify(folders, null, 2));
return { success: true };
} catch (error) {
return { success: false, error: error.message };
}
});