feat: custom formatter support

- format.ts: customFormat() uses child_process.execFile
- Template params: {KEY} {VALUE} {HEX_FILE}
- Value written to temp file, path passed via {HEX_FILE}
- 10s timeout, error handling
- StringEditor: 'custom' format option + command input
- Command persisted to localStorage
- i18n keys (zh-CN + en)
This commit is contained in:
2026-07-07 21:54:13 +08:00
parent 5178aea6d1
commit 1511427e62
8 changed files with 65 additions and 2 deletions
+3
View File
@@ -264,6 +264,9 @@ export function registerIpcHandlers(): void {
ipcMain.handle('redis:detectFormat', (_e, value: string) => {
return redis.detectFormat(value)
})
ipcMain.handle('redis:customFormat', (_e, value: string, command: string, key: string) => {
return redis.customFormat(value, command, key)
})
// Pub/Sub
ipcMain.handle('redis:subscribe', async (_e, id: string, channels: string[], isPattern: boolean) => {
+35 -1
View File
@@ -1,6 +1,10 @@
// electron/main/redis/format.ts
// 值格式转换(解压/编码)
// 值格式转换(解压/编码/自定义
import { gunzipSync, inflateSync, brotliDecompressSync } from 'zlib'
import { execFile } from 'child_process'
import { writeFile, unlink, mkdtemp } from 'fs/promises'
import { tmpdir } from 'os'
import { join } from 'path'
export type FormatType = 'text' | 'hex' | 'json' | 'binary' | 'gzip' | 'deflate' | 'brotli' | 'deflateRaw'
@@ -44,3 +48,33 @@ export function detectFormat(value: string): FormatType {
if (hasNonPrintable) return 'hex'
return 'text'
}
export async function customFormat(
value: string,
command: string,
key?: string
): Promise<string> {
const tmpDir = await mkdtemp(join(tmpdir(), 'jrdm-fmt-'))
const tmpFile = join(tmpDir, 'value.bin')
await writeFile(tmpFile, value, 'utf-8')
return new Promise((resolve) => {
const cmd = command
.replace(/\{KEY\}/g, key || '')
.replace(/\{VALUE\}/g, value)
.replace(/\{HEX_FILE\}/g, tmpFile)
const parts = cmd.split(/\s+/)
const exe = parts[0]
const args = parts.slice(1)
execFile(exe, args, { timeout: 10000, maxBuffer: 10 * 1024 * 1024 }, async (err, stdout) => {
await unlink(tmpFile).catch(() => {})
if (err) {
resolve(`[Custom formatter error: ${err.message}]`)
} else {
resolve(stdout)
}
})
})
}
+1
View File
@@ -111,6 +111,7 @@ export interface ElectronAPI {
clearCommandLog: () => Promise<void>
decodeValue: (value: string, format: string) => Promise<string>
detectFormat: (value: string) => Promise<string>
customFormat: (value: string, command: string, key: string) => Promise<string>
subscribe: (id: string, channels: string[], isPattern: boolean) => Promise<{ success: boolean; error?: string }>
unsubscribe: (id: string) => Promise<void>
onSubscribeMessage: (callback: (msg: any) => void) => void
+2
View File
@@ -135,6 +135,8 @@ const electronAPI = {
ipcRenderer.invoke('redis:decodeValue', value, format),
detectFormat: (value: string): Promise<string> =>
ipcRenderer.invoke('redis:detectFormat', value),
customFormat: (value: string, command: string, key: string): Promise<string> =>
ipcRenderer.invoke('redis:customFormat', value, command, key),
// Pub/Sub
subscribe: (id: string, channels: string[], isPattern: boolean): Promise<any> =>
ipcRenderer.invoke('redis:subscribe', id, channels, isPattern),