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 }; } });