const { app, BrowserWindow, ipcMain } = require('electron'); const path = require('path'); const { Client } = require('ssh2'); 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, '../public/index.html')); } mainWindow.on('closed', () => { // 关闭所有SSH连接 Object.values(sshConnections).forEach(conn => { try { conn.end(); } catch (e) {} }); mainWindow = null; }); } app.whenReady().then(createWindow); app.on('window-all-closed', () => { if (process.platform !== 'darwin') { app.quit(); } }); app.on('activate', () => { if (BrowserWindow.getAllWindows().length === 0) { createWindow(); } }); // ========== SSH 连接管理 ========== // SSH连接 ipcMain.on('ssh-connect', (event, { id, host, port, username, password, privateKey }) => { const conn = new Client(); conn.on('ready', () => { sshConnections[id] = conn; // 通知前端连接成功 if (mainWindow) { mainWindow.webContents.send('ssh-connected', { id }); } // 创建交互式Shell conn.shell({ term: 'xterm-256color', cols: 120, rows: 30 }, (err, stream) => { if (err) { if (mainWindow) { mainWindow.webContents.send('ssh-error', { id, 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 }); } }); }); // 初始化SFTP conn.sftp((err, sftp) => { if (!err) { sftpConnections[id] = sftp; } }); }); conn.on('error', (err) => { 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); }); // SSH断开连接 ipcMain.on('ssh-disconnect', (event, { id }) => { if (sshConnections[id]) { try { sshConnections[id].end(); } catch (e) {} delete sshConnections[id]; delete sftpConnections[id]; delete shellStreams[id]; } }); // 终端输入 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.on('sftp-list', (event, { id, path }) => { const sftp = sftpConnections[id]; if (!sftp) { if (mainWindow) { mainWindow.webContents.send('sftp-list', { id, path, files: [], error: 'SFTP连接不存在' }); } return; } sftp.readdir(path || '/', (err, list) => { if (err) { if (mainWindow) { mainWindow.webContents.send('sftp-list', { id, path, files: [], error: err.message }); } return; } const files = list.map(item => ({ filename: item.filename, longname: item.longname, size: item.attrs.size, mtime: item.attrs.mtime, atime: item.attrs.atime, permissions: item.attrs.mode, isDirectory: (item.attrs.mode & 0o40000) === 0o40000 })); if (mainWindow) { mainWindow.webContents.send('sftp-list', { id, path, files }); } }); }); // 读取文件内容 ipcMain.on('sftp-readfile', (event, { id, path }) => { const sftp = sftpConnections[id]; if (!sftp) return; let content = ''; const stream = sftp.createReadStream(path, { encoding: 'utf-8' }); stream.on('data', (chunk) => { content += chunk; }); stream.on('end', () => { if (mainWindow) { mainWindow.webContents.send('sftp-file-content', { id, path, content }); } }); stream.on('error', (err) => { if (mainWindow) { mainWindow.webContents.send('sftp-file-content', { id, path, error: err.message }); } }); }); // 写入文件内容 ipcMain.on('sftp-writefile', (event, { id, path, content }) => { const sftp = sftpConnections[id]; if (!sftp) return; const stream = sftp.createWriteStream(path); stream.on('close', () => { if (mainWindow) { mainWindow.webContents.send('sftp-write-result', { id, path, success: true }); } }); stream.on('error', (err) => { if (mainWindow) { mainWindow.webContents.send('sftp-write-result', { id, path, success: false, error: err.message }); } }); stream.end(content); }); // 创建目录 ipcMain.on('sftp-mkdir', (event, { id, path }) => { const sftp = sftpConnections[id]; if (!sftp) return; sftp.mkdir(path, (err) => { if (mainWindow) { mainWindow.webContents.send('sftp-mkdir-result', { id, path, success: !err, error: err?.message }); } }); }); // 删除文件/目录 ipcMain.on('sftp-rm', (event, { id, path, isDirectory }) => { const sftp = sftpConnections[id]; if (!sftp) return; const callback = (err) => { if (mainWindow) { mainWindow.webContents.send('sftp-rm-result', { id, path, success: !err, error: err?.message }); } }; if (isDirectory) { sftp.rmdir(path, callback); } else { sftp.unlink(path, callback); } }); // 重命名/移动 ipcMain.on('sftp-rename', (event, { id, oldPath, newPath }) => { const sftp = sftpConnections[id]; if (!sftp) return; sftp.rename(oldPath, newPath, (err) => { if (mainWindow) { mainWindow.webContents.send('sftp-rename-result', { id, oldPath, newPath, success: !err, error: err?.message }); } }); }); // ========== 系统监控 ========== ipcMain.on('monitor-request', (event, { id }) => { const conn = sshConnections[id]; if (!conn) return; const monitorData = { id, cpu: { usage: 0, cores: '-', load: '-', model: '-' }, memory: { percent: 0, total: '-', used: '-', free: '-' }, disks: [], system: { hostname: '-', os: '-', uptime: '-', kernel: '-' }, processes: [] }; let completed = 0; const total = 5; const checkDone = () => { completed++; if (completed >= total && mainWindow) { mainWindow.webContents.send('monitor-data', monitorData); } }; // CPU信息 conn.exec("top -bn1 | head -5; echo '---'; nproc; echo '---'; cat /proc/loadavg; echo '---'; lscpu | grep 'Model name' | sed 's/Model name:[ ]*//'", (err, stream) => { if (err) { checkDone(); return; } let output = ''; stream.on('data', (data) => { output += data.toString(); }); stream.on('close', () => { const lines = output.split('\n'); // 解析CPU使用率 const cpuLine = lines.find(l => l.includes('Cpu(s)')); if (cpuLine) { const match = cpuLine.match(/([\d.]+)\s*(?:%|id)/i); if (match) { monitorData.cpu.usage = 100 - parseFloat(match[1]); } } // 解析核心数 const parts = output.split('---'); if (parts[1]) monitorData.cpu.cores = parts[1].trim(); if (parts[2]) { const loads = parts[2].trim().split(/\s+/); monitorData.cpu.load = loads.slice(0, 3).join(', '); } if (parts[3]) monitorData.cpu.model = parts[3].trim().substring(0, 40); checkDone(); }); }); // 内存信息 conn.exec("free -h | grep Mem", (err, stream) => { if (err) { checkDone(); return; } let output = ''; stream.on('data', (data) => { output += data.toString(); }); stream.on('close', () => { const parts = output.trim().split(/\s+/); if (parts.length >= 4) { monitorData.memory.total = parts[1]; monitorData.memory.used = parts[2]; monitorData.memory.free = parts[3]; // 计算百分比 const totalNum = parseFloat(parts[1]); const usedNum = parseFloat(parts[2]); if (totalNum > 0) { monitorData.memory.percent = (usedNum / totalNum) * 100; } } checkDone(); }); }); // 磁盘信息 conn.exec("df -h | grep -E '^/dev/'", (err, stream) => { if (err) { checkDone(); return; } let output = ''; stream.on('data', (data) => { output += data.toString(); }); stream.on('close', () => { const lines = output.trim().split('\n'); monitorData.disks = lines.map(line => { const parts = line.split(/\s+/); return { device: parts[0], total: parts[1], used: parts[2], available: parts[3], percent: parseInt(parts[4]) || 0, mount: parts[5] }; }); checkDone(); }); }); // 系统信息 conn.exec("hostname; echo '---'; cat /etc/os-release 2>/dev/null | grep PRETTY_NAME | cut -d'\"' -f2; echo '---'; uptime -p 2>/dev/null || uptime; echo '---'; uname -r", (err, stream) => { if (err) { checkDone(); return; } let output = ''; stream.on('data', (data) => { output += data.toString(); }); stream.on('close', () => { const parts = output.split('---'); if (parts[0]) monitorData.system.hostname = parts[0].trim(); if (parts[1]) monitorData.system.os = parts[1].trim(); if (parts[2]) monitorData.system.uptime = parts[2].trim().replace('up ', ''); if (parts[3]) monitorData.system.kernel = parts[3].trim(); checkDone(); }); }); // 进程列表 conn.exec("ps aux --sort=-%mem | head -16 | tail -15", (err, stream) => { if (err) { checkDone(); return; } let output = ''; stream.on('data', (data) => { output += data.toString(); }); stream.on('close', () => { const lines = output.trim().split('\n'); monitorData.processes = lines.map(line => { const parts = line.split(/\s+/); return { user: parts[0], pid: parts[1], cpu: parts[2], mem: parts[3], cmd: parts.slice(10).join(' ').substring(0, 50) }; }); checkDone(); }); }); }); // ========== AI Agent 命令执行 ========== ipcMain.on('exec-command', (event, { id, cmd, context }) => { const conn = sshConnections[id]; if (!conn) { if (mainWindow) { mainWindow.webContents.send('cmd-result', { id, output: '错误: SSH连接不存在', context }); } return; } conn.exec(cmd, (err, stream) => { if (err) { if (mainWindow) { mainWindow.webContents.send('cmd-result', { id, output: '执行错误: ' + err.message, context }); } return; } let output = ''; stream.on('data', (data) => { output += data.toString(); }); stream.stderr.on('data', (data) => { output += data.toString(); }); stream.on('close', (code) => { if (mainWindow) { mainWindow.webContents.send('cmd-result', { id, output, code, context }); } }); }); });