feat(phase2): connection management

- RedisService in main process (ioredis wrapper, execute, scan, hash ops)
- electron-store for connection/settings persistence
- safeStorage credential encrypt/decrypt
- IPC handlers for redis, storage, credential channels
- Preload API typed with all new channels
- useConnectionStore (Pinia) with connect/disconnect/save/delete
- ConnectionCard with 3D hover effect + status indicator
- ConnectionList with search filter
- NewConnectionDialog (create/edit connection form)
- Sidebar component
- StatusBar with live connection status + pulse animation
- App.vue updated with Sidebar layout
This commit is contained in:
2026-07-04 17:45:19 +08:00
parent 7e5d61c099
commit 4f9398c65d
16 changed files with 1387 additions and 24 deletions
+55
View File
@@ -1,4 +1,29 @@
// 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
@@ -14,6 +39,36 @@ export interface ElectronAPI {
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>
}
}
declare global {
+36 -1
View File
@@ -11,13 +11,48 @@ const electronAPI = {
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', (_event, isDark: boolean) => callback(isDark))
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),
},
}
contextBridge.exposeInMainWorld('electronAPI', electronAPI)