feat: multi-format viewer (Text/Hex/JSON/Binary/Gzip/Deflate/Brotli)
- New format.ts: decodeValue + detectFormat using Node.js zlib - IPC: redis:decodeValue, redis:detectFormat - StringEditor: format selector dropdown with auto-detect - Monaco editor shows decoded value, read-only for compressed formats - Auto-detect: JSON, Gzip (1f 8b), Deflate (78 xx), non-printable → Hex - i18n keys for all formats (zh-CN + en)
This commit is contained in:
@@ -257,6 +257,14 @@ export function registerIpcHandlers(): void {
|
||||
redis.clearLog()
|
||||
})
|
||||
|
||||
// Format decode
|
||||
ipcMain.handle('redis:decodeValue', (_e, value: string, format: string) => {
|
||||
return redis.decodeValue(value, format as any)
|
||||
})
|
||||
ipcMain.handle('redis:detectFormat', (_e, value: string) => {
|
||||
return redis.detectFormat(value)
|
||||
})
|
||||
|
||||
// Pub/Sub
|
||||
ipcMain.handle('redis:subscribe', async (_e, id: string, channels: string[], isPattern: boolean) => {
|
||||
try {
|
||||
|
||||
@@ -0,0 +1,46 @@
|
||||
// electron/main/redis/format.ts
|
||||
// 值格式转换(解压/编码)
|
||||
import { gunzipSync, inflateSync, brotliDecompressSync } from 'zlib'
|
||||
|
||||
export type FormatType = 'text' | 'hex' | 'json' | 'binary' | 'gzip' | 'deflate' | 'brotli' | 'deflateRaw'
|
||||
|
||||
export function decodeValue(value: string, format: FormatType): string {
|
||||
const buf = Buffer.from(value, 'utf-8')
|
||||
switch (format) {
|
||||
case 'hex':
|
||||
return buf.toString('hex').match(/.{1,2}/g)?.join(' ') ?? ''
|
||||
case 'binary':
|
||||
return buf.toString('binary')
|
||||
case 'json':
|
||||
try {
|
||||
return JSON.stringify(JSON.parse(value), null, 2)
|
||||
} catch {
|
||||
return value
|
||||
}
|
||||
case 'gzip':
|
||||
try { return gunzipSync(buf).toString('utf-8') } catch { return '[Gzip decode failed]' }
|
||||
case 'deflate':
|
||||
try { return inflateSync(buf).toString('utf-8') } catch { return '[Deflate decode failed]' }
|
||||
case 'brotli':
|
||||
try { return brotliDecompressSync(buf).toString('utf-8') } catch { return '[Brotli decode failed]' }
|
||||
case 'deflateRaw':
|
||||
try { return inflateSync(buf).toString('utf-8') } catch { return '[DeflateRaw decode failed]' }
|
||||
default:
|
||||
return value
|
||||
}
|
||||
}
|
||||
|
||||
export function detectFormat(value: string): FormatType {
|
||||
if (!value) return 'text'
|
||||
// JSON
|
||||
try { JSON.parse(value); return 'json' } catch { /* not json */ }
|
||||
// Gzip magic: 1f 8b
|
||||
if (value.charCodeAt(0) === 0x1f && value.charCodeAt(1) === 0x8b) return 'gzip'
|
||||
// Brotli magic (less reliable, check common patterns)
|
||||
// Deflate (zlib) magic: 78 01/9c/da
|
||||
if (value.charCodeAt(0) === 0x78 && [0x01, 0x9c, 0xda, 0x5e].includes(value.charCodeAt(1))) return 'deflate'
|
||||
// Check for non-printable characters → binary/hex
|
||||
const hasNonPrintable = /[\x00-\x08\x0e-\x1f]/.test(value.substring(0, 100))
|
||||
if (hasNonPrintable) return 'hex'
|
||||
return 'text'
|
||||
}
|
||||
@@ -11,3 +11,4 @@ export * from './stream'
|
||||
export * from './server'
|
||||
export * from './commandLogger'
|
||||
export * from './pubsub'
|
||||
export * from './format'
|
||||
|
||||
Vendored
+2
@@ -109,6 +109,8 @@ export interface ElectronAPI {
|
||||
isConnected: (id: string) => Promise<boolean>
|
||||
getCommandLog: () => Promise<any[]>
|
||||
clearCommandLog: () => Promise<void>
|
||||
decodeValue: (value: string, format: string) => Promise<string>
|
||||
detectFormat: (value: 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
|
||||
|
||||
@@ -130,6 +130,11 @@ const electronAPI = {
|
||||
ipcRenderer.invoke('redis:getCommandLog'),
|
||||
clearCommandLog: (): Promise<void> =>
|
||||
ipcRenderer.invoke('redis:clearCommandLog'),
|
||||
// Format
|
||||
decodeValue: (value: string, format: string): Promise<string> =>
|
||||
ipcRenderer.invoke('redis:decodeValue', value, format),
|
||||
detectFormat: (value: string): Promise<string> =>
|
||||
ipcRenderer.invoke('redis:detectFormat', value),
|
||||
// Pub/Sub
|
||||
subscribe: (id: string, channels: string[], isPattern: boolean): Promise<any> =>
|
||||
ipcRenderer.invoke('redis:subscribe', id, channels, isPattern),
|
||||
|
||||
Reference in New Issue
Block a user