v1.1.0: better-sqlite3→sql.js迁移 + 6项UI/代码修复
迁移: - better-sqlite3(原生C++) → sql.js(WASM纯JS), 兼容Node 24 - database-service.ts: PreparedStatement包装器保持API兼容 - main.ts: initialize()改为async/await - 移除@electron/rebuild和@types/better-sqlite3依赖 - 新增.npmrc阿里云镜像加速 UI/代码修复(任务1-6): 1. TerminalView: 清理111行调试日志(645→534行) 2. 设计系统: cyan/slate→accent-blue/dark-*颜色统一(FileRow/TransferItem/Breadcrumb/ContextMenu) 3. StatusBar: 重写接入实时连接状态,三区布局 4. SessionDialog: 私钥浏览按钮添加onClick处理 5. WelcomeView: 真实session数据替换硬编码假数据 6. SFTPView: emoji→Lucide图标(FolderOpen/Upload/Download/Trash2) 构建验证: tsc零错误 + vite build成功(Node 24.14.0)
This commit is contained in:
1 parent
c5243df040
commit
53274e2291
14 files changed
+1784
-2141
No files matched your search
@@ -1,4 +1,4 @@
|
||||
import Database from 'better-sqlite3'
|
||||
import initSqlJs, { Database as SqlJsDatabase } from 'sql.js'
|
||||
import path from 'path'
|
||||
import { app } from 'electron'
|
||||
import fs from 'fs'
|
||||
@@ -8,30 +8,85 @@ import { createLogger } from '../utils/logger'
|
||||
|
||||
const logger = createLogger('DatabaseService')
|
||||
|
||||
// sql.js statement wrapper to match better-sqlite3 API
|
||||
interface StatementResult {
|
||||
id: number
|
||||
[key: string]: any
|
||||
}
|
||||
|
||||
class PreparedStatement {
|
||||
private db: SqlJsDatabase
|
||||
private sql: string
|
||||
|
||||
constructor(db: SqlJsDatabase, sql: string) {
|
||||
this.db = db
|
||||
this.sql = sql
|
||||
}
|
||||
|
||||
run(...params: any[]): StatementResult {
|
||||
this.db.run(this.sql, params as any)
|
||||
return { id: 0 } as StatementResult
|
||||
}
|
||||
|
||||
get(...params: any[]): any {
|
||||
// 使用 exec 模式获取单行
|
||||
const stmt = this.db.prepare(this.sql)
|
||||
stmt.bind(params as any)
|
||||
let result: any = null
|
||||
if (stmt.step()) {
|
||||
result = stmt.getAsObject()
|
||||
}
|
||||
stmt.free()
|
||||
return result
|
||||
}
|
||||
|
||||
all(...params: any[]): any[] {
|
||||
const stmt = this.db.prepare(this.sql)
|
||||
stmt.bind(params as any)
|
||||
const results: any[] = []
|
||||
while (stmt.step()) {
|
||||
results.push(stmt.getAsObject())
|
||||
}
|
||||
stmt.free()
|
||||
return results
|
||||
}
|
||||
}
|
||||
|
||||
export class DatabaseService {
|
||||
private db: Database.Database | null = null
|
||||
private db: SqlJsDatabase | null = null
|
||||
private dbPath: string
|
||||
private masterKey: string | null = null
|
||||
private initialized = false
|
||||
|
||||
constructor() {
|
||||
// 使用应用数据目录存储数据库
|
||||
const userDataPath = app.getPath('userData')
|
||||
this.dbPath = path.join(userDataPath, 'opsxshell.db')
|
||||
}
|
||||
|
||||
initialize(): void {
|
||||
async initialize(): Promise<void> {
|
||||
try {
|
||||
// 确保目录存在
|
||||
const dbDir = path.dirname(this.dbPath)
|
||||
if (!fs.existsSync(dbDir)) {
|
||||
fs.mkdirSync(dbDir, { recursive: true })
|
||||
}
|
||||
|
||||
// 获取主密钥
|
||||
this.masterKey = keyService.getMasterKey()
|
||||
|
||||
this.db = new Database(this.dbPath)
|
||||
// 初始化 sql.js (WASM, 纯 JS 无需原生编译)
|
||||
const SQL = await initSqlJs()
|
||||
|
||||
// 如果已有数据库文件,加载它
|
||||
if (fs.existsSync(this.dbPath)) {
|
||||
const buffer = fs.readFileSync(this.dbPath)
|
||||
this.db = new SQL.Database(buffer)
|
||||
} else {
|
||||
this.db = new SQL.Database()
|
||||
}
|
||||
|
||||
this.createTables()
|
||||
this.save()
|
||||
|
||||
this.initialized = true
|
||||
logger.info('Database initialized at:', this.dbPath)
|
||||
} catch (error) {
|
||||
logger.error('Failed to initialize database:', error)
|
||||
@@ -42,8 +97,7 @@ export class DatabaseService {
|
||||
private createTables(): void {
|
||||
if (!this.db) return
|
||||
|
||||
// 会话表
|
||||
this.db.exec(`
|
||||
this.db.run(`
|
||||
CREATE TABLE IF NOT EXISTS sessions (
|
||||
id TEXT PRIMARY KEY,
|
||||
name TEXT NOT NULL,
|
||||
@@ -63,13 +117,12 @@ export class DatabaseService {
|
||||
|
||||
// 迁移:如果 password 列不存在,添加它
|
||||
try {
|
||||
this.db.exec(`ALTER TABLE sessions ADD COLUMN password TEXT`)
|
||||
this.db.run(`ALTER TABLE sessions ADD COLUMN password TEXT`)
|
||||
} catch (e) {
|
||||
// 列可能已存在,忽略错误
|
||||
}
|
||||
|
||||
// 连接历史表
|
||||
this.db.exec(`
|
||||
this.db.run(`
|
||||
CREATE TABLE IF NOT EXISTS connection_history (
|
||||
id TEXT PRIMARY KEY,
|
||||
session_id TEXT REFERENCES sessions(id),
|
||||
@@ -80,8 +133,7 @@ export class DatabaseService {
|
||||
)
|
||||
`)
|
||||
|
||||
// 设置表
|
||||
this.db.exec(`
|
||||
this.db.run(`
|
||||
CREATE TABLE IF NOT EXISTS settings (
|
||||
key TEXT PRIMARY KEY,
|
||||
value TEXT,
|
||||
@@ -89,8 +141,7 @@ export class DatabaseService {
|
||||
)
|
||||
`)
|
||||
|
||||
// 密钥表
|
||||
this.db.exec(`
|
||||
this.db.run(`
|
||||
CREATE TABLE IF NOT EXISTS keys (
|
||||
id TEXT PRIMARY KEY,
|
||||
name TEXT NOT NULL,
|
||||
@@ -102,8 +153,7 @@ export class DatabaseService {
|
||||
)
|
||||
`)
|
||||
|
||||
// 端口转发规则表
|
||||
this.db.exec(`
|
||||
this.db.run(`
|
||||
CREATE TABLE IF NOT EXISTS port_forwards (
|
||||
id TEXT PRIMARY KEY,
|
||||
session_id TEXT REFERENCES sessions(id),
|
||||
@@ -117,6 +167,23 @@ export class DatabaseService {
|
||||
`)
|
||||
}
|
||||
|
||||
// 持久化到磁盘
|
||||
private save(): void {
|
||||
if (!this.db) return
|
||||
try {
|
||||
const data = this.db.export()
|
||||
const buffer = Buffer.from(data)
|
||||
fs.writeFileSync(this.dbPath, buffer)
|
||||
} catch (error) {
|
||||
logger.error('Failed to save database:', error)
|
||||
}
|
||||
}
|
||||
|
||||
private prepare(sql: string): PreparedStatement {
|
||||
if (!this.db) throw new Error('Database not initialized')
|
||||
return new PreparedStatement(this.db, sql)
|
||||
}
|
||||
|
||||
// 加密字段
|
||||
private encryptField(value: string | null | undefined): string | null {
|
||||
if (!value) return null
|
||||
@@ -147,7 +214,7 @@ export class DatabaseService {
|
||||
|
||||
logger.info('Creating session with password:', session.password ? `len=${session.password.length}` : 'null/undefined')
|
||||
|
||||
const stmt = this.db.prepare(`
|
||||
const stmt = this.prepare(`
|
||||
INSERT INTO sessions (id, name, host, port, username, auth_type, password, private_key_path, passphrase_encrypted, group_name, tags)
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
||||
`)
|
||||
@@ -164,19 +231,20 @@ export class DatabaseService {
|
||||
session.username,
|
||||
session.authType,
|
||||
encryptedPassword,
|
||||
session.privateKey || null, // 私钥路径不加密
|
||||
session.privateKey || null,
|
||||
this.encryptField(session.passphrase),
|
||||
session.group || 'default',
|
||||
session.tags ? JSON.stringify(session.tags) : null
|
||||
)
|
||||
|
||||
this.save()
|
||||
return this.getSessionById(id)
|
||||
}
|
||||
|
||||
getAllSessions(): any[] {
|
||||
if (!this.db) throw new Error('Database not initialized')
|
||||
|
||||
const stmt = this.db.prepare('SELECT * FROM sessions ORDER BY created_at DESC')
|
||||
const stmt = this.prepare('SELECT * FROM sessions ORDER BY created_at DESC')
|
||||
const sessions = stmt.all()
|
||||
|
||||
return sessions.map((s: any) => ({
|
||||
@@ -197,7 +265,7 @@ export class DatabaseService {
|
||||
getSessionById(id: string): any {
|
||||
if (!this.db) throw new Error('Database not initialized')
|
||||
|
||||
const stmt = this.db.prepare('SELECT * FROM sessions WHERE id = ?')
|
||||
const stmt = this.prepare('SELECT * FROM sessions WHERE id = ?')
|
||||
const session = stmt.get(id) as any
|
||||
|
||||
if (session) {
|
||||
@@ -264,34 +332,36 @@ export class DatabaseService {
|
||||
fields.push('updated_at = CURRENT_TIMESTAMP')
|
||||
values.push(id)
|
||||
|
||||
const stmt = this.db.prepare(`
|
||||
const stmt = this.prepare(`
|
||||
UPDATE sessions SET ${fields.join(', ')} WHERE id = ?
|
||||
`)
|
||||
stmt.run(...values)
|
||||
|
||||
this.save()
|
||||
return this.getSessionById(id)
|
||||
}
|
||||
|
||||
deleteSession(id: string): void {
|
||||
if (!this.db) throw new Error('Database not initialized')
|
||||
|
||||
const stmt = this.db.prepare('DELETE FROM sessions WHERE id = ?')
|
||||
|
||||
const stmt = this.prepare('DELETE FROM sessions WHERE id = ?')
|
||||
stmt.run(id)
|
||||
this.save()
|
||||
}
|
||||
|
||||
// 设置操作
|
||||
getSetting(key: string): any {
|
||||
if (!this.db) throw new Error('Database not initialized')
|
||||
|
||||
const stmt = this.db.prepare('SELECT value FROM settings WHERE key = ?')
|
||||
|
||||
const stmt = this.prepare('SELECT value FROM settings WHERE key = ?')
|
||||
const row: any = stmt.get(key)
|
||||
return row ? JSON.parse(row.value) : null
|
||||
}
|
||||
|
||||
setSetting(key: string, value: any): void {
|
||||
if (!this.db) throw new Error('Database not initialized')
|
||||
|
||||
const stmt = this.db.prepare(`
|
||||
|
||||
const stmt = this.prepare(`
|
||||
INSERT INTO settings (key, value, updated_at)
|
||||
VALUES (?, ?, CURRENT_TIMESTAMP)
|
||||
ON CONFLICT(key) DO UPDATE SET
|
||||
@@ -299,10 +369,12 @@ export class DatabaseService {
|
||||
updated_at = CURRENT_TIMESTAMP
|
||||
`)
|
||||
stmt.run(key, JSON.stringify(value))
|
||||
this.save()
|
||||
}
|
||||
|
||||
close(): void {
|
||||
if (this.db) {
|
||||
this.save()
|
||||
this.db.close()
|
||||
this.db = null
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user