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:
@@ -1,6 +1,7 @@
|
|||||||
node_modules/
|
node_modules/
|
||||||
out/
|
out/
|
||||||
dist/
|
dist/
|
||||||
|
electron-dist/
|
||||||
*.log
|
*.log
|
||||||
npm-debug.log*
|
npm-debug.log*
|
||||||
yarn-debug.log*
|
yarn-debug.log*
|
||||||
|
|||||||
@@ -6,9 +6,10 @@ Fork of [AnotherRedisDesktopManager](https://github.com/qishibo/AnotherRedisDesk
|
|||||||
|
|
||||||
## Stack
|
## Stack
|
||||||
|
|
||||||
- **Shell:** Electron 33 (`src/main/index.ts`)
|
- **Shell:** Electron 43 (`electron/main/index.ts`)
|
||||||
- **Preload:** `src/preload/index.ts` (contextBridge, typed API)
|
- **Preload:** `electron/preload/index.ts` (contextBridge, typed API)
|
||||||
- **Renderer:** Vue 3 + Pinia + Element Plus + Vue Router (`src/renderer/`)
|
- **Renderer:** Vue 3 + Pinia + Element Plus + Vue Router (`src/renderer/`)
|
||||||
|
- **Shared:** `electron/shared/types.ts` (cross-process types)
|
||||||
- **Build:** electron-vite + Vite 6 + TypeScript 5
|
- **Build:** electron-vite + Vite 6 + TypeScript 5
|
||||||
- **Redis client:** ioredis 5 (main process only)
|
- **Redis client:** ioredis 5 (main process only)
|
||||||
- **Persistence:** electron-store + safeStorage (credentials)
|
- **Persistence:** electron-store + safeStorage (credentials)
|
||||||
@@ -17,7 +18,7 @@ Fork of [AnotherRedisDesktopManager](https://github.com/qishibo/AnotherRedisDesk
|
|||||||
|
|
||||||
```bash
|
```bash
|
||||||
npm run dev # electron-vite dev (HMR, renderer on :5173)
|
npm run dev # electron-vite dev (HMR, renderer on :5173)
|
||||||
npm run build # production build → out/
|
npm run build # production build → electron-dist/
|
||||||
npm start # preview built app
|
npm start # preview built app
|
||||||
npm run build:unpack # build + electron-builder --dir
|
npm run build:unpack # build + electron-builder --dir
|
||||||
npm run build:win # build + electron-builder --win
|
npm run build:win # build + electron-builder --win
|
||||||
@@ -28,18 +29,31 @@ npm run build:linux # build + electron-builder --linux
|
|||||||
## Architecture
|
## Architecture
|
||||||
|
|
||||||
```
|
```
|
||||||
Electron main (src/main/)
|
Electron main (electron/main/)
|
||||||
├── index.ts # Window creation, app lifecycle
|
├── index.ts # Window creation, app lifecycle
|
||||||
├── redis-service.ts # ioredis wrapper (connect, scan, CRUD, stream)
|
├── redis/ # Redis service modules
|
||||||
|
│ ├── index.ts # Unified export
|
||||||
|
│ ├── connection.ts # connect, disconnect, isClientConnected
|
||||||
|
│ ├── keys.ts # scan, delete, rename, ttl, batchDelete
|
||||||
|
│ ├── string.ts # get, set
|
||||||
|
│ ├── hash.ts # hscan, hset, hdel
|
||||||
|
│ ├── list.ts # range, push, set, remove
|
||||||
|
│ ├── set.ts # members, add, remove
|
||||||
|
│ ├── zset.ts # range, add, remove
|
||||||
|
│ ├── stream.ts # info, range, add, trim
|
||||||
|
│ └── server.ts # info, select, ping, slowlog
|
||||||
├── store.ts # electron-store (connections, settings)
|
├── store.ts # electron-store (connections, settings)
|
||||||
├── credential.ts # safeStorage encrypt/decrypt
|
├── credential.ts # safeStorage encrypt/decrypt
|
||||||
├── win-state.ts # window position persistence
|
├── win-state.ts # window position persistence
|
||||||
└── ipc-handlers.ts # all IPC channel handlers
|
└── ipc-handlers.ts # all IPC channel handlers
|
||||||
|
|
||||||
Preload (src/preload/)
|
Preload (electron/preload/)
|
||||||
├── index.ts # contextBridge.exposeInMainWorld
|
├── index.ts # contextBridge.exposeInMainWorld
|
||||||
└── index.d.ts # ElectronAPI type declarations
|
└── index.d.ts # ElectronAPI type declarations
|
||||||
|
|
||||||
|
Shared (electron/shared/)
|
||||||
|
└── types.ts # Cross-process type definitions
|
||||||
|
|
||||||
Renderer (src/renderer/src/)
|
Renderer (src/renderer/src/)
|
||||||
├── main.ts # Vue 3 bootstrap (Pinia, Element Plus)
|
├── main.ts # Vue 3 bootstrap (Pinia, Element Plus)
|
||||||
├── App.vue # Shell layout (TitleBar + Sidebar + MainArea + StatusBar)
|
├── App.vue # Shell layout (TitleBar + Sidebar + MainArea + StatusBar)
|
||||||
|
|||||||
@@ -6,10 +6,10 @@ export default defineConfig({
|
|||||||
main: {
|
main: {
|
||||||
plugins: [externalizeDepsPlugin()],
|
plugins: [externalizeDepsPlugin()],
|
||||||
build: {
|
build: {
|
||||||
outDir: 'out/main',
|
outDir: 'electron-dist/main',
|
||||||
rollupOptions: {
|
rollupOptions: {
|
||||||
input: {
|
input: {
|
||||||
index: resolve(__dirname, 'src/main/index.ts')
|
index: resolve(__dirname, 'electron/main/index.ts')
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -17,27 +17,28 @@ export default defineConfig({
|
|||||||
preload: {
|
preload: {
|
||||||
plugins: [externalizeDepsPlugin()],
|
plugins: [externalizeDepsPlugin()],
|
||||||
build: {
|
build: {
|
||||||
outDir: 'out/preload',
|
outDir: 'electron-dist/preload',
|
||||||
rollupOptions: {
|
rollupOptions: {
|
||||||
input: {
|
input: {
|
||||||
index: resolve(__dirname, 'src/preload/index.ts')
|
index: resolve(__dirname, 'electron/preload/index.ts')
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
renderer: {
|
renderer: {
|
||||||
root: 'src/renderer',
|
root: 'src',
|
||||||
build: {
|
build: {
|
||||||
outDir: 'out/renderer',
|
outDir: resolve(__dirname, 'electron-dist/renderer'),
|
||||||
rollupOptions: {
|
rollupOptions: {
|
||||||
input: {
|
input: {
|
||||||
index: resolve(__dirname, 'src/renderer/index.html')
|
index: resolve(__dirname, 'src/index.html')
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
resolve: {
|
resolve: {
|
||||||
alias: {
|
alias: {
|
||||||
'@renderer': resolve(__dirname, 'src/renderer/src')
|
'@renderer': resolve(__dirname, 'src'),
|
||||||
|
'@shared': resolve(__dirname, 'electron/shared')
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
plugins: [vue()],
|
plugins: [vue()],
|
||||||
|
|||||||
@@ -1,8 +1,9 @@
|
|||||||
// src/main/ipc-handlers.ts
|
// src/main/ipc-handlers.ts
|
||||||
|
// IPC 通道处理器
|
||||||
import { ipcMain, BrowserWindow, nativeTheme, dialog } from 'electron'
|
import { ipcMain, BrowserWindow, nativeTheme, dialog } from 'electron'
|
||||||
import store, { ConnectionConfig } from './store'
|
import store, { ConnectionConfig } from './store'
|
||||||
import { encrypt, decrypt } from './credential'
|
import { encrypt, decrypt } from './credential'
|
||||||
import * as redisService from './redis-service'
|
import * as redis from './redis'
|
||||||
|
|
||||||
export function registerIpcHandlers(): void {
|
export function registerIpcHandlers(): void {
|
||||||
// Window
|
// Window
|
||||||
@@ -67,152 +68,152 @@ export function registerIpcHandlers(): void {
|
|||||||
store.set('settings', settings)
|
store.set('settings', settings)
|
||||||
})
|
})
|
||||||
|
|
||||||
// Redis
|
// Redis - 连接
|
||||||
ipcMain.handle('redis:connect', async (_e, id: string, opts) => {
|
ipcMain.handle('redis:connect', async (_e, id: string, opts) => {
|
||||||
try {
|
try {
|
||||||
console.log(`[IPC] redis:connect id=${id}`, opts)
|
await redis.connect(id, opts)
|
||||||
await redisService.connect(id, opts)
|
|
||||||
return { success: true }
|
return { success: true }
|
||||||
} catch (err: any) {
|
} catch (err: any) {
|
||||||
console.error(`[IPC] redis:connect error:`, err.message)
|
return { success: false, error: err.message }
|
||||||
return { success: false, error: err.message || String(err) }
|
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
ipcMain.handle('redis:disconnect', async (_e, id: string) => {
|
ipcMain.handle('redis:disconnect', async (_e, id: string) => {
|
||||||
await redisService.disconnect(id)
|
await redis.disconnect(id)
|
||||||
})
|
|
||||||
ipcMain.handle('redis:execute', async (_e, id: string, command: string, args: string[]) => {
|
|
||||||
return redisService.execute(id, command, args)
|
|
||||||
})
|
|
||||||
ipcMain.handle('redis:getInfo', async (_e, id: string) => {
|
|
||||||
return redisService.getServerInfo(id)
|
|
||||||
})
|
|
||||||
ipcMain.handle('redis:scanKeys', async (_e, id: string, cursor: string, pattern: string, count: number) => {
|
|
||||||
return redisService.scanKeys(id, cursor, pattern, count)
|
|
||||||
})
|
|
||||||
ipcMain.handle('redis:getKeyType', async (_e, id: string, key: string) => {
|
|
||||||
return redisService.getKeyType(id, key)
|
|
||||||
})
|
|
||||||
ipcMain.handle('redis:getKeyTTL', async (_e, id: string, key: string) => {
|
|
||||||
return redisService.getKeyTTL(id, key)
|
|
||||||
})
|
|
||||||
ipcMain.handle('redis:getString', async (_e, id: string, key: string) => {
|
|
||||||
return redisService.getStringValue(id, key)
|
|
||||||
})
|
|
||||||
ipcMain.handle('redis:setString', async (_e, id: string, key: string, value: string) => {
|
|
||||||
await redisService.setStringValue(id, key, value)
|
|
||||||
})
|
|
||||||
ipcMain.handle('redis:deleteKey', async (_e, id: string, key: string) => {
|
|
||||||
await redisService.deleteKey(id, key)
|
|
||||||
})
|
|
||||||
ipcMain.handle('redis:hashScan', async (_e, id: string, key: string, cursor: string, count: number) => {
|
|
||||||
return redisService.getHashFields(id, key, cursor, count)
|
|
||||||
})
|
|
||||||
ipcMain.handle('redis:hashSet', async (_e, id: string, key: string, field: string, value: string) => {
|
|
||||||
await redisService.setHashField(id, key, field, value)
|
|
||||||
})
|
|
||||||
ipcMain.handle('redis:hashDel', async (_e, id: string, key: string, field: string) => {
|
|
||||||
await redisService.deleteHashField(id, key, field)
|
|
||||||
})
|
|
||||||
ipcMain.handle('redis:setTTL', async (_e, id: string, key: string, ttl: number) => {
|
|
||||||
await redisService.setKeyTTL(id, key, ttl)
|
|
||||||
})
|
|
||||||
ipcMain.handle('redis:selectDb', async (_e, id: string, db: number) => {
|
|
||||||
await redisService.selectDb(id, db)
|
|
||||||
})
|
|
||||||
ipcMain.handle('redis:dbSize', async (_e, id: string) => {
|
|
||||||
return redisService.dbSize(id)
|
|
||||||
})
|
|
||||||
|
|
||||||
// List
|
|
||||||
ipcMain.handle('redis:listRange', async (_e, id: string, key: string, start: number, stop: number) => {
|
|
||||||
return redisService.listRange(id, key, start, stop)
|
|
||||||
})
|
|
||||||
ipcMain.handle('redis:listLength', async (_e, id: string, key: string) => {
|
|
||||||
return redisService.listLength(id, key)
|
|
||||||
})
|
|
||||||
ipcMain.handle('redis:listPush', async (_e, id: string, key: string, values: string[]) => {
|
|
||||||
return redisService.listPush(id, key, ...values)
|
|
||||||
})
|
|
||||||
ipcMain.handle('redis:listSet', async (_e, id: string, key: string, index: number, value: string) => {
|
|
||||||
await redisService.listSet(id, key, index, value)
|
|
||||||
})
|
|
||||||
ipcMain.handle('redis:listRemove', async (_e, id: string, key: string, count: number, value: string) => {
|
|
||||||
return redisService.listRemove(id, key, count, value)
|
|
||||||
})
|
|
||||||
|
|
||||||
// Set
|
|
||||||
ipcMain.handle('redis:setMembers', async (_e, id: string, key: string) => {
|
|
||||||
return redisService.setMembers(id, key)
|
|
||||||
})
|
|
||||||
ipcMain.handle('redis:setSize', async (_e, id: string, key: string) => {
|
|
||||||
return redisService.setSize(id, key)
|
|
||||||
})
|
|
||||||
ipcMain.handle('redis:setAdd', async (_e, id: string, key: string, members: string[]) => {
|
|
||||||
return redisService.setAdd(id, key, ...members)
|
|
||||||
})
|
|
||||||
ipcMain.handle('redis:setRemove', async (_e, id: string, key: string, members: string[]) => {
|
|
||||||
return redisService.setRemove(id, key, ...members)
|
|
||||||
})
|
|
||||||
|
|
||||||
// Sorted set
|
|
||||||
ipcMain.handle('redis:zsetRange', async (_e, id: string, key: string, start: number, stop: number, withScores: boolean) => {
|
|
||||||
return redisService.zsetRange(id, key, start, stop, withScores)
|
|
||||||
})
|
|
||||||
ipcMain.handle('redis:zsetSize', async (_e, id: string, key: string) => {
|
|
||||||
return redisService.zsetSize(id, key)
|
|
||||||
})
|
|
||||||
ipcMain.handle('redis:zsetAdd', async (_e, id: string, key: string, score: number, member: string) => {
|
|
||||||
return redisService.zsetAdd(id, key, score, member)
|
|
||||||
})
|
|
||||||
ipcMain.handle('redis:zsetRemove', async (_e, id: string, key: string, members: string[]) => {
|
|
||||||
return redisService.zsetRemove(id, key, ...members)
|
|
||||||
})
|
|
||||||
|
|
||||||
// Slow log
|
|
||||||
ipcMain.handle('redis:slowLogGet', async (_e, id: string, count: number) => {
|
|
||||||
return redisService.slowLogGet(id, count)
|
|
||||||
})
|
|
||||||
ipcMain.handle('redis:slowLogLen', async (_e, id: string) => {
|
|
||||||
return redisService.slowLogLen(id)
|
|
||||||
})
|
|
||||||
|
|
||||||
// Memory
|
|
||||||
ipcMain.handle('redis:memoryUsage', async (_e, id: string, key: string) => {
|
|
||||||
return redisService.memoryUsage(id, key)
|
|
||||||
})
|
|
||||||
ipcMain.handle('redis:batchDelete', async (_e, id: string, keys: string[]) => {
|
|
||||||
return redisService.batchDelete(id, keys)
|
|
||||||
})
|
|
||||||
|
|
||||||
// Stream
|
|
||||||
ipcMain.handle('redis:streamInfo', async (_e, id: string, key: string) => {
|
|
||||||
return redisService.streamInfo(id, key)
|
|
||||||
})
|
|
||||||
ipcMain.handle('redis:streamRange', async (_e, id: string, key: string, start: string, end: string, count: number) => {
|
|
||||||
return redisService.streamRange(id, key, start, end, count)
|
|
||||||
})
|
|
||||||
ipcMain.handle('redis:streamAdd', async (_e, id: string, key: string, streamId: string, fieldValues: string[]) => {
|
|
||||||
return redisService.streamAdd(id, key, streamId, fieldValues)
|
|
||||||
})
|
|
||||||
ipcMain.handle('redis:streamTrim', async (_e, id: string, key: string, maxLen: number) => {
|
|
||||||
return redisService.streamTrim(id, key, maxLen)
|
|
||||||
})
|
|
||||||
ipcMain.handle('redis:streamDel', async (_e, id: string, key: string, ids: string[]) => {
|
|
||||||
return redisService.streamDel(id, key, ...ids)
|
|
||||||
})
|
|
||||||
|
|
||||||
// Key ops
|
|
||||||
ipcMain.handle('redis:renameKey', async (_e, id: string, oldKey: string, newKey: string) => {
|
|
||||||
await redisService.renameKey(id, oldKey, newKey)
|
|
||||||
})
|
|
||||||
ipcMain.handle('redis:existsKey', async (_e, id: string, key: string) => {
|
|
||||||
return redisService.existsKey(id, key)
|
|
||||||
})
|
})
|
||||||
ipcMain.handle('redis:ping', async (_e, id: string) => {
|
ipcMain.handle('redis:ping', async (_e, id: string) => {
|
||||||
return redisService.ping(id)
|
return redis.ping(id)
|
||||||
})
|
})
|
||||||
ipcMain.handle('redis:isConnected', (_e, id: string) => {
|
ipcMain.handle('redis:isConnected', (_e, id: string) => {
|
||||||
return redisService.isConnected(id)
|
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,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 }[]
|
||||||
|
}
|
||||||
@@ -7,6 +7,6 @@
|
|||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<div id="app"></div>
|
<div id="app"></div>
|
||||||
<script type="module" src="./src/main.ts"></script>
|
<script type="module" src="./main.ts"></script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
@@ -1,16 +1,13 @@
|
|||||||
// src/renderer/src/main.ts
|
// src/renderer/main.ts
|
||||||
import { createApp } from 'vue'
|
import { createApp } from 'vue'
|
||||||
import { createPinia } from 'pinia'
|
import { createPinia } from 'pinia'
|
||||||
import ElementPlus from 'element-plus'
|
import ElementPlus from 'element-plus'
|
||||||
import 'element-plus/dist/index.css'
|
import 'element-plus/dist/index.css'
|
||||||
import * as ElementPlusIconsVue from '@element-plus/icons-vue'
|
import * as ElementPlusIconsVue from '@element-plus/icons-vue'
|
||||||
import App from './App.vue'
|
import App from './App.vue'
|
||||||
import router from './router'
|
|
||||||
import './styles/global.css'
|
|
||||||
|
|
||||||
const app = createApp(App)
|
const app = createApp(App)
|
||||||
app.use(createPinia())
|
app.use(createPinia())
|
||||||
app.use(router)
|
|
||||||
app.use(ElementPlus, { size: 'small' })
|
app.use(ElementPlus, { size: 'small' })
|
||||||
|
|
||||||
for (const [key, component] of Object.entries(ElementPlusIconsVue)) {
|
for (const [key, component] of Object.entries(ElementPlusIconsVue)) {
|
||||||
@@ -1,373 +0,0 @@
|
|||||||
// src/main/redis-service.ts
|
|
||||||
import Redis, { RedisOptions, Cluster, ClusterNode } from 'ioredis'
|
|
||||||
|
|
||||||
export type RedisClient = Redis | Cluster
|
|
||||||
|
|
||||||
interface ConnectionOptions {
|
|
||||||
id: string
|
|
||||||
host: string
|
|
||||||
port: number
|
|
||||||
password?: string
|
|
||||||
username?: string
|
|
||||||
tls?: boolean
|
|
||||||
tlsCa?: string
|
|
||||||
tlsCert?: string
|
|
||||||
tlsKey?: string
|
|
||||||
}
|
|
||||||
|
|
||||||
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,
|
|
||||||
}
|
|
||||||
if (opts.tlsCa) options.tls.ca = opts.tlsCa
|
|
||||||
if (opts.tlsCert) options.tls.cert = opts.tlsCert
|
|
||||||
if (opts.tlsKey) options.tls.key = opts.tlsKey
|
|
||||||
}
|
|
||||||
|
|
||||||
return options
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function connect(id: string, opts: ConnectionOptions): Promise<void> {
|
|
||||||
if (clients.has(id)) {
|
|
||||||
await disconnect(id)
|
|
||||||
}
|
|
||||||
|
|
||||||
return new Promise((resolve, reject) => {
|
|
||||||
console.log(`[Redis] Connecting to ${opts.host}:${opts.port}...`)
|
|
||||||
const redis = new Redis(buildRedisOptions(opts))
|
|
||||||
|
|
||||||
redis.once('ready', () => {
|
|
||||||
console.log(`[Redis] Connected to ${opts.host}:${opts.port}`)
|
|
||||||
clients.set(id, redis)
|
|
||||||
resolve()
|
|
||||||
})
|
|
||||||
|
|
||||||
redis.once('error', (err: Error) => {
|
|
||||||
console.error(`[Redis] Connection error:`, err.message)
|
|
||||||
redis.disconnect()
|
|
||||||
reject(err)
|
|
||||||
})
|
|
||||||
|
|
||||||
// Timeout
|
|
||||||
setTimeout(() => {
|
|
||||||
if (!clients.has(id)) {
|
|
||||||
console.error(`[Redis] Connection timeout`)
|
|
||||||
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 getClient(id: string): RedisClient | undefined {
|
|
||||||
return clients.get(id)
|
|
||||||
}
|
|
||||||
|
|
||||||
export function disconnectAll(): void {
|
|
||||||
for (const [id] of clients) {
|
|
||||||
disconnect(id)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function execute(id: string, command: string, args: string[]): Promise<unknown> {
|
|
||||||
const client = clients.get(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 = clients.get(id)
|
|
||||||
if (!client) throw new Error(`Client ${id} not found`)
|
|
||||||
return client.info() as Promise<string>
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function scanKeys(
|
|
||||||
id: string,
|
|
||||||
cursor: string,
|
|
||||||
pattern: string,
|
|
||||||
count: number
|
|
||||||
): Promise<{ cursor: string; keys: string[] }> {
|
|
||||||
const client = clients.get(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 }
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function getKeyType(id: string, key: string): Promise<string> {
|
|
||||||
const client = clients.get(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 = clients.get(id)
|
|
||||||
if (!client) throw new Error(`Client ${id} not found`)
|
|
||||||
return client.ttl(key)
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function getStringValue(id: string, key: string): Promise<string | null> {
|
|
||||||
const client = clients.get(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 = clients.get(id)
|
|
||||||
if (!client) throw new Error(`Client ${id} not found`)
|
|
||||||
await client.set(key, value)
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function deleteKey(id: string, key: string): Promise<void> {
|
|
||||||
const client = clients.get(id)
|
|
||||||
if (!client) throw new Error(`Client ${id} not found`)
|
|
||||||
await client.del(key)
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function getHashFields(
|
|
||||||
id: string, key: string, cursor: string, count: number
|
|
||||||
): Promise<{ cursor: string; fields: [string, string][] }> {
|
|
||||||
const client = clients.get(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 = clients.get(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 = clients.get(id)
|
|
||||||
if (!client) throw new Error(`Client ${id} not found`)
|
|
||||||
await client.hdel(key, field)
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function setKeyTTL(id: string, key: string, ttl: number): Promise<void> {
|
|
||||||
const client = clients.get(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 selectDb(id: string, db: number): Promise<void> {
|
|
||||||
const client = clients.get(id)
|
|
||||||
if (!client) throw new Error(`Client ${id} not found`)
|
|
||||||
await client.select(db)
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function dbSize(id: string): Promise<number> {
|
|
||||||
const client = clients.get(id)
|
|
||||||
if (!client) throw new Error(`Client ${id} not found`)
|
|
||||||
return client.dbsize() as Promise<number>
|
|
||||||
}
|
|
||||||
|
|
||||||
// List operations
|
|
||||||
export async function listRange(id: string, key: string, start: number, stop: number): Promise<string[]> {
|
|
||||||
const client = clients.get(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 = clients.get(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 = clients.get(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 = clients.get(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 = clients.get(id)
|
|
||||||
if (!client) throw new Error(`Client ${id} not found`)
|
|
||||||
return client.lrem(key, count, value)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Set operations
|
|
||||||
export async function setMembers(id: string, key: string): Promise<string[]> {
|
|
||||||
const client = clients.get(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 = clients.get(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 = clients.get(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 = clients.get(id)
|
|
||||||
if (!client) throw new Error(`Client ${id} not found`)
|
|
||||||
return client.srem(key, ...members)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Sorted set operations
|
|
||||||
export async function zsetRange(id: string, key: string, start: number, stop: number, withScores: boolean): Promise<string[]> {
|
|
||||||
const client = clients.get(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 = clients.get(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 = clients.get(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 = clients.get(id)
|
|
||||||
if (!client) throw new Error(`Client ${id} not found`)
|
|
||||||
return client.zrem(key, ...members)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Slow log
|
|
||||||
export async function slowLogGet(id: string, count: number): Promise<any[]> {
|
|
||||||
const client = clients.get(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 = clients.get(id)
|
|
||||||
if (!client) throw new Error(`Client ${id} not found`)
|
|
||||||
return client.slowlog('LEN') as Promise<number>
|
|
||||||
}
|
|
||||||
|
|
||||||
// Memory info
|
|
||||||
export async function memoryUsage(id: string, key: string): Promise<number | null> {
|
|
||||||
const client = clients.get(id)
|
|
||||||
if (!client) throw new Error(`Client ${id} not found`)
|
|
||||||
return client.memory('USAGE', key) as Promise<number | null>
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function batchDelete(id: string, keys: string[]): Promise<number> {
|
|
||||||
const client = clients.get(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
|
|
||||||
}
|
|
||||||
|
|
||||||
// Stream operations
|
|
||||||
export async function streamInfo(id: string, key: string): Promise<any> {
|
|
||||||
const client = clients.get(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 = clients.get(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 = clients.get(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 = clients.get(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 = clients.get(id)
|
|
||||||
if (!client) throw new Error(`Client ${id} not found`)
|
|
||||||
return client.xdel(key, ...ids)
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function renameKey(id: string, oldKey: string, newKey: string): Promise<void> {
|
|
||||||
const client = clients.get(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 = clients.get(id)
|
|
||||||
if (!client) throw new Error(`Client ${id} not found`)
|
|
||||||
return (await client.exists(key)) === 1
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function ping(id: string): Promise<string> {
|
|
||||||
const client = clients.get(id)
|
|
||||||
if (!client) throw new Error(`Client ${id} not found`)
|
|
||||||
return client.ping()
|
|
||||||
}
|
|
||||||
|
|
||||||
export function isConnected(id: string): boolean {
|
|
||||||
const client = clients.get(id)
|
|
||||||
return client?.status === 'ready'
|
|
||||||
}
|
|
||||||
Vendored
-7
@@ -1,7 +0,0 @@
|
|||||||
/// <reference types="vite/client" />
|
|
||||||
|
|
||||||
declare module '*.vue' {
|
|
||||||
import type { DefineComponent } from 'vue'
|
|
||||||
const component: DefineComponent<object, object, unknown>
|
|
||||||
export default component
|
|
||||||
}
|
|
||||||
@@ -1,16 +0,0 @@
|
|||||||
// src/renderer/src/router/index.ts
|
|
||||||
import { createRouter, createMemoryHistory } from 'vue-router'
|
|
||||||
import App from '@renderer/App.vue'
|
|
||||||
|
|
||||||
const router = createRouter({
|
|
||||||
history: createMemoryHistory(),
|
|
||||||
routes: [
|
|
||||||
{
|
|
||||||
path: '/',
|
|
||||||
name: 'home',
|
|
||||||
component: App,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
})
|
|
||||||
|
|
||||||
export default router
|
|
||||||
@@ -1,26 +0,0 @@
|
|||||||
/* src/renderer/src/styles/global.css */
|
|
||||||
@import './variables.css';
|
|
||||||
|
|
||||||
*, *::before, *::after {
|
|
||||||
margin: 0;
|
|
||||||
padding: 0;
|
|
||||||
box-sizing: border-box;
|
|
||||||
}
|
|
||||||
|
|
||||||
html, body, #app {
|
|
||||||
height: 100%;
|
|
||||||
width: 100%;
|
|
||||||
overflow: hidden;
|
|
||||||
}
|
|
||||||
|
|
||||||
body {
|
|
||||||
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;
|
|
||||||
background: var(--bg-primary);
|
|
||||||
color: var(--text-primary);
|
|
||||||
-webkit-font-smoothing: antialiased;
|
|
||||||
-moz-osx-font-smoothing: grayscale;
|
|
||||||
}
|
|
||||||
|
|
||||||
::-webkit-scrollbar { width: 6px; height: 6px; }
|
|
||||||
::-webkit-scrollbar-track { background: transparent; }
|
|
||||||
::-webkit-scrollbar-thumb { background: rgba(255,255,255,0.1); border-radius: 3px; }
|
|
||||||
@@ -1,42 +0,0 @@
|
|||||||
/* src/renderer/src/styles/variables.css */
|
|
||||||
:root {
|
|
||||||
--bg-primary: #0f0f1a;
|
|
||||||
--bg-secondary: #1a1a2e;
|
|
||||||
--bg-card: rgba(255, 255, 255, 0.03);
|
|
||||||
--bg-card-hover: rgba(255, 255, 255, 0.06);
|
|
||||||
--bg-card-active: rgba(99, 102, 241, 0.08);
|
|
||||||
--border-color: rgba(255, 255, 255, 0.06);
|
|
||||||
--border-accent: rgba(99, 102, 241, 0.2);
|
|
||||||
--text-primary: #e2e8f0;
|
|
||||||
--text-secondary: #94a3b8;
|
|
||||||
--text-muted: #64748b;
|
|
||||||
--accent: #6366f1;
|
|
||||||
--accent-light: #a5b4fc;
|
|
||||||
--success: #22c55e;
|
|
||||||
--warning: #eab308;
|
|
||||||
--danger: #ef4444;
|
|
||||||
--shadow-sm: 0 2px 8px rgba(0, 0, 0, 0.2);
|
|
||||||
--shadow-md: 0 4px 16px rgba(0, 0, 0, 0.3);
|
|
||||||
--shadow-lg: 0 8px 32px rgba(0, 0, 0, 0.4);
|
|
||||||
--shadow-glow: 0 0 14px rgba(99, 102, 241, 0.2);
|
|
||||||
--radius-sm: 8px;
|
|
||||||
--radius-md: 12px;
|
|
||||||
--radius-lg: 16px;
|
|
||||||
}
|
|
||||||
|
|
||||||
:root.light {
|
|
||||||
--bg-primary: #f8fafc;
|
|
||||||
--bg-secondary: #f1f5f9;
|
|
||||||
--bg-card: #ffffff;
|
|
||||||
--bg-card-hover: #f1f5f9;
|
|
||||||
--bg-card-active: rgba(99, 102, 241, 0.06);
|
|
||||||
--border-color: rgba(0, 0, 0, 0.06);
|
|
||||||
--border-accent: rgba(99, 102, 241, 0.15);
|
|
||||||
--text-primary: #1e293b;
|
|
||||||
--text-secondary: #64748b;
|
|
||||||
--text-muted: #94a3b8;
|
|
||||||
--shadow-sm: 0 2px 8px rgba(0, 0, 0, 0.04);
|
|
||||||
--shadow-md: 0 4px 16px rgba(0, 0, 0, 0.06);
|
|
||||||
--shadow-lg: 0 8px 24px rgba(0, 0, 0, 0.08);
|
|
||||||
--shadow-glow: 0 0 12px rgba(99, 102, 241, 0.1);
|
|
||||||
}
|
|
||||||
+2
-2
@@ -6,9 +6,9 @@
|
|||||||
"strict": true,
|
"strict": true,
|
||||||
"esModuleInterop": true,
|
"esModuleInterop": true,
|
||||||
"skipLibCheck": true,
|
"skipLibCheck": true,
|
||||||
"outDir": "./out",
|
"outDir": "./electron-dist",
|
||||||
"types": ["node"],
|
"types": ["node"],
|
||||||
"composite": true
|
"composite": true
|
||||||
},
|
},
|
||||||
"include": ["src/main/**/*", "src/preload/**/*", "electron.vite.config.ts"]
|
"include": ["electron/main/**/*", "electron/preload/**/*", "electron/shared/**/*", "electron.vite.config.ts"]
|
||||||
}
|
}
|
||||||
|
|||||||
+3
-2
@@ -11,8 +11,9 @@
|
|||||||
"composite": true,
|
"composite": true,
|
||||||
"baseUrl": ".",
|
"baseUrl": ".",
|
||||||
"paths": {
|
"paths": {
|
||||||
"@renderer/*": ["src/renderer/src/*"]
|
"@renderer/*": ["src/*"],
|
||||||
|
"@shared/*": ["electron/shared/*"]
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"include": ["src/renderer/**/*", "src/renderer/src/**/*.d.ts", "src/preload/index.d.ts"]
|
"include": ["src/**/*", "electron/preload/index.d.ts"]
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user