const { app, BrowserWindow, ipcMain, Menu } = require('electron'); const path = require('path'); const { Client } = require('ssh2'); const Store = require('electron-store'); const store = new Store(); let mainWindow; let sshConnections = {}; let sftpConnections = {}; let shellStreams = {}; function createWindow() { mainWindow = new BrowserWindow({ width: 1400, height: 900, minWidth: 1200, minHeight: 700, webPreferences: { nodeIntegration: true, contextIsolation: false, enableRemoteModule: true }, title: 'SSH Client Pro' }); // 加载页面 if (process.env.NODE_ENV === 'development') { mainWindow.loadURL('http://localhost:5173'); mainWindow.webContents.openDevTools(); } else { mainWindow.loadFile(path.join(__dirname, '../dist/index.html')); } mainWindow.on('closed', () => { Object.values(sshConnections).forEach(conn => { try { conn.end(); } catch (e) {} }); mainWindow = null; }); } app.whenReady().then(() => { Menu.setApplicationMenu(null); createWindow(); }); app.on('window-all-closed', () => { if (process.platform !== 'darwin') { app.quit(); } }); app.on('activate', () => { if (BrowserWindow.getAllWindows().length === 0) { createWindow(); } }); // ========== 连接配置持久化 ========== ipcMain.handle('get-connections', () => { return store.get('connections', []); }); ipcMain.handle('save-connection', (event, config) => { const connections = store.get('connections', []); const idx = connections.findIndex(c => c.id === config.id); if (idx >= 0) { connections[idx] = config; } else { connections.push(config); } store.set('connections', connections); return { success: true }; }); ipcMain.handle('delete-connection', (event, id) => { let connections = store.get('connections', []); connections = connections.filter(c => c.id !== id); store.set('connections', connections); return { success: true }; }); // ========== SSH 连接管理 ========== ipcMain.handle('ssh-connect', (event, { id, host, port, username, password, privateKey }) => { return new Promise((resolve, reject) => { const conn = new Client(); let settled = false; const timeout = setTimeout(() => { if (!settled) { settled = true; try { conn.end(); } catch (e) {} reject(new Error('连接超时')); } }, 15000); conn.on('ready', () => { if (settled) return; settled = true; clearTimeout(timeout); sshConnections[id] = conn; resolve({ success: true }); }); conn.on('error', (err) => { if (!settled) { settled = true; clearTimeout(timeout); reject(new Error(err.message)); } else { if (mainWindow) { mainWindow.webContents.send('ssh-error', { id, error: err.message }); } } }); conn.on('close', () => { delete sshConnections[id]; delete sftpConnections[id]; delete shellStreams[id]; if (mainWindow) { mainWindow.webContents.send('ssh-closed', { id }); } }); const connectConfig = { host, port: port || 22, username, readyTimeout: 15000 }; if (privateKey && privateKey.trim()) { connectConfig.privateKey = privateKey; } else { connectConfig.password = password; } conn.connect(connectConfig); }); }); ipcMain.handle('ssh-disconnect', (event, { id }) => { if (sshConnections[id]) { try { sshConnections[id].end(); } catch (e) {} delete sshConnections[id]; delete sftpConnections[id]; delete shellStreams[id]; } return { success: true }; }); // ========== Shell 会话 ========== ipcMain.handle('ssh-shell', (event, { id }) => { return new Promise((resolve, reject) => { const conn = sshConnections[id]; if (!conn) { reject(new Error('SSH连接不存在')); return; } conn.shell({ term: 'xterm-256color', cols: 120, rows: 30 }, (err, stream) => { if (err) { reject(new Error(err.message)); return; } shellStreams[id] = stream; stream.on('data', (data) => { if (mainWindow) { mainWindow.webContents.send('ssh-data', { id, data: data.toString('utf-8') }); } }); stream.stderr.on('data', (data) => { if (mainWindow) { mainWindow.webContents.send('ssh-data', { id, data: data.toString('utf-8') }); } }); stream.on('close', () => { if (mainWindow) { mainWindow.webContents.send('ssh-closed', { id }); } }); resolve({ success: true }); }); }); }); // 终端输入 ipcMain.on('ssh-input', (event, { id, data }) => { const stream = shellStreams[id]; if (stream) { stream.write(data); } }); // 调整终端大小 ipcMain.on('ssh-resize', (event, { id, cols, rows }) => { const stream = shellStreams[id]; if (stream) { stream.setWindow(rows, cols, 0, 0); } }); // ========== SFTP 文件管理 ========== ipcMain.handle('sftp-init', (event, { id }) => { return new Promise((resolve, reject) => { const conn = sshConnections[id]; if (!conn) { reject(new Error('SSH连接不存在')); return; } conn.sftp((err, sftp) => { if (err) { reject(new Error(err.message)); return; } sftpConnections[id] = sftp; resolve({ success: true }); }); }); }); ipcMain.handle('sftp-readdir', (event, { id, path }) => { return new Promise((resolve, reject) => { const sftp = sftpConnections[id]; if (!sftp) { reject(new Error('SFTP连接不存在')); return; } sftp.readdir(path || '/', (err, list) => { if (err) { reject(new Error(err.message)); return; } const files = list.map(item => ({ filename: item.filename, longname: item.longname, attrs: { size: item.attrs.size, mtime: item.attrs.mtime, atime: item.attrs.atime, permissions: item.attrs.mode, isDirectory: (item.attrs.mode & 0o40000) === 0o40000 } })); resolve({ success: true, files }); }); }); }); ipcMain.handle('sftp-readfile', (event, { id, path }) => { return new Promise((resolve, reject) => { const sftp = sftpConnections[id]; if (!sftp) { reject(new Error('SFTP连接不存在')); return; } let content = ''; const stream = sftp.createReadStream(path, { encoding: 'utf-8' }); stream.on('data', (chunk) => { content += chunk; }); stream.on('end', () => { resolve({ success: true, content }); }); stream.on('error', (err) => { reject(new Error(err.message)); }); }); }); ipcMain.handle('sftp-writefile', (event, { id, path, content }) => { return new Promise((resolve, reject) => { const sftp = sftpConnections[id]; if (!sftp) { reject(new Error('SFTP连接不存在')); return; } const stream = sftp.createWriteStream(path); stream.on('close', () => { resolve({ success: true }); }); stream.on('error', (err) => { reject(new Error(err.message)); }); stream.end(content); }); }); ipcMain.handle('sftp-mkdir', (event, { id, path }) => { return new Promise((resolve, reject) => { const sftp = sftpConnections[id]; if (!sftp) { reject(new Error('SFTP连接不存在')); return; } sftp.mkdir(path, (err) => { if (err) { reject(new Error(err.message)); } else { resolve({ success: true }); } }); }); }); ipcMain.handle('sftp-rm', (event, { id, path, isDirectory }) => { return new Promise((resolve, reject) => { const sftp = sftpConnections[id]; if (!sftp) { reject(new Error('SFTP连接不存在')); return; } const callback = (err) => { if (err) { reject(new Error(err.message)); } else { resolve({ success: true }); } }; if (isDirectory) { sftp.rmdir(path, callback); } else { sftp.unlink(path, callback); } }); }); // ========== 系统监控 ========== function execRemoteCommand(conn, cmd) { return new Promise((resolve) => { conn.exec(cmd, (err, stream) => { if (err) { resolve(''); return; } let output = ''; stream.on('data', (data) => { output += data.toString(); }); stream.stderr.on('data', (data) => { output += data.toString(); }); stream.on('close', () => { resolve(output); }); }); }); } ipcMain.handle('system-monitor', async (event, { id }) => { const conn = sshConnections[id]; if (!conn) { return { success: false, error: 'SSH连接不存在' }; } try { // 并行执行所有命令 const [cpuOutput, memOutput, diskOutput, sysOutput, processOutput, uptimeOutput] = await Promise.all([ execRemoteCommand(conn, "top -bn1 | grep 'Cpu(s)' | head -1"), execRemoteCommand(conn, "free -m | grep Mem"), execRemoteCommand(conn, "df -h | grep -E '^/dev/'"), execRemoteCommand(conn, "cat /etc/os-release 2>/dev/null | grep PRETTY_NAME | cut -d'\"' -f2; echo '---'; uname -r; echo '---'; hostname"), execRemoteCommand(conn, "ps aux | wc -l"), execRemoteCommand(conn, "cat /proc/loadavg; echo '---'; uptime -p 2>/dev/null || uptime") ]); // 解析CPU使用率 let cpuUsage = 0; const cpuMatch = cpuOutput.match(/([\d.]+)\s*id/); if (cpuMatch) { cpuUsage = 100 - parseFloat(cpuMatch[1]); } // 解析内存 (单位MB) let memoryStr = '0,0,0,0'; const memParts = memOutput.trim().split(/\s+/); if (memParts.length >= 7) { const total = parseInt(memParts[1]) || 0; const used = parseInt(memParts[2]) || 0; const free = parseInt(memParts[3]) || 0; const available = parseInt(memParts[6]) || 0; memoryStr = `${total},${used},${free},${available}`; } // 解析磁盘 const diskLines = diskOutput.trim().split('\n').filter(Boolean).map(line => { const parts = line.split(/\s+/); return `${parts[0]},${parts[1]},${parts[2]},${parts[3]},${parseInt(parts[4]) || 0},${parts[5]}`; }).join('\n'); // 解析系统信息 const sysParts = sysOutput.split('---'); const systemInfo = [ sysParts[0]?.trim(), sysParts[1]?.trim() ? `内核: ${sysParts[1].trim()}` : '', sysParts[2]?.trim() ? `主机: ${sysParts[2].trim()}` : '' ].filter(Boolean).join('\n'); // 解析负载 const loadParts = uptimeOutput.split('---'); let loadStr = '0,0,0'; let uptimeStr = '-'; if (loadParts[0]) { const loads = loadParts[0].trim().split(/\s+/); loadStr = loads.slice(0, 3).join(','); } if (loadParts[1]) { uptimeStr = loadParts[1].trim().replace('up ', ''); } // 进程数 const processCount = parseInt(processOutput.trim()) || 0; return { success: true, data: { cpu: cpuUsage.toFixed(1), memory: memoryStr, disk: diskLines, load: loadStr, processes: processCount, uptime: uptimeStr, system: systemInfo } }; } catch (error) { return { success: false, error: error.message }; } }); // ========== AI Agent 命令执行 ========== const TASK_COMMANDS = { system_info: "cat /etc/os-release 2>/dev/null | grep PRETTY_NAME | cut -d'\"' -f2; uname -a", cpu_info: "lscpu 2>/dev/null | head -20 || cat /proc/cpuinfo | head -30", memory_usage: "free -h", disk_usage: "df -h", top_processes: "ps aux --sort=-%mem | head -16", uptime_check: "uptime", network_info: "ip addr 2>/dev/null || ifconfig", port_listen: "ss -tulnp 2>/dev/null || netstat -tulnp", nginx_status: "systemctl status nginx 2>/dev/null || service nginx status 2>/dev/null || echo 'Nginx未安装'", mysql_status: "systemctl status mysql 2>/dev/null || systemctl status mysqld 2>/dev/null || echo 'MySQL未安装'", docker: "docker ps 2>/dev/null || echo 'Docker未安装或未运行'", logged_users: "who", last_logins: "last -10 2>/dev/null || echo '无法获取登录记录'", firewall: "iptables -L -n 2>/dev/null || echo '需要root权限'", ssh_config: "cat /etc/ssh/sshd_config | grep -v '^#' | grep -v '^$'", sudo_users: "cat /etc/sudoers 2>/dev/null | grep -v '^#' | grep -v '^$' || echo '需要root权限'", services: "systemctl list-units --type=service --state=running 2>/dev/null | head -20 || service --status-all 2>/dev/null", cron_jobs: "crontab -l 2>/dev/null || echo '无定时任务'; ls /etc/cron.d/ 2>/dev/null", env_vars: "env | sort | head -30", package_update: "apt list --upgradable 2>/dev/null || yum check-update 2>/dev/null || echo '无法检查更新'", disk_io: "iostat -x 1 1 2>/dev/null || echo '需要安装sysstat'" }; ipcMain.handle('ai-execute', (event, { id, task }) => { return new Promise((resolve) => { const conn = sshConnections[id]; if (!conn) { resolve({ success: false, output: '错误: SSH连接不存在' }); return; } // 如果是预定义任务,使用对应命令;否则直接作为命令执行 const cmd = TASK_COMMANDS[task] || task; conn.exec(cmd, (err, stream) => { if (err) { resolve({ success: false, output: '执行错误: ' + err.message }); return; } let output = ''; stream.on('data', (data) => { output += data.toString(); }); stream.stderr.on('data', (data) => { output += data.toString(); }); stream.on('close', (code) => { resolve({ success: code === 0, output: output || '(无输出)' }); }); }); }); }); // ==================== 进程监控 ==================== ipcMain.handle('get-processes', async (event, { id, sortBy = 'cpu', limit = 20 }) => { const conn = sshConnections[id]; if (!conn) { return { success: false, error: 'SSH连接不存在' }; } try { const sortFlag = sortBy === 'memory' ? '--sort=-%mem' : '--sort=-%cpu'; const cmd = `ps aux ${sortFlag} | head -${limit + 1}`; const output = await execRemoteCommand(conn, cmd); const lines = output.trim().split('\n'); // 跳过表头 const processes = lines.slice(1).filter(Boolean).map(line => { const parts = line.split(/\s+/); return { user: parts[0], pid: parts[1], cpu: parts[2], mem: parts[3], vsz: parts[4], rss: parts[5], stat: parts[7], start: parts[8], time: parts[9], command: parts.slice(10).join(' ') }; }); return { success: true, data: processes }; } catch (error) { return { success: false, error: error.message }; } }); // ==================== AI 设置 ==================== ipcMain.handle('get-ai-settings', () => { return store.get('aiSettings', { apiKey: '', baseUrl: 'https://api.openai.com/v1', model: 'gpt-3.5-turbo' }); }); ipcMain.handle('save-ai-settings', (event, settings) => { store.set('aiSettings', settings); return { success: true }; }); // AI 智能命令解析 ipcMain.handle('ai-parse-command', async (event, { input, apiKey, baseUrl, model }) => { try { const url = `${baseUrl.replace(/\/$/, '')}/chat/completions`; const response = await fetch(url, { method: 'POST', headers: { 'Content-Type': 'application/json', 'Authorization': `Bearer ${apiKey}` }, body: JSON.stringify({ model: model || 'gpt-3.5-turbo', messages: [ { role: 'system', content: '你是一个Linux服务器管理专家。用户会用自然语言描述他们想要执行的服务器操作。你需要返回对应的Linux命令。只返回命令本身,不要任何解释、不要markdown格式、不要代码块标记。如果用户的请求不安全或无法理解,返回 #ERROR: 加上原因。' }, { role: 'user', content: input } ], temperature: 0.1, max_tokens: 200 }) }); if (!response.ok) { const errText = await response.text(); return { success: false, error: `API请求失败 (${response.status}): ${errText}` }; } const data = await response.json(); const command = data.choices[0].message.content.trim(); if (command.startsWith('#ERROR:')) { return { success: false, error: command.replace('#ERROR:', '').trim() }; } return { success: true, command }; } catch (error) { return { success: false, error: `AI请求失败: ${error.message}` }; } });