refactor: restructure project layout
- Move main process to electron/main/ - Move preload to electron/preload/ - Add shared types in electron/shared/ - Split redis-service into modular redis/ directory - Flatten renderer to src/ (remove src/renderer/ nesting) - Update electron.vite.config.ts paths - Update tsconfig paths - Output to electron-dist/
This commit is contained in:
@@ -0,0 +1,16 @@
|
||||
// src/main/credential.ts
|
||||
import { safeStorage } from 'electron'
|
||||
|
||||
export function encrypt(text: string): string {
|
||||
if (!safeStorage.isEncryptionAvailable()) {
|
||||
return Buffer.from(text).toString('base64')
|
||||
}
|
||||
return safeStorage.encryptString(text).toString('base64')
|
||||
}
|
||||
|
||||
export function decrypt(encoded: string): string {
|
||||
if (!safeStorage.isEncryptionAvailable()) {
|
||||
return Buffer.from(encoded, 'base64').toString('utf-8')
|
||||
}
|
||||
return safeStorage.decryptString(Buffer.from(encoded, 'base64'))
|
||||
}
|
||||
@@ -0,0 +1,64 @@
|
||||
// src/main/index.ts
|
||||
import { app, BrowserWindow, nativeTheme, nativeImage } from 'electron'
|
||||
import { join } from 'path'
|
||||
import { restoreAndTrack } from './win-state'
|
||||
import { registerIpcHandlers } from './ipc-handlers'
|
||||
|
||||
let mainWindow: BrowserWindow | null = null
|
||||
|
||||
function getIconPath(): string {
|
||||
return join(__dirname, '../../build/icons/icon_256.png')
|
||||
}
|
||||
|
||||
function createWindow(): void {
|
||||
const iconPath = getIconPath()
|
||||
|
||||
mainWindow = new BrowserWindow({
|
||||
width: 1200,
|
||||
height: 800,
|
||||
minWidth: 900,
|
||||
minHeight: 600,
|
||||
title: 'JRedisDesktop',
|
||||
icon: nativeImage.createFromPath(iconPath),
|
||||
backgroundColor: nativeTheme.shouldUseDarkColors ? '#0f0f1a' : '#f8fafc',
|
||||
webPreferences: {
|
||||
preload: join(__dirname, '../preload/index.mjs'),
|
||||
contextIsolation: true,
|
||||
nodeIntegration: false,
|
||||
sandbox: false,
|
||||
},
|
||||
frame: false,
|
||||
titleBarStyle: 'hidden',
|
||||
})
|
||||
|
||||
// Linux taskbar icon
|
||||
if (process.platform === 'linux' && mainWindow) {
|
||||
mainWindow.setIcon(nativeImage.createFromPath(iconPath))
|
||||
}
|
||||
|
||||
restoreAndTrack(mainWindow)
|
||||
|
||||
if (process.env.ELECTRON_RENDERER_URL) {
|
||||
mainWindow.loadURL(process.env.ELECTRON_RENDERER_URL)
|
||||
mainWindow.webContents.openDevTools()
|
||||
} else {
|
||||
mainWindow.loadFile(join(__dirname, '../renderer/index.html'))
|
||||
}
|
||||
|
||||
mainWindow.on('closed', () => { mainWindow = null })
|
||||
|
||||
nativeTheme.on('updated', () => {
|
||||
mainWindow?.webContents.send('theme:os-updated', nativeTheme.shouldUseDarkColors)
|
||||
})
|
||||
}
|
||||
|
||||
app.whenReady().then(() => {
|
||||
registerIpcHandlers()
|
||||
createWindow()
|
||||
})
|
||||
|
||||
app.on('window-all-closed', () => { app.quit() })
|
||||
|
||||
app.on('activate', () => {
|
||||
if (BrowserWindow.getAllWindows().length === 0) createWindow()
|
||||
})
|
||||
@@ -0,0 +1,219 @@
|
||||
// src/main/ipc-handlers.ts
|
||||
// IPC 通道处理器
|
||||
import { ipcMain, BrowserWindow, nativeTheme, dialog } from 'electron'
|
||||
import store, { ConnectionConfig } from './store'
|
||||
import { encrypt, decrypt } from './credential'
|
||||
import * as redis from './redis'
|
||||
|
||||
export function registerIpcHandlers(): void {
|
||||
// Window
|
||||
ipcMain.on('window:minimize', () => BrowserWindow.getFocusedWindow()?.minimize())
|
||||
ipcMain.on('window:maximize', () => {
|
||||
const win = BrowserWindow.getFocusedWindow()
|
||||
if (win) win.isMaximized() ? win.unmaximize() : win.maximize()
|
||||
})
|
||||
ipcMain.on('window:close', () => BrowserWindow.getFocusedWindow()?.close())
|
||||
|
||||
// Theme
|
||||
ipcMain.handle('theme:get', () => nativeTheme.shouldUseDarkColors)
|
||||
ipcMain.on('theme:set', (_e, theme: 'system' | 'dark' | 'light') => {
|
||||
nativeTheme.themeSource = theme
|
||||
})
|
||||
|
||||
// Dialog
|
||||
ipcMain.handle('dialog:openFile', async (_e, options: Electron.OpenDialogOptions) => {
|
||||
const win = BrowserWindow.getFocusedWindow()
|
||||
if (!win) return null
|
||||
const result = await dialog.showOpenDialog(win, options)
|
||||
if (result.canceled || result.filePaths.length === 0) return null
|
||||
const fs = await import('fs')
|
||||
try {
|
||||
return { path: result.filePaths[0], content: fs.readFileSync(result.filePaths[0], 'utf-8') }
|
||||
} catch { return null }
|
||||
})
|
||||
|
||||
// Credential
|
||||
ipcMain.handle('credential:encrypt', (_e, text: string) => encrypt(text))
|
||||
ipcMain.handle('credential:decrypt', (_e, encoded: string) => decrypt(encoded))
|
||||
|
||||
// Storage - connections
|
||||
ipcMain.handle('storage:getConnections', () => store.get('connections', []))
|
||||
ipcMain.handle('storage:saveConnection', (_e, conn: ConnectionConfig) => {
|
||||
const connections = store.get('connections', [])
|
||||
const idx = connections.findIndex((c: ConnectionConfig) => c.id === conn.id)
|
||||
if (idx >= 0) connections[idx] = conn
|
||||
else connections.push(conn)
|
||||
store.set('connections', connections)
|
||||
return connections
|
||||
})
|
||||
ipcMain.handle('storage:deleteConnection', (_e, id: string) => {
|
||||
const connections = store.get('connections', []).filter((c: ConnectionConfig) => c.id !== id)
|
||||
store.set('connections', connections)
|
||||
return connections
|
||||
})
|
||||
ipcMain.handle('storage:reorderConnections', (_e, ids: string[]) => {
|
||||
const connections = store.get('connections', [])
|
||||
const reordered = ids.map((id, i) => {
|
||||
const conn = connections.find((c: ConnectionConfig) => c.id === id)!
|
||||
conn.order = i
|
||||
return conn
|
||||
})
|
||||
store.set('connections', reordered)
|
||||
return reordered
|
||||
})
|
||||
|
||||
// Storage - settings
|
||||
ipcMain.handle('storage:getSettings', () => store.get('settings'))
|
||||
ipcMain.handle('storage:saveSettings', (_e, settings) => {
|
||||
store.set('settings', settings)
|
||||
})
|
||||
|
||||
// Redis - 连接
|
||||
ipcMain.handle('redis:connect', async (_e, id: string, opts) => {
|
||||
try {
|
||||
await redis.connect(id, opts)
|
||||
return { success: true }
|
||||
} catch (err: any) {
|
||||
return { success: false, error: err.message }
|
||||
}
|
||||
})
|
||||
ipcMain.handle('redis:disconnect', async (_e, id: string) => {
|
||||
await redis.disconnect(id)
|
||||
})
|
||||
ipcMain.handle('redis:ping', async (_e, id: string) => {
|
||||
return redis.ping(id)
|
||||
})
|
||||
ipcMain.handle('redis:isConnected', (_e, id: string) => {
|
||||
return redis.isClientConnected(id)
|
||||
})
|
||||
|
||||
// Redis - 服务器
|
||||
ipcMain.handle('redis:execute', async (_e, id: string, command: string, args: string[]) => {
|
||||
return redis.execute(id, command, args)
|
||||
})
|
||||
ipcMain.handle('redis:getInfo', async (_e, id: string) => {
|
||||
return redis.getServerInfo(id)
|
||||
})
|
||||
ipcMain.handle('redis:selectDb', async (_e, id: string, db: number) => {
|
||||
await redis.selectDb(id, db)
|
||||
})
|
||||
ipcMain.handle('redis:dbSize', async (_e, id: string) => {
|
||||
return redis.dbSize(id)
|
||||
})
|
||||
ipcMain.handle('redis:slowLogGet', async (_e, id: string, count: number) => {
|
||||
return redis.slowLogGet(id, count)
|
||||
})
|
||||
ipcMain.handle('redis:slowLogLen', async (_e, id: string) => {
|
||||
return redis.slowLogLen(id)
|
||||
})
|
||||
|
||||
// Redis - Key 操作
|
||||
ipcMain.handle('redis:scanKeys', async (_e, id: string, cursor: string, pattern: string, count: number) => {
|
||||
return redis.scanKeys(id, cursor, pattern, count)
|
||||
})
|
||||
ipcMain.handle('redis:getKeyType', async (_e, id: string, key: string) => {
|
||||
return redis.getKeyType(id, key)
|
||||
})
|
||||
ipcMain.handle('redis:getKeyTTL', async (_e, id: string, key: string) => {
|
||||
return redis.getKeyTTL(id, key)
|
||||
})
|
||||
ipcMain.handle('redis:setTTL', async (_e, id: string, key: string, ttl: number) => {
|
||||
await redis.setKeyTTL(id, key, ttl)
|
||||
})
|
||||
ipcMain.handle('redis:deleteKey', async (_e, id: string, key: string) => {
|
||||
await redis.deleteKey(id, key)
|
||||
})
|
||||
ipcMain.handle('redis:renameKey', async (_e, id: string, oldKey: string, newKey: string) => {
|
||||
await redis.renameKey(id, oldKey, newKey)
|
||||
})
|
||||
ipcMain.handle('redis:existsKey', async (_e, id: string, key: string) => {
|
||||
return redis.existsKey(id, key)
|
||||
})
|
||||
ipcMain.handle('redis:batchDelete', async (_e, id: string, keys: string[]) => {
|
||||
return redis.batchDelete(id, keys)
|
||||
})
|
||||
ipcMain.handle('redis:memoryUsage', async (_e, id: string, key: string) => {
|
||||
return redis.memoryUsage(id, key)
|
||||
})
|
||||
|
||||
// Redis - String
|
||||
ipcMain.handle('redis:getString', async (_e, id: string, key: string) => {
|
||||
return redis.getStringValue(id, key)
|
||||
})
|
||||
ipcMain.handle('redis:setString', async (_e, id: string, key: string, value: string) => {
|
||||
await redis.setStringValue(id, key, value)
|
||||
})
|
||||
|
||||
// Redis - Hash
|
||||
ipcMain.handle('redis:hashScan', async (_e, id: string, key: string, cursor: string, count: number) => {
|
||||
return redis.getHashFields(id, key, cursor, count)
|
||||
})
|
||||
ipcMain.handle('redis:hashSet', async (_e, id: string, key: string, field: string, value: string) => {
|
||||
await redis.setHashField(id, key, field, value)
|
||||
})
|
||||
ipcMain.handle('redis:hashDel', async (_e, id: string, key: string, field: string) => {
|
||||
await redis.deleteHashField(id, key, field)
|
||||
})
|
||||
|
||||
// Redis - List
|
||||
ipcMain.handle('redis:listRange', async (_e, id: string, key: string, start: number, stop: number) => {
|
||||
return redis.listRange(id, key, start, stop)
|
||||
})
|
||||
ipcMain.handle('redis:listLength', async (_e, id: string, key: string) => {
|
||||
return redis.listLength(id, key)
|
||||
})
|
||||
ipcMain.handle('redis:listPush', async (_e, id: string, key: string, values: string[]) => {
|
||||
return redis.listPush(id, key, ...values)
|
||||
})
|
||||
ipcMain.handle('redis:listSet', async (_e, id: string, key: string, index: number, value: string) => {
|
||||
await redis.listSet(id, key, index, value)
|
||||
})
|
||||
ipcMain.handle('redis:listRemove', async (_e, id: string, key: string, count: number, value: string) => {
|
||||
return redis.listRemove(id, key, count, value)
|
||||
})
|
||||
|
||||
// Redis - Set
|
||||
ipcMain.handle('redis:setMembers', async (_e, id: string, key: string) => {
|
||||
return redis.setMembers(id, key)
|
||||
})
|
||||
ipcMain.handle('redis:setSize', async (_e, id: string, key: string) => {
|
||||
return redis.setSize(id, key)
|
||||
})
|
||||
ipcMain.handle('redis:setAdd', async (_e, id: string, key: string, members: string[]) => {
|
||||
return redis.setAdd(id, key, ...members)
|
||||
})
|
||||
ipcMain.handle('redis:setRemove', async (_e, id: string, key: string, members: string[]) => {
|
||||
return redis.setRemove(id, key, ...members)
|
||||
})
|
||||
|
||||
// Redis - Zset
|
||||
ipcMain.handle('redis:zsetRange', async (_e, id: string, key: string, start: number, stop: number, withScores: boolean) => {
|
||||
return redis.zsetRange(id, key, start, stop, withScores)
|
||||
})
|
||||
ipcMain.handle('redis:zsetSize', async (_e, id: string, key: string) => {
|
||||
return redis.zsetSize(id, key)
|
||||
})
|
||||
ipcMain.handle('redis:zsetAdd', async (_e, id: string, key: string, score: number, member: string) => {
|
||||
return redis.zsetAdd(id, key, score, member)
|
||||
})
|
||||
ipcMain.handle('redis:zsetRemove', async (_e, id: string, key: string, members: string[]) => {
|
||||
return redis.zsetRemove(id, key, ...members)
|
||||
})
|
||||
|
||||
// Redis - Stream
|
||||
ipcMain.handle('redis:streamInfo', async (_e, id: string, key: string) => {
|
||||
return redis.streamInfo(id, key)
|
||||
})
|
||||
ipcMain.handle('redis:streamRange', async (_e, id: string, key: string, start: string, end: string, count: number) => {
|
||||
return redis.streamRange(id, key, start, end, count)
|
||||
})
|
||||
ipcMain.handle('redis:streamAdd', async (_e, id: string, key: string, streamId: string, fieldValues: string[]) => {
|
||||
return redis.streamAdd(id, key, streamId, fieldValues)
|
||||
})
|
||||
ipcMain.handle('redis:streamTrim', async (_e, id: string, key: string, maxLen: number) => {
|
||||
return redis.streamTrim(id, key, maxLen)
|
||||
})
|
||||
ipcMain.handle('redis:streamDel', async (_e, id: string, key: string, ids: string[]) => {
|
||||
return redis.streamDel(id, key, ...ids)
|
||||
})
|
||||
}
|
||||
@@ -0,0 +1,85 @@
|
||||
// src/main/redis/connection.ts
|
||||
// Redis 连接管理
|
||||
import Redis, { RedisOptions } from 'ioredis'
|
||||
import type { ConnectionOptions } from '../../shared/types'
|
||||
|
||||
export type RedisClient = Redis
|
||||
|
||||
const clients = new Map<string, RedisClient>()
|
||||
|
||||
function buildRedisOptions(opts: ConnectionOptions): RedisOptions {
|
||||
const options: RedisOptions = {
|
||||
host: opts.host,
|
||||
port: opts.port,
|
||||
username: opts.username || undefined,
|
||||
password: opts.password || undefined,
|
||||
stringNumbers: true,
|
||||
enableReadyCheck: true,
|
||||
family: 0,
|
||||
connectTimeout: 10000,
|
||||
retryStrategy(times: number) {
|
||||
if (times > 3) return null
|
||||
return Math.min(times * 200, 1000)
|
||||
},
|
||||
maxRetriesPerRequest: 3,
|
||||
}
|
||||
|
||||
if (opts.tls) {
|
||||
options.tls = {
|
||||
rejectUnauthorized: false,
|
||||
checkServerIdentity: () => undefined,
|
||||
}
|
||||
}
|
||||
|
||||
return options
|
||||
}
|
||||
|
||||
export function getClient(id: string): RedisClient | undefined {
|
||||
return clients.get(id)
|
||||
}
|
||||
|
||||
export function isClientConnected(id: string): boolean {
|
||||
const client = clients.get(id)
|
||||
return client?.status === 'ready'
|
||||
}
|
||||
|
||||
export async function connect(id: string, opts: ConnectionOptions): Promise<void> {
|
||||
if (clients.has(id)) {
|
||||
await disconnect(id)
|
||||
}
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
const redis = new Redis(buildRedisOptions(opts))
|
||||
|
||||
redis.once('ready', () => {
|
||||
clients.set(id, redis)
|
||||
resolve()
|
||||
})
|
||||
|
||||
redis.once('error', (err: Error) => {
|
||||
redis.disconnect()
|
||||
reject(err)
|
||||
})
|
||||
|
||||
setTimeout(() => {
|
||||
if (!clients.has(id)) {
|
||||
redis.disconnect()
|
||||
reject(new Error('连接超时,请检查主机和端口'))
|
||||
}
|
||||
}, 12000)
|
||||
})
|
||||
}
|
||||
|
||||
export async function disconnect(id: string): Promise<void> {
|
||||
const client = clients.get(id)
|
||||
if (client) {
|
||||
client.disconnect()
|
||||
clients.delete(id)
|
||||
}
|
||||
}
|
||||
|
||||
export function disconnectAll(): void {
|
||||
for (const [id] of clients) {
|
||||
disconnect(id)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
// src/main/redis/hash.ts
|
||||
// Hash 操作
|
||||
import { getClient } from './connection'
|
||||
|
||||
export async function getHashFields(
|
||||
id: string, key: string, cursor: string, count: number
|
||||
): Promise<{ cursor: string; fields: [string, string][] }> {
|
||||
const client = getClient(id)
|
||||
if (!client) throw new Error(`Client ${id} not found`)
|
||||
const [nextCursor, raw] = await client.hscan(key, cursor, 'COUNT', count)
|
||||
const fields: [string, string][] = []
|
||||
for (let i = 0; i < raw.length; i += 2) {
|
||||
fields.push([raw[i], raw[i + 1]])
|
||||
}
|
||||
return { cursor: nextCursor, fields }
|
||||
}
|
||||
|
||||
export async function setHashField(
|
||||
id: string, key: string, field: string, value: string
|
||||
): Promise<void> {
|
||||
const client = getClient(id)
|
||||
if (!client) throw new Error(`Client ${id} not found`)
|
||||
await client.hset(key, field, value)
|
||||
}
|
||||
|
||||
export async function deleteHashField(id: string, key: string, field: string): Promise<void> {
|
||||
const client = getClient(id)
|
||||
if (!client) throw new Error(`Client ${id} not found`)
|
||||
await client.hdel(key, field)
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
// src/main/redis/index.ts
|
||||
// Redis 服务统一导出
|
||||
export * from './connection'
|
||||
export * from './keys'
|
||||
export * from './string'
|
||||
export * from './hash'
|
||||
export * from './list'
|
||||
export * from './set'
|
||||
export * from './zset'
|
||||
export * from './stream'
|
||||
export * from './server'
|
||||
@@ -0,0 +1,73 @@
|
||||
// src/main/redis/keys.ts
|
||||
// Key 操作
|
||||
import { getClient } from './connection'
|
||||
|
||||
export async function getKeyType(id: string, key: string): Promise<string> {
|
||||
const client = getClient(id)
|
||||
if (!client) throw new Error(`Client ${id} not found`)
|
||||
return client.type(key)
|
||||
}
|
||||
|
||||
export async function getKeyTTL(id: string, key: string): Promise<number> {
|
||||
const client = getClient(id)
|
||||
if (!client) throw new Error(`Client ${id} not found`)
|
||||
return client.ttl(key)
|
||||
}
|
||||
|
||||
export async function deleteKey(id: string, key: string): Promise<void> {
|
||||
const client = getClient(id)
|
||||
if (!client) throw new Error(`Client ${id} not found`)
|
||||
await client.del(key)
|
||||
}
|
||||
|
||||
export async function setKeyTTL(id: string, key: string, ttl: number): Promise<void> {
|
||||
const client = getClient(id)
|
||||
if (!client) throw new Error(`Client ${id} not found`)
|
||||
if (ttl < 0) {
|
||||
await client.persist(key)
|
||||
} else {
|
||||
await client.expire(key, ttl)
|
||||
}
|
||||
}
|
||||
|
||||
export async function renameKey(id: string, oldKey: string, newKey: string): Promise<void> {
|
||||
const client = getClient(id)
|
||||
if (!client) throw new Error(`Client ${id} not found`)
|
||||
await client.rename(oldKey, newKey)
|
||||
}
|
||||
|
||||
export async function existsKey(id: string, key: string): Promise<boolean> {
|
||||
const client = getClient(id)
|
||||
if (!client) throw new Error(`Client ${id} not found`)
|
||||
return (await client.exists(key)) === 1
|
||||
}
|
||||
|
||||
export async function batchDelete(id: string, keys: string[]): Promise<number> {
|
||||
const client = getClient(id)
|
||||
if (!client) throw new Error(`Client ${id} not found`)
|
||||
if (keys.length === 0) return 0
|
||||
const pipeline = client.pipeline()
|
||||
for (const key of keys) {
|
||||
pipeline.del(key)
|
||||
}
|
||||
const results = await pipeline.exec()
|
||||
return results?.filter(([err]) => !err).length ?? 0
|
||||
}
|
||||
|
||||
export async function memoryUsage(id: string, key: string): Promise<number | null> {
|
||||
const client = getClient(id)
|
||||
if (!client) throw new Error(`Client ${id} not found`)
|
||||
return client.memory('USAGE', key) as Promise<number | null>
|
||||
}
|
||||
|
||||
export async function scanKeys(
|
||||
id: string,
|
||||
cursor: string,
|
||||
pattern: string,
|
||||
count: number
|
||||
): Promise<{ cursor: string; keys: string[] }> {
|
||||
const client = getClient(id)
|
||||
if (!client) throw new Error(`Client ${id} not found`)
|
||||
const [nextCursor, keys] = await client.scan(cursor, 'MATCH', pattern, 'COUNT', count)
|
||||
return { cursor: nextCursor, keys }
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
// src/main/redis/list.ts
|
||||
// List 操作
|
||||
import { getClient } from './connection'
|
||||
|
||||
export async function listRange(id: string, key: string, start: number, stop: number): Promise<string[]> {
|
||||
const client = getClient(id)
|
||||
if (!client) throw new Error(`Client ${id} not found`)
|
||||
return client.lrange(key, start, stop)
|
||||
}
|
||||
|
||||
export async function listLength(id: string, key: string): Promise<number> {
|
||||
const client = getClient(id)
|
||||
if (!client) throw new Error(`Client ${id} not found`)
|
||||
return client.llen(key)
|
||||
}
|
||||
|
||||
export async function listPush(id: string, key: string, ...values: string[]): Promise<number> {
|
||||
const client = getClient(id)
|
||||
if (!client) throw new Error(`Client ${id} not found`)
|
||||
return client.rpush(key, ...values)
|
||||
}
|
||||
|
||||
export async function listSet(id: string, key: string, index: number, value: string): Promise<void> {
|
||||
const client = getClient(id)
|
||||
if (!client) throw new Error(`Client ${id} not found`)
|
||||
await client.lset(key, index, value)
|
||||
}
|
||||
|
||||
export async function listRemove(id: string, key: string, count: number, value: string): Promise<number> {
|
||||
const client = getClient(id)
|
||||
if (!client) throw new Error(`Client ${id} not found`)
|
||||
return client.lrem(key, count, value)
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
// src/main/redis/server.ts
|
||||
// 服务器操作
|
||||
import { getClient } from './connection'
|
||||
|
||||
export async function execute(id: string, command: string, args: string[]): Promise<unknown> {
|
||||
const client = getClient(id)
|
||||
if (!client) throw new Error(`Client ${id} not found`)
|
||||
return client.call(command, ...args)
|
||||
}
|
||||
|
||||
export async function getServerInfo(id: string): Promise<string> {
|
||||
const client = getClient(id)
|
||||
if (!client) throw new Error(`Client ${id} not found`)
|
||||
return client.info() as Promise<string>
|
||||
}
|
||||
|
||||
export async function selectDb(id: string, db: number): Promise<void> {
|
||||
const client = getClient(id)
|
||||
if (!client) throw new Error(`Client ${id} not found`)
|
||||
await client.select(db)
|
||||
}
|
||||
|
||||
export async function dbSize(id: string): Promise<number> {
|
||||
const client = getClient(id)
|
||||
if (!client) throw new Error(`Client ${id} not found`)
|
||||
return client.dbsize() as Promise<number>
|
||||
}
|
||||
|
||||
export async function ping(id: string): Promise<string> {
|
||||
const client = getClient(id)
|
||||
if (!client) throw new Error(`Client ${id} not found`)
|
||||
return client.ping()
|
||||
}
|
||||
|
||||
export async function slowLogGet(id: string, count: number): Promise<any[]> {
|
||||
const client = getClient(id)
|
||||
if (!client) throw new Error(`Client ${id} not found`)
|
||||
return client.slowlog('GET', count) as Promise<any[]>
|
||||
}
|
||||
|
||||
export async function slowLogLen(id: string): Promise<number> {
|
||||
const client = getClient(id)
|
||||
if (!client) throw new Error(`Client ${id} not found`)
|
||||
return client.slowlog('LEN') as Promise<number>
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
// src/main/redis/set.ts
|
||||
// Set 操作
|
||||
import { getClient } from './connection'
|
||||
|
||||
export async function setMembers(id: string, key: string): Promise<string[]> {
|
||||
const client = getClient(id)
|
||||
if (!client) throw new Error(`Client ${id} not found`)
|
||||
return client.smembers(key)
|
||||
}
|
||||
|
||||
export async function setSize(id: string, key: string): Promise<number> {
|
||||
const client = getClient(id)
|
||||
if (!client) throw new Error(`Client ${id} not found`)
|
||||
return client.scard(key)
|
||||
}
|
||||
|
||||
export async function setAdd(id: string, key: string, ...members: string[]): Promise<number> {
|
||||
const client = getClient(id)
|
||||
if (!client) throw new Error(`Client ${id} not found`)
|
||||
return client.sadd(key, ...members)
|
||||
}
|
||||
|
||||
export async function setRemove(id: string, key: string, ...members: string[]): Promise<number> {
|
||||
const client = getClient(id)
|
||||
if (!client) throw new Error(`Client ${id} not found`)
|
||||
return client.srem(key, ...members)
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
// src/main/redis/stream.ts
|
||||
// Stream 操作
|
||||
import { getClient } from './connection'
|
||||
|
||||
export async function streamInfo(id: string, key: string): Promise<any> {
|
||||
const client = getClient(id)
|
||||
if (!client) throw new Error(`Client ${id} not found`)
|
||||
return client.xinfo('STREAM', key)
|
||||
}
|
||||
|
||||
export async function streamRange(
|
||||
id: string, key: string, start: string, end: string, count: number
|
||||
): Promise<any[]> {
|
||||
const client = getClient(id)
|
||||
if (!client) throw new Error(`Client ${id} not found`)
|
||||
return client.xrange(key, start, end, 'COUNT', count)
|
||||
}
|
||||
|
||||
export async function streamAdd(
|
||||
id: string, key: string, streamId: string, fieldValues: string[]
|
||||
): Promise<string | null> {
|
||||
const client = getClient(id)
|
||||
if (!client) throw new Error(`Client ${id} not found`)
|
||||
return client.xadd(key, streamId, ...fieldValues)
|
||||
}
|
||||
|
||||
export async function streamTrim(id: string, key: string, maxLen: number): Promise<number> {
|
||||
const client = getClient(id)
|
||||
if (!client) throw new Error(`Client ${id} not found`)
|
||||
return client.xtrim(key, 'MAXLEN', maxLen)
|
||||
}
|
||||
|
||||
export async function streamDel(id: string, key: string, ...ids: string[]): Promise<number> {
|
||||
const client = getClient(id)
|
||||
if (!client) throw new Error(`Client ${id} not found`)
|
||||
return client.xdel(key, ...ids)
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
// src/main/redis/string.ts
|
||||
// String 操作
|
||||
import { getClient } from './connection'
|
||||
|
||||
export async function getStringValue(id: string, key: string): Promise<string | null> {
|
||||
const client = getClient(id)
|
||||
if (!client) throw new Error(`Client ${id} not found`)
|
||||
return client.get(key)
|
||||
}
|
||||
|
||||
export async function setStringValue(id: string, key: string, value: string): Promise<void> {
|
||||
const client = getClient(id)
|
||||
if (!client) throw new Error(`Client ${id} not found`)
|
||||
await client.set(key, value)
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
// src/main/redis/zset.ts
|
||||
// Sorted Set 操作
|
||||
import { getClient } from './connection'
|
||||
|
||||
export async function zsetRange(id: string, key: string, start: number, stop: number, withScores: boolean): Promise<string[]> {
|
||||
const client = getClient(id)
|
||||
if (!client) throw new Error(`Client ${id} not found`)
|
||||
if (withScores) return client.zrange(key, start, stop, 'WITHSCORES')
|
||||
return client.zrange(key, start, stop)
|
||||
}
|
||||
|
||||
export async function zsetSize(id: string, key: string): Promise<number> {
|
||||
const client = getClient(id)
|
||||
if (!client) throw new Error(`Client ${id} not found`)
|
||||
return client.zcard(key)
|
||||
}
|
||||
|
||||
export async function zsetAdd(id: string, key: string, score: number, member: string): Promise<number> {
|
||||
const client = getClient(id)
|
||||
if (!client) throw new Error(`Client ${id} not found`)
|
||||
return client.zadd(key, score, member)
|
||||
}
|
||||
|
||||
export async function zsetRemove(id: string, key: string, ...members: string[]): Promise<number> {
|
||||
const client = getClient(id)
|
||||
if (!client) throw new Error(`Client ${id} not found`)
|
||||
return client.zrem(key, ...members)
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
// src/main/store.ts
|
||||
import Store from 'electron-store'
|
||||
|
||||
interface ConnectionConfig {
|
||||
id: string
|
||||
name: string
|
||||
host: string
|
||||
port: number
|
||||
auth: string
|
||||
username: string
|
||||
tls: boolean
|
||||
tlsCaPath: string
|
||||
tlsCertPath: string
|
||||
tlsKeyPath: string
|
||||
sshEnabled: boolean
|
||||
sshHost: string
|
||||
sshPort: number
|
||||
sshUsername: string
|
||||
sshPassword: string
|
||||
sshPrivateKeyPath: string
|
||||
cluster: boolean
|
||||
sentinelMasterName: string
|
||||
separator: string
|
||||
order: number
|
||||
createdAt: number
|
||||
color?: string
|
||||
}
|
||||
|
||||
interface AppSettings {
|
||||
theme: 'system' | 'dark' | 'light'
|
||||
locale: string
|
||||
fontSize: number
|
||||
scanCount: number
|
||||
}
|
||||
|
||||
interface AppStorage {
|
||||
connections: ConnectionConfig[]
|
||||
settings: AppSettings
|
||||
}
|
||||
|
||||
const store = new Store<AppStorage>({
|
||||
name: 'jrdm-config',
|
||||
defaults: {
|
||||
connections: [],
|
||||
settings: {
|
||||
theme: 'system',
|
||||
locale: 'en',
|
||||
fontSize: 13,
|
||||
scanCount: 500,
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
export default store
|
||||
export type { ConnectionConfig, AppSettings }
|
||||
@@ -0,0 +1,60 @@
|
||||
// src/main/win-state.ts
|
||||
import { app, screen, BrowserWindow } from 'electron'
|
||||
import { join } from 'path'
|
||||
import { readFileSync, writeFile } from 'fs'
|
||||
|
||||
interface WinState {
|
||||
x: number | null
|
||||
y: number | null
|
||||
width: number
|
||||
height: number
|
||||
maximized: boolean
|
||||
}
|
||||
|
||||
const STATE_FILE = join(app.getPath('userData'), 'jrdm-win-state.json')
|
||||
const MIN_WIDTH = 900
|
||||
const MIN_HEIGHT = 600
|
||||
const DEFAULT_WIDTH = 1200
|
||||
const DEFAULT_HEIGHT = 800
|
||||
|
||||
function parseJson(str: string): WinState | null {
|
||||
try { return JSON.parse(str) }
|
||||
catch { return null }
|
||||
}
|
||||
|
||||
function getLastState(): WinState {
|
||||
try {
|
||||
const raw = readFileSync(STATE_FILE, 'utf-8')
|
||||
const state = parseJson(raw)
|
||||
if (state) {
|
||||
const display = screen.getPrimaryDisplay().workArea
|
||||
if (state.x != null && (state.x < 0 || state.x > display.width - 100)) {
|
||||
state.x = null; state.y = null
|
||||
}
|
||||
if (state.y != null && (state.y < 0 || state.y > display.height - 100)) {
|
||||
state.x = null; state.y = null
|
||||
}
|
||||
state.width = Math.max(state.width || DEFAULT_WIDTH, MIN_WIDTH)
|
||||
state.height = Math.max(state.height || DEFAULT_HEIGHT, MIN_HEIGHT)
|
||||
return state
|
||||
}
|
||||
} catch { /* file doesn't exist yet */ }
|
||||
return { x: null, y: null, width: DEFAULT_WIDTH, height: DEFAULT_HEIGHT, maximized: false }
|
||||
}
|
||||
|
||||
function saveState(win: BrowserWindow): void {
|
||||
const bounds = win.getBounds()
|
||||
const state = { ...bounds, maximized: win.isMaximized() }
|
||||
writeFile(STATE_FILE, JSON.stringify(state), 'utf-8', () => {})
|
||||
}
|
||||
|
||||
export function restoreAndTrack(win: BrowserWindow): void {
|
||||
const lastState = getLastState()
|
||||
if (lastState.x != null && lastState.y != null) {
|
||||
win.setBounds({ x: lastState.x, y: lastState.y, width: lastState.width, height: lastState.height })
|
||||
}
|
||||
if (lastState.maximized) {
|
||||
win.maximize()
|
||||
}
|
||||
win.on('close', () => saveState(win))
|
||||
}
|
||||
Vendored
+106
@@ -0,0 +1,106 @@
|
||||
// src/preload/index.d.ts
|
||||
export interface ConnectionConfig {
|
||||
id: string
|
||||
name: string
|
||||
host: string
|
||||
port: number
|
||||
auth: string
|
||||
username: string
|
||||
tls: boolean
|
||||
tlsCaPath: string
|
||||
tlsCertPath: string
|
||||
tlsKeyPath: string
|
||||
sshEnabled: boolean
|
||||
sshHost: string
|
||||
sshPort: number
|
||||
sshUsername: string
|
||||
sshPassword: string
|
||||
sshPrivateKeyPath: string
|
||||
cluster: boolean
|
||||
sentinelMasterName: string
|
||||
separator: string
|
||||
order: number
|
||||
createdAt: number
|
||||
color?: string
|
||||
}
|
||||
|
||||
export interface ElectronAPI {
|
||||
window: {
|
||||
minimize: () => void
|
||||
maximize: () => void
|
||||
close: () => void
|
||||
}
|
||||
theme: {
|
||||
get: () => Promise<boolean>
|
||||
set: (theme: 'system' | 'dark' | 'light') => void
|
||||
onOsUpdated: (callback: (isDark: boolean) => void) => void
|
||||
}
|
||||
dialog: {
|
||||
openFile: (options: { title?: string; filters?: { name: string; extensions: string[] }[] }) =>
|
||||
Promise<{ path: string; content: string } | null>
|
||||
}
|
||||
credential: {
|
||||
encrypt: (text: string) => Promise<string>
|
||||
decrypt: (encoded: string) => Promise<string>
|
||||
}
|
||||
storage: {
|
||||
getConnections: () => Promise<ConnectionConfig[]>
|
||||
saveConnection: (conn: ConnectionConfig) => Promise<ConnectionConfig[]>
|
||||
deleteConnection: (id: string) => Promise<ConnectionConfig[]>
|
||||
reorderConnections: (ids: string[]) => Promise<ConnectionConfig[]>
|
||||
getSettings: () => Promise<any>
|
||||
saveSettings: (settings: any) => Promise<void>
|
||||
}
|
||||
redis: {
|
||||
connect: (id: string, opts: any) => Promise<{ success: boolean; error?: string }>
|
||||
disconnect: (id: string) => Promise<void>
|
||||
execute: (id: string, command: string, args: string[]) => Promise<any>
|
||||
getInfo: (id: string) => Promise<string>
|
||||
scanKeys: (id: string, cursor: string, pattern: string, count: number) =>
|
||||
Promise<{ cursor: string; keys: string[] }>
|
||||
getKeyType: (id: string, key: string) => Promise<string>
|
||||
getKeyTTL: (id: string, key: string) => Promise<number>
|
||||
getString: (id: string, key: string) => Promise<string | null>
|
||||
setString: (id: string, key: string, value: string) => Promise<void>
|
||||
deleteKey: (id: string, key: string) => Promise<void>
|
||||
hashScan: (id: string, key: string, cursor: string, count: number) =>
|
||||
Promise<{ cursor: string; fields: [string, string][] }>
|
||||
hashSet: (id: string, key: string, field: string, value: string) => Promise<void>
|
||||
hashDel: (id: string, key: string, field: string) => Promise<void>
|
||||
setTTL: (id: string, key: string, ttl: number) => Promise<void>
|
||||
selectDb: (id: string, db: number) => Promise<void>
|
||||
dbSize: (id: string) => Promise<number>
|
||||
listRange: (id: string, key: string, start: number, stop: number) => Promise<string[]>
|
||||
listLength: (id: string, key: string) => Promise<number>
|
||||
listPush: (id: string, key: string, values: string[]) => Promise<number>
|
||||
listSet: (id: string, key: string, index: number, value: string) => Promise<void>
|
||||
listRemove: (id: string, key: string, count: number, value: string) => Promise<number>
|
||||
setMembers: (id: string, key: string) => Promise<string[]>
|
||||
setSize: (id: string, key: string) => Promise<number>
|
||||
setAdd: (id: string, key: string, members: string[]) => Promise<number>
|
||||
setRemove: (id: string, key: string, members: string[]) => Promise<number>
|
||||
zsetRange: (id: string, key: string, start: number, stop: number, withScores: boolean) => Promise<string[]>
|
||||
zsetSize: (id: string, key: string) => Promise<number>
|
||||
zsetAdd: (id: string, key: string, score: number, member: string) => Promise<number>
|
||||
zsetRemove: (id: string, key: string, members: string[]) => Promise<number>
|
||||
slowLogGet: (id: string, count: number) => Promise<any[]>
|
||||
slowLogLen: (id: string) => Promise<number>
|
||||
memoryUsage: (id: string, key: string) => Promise<number | null>
|
||||
batchDelete: (id: string, keys: string[]) => Promise<number>
|
||||
streamInfo: (id: string, key: string) => Promise<any>
|
||||
streamRange: (id: string, key: string, start: string, end: string, count: number) => Promise<any[]>
|
||||
streamAdd: (id: string, key: string, streamId: string, fieldValues: string[]) => Promise<string>
|
||||
streamTrim: (id: string, key: string, maxLen: number) => Promise<number>
|
||||
streamDel: (id: string, key: string, ids: string[]) => Promise<number>
|
||||
renameKey: (id: string, oldKey: string, newKey: string) => Promise<void>
|
||||
existsKey: (id: string, key: string) => Promise<boolean>
|
||||
ping: (id: string) => Promise<string>
|
||||
isConnected: (id: string) => Promise<boolean>
|
||||
}
|
||||
}
|
||||
|
||||
declare global {
|
||||
interface Window {
|
||||
electronAPI: ElectronAPI
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,120 @@
|
||||
// src/preload/index.ts
|
||||
import { contextBridge, ipcRenderer } from 'electron'
|
||||
|
||||
const electronAPI = {
|
||||
window: {
|
||||
minimize: () => ipcRenderer.send('window:minimize'),
|
||||
maximize: () => ipcRenderer.send('window:maximize'),
|
||||
close: () => ipcRenderer.send('window:close'),
|
||||
},
|
||||
theme: {
|
||||
get: (): Promise<boolean> => ipcRenderer.invoke('theme:get'),
|
||||
set: (theme: 'system' | 'dark' | 'light') => ipcRenderer.send('theme:set', theme),
|
||||
onOsUpdated: (callback: (isDark: boolean) => void) => {
|
||||
ipcRenderer.on('theme:os-updated', (_e, isDark: boolean) => callback(isDark))
|
||||
},
|
||||
},
|
||||
dialog: {
|
||||
openFile: (options: { title?: string; filters?: { name: string; extensions: string[] }[] }) =>
|
||||
ipcRenderer.invoke('dialog:openFile', options),
|
||||
},
|
||||
credential: {
|
||||
encrypt: (text: string): Promise<string> => ipcRenderer.invoke('credential:encrypt', text),
|
||||
decrypt: (encoded: string): Promise<string> => ipcRenderer.invoke('credential:decrypt', encoded),
|
||||
},
|
||||
storage: {
|
||||
getConnections: (): Promise<any[]> => ipcRenderer.invoke('storage:getConnections'),
|
||||
saveConnection: (conn: any): Promise<any[]> => ipcRenderer.invoke('storage:saveConnection', conn),
|
||||
deleteConnection: (id: string): Promise<any[]> => ipcRenderer.invoke('storage:deleteConnection', id),
|
||||
reorderConnections: (ids: string[]): Promise<any[]> => ipcRenderer.invoke('storage:reorderConnections', ids),
|
||||
getSettings: (): Promise<any> => ipcRenderer.invoke('storage:getSettings'),
|
||||
saveSettings: (settings: any): Promise<void> => ipcRenderer.invoke('storage:saveSettings', settings),
|
||||
},
|
||||
redis: {
|
||||
connect: (id: string, opts: any): Promise<any> => ipcRenderer.invoke('redis:connect', id, opts),
|
||||
disconnect: (id: string): Promise<void> => ipcRenderer.invoke('redis:disconnect', id),
|
||||
execute: (id: string, command: string, args: string[]): Promise<any> =>
|
||||
ipcRenderer.invoke('redis:execute', id, command, args),
|
||||
getInfo: (id: string): Promise<string> => ipcRenderer.invoke('redis:getInfo', id),
|
||||
scanKeys: (id: string, cursor: string, pattern: string, count: number): Promise<any> =>
|
||||
ipcRenderer.invoke('redis:scanKeys', id, cursor, pattern, count),
|
||||
getKeyType: (id: string, key: string): Promise<string> => ipcRenderer.invoke('redis:getKeyType', id, key),
|
||||
getKeyTTL: (id: string, key: string): Promise<number> => ipcRenderer.invoke('redis:getKeyTTL', id, key),
|
||||
getString: (id: string, key: string): Promise<string | null> => ipcRenderer.invoke('redis:getString', id, key),
|
||||
setString: (id: string, key: string, value: string): Promise<void> =>
|
||||
ipcRenderer.invoke('redis:setString', id, key, value),
|
||||
deleteKey: (id: string, key: string): Promise<void> => ipcRenderer.invoke('redis:deleteKey', id, key),
|
||||
hashScan: (id: string, key: string, cursor: string, count: number): Promise<any> =>
|
||||
ipcRenderer.invoke('redis:hashScan', id, key, cursor, count),
|
||||
hashSet: (id: string, key: string, field: string, value: string): Promise<void> =>
|
||||
ipcRenderer.invoke('redis:hashSet', id, key, field, value),
|
||||
hashDel: (id: string, key: string, field: string): Promise<void> =>
|
||||
ipcRenderer.invoke('redis:hashDel', id, key, field),
|
||||
setTTL: (id: string, key: string, ttl: number): Promise<void> =>
|
||||
ipcRenderer.invoke('redis:setTTL', id, key, ttl),
|
||||
selectDb: (id: string, db: number): Promise<void> =>
|
||||
ipcRenderer.invoke('redis:selectDb', id, db),
|
||||
dbSize: (id: string): Promise<number> => ipcRenderer.invoke('redis:dbSize', id),
|
||||
// List
|
||||
listRange: (id: string, key: string, start: number, stop: number): Promise<string[]> =>
|
||||
ipcRenderer.invoke('redis:listRange', id, key, start, stop),
|
||||
listLength: (id: string, key: string): Promise<number> =>
|
||||
ipcRenderer.invoke('redis:listLength', id, key),
|
||||
listPush: (id: string, key: string, values: string[]): Promise<number> =>
|
||||
ipcRenderer.invoke('redis:listPush', id, key, values),
|
||||
listSet: (id: string, key: string, index: number, value: string): Promise<void> =>
|
||||
ipcRenderer.invoke('redis:listSet', id, key, index, value),
|
||||
listRemove: (id: string, key: string, count: number, value: string): Promise<number> =>
|
||||
ipcRenderer.invoke('redis:listRemove', id, key, count, value),
|
||||
// Set
|
||||
setMembers: (id: string, key: string): Promise<string[]> =>
|
||||
ipcRenderer.invoke('redis:setMembers', id, key),
|
||||
setSize: (id: string, key: string): Promise<number> =>
|
||||
ipcRenderer.invoke('redis:setSize', id, key),
|
||||
setAdd: (id: string, key: string, members: string[]): Promise<number> =>
|
||||
ipcRenderer.invoke('redis:setAdd', id, key, members),
|
||||
setRemove: (id: string, key: string, members: string[]): Promise<number> =>
|
||||
ipcRenderer.invoke('redis:setRemove', id, key, members),
|
||||
// Sorted set
|
||||
zsetRange: (id: string, key: string, start: number, stop: number, withScores: boolean): Promise<string[]> =>
|
||||
ipcRenderer.invoke('redis:zsetRange', id, key, start, stop, withScores),
|
||||
zsetSize: (id: string, key: string): Promise<number> =>
|
||||
ipcRenderer.invoke('redis:zsetSize', id, key),
|
||||
zsetAdd: (id: string, key: string, score: number, member: string): Promise<number> =>
|
||||
ipcRenderer.invoke('redis:zsetAdd', id, key, score, member),
|
||||
zsetRemove: (id: string, key: string, members: string[]): Promise<number> =>
|
||||
ipcRenderer.invoke('redis:zsetRemove', id, key, members),
|
||||
// Slow log
|
||||
slowLogGet: (id: string, count: number): Promise<any[]> =>
|
||||
ipcRenderer.invoke('redis:slowLogGet', id, count),
|
||||
slowLogLen: (id: string): Promise<number> =>
|
||||
ipcRenderer.invoke('redis:slowLogLen', id),
|
||||
// Memory
|
||||
memoryUsage: (id: string, key: string): Promise<number | null> =>
|
||||
ipcRenderer.invoke('redis:memoryUsage', id, key),
|
||||
batchDelete: (id: string, keys: string[]): Promise<number> =>
|
||||
ipcRenderer.invoke('redis:batchDelete', id, keys),
|
||||
// Stream
|
||||
streamInfo: (id: string, key: string): Promise<any> =>
|
||||
ipcRenderer.invoke('redis:streamInfo', id, key),
|
||||
streamRange: (id: string, key: string, start: string, end: string, count: number): Promise<any[]> =>
|
||||
ipcRenderer.invoke('redis:streamRange', id, key, start, end, count),
|
||||
streamAdd: (id: string, key: string, streamId: string, fieldValues: string[]): Promise<string> =>
|
||||
ipcRenderer.invoke('redis:streamAdd', id, key, streamId, fieldValues),
|
||||
streamTrim: (id: string, key: string, maxLen: number): Promise<number> =>
|
||||
ipcRenderer.invoke('redis:streamTrim', id, key, maxLen),
|
||||
streamDel: (id: string, key: string, ids: string[]): Promise<number> =>
|
||||
ipcRenderer.invoke('redis:streamDel', id, key, ids),
|
||||
// Key ops
|
||||
renameKey: (id: string, oldKey: string, newKey: string): Promise<void> =>
|
||||
ipcRenderer.invoke('redis:renameKey', id, oldKey, newKey),
|
||||
existsKey: (id: string, key: string): Promise<boolean> =>
|
||||
ipcRenderer.invoke('redis:existsKey', id, key),
|
||||
ping: (id: string): Promise<string> =>
|
||||
ipcRenderer.invoke('redis:ping', id),
|
||||
isConnected: (id: string): Promise<boolean> =>
|
||||
ipcRenderer.invoke('redis:isConnected', id),
|
||||
},
|
||||
}
|
||||
|
||||
contextBridge.exposeInMainWorld('electronAPI', electronAPI)
|
||||
@@ -0,0 +1,95 @@
|
||||
// src/shared/types.ts
|
||||
// 共享类型定义
|
||||
|
||||
// 连接配置
|
||||
export interface ConnectionConfig {
|
||||
id: string
|
||||
name: string
|
||||
host: string
|
||||
port: number
|
||||
auth: string
|
||||
username: string
|
||||
tls: boolean
|
||||
tlsCaPath: string
|
||||
tlsCertPath: string
|
||||
tlsKeyPath: string
|
||||
sshEnabled: boolean
|
||||
sshHost: string
|
||||
sshPort: number
|
||||
sshUsername: string
|
||||
sshPassword: string
|
||||
sshPrivateKeyPath: string
|
||||
cluster: boolean
|
||||
sentinelMasterName: string
|
||||
separator: string
|
||||
order: number
|
||||
createdAt: number
|
||||
color?: string
|
||||
}
|
||||
|
||||
// 连接选项(用于 Redis 连接)
|
||||
export interface ConnectionOptions {
|
||||
host: string
|
||||
port: number
|
||||
password?: string
|
||||
username?: string
|
||||
tls?: boolean
|
||||
}
|
||||
|
||||
// IPC 响应
|
||||
export interface IpcResponse<T = void> {
|
||||
success: boolean
|
||||
data?: T
|
||||
error?: string
|
||||
}
|
||||
|
||||
// Key 类型
|
||||
export type RedisKeyType = 'string' | 'hash' | 'list' | 'set' | 'zset' | 'stream' | 'none'
|
||||
|
||||
// Key 项
|
||||
export interface KeyItem {
|
||||
name: string
|
||||
type: RedisKeyType | string
|
||||
ttl: number
|
||||
level: number
|
||||
children?: KeyItem[]
|
||||
isLeaf: boolean
|
||||
}
|
||||
|
||||
// Scan 结果
|
||||
export interface ScanResult {
|
||||
cursor: string
|
||||
keys: string[]
|
||||
}
|
||||
|
||||
// Hash 字段
|
||||
export interface HashField {
|
||||
field: string
|
||||
value: string
|
||||
}
|
||||
|
||||
// Stream Entry
|
||||
export interface StreamEntry {
|
||||
id: string
|
||||
fields: Record<string, string>
|
||||
}
|
||||
|
||||
// Zset Item
|
||||
export interface ZsetItem {
|
||||
member: string
|
||||
score: number
|
||||
}
|
||||
|
||||
// 慢日志条目
|
||||
export interface SlowLogEntry {
|
||||
id: number
|
||||
timestamp: number
|
||||
duration: number
|
||||
command: string
|
||||
}
|
||||
|
||||
// INFO 段
|
||||
export interface InfoSection {
|
||||
title: string
|
||||
items: { key: string; value: string }[]
|
||||
}
|
||||
Reference in New Issue
Block a user