Files
opsxshell/electron/main.ts
T
Logan.Li 484d850abd feat: v0.3.0 版本 - SSH终端、SFTP、端口转发功能实现
核心功能:
- SSH终端: 密码/密钥认证、XTerm.js终端、多主题配色、IP高亮
- SFTP管理: 双栏文件管理、上传下载、传输队列
- 端口转发: 本地/远程转发、SOCKS5动态代理、规则管理

架构改进:
- 拆分大型组件 (SFTPView/TabBar/Sidebar)
- 添加统一日志和错误处理
- 修复React严格模式下的终端黑屏问题

UI更新:
- 新增端口转发视图
- 更新UI预览文档
- 左侧栏右键菜单支持SFTP快捷打开
2026-04-06 01:20:47 +08:00

252 lines
5.9 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 v1.0.0',
detail: '跨平台 SSH/SFTP 终端工具 · 简洁高效 · 安全可靠\n\nMIT License',
})
},
},
{ 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(() => {
// Initialize encryption key first
keyService.getMasterKey()
logger.info('[App] Encryption key initialized')
// Initialize services
sshService = new SSHService()
zmodemService = new ZmodemService()
dbService = new DatabaseService()
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()
})