555 lines
18 KiB
JavaScript
555 lines
18 KiB
JavaScript
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 };
|
|
}
|
|
}); |