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 };
}
});
Binary file not shown.

After

Width:  |  Height:  |  Size: 3.8 KiB

+13
View File
@@ -0,0 +1,13 @@
<svg width="512" height="512" viewBox="0 0 512 512" xmlns="http://www.w3.org/2000/svg">
<defs>
<linearGradient id="grad" x1="0%" y1="0%" x2="100%" y2="100%">
<stop offset="0%" style="stop-color:#0ea5e9;stop-opacity:1" />
<stop offset="100%" style="stop-color:#0369a1;stop-opacity:1" />
</linearGradient>
</defs>
<rect width="512" height="512" rx="80" fill="url(#grad)"/>
<text x="256" y="280" font-family="Arial, sans-serif" font-size="180" font-weight="bold"
text-anchor="middle" fill="white">SM</text>
<text x="256" y="380" font-family="Arial, sans-serif" font-size="40"
text-anchor="middle" fill="white">Pro</text>
</svg>

After

Width:  |  Height:  |  Size: 673 B

+18
View File
@@ -0,0 +1,18 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<link rel="icon" href="%PUBLIC_URL%/favicon.ico" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<meta name="theme-color" content="#000000" />
<meta
name="description"
content="WireGuard Manager - управление VPN серверами"
/>
<title>WireGuard Manager</title>
</head>
<body>
<noscript>You need to enable JavaScript to run this app.</noscript>
<div id="root"></div>
</body>
</html>
Binary file not shown.

After

Width:  |  Height:  |  Size: 5.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.4 KiB

+25
View File
@@ -0,0 +1,25 @@
{
"short_name": "React App",
"name": "Create React App Sample",
"icons": [
{
"src": "favicon.ico",
"sizes": "64x64 32x32 24x24 16x16",
"type": "image/x-icon"
},
{
"src": "logo192.png",
"type": "image/png",
"sizes": "192x192"
},
{
"src": "logo512.png",
"type": "image/png",
"sizes": "512x512"
}
],
"start_url": ".",
"display": "standalone",
"theme_color": "#000000",
"background_color": "#ffffff"
}
+40
View File
@@ -0,0 +1,40 @@
const { contextBridge, ipcRenderer } = require('electron');
contextBridge.exposeInMainWorld('electronAPI', {
// Основные функции
loadServers: () => ipcRenderer.invoke('load-servers'),
saveServers: (servers) => ipcRenderer.invoke('save-servers', servers),
connectSSH: (server) => ipcRenderer.invoke('connect-ssh', server),
openWebInterface: (server) => ipcRenderer.invoke('open-web-interface', server),
// Пароли
loadPasswords: () => ipcRenderer.invoke('load-passwords'),
savePasswords: (passwords) => ipcRenderer.invoke('save-passwords', passwords),
// Шаблоны команд
loadCommandTemplates: () => ipcRenderer.invoke('load-command-templates'),
saveCommandTemplates: (templates) => ipcRenderer.invoke('save-command-templates', templates),
// Утилиты
copyToClipboard: (text) => ipcRenderer.invoke('copy-to-clipboard', text),
exportData: (data) => ipcRenderer.invoke('export-data', data),
importData: () => ipcRenderer.invoke('import-data'),
// Новые функции
showNotification: (title, body) => ipcRenderer.invoke('show-notification', title, body),
logAction: (action) => ipcRenderer.invoke('log-action', action),
// Мониторинг сети
getNetworkAdapters: () => ipcRenderer.invoke('get-network-adapters'),
getVpnConnections: () => ipcRenderer.invoke('get-vpn-connections'),
// Ping функция
pingServer: (server) => ipcRenderer.invoke('ping-server', server),
// Заметки
loadNotes: () => ipcRenderer.invoke('load-notes'),
saveNotes: (notes) => ipcRenderer.invoke('save-notes', notes),
loadNoteFolders: () => ipcRenderer.invoke('load-note-folders'),
saveNoteFolders: (folders) => ipcRenderer.invoke('save-note-folders', folders),
});
+3
View File
@@ -0,0 +1,3 @@
# https://www.robotstxt.org/robotstxt.html
User-agent: *
Disallow:
+10
View File
@@ -0,0 +1,10 @@
[Desktop Entry]
Version=2.0.0
Name=ServerManager Pro
Comment=Professional Server Management Tool
Exec=/opt/ServerManager Pro/servermanager-pro
Icon=/opt/ServerManager Pro/icon.png
Terminal=false
Type=Application
Categories=Development;Network;
StartupWMClass=ServerManager Pro