核心修复:
- ssh-service.ts: 新增 resolvePrivateKey(),将私钥文件路径(~/.ssh/id_rsa)解析为内容后传给 ssh2,
解决 'Cannot parse privateKey: Unsupported key format' 错误
- useTerminalConnection.ts + sessionHelper.ts: 根据 authType 过滤字段,password/agent 认证不再传 privateKey
- sessionStore.ts: addSession() 现在先调用 sessionCreate() 持久化到 SQLite,解决恢复标签'无可用连接信息'问题
- database-service.ts: updateSession() 补充 private_key_path 字段持久化
- SessionDialog.tsx: 默认 username='root'、privateKey='~/.ssh/id_rsa'
UI/UX:
- 语义化主题系统(app-theme.ts),强制深色模式,全局无边框设计
- macOS 标题栏支持双击最大化(window:toggleMaximize IPC)
- macOS 菜单栏('OpsXShell' 应用菜单 + Settings Cmd+,)
- WelcomeView 4卡片布局,移除 Settings 按钮
- TabBar/TabItem 背景色统一(bg-dark-800/900),消除白色边框线
架构优化:
- SFTP 生命周期修复(useSFTP.ts useRef + autoConnectAttempted guard)
- 连接池竞态修复,支持快速多标签连接
- IPC 接口规范化,安全错误处理
- Vite 代码分割优化
质量: lint零错误 | tsc零错误 | build成功 | 49 tests pass
281 lines
6.6 KiB
TypeScript
281 lines
6.6 KiB
TypeScript
/**
|
|
* OpsXShell - Main Process Entry
|
|
* 跨平台 SSH/SFTP 终端工具 · 简洁高效 · 安全可靠
|
|
*/
|
|
|
|
import { app, BrowserWindow, Menu, shell, dialog } from 'electron'
|
|
import path from 'path'
|
|
import { createLogger } from './utils/logger'
|
|
|
|
const logger = createLogger('Main')
|
|
|
|
// Services
|
|
import { SSHService } from './services/ssh-service'
|
|
import { DatabaseService } from './services/database-service'
|
|
import { keyService } from './services/key-service'
|
|
import { ZmodemService } from './services/zmodem-service'
|
|
|
|
// IPC Handlers
|
|
import { registerAllIPCHandlers } from './ipc'
|
|
|
|
// ============== Global State ==============
|
|
|
|
let mainWindow: BrowserWindow | null = null
|
|
|
|
// Services (initialized after app ready)
|
|
let sshService: SSHService
|
|
let zmodemService: ZmodemService
|
|
let dbService: DatabaseService | null = null
|
|
|
|
// ============== Window Management ==============
|
|
|
|
function createWindow(): void {
|
|
mainWindow = new BrowserWindow({
|
|
width: 1400,
|
|
height: 900,
|
|
minWidth: 1000,
|
|
minHeight: 600,
|
|
titleBarStyle: 'hiddenInset',
|
|
webPreferences: {
|
|
preload: path.join(__dirname, 'preload.js'),
|
|
contextIsolation: true,
|
|
nodeIntegration: false,
|
|
sandbox: false,
|
|
},
|
|
show: false,
|
|
backgroundColor: '#0f172a',
|
|
})
|
|
|
|
// Load the app
|
|
if (process.env.VITE_DEV_SERVER_URL) {
|
|
mainWindow.loadURL(process.env.VITE_DEV_SERVER_URL)
|
|
// DevTools 通过快捷键 Cmd+Option+I 打开,不自动调用
|
|
} else {
|
|
mainWindow.loadFile(path.join(__dirname, '../dist/index.html'))
|
|
}
|
|
|
|
// Show window when ready
|
|
mainWindow.once('ready-to-show', () => {
|
|
mainWindow?.show()
|
|
})
|
|
|
|
// Handle window closed
|
|
mainWindow.on('closed', () => {
|
|
mainWindow = null
|
|
})
|
|
|
|
// Open external links in browser
|
|
mainWindow.webContents.setWindowOpenHandler(({ url }) => {
|
|
shell.openExternal(url)
|
|
return { action: 'deny' }
|
|
})
|
|
|
|
// Set up application menu
|
|
createApplicationMenu()
|
|
}
|
|
|
|
// ============== Application Menu ==============
|
|
|
|
function createApplicationMenu(): void {
|
|
const template: Electron.MenuItemConstructorOptions[] = [
|
|
{
|
|
label: 'OpsXShell',
|
|
submenu: [
|
|
{
|
|
label: '关于 OpsXShell',
|
|
click: () => {
|
|
dialog.showMessageBox(mainWindow!, {
|
|
type: 'info',
|
|
title: '关于 OpsXShell',
|
|
message: 'OpsXShell v2.0.0',
|
|
detail: '跨平台 SSH/SFTP 终端工具 · 简洁高效 · 安全可靠\n\nMIT License',
|
|
})
|
|
},
|
|
},
|
|
{ type: 'separator' },
|
|
{
|
|
label: '设置',
|
|
accelerator: process.platform === 'darwin' ? 'Cmd+,' : 'Ctrl+,',
|
|
click: () => {
|
|
mainWindow?.webContents.send('menu-open-settings')
|
|
},
|
|
},
|
|
{ type: 'separator' },
|
|
{
|
|
label: '服务',
|
|
role: 'services',
|
|
submenu: [],
|
|
},
|
|
{ type: 'separator' },
|
|
{
|
|
label: `隐藏 ${app.name}`,
|
|
accelerator: 'Cmd+H',
|
|
role: 'hide',
|
|
},
|
|
{
|
|
label: '隐藏其他',
|
|
accelerator: 'Cmd+Alt+H',
|
|
role: 'hideOthers',
|
|
},
|
|
{
|
|
label: '全部显示',
|
|
role: 'unhide',
|
|
},
|
|
{ type: 'separator' },
|
|
{
|
|
label: '退出',
|
|
accelerator: process.platform === 'darwin' ? 'Cmd+Q' : 'Ctrl+Q',
|
|
click: () => {
|
|
app.quit()
|
|
},
|
|
},
|
|
],
|
|
},
|
|
{
|
|
label: '会话',
|
|
submenu: [
|
|
{
|
|
label: '新建会话',
|
|
accelerator: 'CmdOrCtrl+N',
|
|
click: () => {
|
|
mainWindow?.webContents.send('menu-new-session')
|
|
},
|
|
},
|
|
{
|
|
label: '快速连接',
|
|
accelerator: 'CmdOrCtrl+K',
|
|
click: () => {
|
|
mainWindow?.webContents.send('menu-quick-connect')
|
|
},
|
|
},
|
|
{ type: 'separator' },
|
|
{
|
|
label: '导入会话',
|
|
click: () => {
|
|
mainWindow?.webContents.send('menu-import-sessions')
|
|
},
|
|
},
|
|
{
|
|
label: '导出会话',
|
|
click: () => {
|
|
mainWindow?.webContents.send('menu-export-sessions')
|
|
},
|
|
},
|
|
],
|
|
},
|
|
{
|
|
label: '编辑',
|
|
submenu: [
|
|
{ role: 'undo' },
|
|
{ role: 'redo' },
|
|
{ type: 'separator' },
|
|
{ role: 'cut' },
|
|
{ role: 'copy' },
|
|
{ role: 'paste' },
|
|
{ role: 'selectAll' },
|
|
],
|
|
},
|
|
{
|
|
label: '视图',
|
|
submenu: [
|
|
{
|
|
label: '重新加载',
|
|
accelerator: 'CmdOrCtrl+R',
|
|
click: () => {
|
|
mainWindow?.webContents.reload()
|
|
},
|
|
},
|
|
{
|
|
label: '切换开发者工具',
|
|
accelerator: process.platform === 'darwin' ? 'Alt+Cmd+I' : 'F12',
|
|
click: () => {
|
|
mainWindow?.webContents.toggleDevTools()
|
|
},
|
|
},
|
|
{ type: 'separator' },
|
|
{ role: 'resetZoom' },
|
|
{ role: 'zoomIn' },
|
|
{ role: 'zoomOut' },
|
|
{ type: 'separator' },
|
|
{ role: 'togglefullscreen' },
|
|
],
|
|
},
|
|
{
|
|
label: '窗口',
|
|
submenu: [
|
|
{ role: 'minimize' },
|
|
{ role: 'close' },
|
|
],
|
|
},
|
|
{
|
|
label: '帮助',
|
|
submenu: [
|
|
{
|
|
label: '文档',
|
|
click: () => {
|
|
shell.openExternal('https://github.com/opsxshell/docs')
|
|
},
|
|
},
|
|
{
|
|
label: '报告问题',
|
|
click: () => {
|
|
shell.openExternal('https://github.com/opsxshell/issues')
|
|
},
|
|
},
|
|
],
|
|
},
|
|
]
|
|
|
|
const menu = Menu.buildFromTemplate(template)
|
|
Menu.setApplicationMenu(menu)
|
|
}
|
|
|
|
// ============== App Lifecycle ==============
|
|
|
|
app.whenReady().then(async () => {
|
|
// Initialize encryption key first
|
|
keyService.getMasterKey()
|
|
logger.info('[App] Encryption key initialized')
|
|
|
|
// Initialize services
|
|
sshService = new SSHService()
|
|
zmodemService = new ZmodemService()
|
|
dbService = new DatabaseService()
|
|
await dbService.initialize()
|
|
|
|
// Register IPC handlers
|
|
registerAllIPCHandlers({
|
|
sshService,
|
|
databaseService: dbService,
|
|
zmodemService,
|
|
mainWindowGetter: () => mainWindow,
|
|
})
|
|
logger.info('[App] IPC handlers registered')
|
|
|
|
// Create main window
|
|
createWindow()
|
|
|
|
app.on('activate', () => {
|
|
if (BrowserWindow.getAllWindows().length === 0) {
|
|
createWindow()
|
|
}
|
|
})
|
|
})
|
|
|
|
app.on('window-all-closed', () => {
|
|
// Close all SSH connections
|
|
sshService?.disconnectAll()
|
|
|
|
// Close database
|
|
dbService?.close()
|
|
|
|
if (process.platform !== 'darwin') {
|
|
app.quit()
|
|
}
|
|
})
|
|
|
|
app.on('before-quit', () => {
|
|
sshService?.disconnectAll()
|
|
dbService?.close()
|
|
})
|